diff options
Diffstat (limited to 'src')
254 files changed, 23674 insertions, 6322 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31694cf81..d83242a3c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -34,13 +34,13 @@ if (WIN32 OR STATIC) add_definitions(-DMINIUPNP_STATICLIB) endif () -function (bitmonero_private_headers group) +function (monero_private_headers group) source_group("${group}\\Private" FILES ${ARGN}) endfunction () -function (bitmonero_install_headers subdir) +function (monero_install_headers subdir) install( FILES ${ARGN} DESTINATION "include/${subdir}" @@ -58,7 +58,7 @@ function (enable_stack_trace target) endif() endfunction() -function (bitmonero_add_executable name) +function (monero_add_executable name) source_group("${name}" FILES ${ARGN}) @@ -77,7 +77,7 @@ function (bitmonero_add_executable name) enable_stack_trace("${name}") endfunction () -function (bitmonero_add_library name) +function (monero_add_library name) source_group("${name}" FILES ${ARGN}) @@ -99,19 +99,26 @@ endfunction () add_subdirectory(common) add_subdirectory(crypto) add_subdirectory(ringct) +add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_core) -add_subdirectory(blockchain_db) +if(NOT IOS) + add_subdirectory(blockchain_db) +endif() add_subdirectory(mnemonics) -add_subdirectory(rpc) +if(NOT IOS) + add_subdirectory(rpc) +endif() add_subdirectory(wallet) -add_subdirectory(p2p) +if(NOT IOS) + add_subdirectory(p2p) +endif() add_subdirectory(cryptonote_protocol) - -add_subdirectory(simplewallet) -add_subdirectory(daemonizer) -add_subdirectory(daemon) - -add_subdirectory(blockchain_utilities) +if(NOT IOS) + add_subdirectory(simplewallet) + add_subdirectory(daemonizer) + add_subdirectory(daemon) + add_subdirectory(blockchain_utilities) +endif() if(PER_BLOCK_CHECKPOINT) add_subdirectory(blocks) diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index 7bca66b8b..c27aa73b0 100644 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -53,9 +53,9 @@ if (BERKELEY_DB) ) endif() -bitmonero_private_headers(blockchain_db +monero_private_headers(blockchain_db ${crypto_private_headers}) -bitmonero_add_library(blockchain_db +monero_add_library(blockchain_db ${blockchain_db_sources} ${blockchain_db_headers} ${blockchain_db_private_headers}) @@ -63,6 +63,7 @@ target_link_libraries(blockchain_db PUBLIC common crypto + ringct ${LMDB_LIBRARY} ${BDB_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp index 137ed9dc6..c954a7751 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.cpp +++ b/src/blockchain_db/berkeleydb/db_bdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -31,7 +31,7 @@ #include <memory> // std::unique_ptr #include <cstring> // memcpy -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "crypto/crypto.h" #include "profile_tools.h" @@ -1813,9 +1813,10 @@ bool BlockchainBDB::has_key_image(const crypto::key_image& img) const // Ostensibly BerkeleyDB has batch transaction support built-in, // so the following few functions will be NOP. -void BlockchainBDB::batch_start(uint64_t batch_num_blocks) +bool BlockchainBDB::batch_start(uint64_t batch_num_blocks) { LOG_PRINT_L3("BlockchainBDB::" << __func__); + return false; } void BlockchainBDB::batch_commit() diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index f320ab0e3..a040a70ef 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -324,7 +324,7 @@ public: ); virtual void set_batch_transactions(bool batch_transactions); - virtual void batch_start(uint64_t batch_num_blocks=0); + virtual bool batch_start(uint64_t batch_num_blocks=0); virtual void batch_commit(); virtual void batch_stop(); virtual void batch_abort(); diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 7eb81d933..136d4fa80 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,9 +29,12 @@ #include <boost/range/adaptor/reversed.hpp> #include "blockchain_db.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "profile_tools.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "blockchain.db" + using epee::string_tools::pod_to_hex; namespace cryptonote @@ -125,11 +128,7 @@ uint64_t BlockchainDB::add_block( const block& blk TIME_MEASURE_FINISH(time1); time_blk_hash += time1; - // call out to subclass implementation to add the block & metadata - time1 = epee::misc_utils::get_tick_count(); - add_block(blk, block_size, cumulative_difficulty, coins_generated, blk_hash); - TIME_MEASURE_FINISH(time1); - time_add_block1 += time1; + uint64_t prev_height = height(); // call out to add the transactions @@ -146,9 +145,12 @@ uint64_t BlockchainDB::add_block( const block& blk TIME_MEASURE_FINISH(time1); time_add_transaction += time1; - // DB's new height based on this added block is only incremented after this - // function returns, so height() here returns the new previous height. - uint64_t prev_height = height(); + // call out to subclass implementation to add the block & metadata + time1 = epee::misc_utils::get_tick_count(); + add_block(blk, block_size, cumulative_difficulty, coins_generated, blk_hash); + TIME_MEASURE_FINISH(time1); + time_add_block1 += time1; + m_hardfork->add(blk, prev_height); block_txn_stop(); @@ -198,6 +200,45 @@ void BlockchainDB::remove_transaction(const crypto::hash& tx_hash) remove_transaction_data(tx_hash, tx); } +block BlockchainDB::get_block_from_height(const uint64_t& height) const +{ + blobdata bd = get_block_blob_from_height(height); + block b; + if (!parse_and_validate_block_from_blob(bd, b)) + throw new DB_ERROR("Failed to parse block from blob retrieved from the db"); + + return b; +} + +block BlockchainDB::get_block(const crypto::hash& h) const +{ + blobdata bd = get_block_blob(h); + block b; + if (!parse_and_validate_block_from_blob(bd, b)) + throw new DB_ERROR("Failed to parse block from blob retrieved from the db"); + + return b; +} + +bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) const +{ + blobdata bd; + if (!get_tx_blob(h, bd)) + return false; + if (!parse_and_validate_tx_from_blob(bd, tx)) + throw new DB_ERROR("Failed to parse transaction from blob retrieved from the db"); + + return true; +} + +transaction BlockchainDB::get_tx(const crypto::hash& h) const +{ + transaction tx; + if (!get_tx(h, tx)) + throw new TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()); + return tx; +} + void BlockchainDB::reset_stats() { num_calls = 0; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 5b6a793d8..f5710550b 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,9 +34,10 @@ #include <string> #include <exception> #include "crypto/hash.h" -#include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/difficulty.h" -#include "cryptonote_core/hardfork.h" +#include "cryptonote_protocol/blobdatatype.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/difficulty.h" +#include "cryptonote_basic/hardfork.h" /** \file * Cryptonote Blockchain Database Interface @@ -655,16 +656,17 @@ public: * been called. In either case, it should end the batch and write to its * backing store. * - * If a batch is already in-progress, this function should throw a DB_ERROR. - * This exception may change in the future if it is deemed necessary to - * have a more granular exception type for this scenario. + * If a batch is already in-progress, this function must return false. + * If a batch was started by this call, it must return true. * * If any of this cannot be done, the subclass should throw the corresponding * subclass of DB_EXCEPTION * * @param batch_num_blocks number of blocks to batch together + * + * @return true if we started the batch, false if already started */ - virtual void batch_start(uint64_t batch_num_blocks=0) = 0; + virtual bool batch_start(uint64_t batch_num_blocks=0) = 0; /** * @brief ends a batch transaction @@ -753,7 +755,20 @@ public: * * @return the block requested */ - virtual block get_block(const crypto::hash& h) const = 0; + virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const = 0; + + /** + * @brief fetches the block with the given hash + * + * Returns the requested block. + * + * If the block does not exist, the subclass should throw BLOCK_DNE + * + * @param h the hash to look for + * + * @return the block requested + */ + virtual block get_block(const crypto::hash& h) const; /** * @brief gets the height of the block with a given hash @@ -783,7 +798,7 @@ public: virtual block_header get_block_header(const crypto::hash& h) const = 0; /** - * @brief fetch a block by height + * @brief fetch a block blob by height * * The subclass should return the block at the given height. * @@ -792,9 +807,21 @@ public: * * @param height the height to look for * + * @return the block blob + */ + virtual cryptonote::blobdata get_block_blob_from_height(const uint64_t& height) const = 0; + + /** + * @brief fetch a block by height + * + * If the block does not exist, that is to say if the blockchain is not + * that high, then the subclass should throw BLOCK_DNE + * + * @param height the height to look for + * * @return the block */ - virtual block get_block_from_height(const uint64_t& height) const = 0; + virtual block get_block_from_height(const uint64_t& height) const; /** * @brief fetch a block's timestamp @@ -1008,16 +1035,38 @@ public: /** * @brief fetches the transaction with the given hash * + * If the transaction does not exist, the subclass should throw TX_DNE. + * + * @param h the hash to look for + * + * @return the transaction with the given hash + */ + virtual transaction get_tx(const crypto::hash& h) const; + + /** + * @brief fetches the transaction with the given hash + * + * If the transaction does not exist, the subclass should return false. + * + * @param h the hash to look for + * + * @return true iff the transaction was found + */ + virtual bool get_tx(const crypto::hash& h, transaction &tx) const; + + /** + * @brief fetches the transaction blob with the given hash + * * The subclass should return the transaction stored which has the given * hash. * - * If the transaction does not exist, the subclass should throw TX_DNE. + * If the transaction does not exist, the subclass should return false. * * @param h the hash to look for * - * @return the transaction with the given hash + * @return true iff the transaction was found */ - virtual transaction get_tx(const crypto::hash& h) const = 0; + virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0; /** * @brief fetches the total number of transactions ever @@ -1169,7 +1218,7 @@ public: * @param offsets a list of amount-specific output indices * @param outputs return-by-reference a list of outputs' metadata */ - virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs) = 0; + virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs, bool allow_partial = false) = 0; /* * FIXME: Need to check with git blame and ask what this does to @@ -1309,10 +1358,11 @@ public: * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict count to unlocked outputs + * @param recent_cutoff timestamp to determine whether an output is recent * * @return a set of amount/instances */ - virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const = 0; + virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const = 0; /** * @brief is BlockchainDB in read-only mode? diff --git a/src/blockchain_db/db_types.h b/src/blockchain_db/db_types.h index ca4abf219..6c21b029e 100644 --- a/src/blockchain_db/db_types.h +++ b/src/blockchain_db/db_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,7 +34,6 @@ namespace cryptonote const std::unordered_set<std::string> blockchain_db_types = { "lmdb" - , "berkeley" }; } // namespace cryptonote diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index acb7d2cf6..dab09e8f4 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -34,10 +34,14 @@ #include <cstring> // memcpy #include <random> -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "crypto/crypto.h" #include "profile_tools.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "blockchain.db.lmdb" + + #if defined(__i386) || defined(__x86_64) #define MISALIGNED_OK 1 #endif @@ -51,12 +55,15 @@ using epee::string_tools::pod_to_hex; namespace { +#pragma pack(push, 1) +// This MUST be identical to output_data_t, without the extra rct data at the end struct pre_rct_output_data_t { crypto::public_key pubkey; //!< the output's public key (for spend verification) uint64_t unlock_time; //!< the output's unlock time (or height) uint64_t height; //!< the height of the block which created the output }; +#pragma pack(pop) template <typename T> inline void throw0(const T &e) @@ -370,7 +377,50 @@ void mdb_txn_safe::allow_new_txns() creation_gate.clear(); } +void lmdb_resized(MDB_env *env) +{ + mdb_txn_safe::prevent_new_txns(); + + MGINFO("LMDB map resize detected."); + + MDB_envinfo mei; + + mdb_env_info(env, &mei); + uint64_t old = mei.me_mapsize; + + mdb_txn_safe::wait_no_active_txns(); + + int result = mdb_env_set_mapsize(env, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to set new mapsize: ", result).c_str())); + + mdb_env_info(env, &mei); + uint64_t new_mapsize = mei.me_mapsize; + + MGINFO("LMDB Mapsize increased." << " Old: " << old / (1024 * 1024) << "MiB" << ", New: " << new_mapsize / (1024 * 1024) << "MiB"); + + mdb_txn_safe::allow_new_txns(); +} + +inline int lmdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **txn) +{ + int res = mdb_txn_begin(env, parent, flags, txn); + if (res == MDB_MAP_RESIZED) { + lmdb_resized(env); + res = mdb_txn_begin(env, parent, flags, txn); + } + return res; +} +inline int lmdb_txn_renew(MDB_txn *txn) +{ + int res = mdb_txn_renew(txn); + if (res == MDB_MAP_RESIZED) { + lmdb_resized(mdb_txn_env(txn)); + res = mdb_txn_renew(txn); + } + return res; +} void BlockchainLMDB::do_resize(uint64_t increase_size) { @@ -385,14 +435,14 @@ void BlockchainLMDB::do_resize(uint64_t increase_size) boost::filesystem::space_info si = boost::filesystem::space(path); if(si.available < add_size) { - LOG_PRINT_RED_L0("!! WARNING: Insufficient free space to extend database !!: " << si.available / 1LL << 20L); + MERROR("!! WARNING: Insufficient free space to extend database !!: " << si.available / 1LL << 20L); return; } } catch(...) { // print something but proceed. - LOG_PRINT_YELLOW("Unable to query free disk space.", LOG_LEVEL_0); + MWARNING("Unable to query free disk space."); } MDB_envinfo mei; @@ -434,7 +484,7 @@ void BlockchainLMDB::do_resize(uint64_t increase_size) if (result) throw0(DB_ERROR(lmdb_error("Failed to set new mapsize: ", result).c_str())); - LOG_PRINT_GREEN("LMDB Mapsize increased." << " Old: " << mei.me_mapsize / (1024 * 1024) << "MiB" << ", New: " << new_mapsize / (1024 * 1024) << "MiB", LOG_LEVEL_0); + MGINFO("LMDB Mapsize increased." << " Old: " << mei.me_mapsize / (1024 * 1024) << "MiB" << ", New: " << new_mapsize / (1024 * 1024) << "MiB"); mdb_txn_safe::allow_new_txns(); } @@ -518,7 +568,7 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks) // size-based check if (need_resize(threshold_size)) { - LOG_PRINT_L0("[batch] DB resize needed"); + MGINFO("[batch] DB resize needed"); do_resize(increase_size); } } @@ -540,6 +590,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con uint64_t min_block_size = 4 * 1024; uint64_t block_stop = 0; + uint64_t m_height = height(); if (m_height > 1) block_stop = m_height - 1; uint64_t block_start = 0; @@ -553,7 +604,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con { LOG_PRINT_L1("No existing blocks to check for average block size"); } - else if (m_cum_count) + else if (m_cum_count >= num_prev_blocks) { avg_block_size = m_cum_size / m_cum_count; LOG_PRINT_L1("average block size across recent " << m_cum_count << " blocks: " << avg_block_size); @@ -562,6 +613,9 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con } else { + MDB_txn *rtxn; + mdb_txn_cursors *rcurs; + block_rtxn_start(&rtxn, &rcurs); for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num) { uint32_t block_size = get_block_size(block_num); @@ -570,6 +624,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con // some blocks were to be skipped for being outliers. ++num_blocks_used; } + block_rtxn_stop(); avg_block_size = total_block_size / num_blocks_used; LOG_PRINT_L1("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size); } @@ -590,6 +645,7 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); mdb_txn_cursors *m_cursors = &m_wcursors; + uint64_t m_height = height(); CURSOR(block_heights) blk_height bh = {blk_hash, m_height}; @@ -619,6 +675,7 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const CURSOR(blocks) CURSOR(block_info) + // this call to mdb_cursor_put will change height() MDB_val_copy<blobdata> blob(block_to_blob(blk)); result = mdb_cursor_put(m_cur_blocks, &key, &blob, MDB_APPEND); if (result) @@ -651,6 +708,7 @@ void BlockchainLMDB::remove_block() LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + uint64_t m_height = height(); if (m_height == 0) throw0(BLOCK_DNE ("Attempting to remove block from an empty blockchain")); @@ -688,9 +746,10 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); mdb_txn_cursors *m_cursors = &m_wcursors; + uint64_t m_height = height(); int result; - uint64_t tx_id = m_num_txs; + uint64_t tx_id = get_tx_count(); CURSOR(txs) CURSOR(tx_indices) @@ -723,7 +782,6 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons if (result) throw0(DB_ERROR(lmdb_error("Failed to add tx blob to db transaction: ", result).c_str())); - m_num_txs++; return tx_id; } @@ -771,8 +829,6 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const // Don't delete the tx_indices entry until the end, after we're done with val_tx_id if (mdb_cursor_del(m_cur_tx_indices, 0)) throw1(DB_ERROR("Failed to add removal of tx index to db transaction")); - - m_num_txs--; } uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, @@ -784,6 +840,8 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); mdb_txn_cursors *m_cursors = &m_wcursors; + uint64_t m_height = height(); + uint64_t m_num_outputs = num_outputs(); int result = 0; @@ -836,7 +894,6 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, if ((result = mdb_cursor_put(m_cur_output_amounts, &val_amount, &data, MDB_APPENDDUP))) throw0(DB_ERROR(lmdb_error("Failed to add output pubkey to db transaction: ", result).c_str())); - m_num_outputs++; return ok.amount_index; } @@ -877,12 +934,11 @@ void BlockchainLMDB::remove_tx_outputs(const uint64_t tx_id, const transaction& throw0(DB_ERROR("tx has outputs, but no output indices found")); } - bool is_miner_tx = tx.vin.size() == 1 && tx.vin[0].type() == typeid(txin_gen); - for (uint64_t i = tx.vout.size(); i > 0; --i) + bool is_pseudo_rct = tx.version >= 2 && tx.vin.size() == 1 && tx.vin[0].type() == typeid(txin_gen); + for (size_t i = tx.vout.size(); i-- > 0;) { - const tx_out tx_output = tx.vout[i-1]; - uint64_t amount = is_miner_tx && tx.version >= 2 ? 0 : tx_output.amount; - remove_output(amount, amount_output_indices[i-1]); + uint64_t amount = is_pseudo_rct ? 0 : tx.vout[i].amount; + remove_output(amount, amount_output_indices[i]); } } @@ -903,12 +959,12 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in else if (result) throw0(DB_ERROR(lmdb_error("DB error attempting to get an output", result).c_str())); - outkey *ok = (outkey *)v.mv_data; + const pre_rct_outkey *ok = (const pre_rct_outkey *)v.mv_data; MDB_val_set(otxk, ok->output_id); result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &otxk, MDB_GET_BOTH); if (result == MDB_NOTFOUND) { - LOG_PRINT_L0("Unexpected: global output index not found in m_output_txs"); + throw0(DB_ERROR("Unexpected: global output index not found in m_output_txs")); } else if (result) { @@ -922,8 +978,6 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in result = mdb_cursor_del(m_cur_output_amounts, 0); if (result) throw0(DB_ERROR(lmdb_error(std::string("Error deleting amount for output index ").append(boost::lexical_cast<std::string>(out_index).append(": ")).c_str(), result).c_str())); - - m_num_outputs--; } void BlockchainLMDB::add_spent_key(const crypto::key_image& k_image) @@ -988,7 +1042,7 @@ tx_out BlockchainLMDB::output_from_blob(const blobdata& blob) const void BlockchainLMDB::check_open() const { - LOG_PRINT_L3("BlockchainLMDB::" << __func__); +// LOG_PRINT_L3("BlockchainLMDB::" << __func__); if (!m_open) throw0(DB_ERROR("DB operation attempted on a not-open DB instance")); } @@ -1016,7 +1070,6 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions) m_write_txn = nullptr; m_write_batch_txn = nullptr; m_batch_active = false; - m_height = 0; m_cum_size = 0; m_cum_count = 0; @@ -1046,10 +1099,11 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) // check for existing LMDB files in base directory boost::filesystem::path old_files = direc.parent_path(); - if (boost::filesystem::exists(old_files / "data.mdb") || boost::filesystem::exists(old_files / "lock.mdb")) + if (boost::filesystem::exists(old_files / CRYPTONOTE_BLOCKCHAINDATA_FILENAME) + || boost::filesystem::exists(old_files / CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME)) { LOG_PRINT_L0("Found existing LMDB files in " << old_files.string()); - LOG_PRINT_L0("Move data.mdb and/or lock.mdb to " << filename << ", or delete them, and then restart"); + LOG_PRINT_L0("Move " << CRYPTONOTE_BLOCKCHAINDATA_FILENAME << " and/or " << CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME << " to " << filename << ", or delete them, and then restart"); throw DB_ERROR("Database could not be opened"); } @@ -1141,17 +1195,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) if ((result = mdb_stat(txn, m_blocks, &db_stats))) throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); LOG_PRINT_L2("Setting m_height to: " << db_stats.ms_entries); - m_height = db_stats.ms_entries; - - // get and keep current number of txs - if ((result = mdb_stat(txn, m_txs, &db_stats))) - throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str())); - m_num_txs = db_stats.ms_entries; - - // get and keep current number of outputs - if ((result = mdb_stat(txn, m_output_txs, &db_stats))) - throw0(DB_ERROR(lmdb_error("Failed to query m_output_txs: ", result).c_str())); - m_num_outputs = db_stats.ms_entries; + uint64_t m_height = db_stats.ms_entries; bool compatible = true; @@ -1162,7 +1206,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) { if (*(const uint32_t*)v.mv_data > VERSION) { - LOG_PRINT_RED_L0("Existing lmdb database was made by a later version. We don't know how it will change yet."); + MWARNING("Existing lmdb database was made by a later version. We don't know how it will change yet."); compatible = false; } #if VERSION > 0 @@ -1192,8 +1236,8 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) txn.abort(); mdb_env_close(m_env); m_open = false; - LOG_PRINT_RED_L0("Existing lmdb database is incompatible with this version."); - LOG_PRINT_RED_L0("Please delete the existing database and resync."); + MFATAL("Existing lmdb database is incompatible with this version."); + MFATAL("Please delete the existing database and resync."); return; } @@ -1210,7 +1254,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags) txn.abort(); mdb_env_close(m_env); m_open = false; - LOG_PRINT_RED_L0("Failed to write version to database."); + MERROR("Failed to write version to database."); return; } } @@ -1258,7 +1302,7 @@ void BlockchainLMDB::reset() check_open(); mdb_txn_safe txn; - if (auto result = mdb_txn_begin(m_env, NULL, 0, txn)) + if (auto result = lmdb_txn_begin(m_env, NULL, 0, txn)) throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); if (auto result = mdb_drop(txn, m_blocks, 0)) @@ -1292,8 +1336,6 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to write version to database: ", result).c_str())); txn.commit(); - m_height = 0; - m_num_outputs = 0; m_cum_size = 0; m_cum_count = 0; } @@ -1304,9 +1346,9 @@ std::vector<std::string> BlockchainLMDB::get_filenames() const std::vector<std::string> filenames; boost::filesystem::path datafile(m_folder); - datafile /= "data.mdb"; + datafile /= CRYPTONOTE_BLOCKCHAINDATA_FILENAME; boost::filesystem::path lockfile(m_folder); - lockfile /= "lock.mdb"; + lockfile /= CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME; filenames.push_back(datafile.string()); filenames.push_back(lockfile.string()); @@ -1343,7 +1385,7 @@ void BlockchainLMDB::unlock() txn_ptr = m_write_txn; \ else \ { \ - if (auto mdb_res = mdb_txn_begin(m_env, NULL, flags, auto_txn)) \ + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ } \ @@ -1377,7 +1419,7 @@ void BlockchainLMDB::unlock() txn_ptr = m_write_txn; \ else \ { \ - if (auto mdb_res = mdb_txn_begin(m_env, NULL, flags, auto_txn)) \ + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ } \ @@ -1418,12 +1460,12 @@ bool BlockchainLMDB::block_exists(const crypto::hash& h, uint64_t *height) const return ret; } -block BlockchainLMDB::get_block(const crypto::hash& h) const +cryptonote::blobdata BlockchainLMDB::get_block_blob(const crypto::hash& h) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); - return get_block_from_height(get_block_height(h)); + return get_block_blob_from_height(get_block_height(h)); } uint64_t BlockchainLMDB::get_block_height(const crypto::hash& h) const @@ -1456,7 +1498,7 @@ block_header BlockchainLMDB::get_block_header(const crypto::hash& h) const return get_block(h); } -block BlockchainLMDB::get_block_from_height(const uint64_t& height) const +cryptonote::blobdata BlockchainLMDB::get_block_blob_from_height(const uint64_t& height) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1477,13 +1519,9 @@ block BlockchainLMDB::get_block_from_height(const uint64_t& height) const blobdata bd; bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size); - block b; - if (!parse_and_validate_block_from_blob(bd, b)) - throw0(DB_ERROR("Failed to parse block from blob retrieved from the db")); - TXN_POSTFIX_RDONLY(); - return b; + return bd; } uint64_t BlockchainLMDB::get_block_timestamp(const uint64_t& height) const @@ -1513,6 +1551,7 @@ uint64_t BlockchainLMDB::get_top_block_timestamp() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + uint64_t m_height = height(); // if no blocks, return 0 if (m_height == 0) @@ -1664,6 +1703,7 @@ crypto::hash BlockchainLMDB::top_block_hash() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + uint64_t m_height = height(); if (m_height != 0) { return get_block_hash_from_height(m_height - 1); @@ -1676,6 +1716,7 @@ block BlockchainLMDB::get_top_block() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + uint64_t m_height = height(); if (m_height != 0) { @@ -1690,8 +1731,28 @@ uint64_t BlockchainLMDB::height() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + TXN_PREFIX_RDONLY(); + int result; - return m_height; + // get current height + MDB_stat db_stats; + if ((result = mdb_stat(m_txn, m_blocks, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); + return db_stats.ms_entries; +} + +uint64_t BlockchainLMDB::num_outputs() const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + TXN_PREFIX_RDONLY(); + int result; + + // get current height + MDB_stat db_stats; + if ((result = mdb_stat(m_txn, m_output_txs, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_output_txs: ", result).c_str())); + return db_stats.ms_entries; } bool BlockchainLMDB::tx_exists(const crypto::hash& h) const @@ -1789,7 +1850,7 @@ uint64_t BlockchainLMDB::get_tx_unlock_time(const crypto::hash& h) const return ret; } -transaction BlockchainLMDB::get_tx(const crypto::hash& h) const +bool BlockchainLMDB::get_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1808,20 +1869,15 @@ transaction BlockchainLMDB::get_tx(const crypto::hash& h) const get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET); } if (get_result == MDB_NOTFOUND) - throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str())); + return false; else if (get_result) throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str())); - blobdata bd; bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size); - transaction tx; - if (!parse_and_validate_tx_from_blob(bd, tx)) - throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); - TXN_POSTFIX_RDONLY(); - return tx; + return true; } uint64_t BlockchainLMDB::get_tx_count() const @@ -1830,10 +1886,11 @@ uint64_t BlockchainLMDB::get_tx_count() const check_open(); TXN_PREFIX_RDONLY(); + int result; MDB_stat db_stats; - if (mdb_stat(m_txn, m_tx_indices, &db_stats)) - throw0(DB_ERROR("Failed to query m_tx_indices")); + if ((result = mdb_stat(m_txn, m_txs, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str())); TXN_POSTFIX_RDONLY(); @@ -2041,9 +2098,10 @@ std::vector<uint64_t> BlockchainLMDB::get_tx_amount_output_indices(const uint64_ else if (result) throw0(DB_ERROR(lmdb_error("DB error attempting to get data for tx_outputs[tx_index]", result).c_str())); - uint64_t* indices = (uint64_t*)v.mv_data; + const uint64_t* indices = (const uint64_t*)v.mv_data; int num_outputs = v.mv_size / sizeof(uint64_t); + amount_output_indices.reserve(num_outputs); for (int i = 0; i < num_outputs; ++i) { // LOG_PRINT_L0("amount output index[" << 2*i << "]" << ": " << paired_indices[2*i] << " global output index: " << paired_indices[2*i+1]); @@ -2231,15 +2289,15 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c } // batch_num_blocks: (optional) Used to check if resize needed before batch transaction starts. -void BlockchainLMDB::batch_start(uint64_t batch_num_blocks) +bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); if (! m_batch_transactions) throw0(DB_ERROR("batch transactions not enabled")); if (m_batch_active) - throw0(DB_ERROR("batch transaction already in progress")); + return false; if (m_write_batch_txn != nullptr) - throw0(DB_ERROR("batch transaction already in progress")); + return false; if (m_write_txn) throw0(DB_ERROR("batch transaction attempted, but m_write_txn already in use")); check_open(); @@ -2250,7 +2308,7 @@ void BlockchainLMDB::batch_start(uint64_t batch_num_blocks) m_write_batch_txn = new mdb_txn_safe(); // NOTE: need to make sure it's destroyed properly when done - if (auto mdb_res = mdb_txn_begin(m_env, NULL, 0, *m_write_batch_txn)) + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, 0, *m_write_batch_txn)) { delete m_write_batch_txn; m_write_batch_txn = nullptr; @@ -2265,6 +2323,7 @@ void BlockchainLMDB::batch_start(uint64_t batch_num_blocks) memset(&m_wcursors, 0, sizeof(m_wcursors)); LOG_PRINT_L3("batch transaction: begin"); + return true; } void BlockchainLMDB::batch_commit() @@ -2273,9 +2332,12 @@ void BlockchainLMDB::batch_commit() if (! m_batch_transactions) throw0(DB_ERROR("batch transactions not enabled")); if (! m_batch_active) - throw0(DB_ERROR("batch transaction not in progress")); + throw1(DB_ERROR("batch transaction not in progress")); if (m_write_batch_txn == nullptr) - throw0(DB_ERROR("batch transaction not in progress")); + throw1(DB_ERROR("batch transaction not in progress")); + if (m_writer != boost::this_thread::get_id()) + throw1(DB_ERROR("batch transaction owned by other thread")); + check_open(); LOG_PRINT_L3("batch transaction: committing..."); @@ -2297,9 +2359,11 @@ void BlockchainLMDB::batch_stop() if (! m_batch_transactions) throw0(DB_ERROR("batch transactions not enabled")); if (! m_batch_active) - throw0(DB_ERROR("batch transaction not in progress")); + throw1(DB_ERROR("batch transaction not in progress")); if (m_write_batch_txn == nullptr) - throw0(DB_ERROR("batch transaction not in progress")); + throw1(DB_ERROR("batch transaction not in progress")); + if (m_writer != boost::this_thread::get_id()) + throw1(DB_ERROR("batch transaction owned by other thread")); check_open(); LOG_PRINT_L3("batch transaction: committing..."); TIME_MEASURE_START(time1); @@ -2321,7 +2385,11 @@ void BlockchainLMDB::batch_abort() if (! m_batch_transactions) throw0(DB_ERROR("batch transactions not enabled")); if (! m_batch_active) - throw0(DB_ERROR("batch transaction not in progress")); + throw1(DB_ERROR("batch transaction not in progress")); + if (m_write_batch_txn == nullptr) + throw1(DB_ERROR("batch transaction not in progress")); + if (m_writer != boost::this_thread::get_id()) + throw1(DB_ERROR("batch transaction owned by other thread")); check_open(); // for destruction of batch transaction m_write_txn = nullptr; @@ -2359,12 +2427,12 @@ bool BlockchainLMDB::block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) co m_tinfo.reset(new mdb_threadinfo); memset(&m_tinfo->m_ti_rcursors, 0, sizeof(m_tinfo->m_ti_rcursors)); memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - if (auto mdb_res = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a read transaction for the db: ", mdb_res).c_str())); ret = true; } else if (!m_tinfo->m_ti_rflags.m_rf_txn) { - if (auto mdb_res = mdb_txn_renew(m_tinfo->m_ti_rtxn)) + if (auto mdb_res = lmdb_txn_renew(m_tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to renew a read transaction for the db: ", mdb_res).c_str())); ret = true; } @@ -2397,12 +2465,12 @@ void BlockchainLMDB::block_txn_start(bool readonly) m_tinfo.reset(new mdb_threadinfo); memset(&m_tinfo->m_ti_rcursors, 0, sizeof(m_tinfo->m_ti_rcursors)); memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - if (auto mdb_res = mdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a read transaction for the db: ", mdb_res).c_str())); didit = true; } else if (!m_tinfo->m_ti_rflags.m_rf_txn) { - if (auto mdb_res = mdb_txn_renew(m_tinfo->m_ti_rtxn)) + if (auto mdb_res = lmdb_txn_renew(m_tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to renew a read transaction for the db: ", mdb_res).c_str())); didit = true; } @@ -2428,14 +2496,15 @@ void BlockchainLMDB::block_txn_start(bool readonly) { m_writer = boost::this_thread::get_id(); m_write_txn = new mdb_txn_safe(); - if (auto mdb_res = mdb_txn_begin(m_env, NULL, 0, *m_write_txn)) + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, 0, *m_write_txn)) { delete m_write_txn; m_write_txn = nullptr; throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a transaction for the db: ", mdb_res).c_str())); } memset(&m_wcursors, 0, sizeof(m_wcursors)); - } + } else if (m_writer != boost::this_thread::get_id()) + throw0(DB_ERROR_TXN_START((std::string("Attempted to start new write txn when batch txn already exists in ")+__FUNCTION__).c_str())); } void BlockchainLMDB::block_txn_stop() @@ -2494,6 +2563,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); + uint64_t m_height = height(); if (m_height % 1000 == 0) { @@ -2505,8 +2575,6 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c } } - uint64_t num_txs = m_num_txs; - uint64_t num_outputs = m_num_outputs; try { BlockchainDB::add_block(blk, block_size, cumulative_difficulty, coins_generated, txs); @@ -2517,8 +2585,6 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c } catch (...) { - m_num_txs = num_txs; - m_num_outputs = num_outputs; block_txn_abort(); throw; } @@ -2533,8 +2599,6 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs) block_txn_start(false); - uint64_t num_txs = m_num_txs; - uint64_t num_outputs = m_num_outputs; try { BlockchainDB::pop_block(blk, txs); @@ -2542,13 +2606,9 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs) } catch (...) { - m_num_txs = num_txs; - m_num_outputs = num_outputs; block_txn_abort(); throw; } - - --m_height; } void BlockchainLMDB::get_output_tx_and_index_from_global(const std::vector<uint64_t> &global_indices, @@ -2579,7 +2639,7 @@ void BlockchainLMDB::get_output_tx_and_index_from_global(const std::vector<uint6 TXN_POSTFIX_RDONLY(); } -void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs) +void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs, bool allow_partial) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); TIME_MEASURE_START(db3); @@ -2597,7 +2657,14 @@ void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<ui auto get_result = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_GET_BOTH); if (get_result == MDB_NOTFOUND) - throw1(OUTPUT_DNE("Attempting to get output pubkey by global index, but key does not exist")); + { + if (allow_partial) + { + MDEBUG("Partial result: " << outputs.size() << "/" << offsets.size()); + break; + } + throw1(OUTPUT_DNE((std::string("Attempting to get output pubkey by global index (amount ") + boost::lexical_cast<std::string>(amount) + ", index " + boost::lexical_cast<std::string>(index) + ", count " + boost::lexical_cast<std::string>(get_num_outputs(amount)) + "), but key does not exist").c_str())); + } else if (get_result) throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output pubkey from the db", get_result).c_str())); @@ -2644,7 +2711,7 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std:: else if (get_result) throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output from the db", get_result).c_str())); - outkey *okp = (outkey *)v.mv_data; + const outkey *okp = (const outkey *)v.mv_data; tx_indices.push_back(okp->output_id); } @@ -2657,7 +2724,7 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std:: LOG_PRINT_L3("db3: " << db3); } -std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const +std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -2665,7 +2732,7 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec TXN_PREFIX_RDONLY(); RCURSOR(output_amounts); - std::map<uint64_t, uint64_t> histogram; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; MDB_val k; MDB_val v; @@ -2683,7 +2750,7 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec mdb_size_t num_elems = 0; mdb_cursor_count(m_cur_output_amounts, &num_elems); uint64_t amount = *(const uint64_t*)k.mv_data; - histogram[amount] = num_elems; + histogram[amount] = std::make_tuple(num_elems, 0, 0); } } else @@ -2694,13 +2761,13 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET); if (ret == MDB_NOTFOUND) { - histogram[amount] = 0; + histogram[amount] = std::make_tuple(0, 0, 0); } else if (ret == MDB_SUCCESS) { mdb_size_t num_elems = 0; mdb_cursor_count(m_cur_output_amounts, &num_elems); - histogram[amount] = num_elems; + histogram[amount] = std::make_tuple(num_elems, 0, 0); } else { @@ -2709,11 +2776,11 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec } } - if (unlocked) { + if (unlocked || recent_cutoff > 0) { const uint64_t blockchain_height = height(); - for (auto i: histogram) { - uint64_t amount = i.first; - uint64_t num_elems = i.second; + for (std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>::iterator i = histogram.begin(); i != histogram.end(); ++i) { + uint64_t amount = i->first; + uint64_t num_elems = std::get<0>(i->second); while (num_elems > 0) { const tx_out_index toi = get_output_tx_and_index(amount, num_elems - 1); const uint64_t height = get_tx_block_height(toi.first); @@ -2722,7 +2789,23 @@ std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vec --num_elems; } // modifying second does not invalidate the iterator - i.second = num_elems; + std::get<1>(i->second) = num_elems; + + if (recent_cutoff > 0) + { + uint64_t recent = 0; + while (num_elems > 0) { + const tx_out_index toi = get_output_tx_and_index(amount, num_elems - 1); + const uint64_t height = get_tx_block_height(toi.first); + const uint64_t ts = get_block_timestamp(height); + if (ts < recent_cutoff) + break; + --num_elems; + ++recent; + } + // modifying second does not invalidate the iterator + std::get<2>(i->second) = recent; + } } } @@ -2818,31 +2901,36 @@ void BlockchainLMDB::fixup() ptr = (char *)k.mv_data; \ ptr[sizeof(name)-2] = 's' -#define LOGIF(y) if (y <= epee::log_space::log_singletone::get_log_detalisation_level()) +#define LOGIF(y) if (ELPP->vRegistry()->allowed(y, MONERO_DEFAULT_LOG_CATEGORY)) void BlockchainLMDB::migrate_0_1() { LOG_PRINT_L3("BlockchainLMDB::" << __func__); - uint64_t i, z; + uint64_t i, z, m_height; int result; mdb_txn_safe txn(false); MDB_val k, v; char *ptr; - LOG_PRINT_YELLOW("Migrating blockchain from DB version 0 to 1 - this may take a while:", LOG_LEVEL_0); - LOG_PRINT_L0("updating blocks, hf_versions, outputs, txs, and spent_keys tables..."); - - LOG_PRINT_L0("Total number of blocks: " << m_height); - LOG_PRINT_L1("block migration will update block_heights, block_info, and hf_versions..."); + MLOG_YELLOW(el::Level::Info, "Migrating blockchain from DB version 0 to 1 - this may take a while:"); + MINFO("updating blocks, hf_versions, outputs, txs, and spent_keys tables..."); do { - LOG_PRINT_L1("migrating block_heights:"); - MDB_dbi o_heights; - - unsigned int flags; result = mdb_txn_begin(m_env, NULL, 0, txn); if (result) throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + + MDB_stat db_stats; + if ((result = mdb_stat(txn, m_blocks, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); + m_height = db_stats.ms_entries; + MINFO("Total number of blocks: " << m_height); + MINFO("block migration will update block_heights, block_info, and hf_versions..."); + + MINFO("migrating block_heights:"); + MDB_dbi o_heights; + + unsigned int flags; result = mdb_dbi_flags(txn, m_block_heights, &flags); if (result) throw0(DB_ERROR(lmdb_error("Failed to retrieve block_heights flags: ", result).c_str())); @@ -2874,7 +2962,7 @@ void BlockchainLMDB::migrate_0_1() while(1) { if (!(i % 2000)) { if (i) { - LOGIF(1) { + LOGIF(el::Level::Info) { std::cout << i << " / " << z << " \r" << std::flush; } txn.commit(); @@ -2965,7 +3053,7 @@ void BlockchainLMDB::migrate_0_1() MDB_val k, v; if (!(i % 2000)) { if (i) { - LOGIF(1) { + LOGIF(el::Level::Info) { std::cout << i << " / " << z << " \r" << std::flush; } txn.commit(); @@ -3097,7 +3185,7 @@ void BlockchainLMDB::migrate_0_1() while(1) { if (!(i % 2000)) { if (i) { - LOGIF(1) { + LOGIF(el::Level::Info) { std::cout << i << " / " << z << " \r" << std::flush; } txn.commit(); @@ -3198,8 +3286,6 @@ void BlockchainLMDB::migrate_0_1() lmdb_db_open(txn, LMDB_OUTPUT_AMOUNTS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_output_amounts, "Failed to open db handle for m_output_amounts"); mdb_set_dupsort(txn, m_output_amounts, compare_uint64); txn.commit(); - m_num_txs = 0; - m_num_outputs = 0; } while(0); do { @@ -3243,7 +3329,7 @@ void BlockchainLMDB::migrate_0_1() while(1) { if (!(i % 1000)) { if (i) { - LOGIF(1) { + LOGIF(el::Level::Info) { std::cout << i << " / " << z << " \r" << std::flush; } MDB_val_set(pk, "txblk"); @@ -3270,12 +3356,9 @@ void BlockchainLMDB::migrate_0_1() throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs: ", result).c_str())); if (!i) { MDB_stat ms; - mdb_stat(txn, m_output_txs, &ms); - m_num_outputs = ms.ms_entries; mdb_stat(txn, m_txs, &ms); - m_num_txs = i = ms.ms_entries; + i = ms.ms_entries; if (i) { - m_num_txs = i; MDB_val_set(pk, "txblk"); result = mdb_cursor_get(c_props, &pk, &k, MDB_SET); if (result) diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 9df4b86d1..8a5677566 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -170,13 +170,13 @@ public: virtual bool block_exists(const crypto::hash& h, uint64_t *height = NULL) const; - virtual block get_block(const crypto::hash& h) const; - virtual uint64_t get_block_height(const crypto::hash& h) const; virtual block_header get_block_header(const crypto::hash& h) const; - virtual block get_block_from_height(const uint64_t& height) const; + virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const; + + virtual cryptonote::blobdata get_block_blob_from_height(const uint64_t& height) const; virtual uint64_t get_block_timestamp(const uint64_t& height) const; @@ -207,7 +207,7 @@ public: virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const; - virtual transaction get_tx(const crypto::hash& h) const; + virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual uint64_t get_tx_count() const; @@ -219,7 +219,7 @@ public: virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index); virtual output_data_t get_output_key(const uint64_t& global_index) const; - virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs); + virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs, bool allow_partial = false); virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const; virtual void get_output_tx_and_index_from_global(const std::vector<uint64_t> &global_indices, @@ -245,7 +245,7 @@ public: ); virtual void set_batch_transactions(bool batch_transactions); - virtual void batch_start(uint64_t batch_num_blocks=0); + virtual bool batch_start(uint64_t batch_num_blocks=0); virtual void batch_commit(); virtual void batch_stop(); virtual void batch_abort(); @@ -265,10 +265,11 @@ public: * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict count to unlocked outputs + * @param recent_cutoff timestamp to determine which outputs are recent * * @return a set of amount/instances */ - std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const; private: void do_resize(uint64_t size_increase=0); @@ -309,6 +310,8 @@ private: virtual void remove_spent_key(const crypto::key_image& k_image); + uint64_t num_outputs() const; + // Hard fork virtual void set_hard_fork_version(uint64_t height, uint8_t version); virtual uint8_t get_hard_fork_version(uint64_t height) const; @@ -366,11 +369,8 @@ private: MDB_dbi m_properties; - uint64_t m_height; - uint64_t m_num_txs; - uint64_t m_num_outputs; mutable uint64_t m_cum_size; // used in batch size estimation - mutable int m_cum_count; + mutable unsigned int m_cum_count; std::string m_folder; mdb_txn_safe* m_write_txn; // may point to either a short-lived txn or a batch txn mdb_txn_safe* m_write_batch_txn; // persist batch txn outside of BlockchainLMDB diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index 13093f6ad..bb23cdc8b 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -39,7 +39,7 @@ set(blockchain_import_private_headers bootstrap_serialization.h ) -bitmonero_private_headers(blockchain_import +monero_private_headers(blockchain_import ${blockchain_import_private_headers}) set(blockchain_export_sources @@ -54,11 +54,16 @@ set(blockchain_export_private_headers bootstrap_serialization.h ) -bitmonero_private_headers(blockchain_export +monero_private_headers(blockchain_export ${blockchain_export_private_headers}) -bitmonero_add_executable(blockchain_import +set(cn_deserialize_sources + cn_deserialize.cpp + ) + + +monero_add_executable(blockchain_import ${blockchain_import_sources} ${blockchain_import_private_headers}) @@ -67,6 +72,7 @@ target_link_libraries(blockchain_import cryptonote_core blockchain_db p2p + epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} @@ -84,7 +90,7 @@ set_property(TARGET blockchain_import PROPERTY OUTPUT_NAME "monero-blockchain-import") -bitmonero_add_executable(blockchain_export +monero_add_executable(blockchain_export ${blockchain_export_sources} ${blockchain_export_private_headers}) @@ -93,6 +99,7 @@ target_link_libraries(blockchain_export cryptonote_core blockchain_db p2p + epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} @@ -104,3 +111,22 @@ add_dependencies(blockchain_export set_property(TARGET blockchain_export PROPERTY OUTPUT_NAME "monero-blockchain-export") + +monero_add_executable(cn_deserialize + ${cn_deserialize_sources} + ${cn_deserialize_private_headers}) + +target_link_libraries(cn_deserialize + LINK_PRIVATE + cryptonote_core + blockchain_db + p2p + epee + ${CMAKE_THREAD_LIBS_INIT}) + +add_dependencies(cn_deserialize + version) +set_property(TARGET cn_deserialize + PROPERTY + OUTPUT_NAME "monero-utils-deserialize") + diff --git a/src/blockchain_utilities/README.md b/src/blockchain_utilities/README.md index 809df3bfa..d18dcba8c 100644 --- a/src/blockchain_utilities/README.md +++ b/src/blockchain_utilities/README.md @@ -1,6 +1,6 @@ # Monero Blockchain Utilities -Copyright (c) 2014-2016, The Monero Project +Copyright (c) 2014-2017, The Monero Project ## Introduction diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 231bea337..f145bc107 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -38,8 +38,11 @@ #include "blockchain_db/db_types.h" #include "version.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" + namespace po = boost::program_options; -using namespace epee; // log_space +using namespace epee; std::string join_set_strings(const std::unordered_set<std::string>& db_types_all, const char* delim) { @@ -54,6 +57,10 @@ std::string join_set_strings(const std::unordered_set<std::string>& db_types_all int main(int argc, char* argv[]) { + TRY_ENTRY(); + + epee::string_tools::set_module_name_and_folder(argv[0]); + std::string default_db_type = "lmdb"; std::unordered_set<std::string> db_types_all = cryptonote::blockchain_db_types; @@ -75,7 +82,7 @@ int main(int argc, char* argv[]) po::options_description desc_cmd_only("Command line options"); po::options_description desc_cmd_sett("Command line options and settings options"); const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; - const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; + const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop}; const command_line::arg_descriptor<bool> arg_testnet_on = { "testnet" @@ -119,13 +126,14 @@ int main(int argc, char* argv[]) return 1; } - log_level = command_line::get_arg(vm, arg_log_level); + mlog_configure(mlog_get_default_log_path("monero-blockchain-export.log"), true); + if (!vm["log-level"].defaulted()) + mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); + else + mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str()); block_stop = command_line::get_arg(vm, arg_block_stop); - log_space::get_set_log_detalisation_level(true, log_level); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); LOG_PRINT_L0("Starting..."); - LOG_PRINT_L0("Setting log level = " << log_level); bool opt_testnet = command_line::get_arg(vm, arg_testnet_on); bool opt_blocks_dat = command_line::get_arg(vm, arg_blocks_dat); @@ -222,4 +230,7 @@ int main(int argc, char* argv[]) } CHECK_AND_ASSERT_MES(r, false, "Failed to export blockchain raw data"); LOG_PRINT_L0("Blockchain raw data exported OK"); + return 0; + + CATCH_ENTRY("Export error", 1); } diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 27c9050f8..b2c217ca1 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,7 +34,7 @@ #include <boost/filesystem.hpp> #include "bootstrap_file.h" #include "bootstrap_serialization.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "serialization/binary_utils.h" // dump_binary(), parse_binary() #include "serialization/json_utils.h" // dump_json() #include "include_base_utils.h" @@ -44,6 +44,9 @@ #include "fake_core.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" + namespace { // CONFIG @@ -132,7 +135,7 @@ int parse_db_arguments(const std::string& db_arg_str, std::string& db_type, int& #if !defined(BERKELEY_DB) if (db_type == "berkeley") { - LOG_ERROR("BerkeleyDB support disabled."); + MFATAL("BerkeleyDB support disabled."); return false; } #endif @@ -163,7 +166,7 @@ int parse_db_arguments(const std::string& db_arg_str, std::string& db_type, int& continue; if (db_type == "lmdb") { - LOG_PRINT_L1("LMDB flag: " << it); + MINFO("LMDB flag: " << it); if (it == "nosync") db_flags |= MDB_NOSYNC; else if (it == "nometasync") @@ -211,7 +214,7 @@ int pop_blocks(FakeCore& simple_core, int num_blocks) if (simple_core.support_batch) use_batch = true; else - LOG_PRINT_L0("WARNING: batch transactions enabled but unsupported or unnecessary for this database type - ignoring"); + MWARNING("WARNING: batch transactions enabled but unsupported or unnecessary for this database type - ignoring"); } if (use_batch) @@ -260,14 +263,14 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, boost::system::error_code ec; if (!boost::filesystem::exists(fs_import_file_path, ec)) { - LOG_PRINT_L0("bootstrap file not found: " << fs_import_file_path); + MFATAL("bootstrap file not found: " << fs_import_file_path); return false; } BootstrapFile bootstrap; // BootstrapFile bootstrap(import_file_path); uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path); - LOG_PRINT_L0("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks); + MINFO("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks); std::cout << ENDL; std::cout << "Preparing to read blocks..." << ENDL; @@ -280,7 +283,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, uint64_t num_imported = 0; if (import_file.fail()) { - LOG_PRINT_L0("import_file.open() fail"); + MFATAL("import_file.open() fail"); return false; } @@ -309,7 +312,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, // These are what we'll try to use, and they don't have to be a determination // from source and destination blockchains, but those are the defaults. - LOG_PRINT_L0("start block: " << start_height << " stop block: " << + MINFO("start block: " << start_height << " stop block: " << block_stop); bool use_batch = false; @@ -318,13 +321,13 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, if (simple_core.support_batch) use_batch = true; else - LOG_PRINT_L0("WARNING: batch transactions enabled but unsupported or unnecessary for this database type - ignoring"); + MWARNING("WARNING: batch transactions enabled but unsupported or unnecessary for this database type - ignoring"); } if (use_batch) simple_core.batch_start(db_batch_size); - LOG_PRINT_L0("Reading blockchain from bootstrap file..."); + MINFO("Reading blockchain from bootstrap file..."); std::cout << ENDL; // Within the loop, we skip to start_height before we start adding. @@ -338,7 +341,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, // TODO: bootstrap.read_chunk(); if (! import_file) { std::cout << refresh_string; - LOG_PRINT_L0("End of file reached"); + MINFO("End of file reached"); quit = 1; break; } @@ -349,29 +352,29 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, { throw std::runtime_error("Error in deserialization of chunk size"); } - LOG_PRINT_L3("chunk_size: " << chunk_size); + MDEBUG("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) { - LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); + MWARNING("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); throw std::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { - LOG_PRINT_L0("NOTE: chunk_size " << chunk_size << " > 100000"); + MINFO("NOTE: chunk_size " << chunk_size << " > 100000"); } else if (chunk_size == 0) { - LOG_PRINT_L0("ERROR: chunk_size == 0"); + MFATAL("ERROR: chunk_size == 0"); return 2; } import_file.read(buffer_block, chunk_size); if (! import_file) { - LOG_PRINT_L0("ERROR: unexpected end of file: bytes read before error: " + MFATAL("ERROR: unexpected end of file: bytes read before error: " << import_file.gcount() << " of chunk_size " << chunk_size); return 2; } bytes_read += chunk_size; - LOG_PRINT_L3("Total bytes read: " << bytes_read); + MDEBUG("Total bytes read: " << bytes_read); if (h + NUM_BLOCKS_PER_CHUNK < start_height + 1) { @@ -384,7 +387,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, << " / " << block_stop << std::flush; std::cout << ENDL << ENDL; - LOG_PRINT_L0("Specified block number reached - stopping. block: " << h-1 << " total blocks: " << h); + MINFO("Specified block number reached - stopping. block: " << h-1 << " total blocks: " << h); quit = 1; break; } @@ -405,14 +408,14 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, if ((h-1) % display_interval == 0) { std::cout << refresh_string; - LOG_PRINT_L0("loading block number " << h-1); + MDEBUG("loading block number " << h-1); } else { - LOG_PRINT_L3("loading block number " << h-1); + MDEBUG("loading block number " << h-1); } b = bp.block; - LOG_PRINT_L2("block prev_id: " << b.prev_id << ENDL); + MDEBUG("block prev_id: " << b.prev_id << ENDL); if ((h-1) % progress_interval == 0) { @@ -427,12 +430,12 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, archived_txs = bp.txs; // std::cout << refresh_string; - // LOG_PRINT_L1("txs: " << archived_txs.size()); + // MDEBUG("txs: " << archived_txs.size()); // if archived_txs is invalid // { // std::cout << refresh_string; - // LOG_PRINT_RED_L0("exception while de-archiving txs, height=" << h); + // MFATAL("exception while de-archiving txs, height=" << h); // quit = 1; // break; // } @@ -445,20 +448,20 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, ++tx_num; // if tx is invalid // { - // LOG_PRINT_RED_L0("exception while indexing tx from txs, height=" << h <<", tx_num=" << tx_num); + // MFATAL("exception while indexing tx from txs, height=" << h <<", tx_num=" << tx_num); // quit = 1; // break; // } // std::cout << refresh_string; - // LOG_PRINT_L1("tx hash: " << get_transaction_hash(tx)); + // MDEBUG("tx hash: " << get_transaction_hash(tx)); // crypto::hash hsh = null_hash; // size_t blob_size = 0; // NOTE: all tx hashes except for coinbase tx are available in the block data // get_transaction_hash(tx, hsh, blob_size); - // LOG_PRINT_L0("tx " << tx_num << " " << hsh << " : " << ENDL); - // LOG_PRINT_L0(obj_to_json_str(tx) << ENDL); + // MDEBUG("tx " << tx_num << " " << hsh << " : " << ENDL); + // MDEBUG(obj_to_json_str(tx) << ENDL); // add blocks with verification. // for Blockchain and blockchain_storage add_new_block(). @@ -472,10 +475,10 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, uint8_t version = simple_core.m_storage.get_current_hard_fork_version(); tx_verification_context tvc = AUTO_VAL_INIT(tvc); bool r = true; - r = simple_core.m_pool.add_tx(tx, tvc, true, true, version); + r = simple_core.m_pool.add_tx(tx, tvc, true, true, false, version); if (!r) { - LOG_PRINT_RED_L0("failed to add transaction to transaction pool, height=" << h <<", tx_num=" << tx_num); + MFATAL("failed to add transaction to transaction pool, height=" << h <<", tx_num=" << tx_num); quit = 1; break; } @@ -499,8 +502,8 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, if (bvc.m_verifivation_failed) { - LOG_PRINT_L0("Failed to add block to blockchain, verification failed, height = " << h); - LOG_PRINT_L0("skipping rest of file"); + MFATAL("Failed to add block to blockchain, verification failed, height = " << h); + MFATAL("skipping rest of file"); // ok to commit previously batched data because it failed only in // verification of potential new block with nothing added to batch // yet @@ -509,8 +512,8 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, } if (! bvc.m_added_to_main_chain) { - LOG_PRINT_L0("Failed to add block to blockchain, height = " << h); - LOG_PRINT_L0("skipping rest of file"); + MFATAL("Failed to add block to blockchain, height = " << h); + MFATAL("skipping rest of file"); // make sure we don't commit partial block data quit = 2; break; @@ -527,9 +530,9 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, coins_generated = bp.coins_generated; // std::cout << refresh_string; - // LOG_PRINT_L2("block_size: " << block_size); - // LOG_PRINT_L2("cumulative_difficulty: " << cumulative_difficulty); - // LOG_PRINT_L2("coins_generated: " << coins_generated); + // MDEBUG("block_size: " << block_size); + // MDEBUG("cumulative_difficulty: " << cumulative_difficulty); + // MDEBUG("coins_generated: " << coins_generated); try { @@ -538,7 +541,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, catch (const std::exception& e) { std::cout << refresh_string; - LOG_PRINT_RED_L0("Error adding block to blockchain: " << e.what()); + MFATAL("Error adding block to blockchain: " << e.what()); quit = 2; // make sure we don't commit partial block data break; } @@ -563,7 +566,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, catch (const std::exception& e) { std::cout << refresh_string; - LOG_PRINT_RED_L0("exception while reading from file, height=" << h << ": " << e.what()); + MFATAL("exception while reading from file, height=" << h << ": " << e.what()); return 2; } } // while @@ -582,10 +585,10 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, simple_core.batch_stop(); } simple_core.m_storage.get_db().show_stats(); - LOG_PRINT_L0("Number of blocks imported: " << num_imported); + MINFO("Number of blocks imported: " << num_imported); if (h > 0) // TODO: if there was an error, the last added block is probably at zero-based height h-2 - LOG_PRINT_L0("Finished at block: " << h-1 << " total blocks: " << h); + MINFO("Finished at block: " << h-1 << " total blocks: " << h); } std::cout << ENDL; return 0; @@ -593,6 +596,10 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, int main(int argc, char* argv[]) { + TRY_ENTRY(); + + epee::string_tools::set_module_name_and_folder(argv[0]); + std::string default_db_type = "lmdb"; std::string default_db_engine_compiled = "blockchain_db"; @@ -602,7 +609,7 @@ int main(int argc, char* argv[]) std::string available_dbs = join_set_strings(db_types_all, ", "); available_dbs = "available: " + available_dbs; - uint32_t log_level = LOG_LEVEL_0; + uint32_t log_level = 0; uint64_t num_blocks = 0; uint64_t block_stop = 0; std::string m_config_folder; @@ -617,7 +624,7 @@ int main(int argc, char* argv[]) po::options_description desc_cmd_only("Command line options"); po::options_description desc_cmd_sett("Command line options and settings options"); const command_line::arg_descriptor<std::string> arg_input_file = {"input-file", "Specify input file", "", true}; - const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; + const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop}; const command_line::arg_descriptor<uint64_t> arg_batch_size = {"batch-size", "", db_batch_size}; const command_line::arg_descriptor<uint64_t> arg_pop_blocks = {"pop-blocks", "Remove blocks from end of blockchain", num_blocks}; @@ -677,7 +684,6 @@ int main(int argc, char* argv[]) if (! r) return 1; - log_level = command_line::get_arg(vm, arg_log_level); opt_verify = command_line::get_arg(vm, arg_verify); opt_batch = command_line::get_arg(vm, arg_batch); opt_resume = command_line::get_arg(vm, arg_resume); @@ -719,10 +725,13 @@ int main(int argc, char* argv[]) m_config_folder = command_line::get_arg(vm, data_dir_arg); db_arg_str = command_line::get_arg(vm, arg_database); - log_space::get_set_log_detalisation_level(true, log_level); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - LOG_PRINT_L0("Starting..."); - LOG_PRINT_L0("Setting log level = " << log_level); + mlog_configure(mlog_get_default_log_path("monero-blockchain-import.log"), true); + if (!vm["log-level"].defaulted()) + mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); + else + mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str()); + + MINFO("Starting..."); boost::filesystem::path fs_import_file_path; @@ -758,7 +767,11 @@ int main(int argc, char* argv[]) return 1; } - if ((db_type == "lmdb") || (db_type == "berkeley")) + if ((db_type == "lmdb") +#if defined(BERKELEY_DB) + || (db_type == "berkeley") +#endif + ) { db_engine_compiled = "blockchain_db"; } @@ -767,23 +780,23 @@ int main(int argc, char* argv[]) db_engine_compiled = "memory"; } - LOG_PRINT_L0("database: " << db_type); - LOG_PRINT_L0("database flags: " << db_flags); - LOG_PRINT_L0("verify: " << std::boolalpha << opt_verify << std::noboolalpha); + MINFO("database: " << db_type); + MINFO("database flags: " << db_flags); + MINFO("verify: " << std::boolalpha << opt_verify << std::noboolalpha); if (opt_batch) { - LOG_PRINT_L0("batch: " << std::boolalpha << opt_batch << std::noboolalpha + MINFO("batch: " << std::boolalpha << opt_batch << std::noboolalpha << " batch size: " << db_batch_size); } else { - LOG_PRINT_L0("batch: " << std::boolalpha << opt_batch << std::noboolalpha); + MINFO("batch: " << std::boolalpha << opt_batch << std::noboolalpha); } - LOG_PRINT_L0("resume: " << std::boolalpha << opt_resume << std::noboolalpha); - LOG_PRINT_L0("testnet: " << std::boolalpha << opt_testnet << std::noboolalpha); + MINFO("resume: " << std::boolalpha << opt_resume << std::noboolalpha); + MINFO("testnet: " << std::boolalpha << opt_testnet << std::noboolalpha); - LOG_PRINT_L0("bootstrap file path: " << import_file_path); - LOG_PRINT_L0("database path: " << m_config_folder); + MINFO("bootstrap file path: " << import_file_path); + MINFO("database path: " << m_config_folder); try { @@ -795,13 +808,11 @@ int main(int argc, char* argv[]) // properties to do so. Both ways work, but fake core isn't necessary in that // circumstance. - // for multi_db_runtime: - if (db_type == "lmdb" || db_type == "berkeley") - { - fake_core_db simple_core(m_config_folder, opt_testnet, opt_batch, db_type, db_flags); - import_from_file(simple_core, import_file_path, block_stop); - } - else + if (db_type != "lmdb" +#if defined(BERKELEY_DB) + && db_type != "berkeley" +#endif + ) { std::cerr << "database type unrecognized" << ENDL; return 1; @@ -813,15 +824,15 @@ int main(int argc, char* argv[]) if (! vm["pop-blocks"].defaulted()) { num_blocks = command_line::get_arg(vm, arg_pop_blocks); - LOG_PRINT_L0("height: " << simple_core.m_storage.get_current_blockchain_height()); + MINFO("height: " << simple_core.m_storage.get_current_blockchain_height()); pop_blocks(simple_core, num_blocks); - LOG_PRINT_L0("height: " << simple_core.m_storage.get_current_blockchain_height()); + MINFO("height: " << simple_core.m_storage.get_current_blockchain_height()); return 0; } if (! vm["drop-hard-fork"].defaulted()) { - LOG_PRINT_L0("Dropping hard fork tables..."); + MINFO("Dropping hard fork tables..."); simple_core.m_storage.get_db().drop_hard_fork_info(); return 0; } @@ -846,4 +857,6 @@ int main(int argc, char* argv[]) // calls delete on its BlockchainDB derived class' object, which closes its // files. return 0; + + CATCH_ENTRY("Import error", 1); } diff --git a/src/blockchain_utilities/blockchain_utilities.h b/src/blockchain_utilities/blockchain_utilities.h index 6ddda65ec..af934bf29 100644 --- a/src/blockchain_utilities/blockchain_utilities.h +++ b/src/blockchain_utilities/blockchain_utilities.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index 926562ba2..63224225f 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,6 +28,8 @@ #include "blocksdat_file.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" namespace po = boost::program_options; @@ -50,7 +52,7 @@ bool BlocksdatFile::open_writer(const boost::filesystem::path& file_path, uint64 { if (!boost::filesystem::is_directory(dir_path)) { - LOG_PRINT_RED_L0("export directory path is a file: " << dir_path); + MFATAL("export directory path is a file: " << dir_path); return false; } } @@ -58,7 +60,7 @@ bool BlocksdatFile::open_writer(const boost::filesystem::path& file_path, uint64 { if (!boost::filesystem::create_directory(dir_path)) { - LOG_PRINT_RED_L0("Failed to create directory " << dir_path); + MFATAL("Failed to create directory " << dir_path); return false; } } @@ -66,7 +68,7 @@ bool BlocksdatFile::open_writer(const boost::filesystem::path& file_path, uint64 m_raw_data_file = new std::ofstream(); - LOG_PRINT_L0("creating file"); + MINFO("creating file"); m_raw_data_file->open(file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc); if (m_raw_data_file->fail()) @@ -123,21 +125,21 @@ bool BlocksdatFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem uint64_t block_start = 0; uint64_t block_stop = 0; - LOG_PRINT_L0("source blockchain height: " << m_blockchain_storage->get_current_blockchain_height()-1); + MINFO("source blockchain height: " << m_blockchain_storage->get_current_blockchain_height()-1); if ((requested_block_stop > 0) && (requested_block_stop < m_blockchain_storage->get_current_blockchain_height())) { - LOG_PRINT_L0("Using requested block height: " << requested_block_stop); + MINFO("Using requested block height: " << requested_block_stop); block_stop = requested_block_stop; } else { block_stop = m_blockchain_storage->get_current_blockchain_height() - 1; - LOG_PRINT_L0("Using block height of source blockchain: " << block_stop); + MINFO("Using block height of source blockchain: " << block_stop); } - LOG_PRINT_L0("Storing blocks raw data..."); + MINFO("Storing blocks raw data..."); if (!BlocksdatFile::open_writer(output_file, block_stop)) { - LOG_PRINT_RED_L0("failed to open raw file for write"); + MFATAL("failed to open raw file for write"); return false; } for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height) @@ -157,7 +159,7 @@ bool BlocksdatFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem std::cout << refresh_string; std::cout << "block " << m_cur_height-1 << "/" << block_stop << ENDL; - LOG_PRINT_L0("Number of blocks exported: " << num_blocks_written); + MINFO("Number of blocks exported: " << num_blocks_written); return BlocksdatFile::close(); } diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index ed821cd6d..4e9d8173b 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,8 +34,8 @@ #include <boost/iostreams/filtering_streambuf.hpp> -#include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_core/blockchain.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 61bd35a6f..d5bb37d93 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,6 +32,8 @@ #include "bootstrap_file.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" namespace po = boost::program_options; @@ -59,7 +61,7 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path) { if (!boost::filesystem::is_directory(dir_path)) { - LOG_PRINT_RED_L0("export directory path is a file: " << dir_path); + MFATAL("export directory path is a file: " << dir_path); return false; } } @@ -67,7 +69,7 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path) { if (!boost::filesystem::create_directory(dir_path)) { - LOG_PRINT_RED_L0("Failed to create directory " << dir_path); + MFATAL("Failed to create directory " << dir_path); return false; } } @@ -80,14 +82,14 @@ bool BootstrapFile::open_writer(const boost::filesystem::path& file_path) if (! boost::filesystem::exists(file_path)) { - LOG_PRINT_L0("creating file"); + MDEBUG("creating file"); do_initialize_file = true; num_blocks = 0; } else { num_blocks = count_blocks(file_path.string()); - LOG_PRINT_L0("appending to existing file with height: " << num_blocks-1 << " total blocks: " << num_blocks); + MDEBUG("appending to existing file with height: " << num_blocks-1 << " total blocks: " << num_blocks); } m_height = num_blocks; @@ -138,7 +140,7 @@ bool BootstrapFile::initialize_file() uint32_t bd_size = 0; blobdata bd = t_serializable_object_to_blob(bfi); - LOG_PRINT_L1("bootstrap::file_info size: " << bd.size()); + MDEBUG("bootstrap::file_info size: " << bd.size()); bd_size = bd.size(); if (! ::serialization::dump_binary(bd_size, blob)) @@ -149,7 +151,7 @@ bool BootstrapFile::initialize_file() *output_stream_header << bd; bd = t_serializable_object_to_blob(bbi); - LOG_PRINT_L1("bootstrap::blocks_info size: " << bd.size()); + MDEBUG("bootstrap::blocks_info size: " << bd.size()); bd_size = bd.size(); if (! ::serialization::dump_binary(bd_size, blob)) @@ -172,10 +174,10 @@ void BootstrapFile::flush_chunk() m_output_stream->flush(); uint32_t chunk_size = m_buffer.size(); - // LOG_PRINT_L0("chunk_size " << chunk_size); + // MTRACE("chunk_size " << chunk_size); if (chunk_size > BUFFER_SIZE) { - LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); + MWARNING("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE); } std::string blob; @@ -196,14 +198,14 @@ void BootstrapFile::flush_chunk() long num_chars_written = pos_after - pos_before; if (static_cast<unsigned long>(num_chars_written) != chunk_size) { - LOG_PRINT_RED_L0("Error writing chunk: height: " << m_cur_height << " chunk_size: " << chunk_size << " num chars written: " << num_chars_written); + MFATAL("Error writing chunk: height: " << m_cur_height << " chunk_size: " << chunk_size << " num chars written: " << num_chars_written); throw std::runtime_error("Error writing chunk"); } m_buffer.clear(); delete m_output_stream; m_output_stream = new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(m_buffer); - LOG_PRINT_L1("flushed chunk: chunk_size: " << chunk_size); + MDEBUG("flushed chunk: chunk_size: " << chunk_size); } void BootstrapFile::write_block(block& block) @@ -267,10 +269,10 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem m_blockchain_storage = _blockchain_storage; m_tx_pool = _tx_pool; uint64_t progress_interval = 100; - LOG_PRINT_L0("Storing blocks raw data..."); + MINFO("Storing blocks raw data..."); if (!BootstrapFile::open_writer(output_file)) { - LOG_PRINT_RED_L0("failed to open raw file for write"); + MFATAL("failed to open raw file for write"); return false; } block b; @@ -280,16 +282,16 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem // height. uint64_t block_start = m_height; uint64_t block_stop = 0; - LOG_PRINT_L0("source blockchain height: " << m_blockchain_storage->get_current_blockchain_height()-1); + MINFO("source blockchain height: " << m_blockchain_storage->get_current_blockchain_height()-1); if ((requested_block_stop > 0) && (requested_block_stop < m_blockchain_storage->get_current_blockchain_height())) { - LOG_PRINT_L0("Using requested block height: " << requested_block_stop); + MINFO("Using requested block height: " << requested_block_stop); block_stop = requested_block_stop; } else { block_stop = m_blockchain_storage->get_current_blockchain_height() - 1; - LOG_PRINT_L0("Using block height of source blockchain: " << block_stop); + MINFO("Using block height of source blockchain: " << block_stop); } for (m_cur_height = block_start; m_cur_height <= block_stop; ++m_cur_height) { @@ -315,9 +317,9 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem std::cout << refresh_string; std::cout << "block " << m_cur_height-1 << "/" << block_stop << ENDL; - LOG_PRINT_L0("Number of blocks exported: " << num_blocks_written); + MINFO("Number of blocks exported: " << num_blocks_written); if (num_blocks_written > 0) - LOG_PRINT_L0("Largest chunk: " << m_max_chunk << " bytes"); + MINFO("Largest chunk: " << m_max_chunk << " bytes"); return BootstrapFile::close(); } @@ -338,11 +340,11 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) if (file_magic != blockchain_raw_magic) { - LOG_PRINT_RED_L0("bootstrap file not recognized"); + MFATAL("bootstrap file not recognized"); throw std::runtime_error("Aborting"); } else - LOG_PRINT_L0("bootstrap file recognized"); + MINFO("bootstrap file recognized"); uint32_t buflen_file_info; @@ -352,7 +354,7 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) throw std::runtime_error("Error reading expected number of bytes"); if (! ::serialization::parse_binary(str1, buflen_file_info)) throw std::runtime_error("Error in deserialization of buflen_file_info"); - LOG_PRINT_L1("bootstrap::file_info size: " << buflen_file_info); + MINFO("bootstrap::file_info size: " << buflen_file_info); if (buflen_file_info > sizeof(buf1)) throw std::runtime_error("Error: bootstrap::file_info size exceeds buffer size"); @@ -363,9 +365,9 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) bootstrap::file_info bfi; if (! ::serialization::parse_binary(str1, bfi)) throw std::runtime_error("Error in deserialization of bootstrap::file_info"); - LOG_PRINT_L0("bootstrap file v" << unsigned(bfi.major_version) << "." << unsigned(bfi.minor_version)); - LOG_PRINT_L0("bootstrap magic size: " << sizeof(file_magic)); - LOG_PRINT_L0("bootstrap header size: " << bfi.header_size); + MINFO("bootstrap file v" << unsigned(bfi.major_version) << "." << unsigned(bfi.minor_version)); + MINFO("bootstrap magic size: " << sizeof(file_magic)); + MINFO("bootstrap header size: " << bfi.header_size); uint64_t full_header_size = sizeof(file_magic) + bfi.header_size; import_file.seekg(full_header_size); @@ -379,7 +381,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) boost::system::error_code ec; if (!boost::filesystem::exists(raw_file_path, ec)) { - LOG_PRINT_L0("bootstrap file not found: " << raw_file_path); + MFATAL("bootstrap file not found: " << raw_file_path); throw std::runtime_error("Aborting"); } std::ifstream import_file; @@ -388,14 +390,14 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) uint64_t h = 0; if (import_file.fail()) { - LOG_PRINT_L0("import_file.open() fail"); + MFATAL("import_file.open() fail"); throw std::runtime_error("Aborting"); } uint64_t full_header_size; // 4 byte magic + length of header structures full_header_size = seek_to_first_chunk(import_file); - LOG_PRINT_L0("Scanning blockchain from bootstrap file..."); + MINFO("Scanning blockchain from bootstrap file..."); block b; bool quit = false; uint64_t bytes_read = 0; @@ -409,7 +411,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) import_file.read(buf1, sizeof(chunk_size)); if (!import_file) { std::cout << refresh_string; - LOG_PRINT_L1("End of file reached"); + MDEBUG("End of file reached"); quit = true; break; } @@ -425,38 +427,38 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) str1.assign(buf1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) throw std::runtime_error("Error in deserialization of chunk_size"); - LOG_PRINT_L3("chunk_size: " << chunk_size); + MDEBUG("chunk_size: " << chunk_size); if (chunk_size > BUFFER_SIZE) { std::cout << refresh_string; - LOG_PRINT_L0("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE + MWARNING("WARNING: chunk_size " << chunk_size << " > BUFFER_SIZE " << BUFFER_SIZE << " height: " << h-1); throw std::runtime_error("Aborting: chunk size exceeds buffer size"); } if (chunk_size > 100000) { std::cout << refresh_string; - LOG_PRINT_L0("NOTE: chunk_size " << chunk_size << " > 100000" << " height: " + MDEBUG("NOTE: chunk_size " << chunk_size << " > 100000" << " height: " << h-1); } else if (chunk_size <= 0) { std::cout << refresh_string; - LOG_PRINT_L0("ERROR: chunk_size " << chunk_size << " <= 0" << " height: " << h-1); + MDEBUG("ERROR: chunk_size " << chunk_size << " <= 0" << " height: " << h-1); throw std::runtime_error("Aborting"); } // skip to next expected block size value import_file.seekg(chunk_size, std::ios_base::cur); if (! import_file) { std::cout << refresh_string; - LOG_PRINT_L0("ERROR: unexpected end of file: bytes read before error: " + MFATAL("ERROR: unexpected end of file: bytes read before error: " << import_file.gcount() << " of chunk_size " << chunk_size); throw std::runtime_error("Aborting"); } bytes_read += chunk_size; // std::cout << refresh_string; - LOG_PRINT_L3("Number bytes scanned: " << bytes_read); + MDEBUG("Number bytes scanned: " << bytes_read); } import_file.close(); diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index 0bbe28f17..1a646b54b 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,7 +34,7 @@ #include <boost/iostreams/filtering_streambuf.hpp> -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_core/blockchain.h" #include <algorithm> diff --git a/src/blockchain_utilities/bootstrap_serialization.h b/src/blockchain_utilities/bootstrap_serialization.h index a7d88907f..f9f50f00b 100644 --- a/src/blockchain_utilities/bootstrap_serialization.h +++ b/src/blockchain_utilities/bootstrap_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,8 +28,8 @@ #pragma once -#include "cryptonote_core/cryptonote_boost_serialization.h" -#include "cryptonote_core/difficulty.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/difficulty.h" namespace cryptonote diff --git a/src/blockchain_utilities/cn_deserialize.cpp b/src/blockchain_utilities/cn_deserialize.cpp new file mode 100644 index 000000000..b178e4e03 --- /dev/null +++ b/src/blockchain_utilities/cn_deserialize.cpp @@ -0,0 +1,192 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/tx_extra.h" +#include "cryptonote_core/blockchain.h" +#include "blockchain_utilities.h" +#include "common/command_line.h" +#include "version.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bcutil" + +namespace po = boost::program_options; +using namespace epee; + +using namespace cryptonote; + +int main(int argc, char* argv[]) +{ + uint32_t log_level = 0; + std::string input; + + tools::sanitize_locale(); + + boost::filesystem::path output_file_path; + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; + const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; + const command_line::arg_descriptor<std::string> arg_input = {"input", "Specify input has a hexadecimal string", ""}; + + command_line::add_arg(desc_cmd_sett, arg_output_file); + command_line::add_arg(desc_cmd_sett, arg_log_level); + command_line::add_arg(desc_cmd_sett, arg_input); + + command_line::add_arg(desc_cmd_only, command_line::arg_help); + + po::options_description desc_options("Allowed options"); + desc_options.add(desc_cmd_only).add(desc_cmd_sett); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (! r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 1; + } + + log_level = command_line::get_arg(vm, arg_log_level); + input = command_line::get_arg(vm, arg_input); + if (input.empty()) + { + std::cerr << "--input is mandatory" << std::endl; + return 1; + } + + mlog_configure("", true); + + std::string m_config_folder; + + std::ostream *output; + std::ofstream *raw_data_file = NULL; + if (command_line::has_arg(vm, arg_output_file)) + { + output_file_path = boost::filesystem::path(command_line::get_arg(vm, arg_output_file)); + + const boost::filesystem::path dir_path = output_file_path.parent_path(); + if (!dir_path.empty()) + { + if (boost::filesystem::exists(dir_path)) + { + if (!boost::filesystem::is_directory(dir_path)) + { + std::cerr << "output directory path is a file: " << dir_path << std::endl; + return 1; + } + } + else + { + if (!boost::filesystem::create_directory(dir_path)) + { + std::cerr << "Failed to create directory " << dir_path << std::endl; + return 1; + } + } + } + + raw_data_file = new std::ofstream(); + raw_data_file->open(output_file_path.string(), std::ios_base::out | std::ios::trunc); + if (raw_data_file->fail()) + return 1; + output = raw_data_file; + } + else + { + output_file_path = ""; + output = &std::cout; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(input, blob)) + { + std::cerr << "Invalid hex input" << std::endl; + std::cerr << "Invalid hex input: " << input << std::endl; + return 1; + } + + cryptonote::block block; + cryptonote::transaction tx; + std::vector<cryptonote::tx_extra_field> fields; + if (cryptonote::parse_and_validate_block_from_blob(blob, block)) + { + std::cout << "Parsed block:" << std::endl; + std::cout << cryptonote::obj_to_json_str(block) << std::endl; + } + else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx)) + { + std::cout << "Parsed transaction:" << std::endl; + std::cout << cryptonote::obj_to_json_str(tx) << std::endl; + + if (cryptonote::parse_tx_extra(tx.extra, fields)) + { + std::cout << "tx_extra has " << fields.size() << " field(s)" << std::endl; + for (size_t n = 0; n < fields.size(); ++n) + { + std::cout << "field " << n << ": "; + if (typeid(cryptonote::tx_extra_padding) == fields[n].type()) std::cout << "extra padding: " << boost::get<cryptonote::tx_extra_padding>(fields[n]).size << " bytes"; + else if (typeid(cryptonote::tx_extra_pub_key) == fields[n].type()) std::cout << "extra pub key: " << boost::get<cryptonote::tx_extra_pub_key>(fields[n]).pub_key; + else if (typeid(cryptonote::tx_extra_nonce) == fields[n].type()) std::cout << "extra nonce: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_nonce>(fields[n]).nonce); + else if (typeid(cryptonote::tx_extra_merge_mining_tag) == fields[n].type()) std::cout << "extra merge mining tag: depth " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).depth << ", merkle root " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).merkle_root; + else if (typeid(cryptonote::tx_extra_mysterious_minergate) == fields[n].type()) std::cout << "extra minergate custom: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_mysterious_minergate>(fields[n]).data); + else std::cout << "unknown"; + std::cout << std::endl; + } + } + else + { + std::cout << "Failed to parse tx_extra" << std::endl; + } + } + else + { + std::cerr << "Not a recognized CN type" << std::endl; + return 1; + } + + + + if (output->fail()) + return 1; + output->flush(); + if (raw_data_file) + delete raw_data_file; + + return 0; +} diff --git a/src/blockchain_utilities/fake_core.h b/src/blockchain_utilities/fake_core.h index ba1c2ed72..70144184b 100644 --- a/src/blockchain_utilities/fake_core.h +++ b/src/blockchain_utilities/fake_core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -119,9 +119,9 @@ struct fake_core_db return m_storage.get_db().add_block(blk, block_size, cumulative_difficulty, coins_generated, txs); } - void batch_start(uint64_t batch_num_blocks = 0) + bool batch_start(uint64_t batch_num_blocks = 0) { - m_storage.get_db().batch_start(batch_num_blocks); + return m_storage.get_db().batch_start(batch_num_blocks); } void batch_stop() diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt index f65503fbc..3a866af5b 100644 --- a/src/blocks/CMakeLists.txt +++ b/src/blocks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -30,8 +30,8 @@ if(APPLE) add_library(blocks STATIC blockexports.c) set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) else() - add_custom_command(OUTPUT blocks.o COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) - add_custom_command(OUTPUT testnet_blocks.o COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) + add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) + add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) add_library(blocks STATIC blocks.o testnet_blocks.o blockexports.c) set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) endif() diff --git a/src/blocks/blockexports.c b/src/blocks/blockexports.c index 26adaad62..477167a12 100644 --- a/src/blocks/blockexports.c +++ b/src/blocks/blockexports.c @@ -2,12 +2,19 @@ #if defined(__APPLE__) #include <mach-o/getsect.h> - +#ifdef BUILD_SHARED_LIBS +#if !defined(__LP64__) +const struct mach_header _mh_execute_header; +#else +const struct mach_header_64 _mh_execute_header; +#endif +#else #if !defined(__LP64__) extern const struct mach_header _mh_execute_header; #else extern const struct mach_header_64 _mh_execute_header; #endif +#endif const unsigned char *get_blocks_dat_start(int testnet) { diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex 24cfffa38..a15f53e67 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 744559072..0a3e8cd19 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -26,12 +26,20 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) + set(common_sources base58.cpp command_line.cpp dns_utils.cpp + download.cpp util.cpp - i18n.cpp) + i18n.cpp + password.cpp + perf_timer.cpp + task_region.cpp + thread_group.cpp + updates.cpp) if (STACK_TRACE) list(APPEND common_sources stack_trace.cpp) @@ -43,7 +51,9 @@ set(common_private_headers base58.h boost_serialization_helper.h command_line.h + common_fwd.h dns_utils.h + download.h http_connection.h int-util.h pod-class.h @@ -53,16 +63,22 @@ set(common_private_headers util.h varint.h i18n.h - stack_trace.h) + password.h + perf_timer.h + stack_trace.h + task_region.h + thread_group.h + updates.h) -bitmonero_private_headers(common +monero_private_headers(common ${common_private_headers}) -bitmonero_add_library(common +monero_add_library(common ${common_sources} ${common_headers} ${common_private_headers}) target_link_libraries(common PUBLIC + epee crypto ${UNBOUND_LIBRARY} ${LIBUNWIND_LIBRARIES} @@ -70,8 +86,10 @@ target_link_libraries(common ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} + ${Boost_REGEX_LIBRARY} PRIVATE + ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) -#bitmonero_install_headers(common +#monero_install_headers(common # ${common_headers}) diff --git a/src/common/base58.cpp b/src/common/base58.cpp index 355d1e209..64cb7c0de 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/common/base58.h b/src/common/base58.h index df2ac2e9b..6dd850c03 100644 --- a/src/common/base58.h +++ b/src/common/base58.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/common/boost_serialization_helper.h b/src/common/boost_serialization_helper.h index c640a1705..4a503d830 100644 --- a/src/common/boost_serialization_helper.h +++ b/src/common/boost_serialization_helper.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,8 +30,9 @@ #pragma once -#include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> namespace tools @@ -53,8 +54,8 @@ namespace tools return false; } - FILE* data_file_file = _fdopen(data_file_descriptor, "wb"); - if (0 == data_file_file) + const std::unique_ptr<FILE, tools::close_file> data_file_file{_fdopen(data_file_descriptor, "wb")}; + if (nullptr == data_file_file) { // Call CloseHandle is not necessary _close(data_file_descriptor); @@ -62,11 +63,10 @@ namespace tools } // HACK: undocumented constructor, this code may not compile - std::ofstream data_file(data_file_file); + std::ofstream data_file(data_file_file.get()); if (data_file.fail()) { // Call CloseHandle and _close are not necessary - fclose(data_file_file); return false; } #else @@ -76,7 +76,7 @@ namespace tools return false; #endif - boost::archive::binary_oarchive a(data_file); + boost::archive::portable_binary_oarchive a(data_file); a << obj; if (data_file.fail()) return false; @@ -85,7 +85,6 @@ namespace tools #if defined(_MSC_VER) // To make sure the file is fully stored on disk ::FlushFileBuffers(data_file_handle); - fclose(data_file_file); #endif return true; @@ -101,9 +100,23 @@ namespace tools data_file.open( file_path, std::ios_base::binary | std::ios_base::in); if(data_file.fail()) return false; - boost::archive::binary_iarchive a(data_file); - - a >> obj; + try + { + // first try reading in portable mode + boost::archive::portable_binary_iarchive a(data_file); + a >> obj; + } + catch(...) + { + // if failed, try reading in unportable mode + boost::filesystem::copy_file(file_path, file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + data_file.close(); + data_file.open( file_path, std::ios_base::binary | std::ios_base::in); + if(data_file.fail()) + return false; + boost::archive::binary_iarchive a(data_file); + a >> obj; + } return !data_file.fail(); CATCH_ENTRY_L0("unserialize_obj_from_file", false); } diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index b3f488447..f71b3e576 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,11 +29,24 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "command_line.h" -#include "string_tools.h" +#include <boost/algorithm/string/compare.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <unordered_set> +#include "blockchain_db/db_types.h" +#include "common/i18n.h" #include "cryptonote_config.h" +#include "string_tools.h" namespace command_line { + namespace + { + const char* tr(const char* str) + { + return i18n_translate(str, "command_line"); + } + } + std::string input_line(const std::string& prompt) { std::cout << prompt; @@ -45,11 +58,24 @@ namespace command_line } + bool is_yes(const std::string& str) + { + if (str == "y" || str == "Y") + return true; + + boost::algorithm::is_iequal ignore_case{}; + if (boost::algorithm::equals("yes", str, ignore_case)) + return true; + if (boost::algorithm::equals(command_line::tr("yes"), str, ignore_case)) + return true; + + return false; + } + const arg_descriptor<bool> arg_help = {"help", "Produce help message"}; const arg_descriptor<bool> arg_version = {"version", "Output version information"}; const arg_descriptor<std::string> arg_data_dir = {"data-dir", "Specify data directory"}; const arg_descriptor<std::string> arg_testnet_data_dir = {"testnet-data-dir", "Specify testnet data directory"}; - const arg_descriptor<std::string> arg_user_agent = {"user-agent", "Restrict RPC use to clients using this user agent"}; const arg_descriptor<bool> arg_test_drop_download = {"test-drop-download", "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)"}; const arg_descriptor<uint64_t> arg_test_drop_download_height = {"test-drop-download-height", "Like test-drop-download but disards only after around certain height", 0}; const arg_descriptor<int> arg_test_dbg_lock_sleep = {"test-dbg-lock-sleep", "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests."}; @@ -63,9 +89,10 @@ namespace command_line , "checkpoints from DNS server will be enforced" , false }; + std::string arg_db_type_description = "Specify database type, available: " + boost::algorithm::join(cryptonote::blockchain_db_types, ", "); const command_line::arg_descriptor<std::string> arg_db_type = { "db-type" - , "Specify database type" + , arg_db_type_description.c_str() , DEFAULT_DB_TYPE }; const command_line::arg_descriptor<std::string> arg_db_sync_mode = { @@ -83,11 +110,6 @@ namespace command_line , "Max number of threads to use when preparing block hashes in groups." , 4 }; - const command_line::arg_descriptor<uint64_t> arg_db_auto_remove_logs = { - "db-auto-remove-logs" - , "For BerkeleyDB only. Remove transactions logs automatically." - , 1 - }; const command_line::arg_descriptor<uint64_t> arg_show_time_stats = { "show-time-stats" , "Show time-stats when processing blocks/txs and disk synchronization." @@ -98,4 +120,9 @@ namespace command_line , "How many blocks to sync at once during chain synchronization." , BLOCKS_SYNCHRONIZING_DEFAULT_COUNT }; + const command_line::arg_descriptor<std::string> arg_check_updates = { + "check-updates" + , "Check for new versions of monero: [disabled|notify|download|update]" + , "notify" + }; } diff --git a/src/common/command_line.h b/src/common/command_line.h index 0ea749168..2110b8849 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -43,6 +43,9 @@ namespace command_line std::string input_line(const std::string& prompt); + //! \return True if `str` is `is_iequal("y" || "yes" || `tr("yes"))`. + bool is_yes(const std::string& str); + template<typename T, bool required = false> struct arg_descriptor; @@ -165,7 +168,7 @@ namespace command_line { return parser(); } - catch (std::exception& e) + catch (const std::exception& e) { std::cerr << "Failed to parse arguments: " << e.what() << std::endl; std::cerr << desc << std::endl; @@ -204,7 +207,6 @@ namespace command_line extern const arg_descriptor<bool> arg_version; extern const arg_descriptor<std::string> arg_data_dir; extern const arg_descriptor<std::string> arg_testnet_data_dir; - extern const arg_descriptor<std::string> arg_user_agent; extern const arg_descriptor<bool> arg_test_drop_download; extern const arg_descriptor<uint64_t> arg_test_drop_download_height; extern const arg_descriptor<int> arg_test_dbg_lock_sleep; @@ -214,7 +216,7 @@ namespace command_line extern const arg_descriptor<std::string> arg_db_sync_mode; extern const arg_descriptor<uint64_t> arg_fast_block_sync; extern const arg_descriptor<uint64_t> arg_prep_blocks_threads; - extern const arg_descriptor<uint64_t> arg_db_auto_remove_logs; extern const arg_descriptor<uint64_t> arg_show_time_stats; extern const arg_descriptor<size_t> arg_block_sync_size; + extern const arg_descriptor<std::string> arg_check_updates; } diff --git a/src/simplewallet/password_container.h b/src/common/common_fwd.h index 62f43aa37..5d67251b1 100644 --- a/src/simplewallet/password_container.h +++ b/src/common/common_fwd.h @@ -1,21 +1,21 @@ -// Copyright (c) 2014-2016, The Monero Project -// +// Copyright (c) 2014-2017, The Monero Project +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL @@ -30,35 +30,12 @@ #pragma once -#include <string> -#include <boost/program_options/variables_map.hpp> - namespace tools { - class password_container - { - public: - static const size_t max_password_size = 1024; - password_container(bool verify); - password_container(password_container&& rhs); - password_container(std::string&& password); - ~password_container(); - - void clear(); - bool empty() const { return m_empty; } - const std::string& password() const { return m_password; } - void password(std::string&& val) { m_password = std::move(val); m_empty = false; } - bool read_password(const char *message = "password"); - - private: - //delete constructor with no parameters - password_container(); - bool read_from_file(); - bool read_from_tty(std::string & pass); - bool read_from_tty_double_check(const char *message); - - bool m_empty; - std::string m_password; - bool m_verify; - }; + class DNSResolver; + struct login; + class password_container; + class t_http_connection; + class task_region; + class thread_group; } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index e6e53a5c0..ab38cbbae 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -26,7 +26,10 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/command_line.h" +#include "common/i18n.h" #include "common/dns_utils.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include <cstring> #include <sstream> // check local first (in the event of static or in-source compilation of libunbound) @@ -34,10 +37,14 @@ #include <stdlib.h> #include "include_base_utils.h" +#include <random> #include <boost/filesystem/fstream.hpp> using namespace epee; namespace bf = boost::filesystem; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.dns" + static boost::mutex instance_lock; namespace @@ -323,4 +330,234 @@ bool DNSResolver::check_address_syntax(const char *addr) const return true; } +namespace dns_utils +{ + +const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); } + +//----------------------------------------------------------------------- +// TODO: parse the string in a less stupid way, probably with regex +std::string address_from_txt_record(const std::string& s) +{ + // make sure the txt record has "oa1:xmr" and find it + auto pos = s.find("oa1:xmr"); + if (pos == std::string::npos) + return {}; + // search from there to find "recipient_address=" + pos = s.find("recipient_address=", pos); + if (pos == std::string::npos) + return {}; + pos += 18; // move past "recipient_address=" + // find the next semicolon + auto pos2 = s.find(";", pos); + if (pos2 != std::string::npos) + { + // length of address == 95, we can at least validate that much here + if (pos2 - pos == 95) + { + return s.substr(pos, 95); + } + else if (pos2 - pos == 106) // length of address == 106 --> integrated address + { + return s.substr(pos, 106); + } + } + return {}; +} +/** + * @brief gets a monero address from the TXT record of a DNS entry + * + * gets the monero address from the TXT record of the DNS entry associated + * with <url>. If this lookup fails, or the TXT record does not contain an + * XMR address in the correct format, returns an empty string. <dnssec_valid> + * will be set true or false according to whether or not the DNS query passes + * DNSSEC validation. + * + * @param url the url to look up + * @param dnssec_valid return-by-reference for DNSSEC status of query + * + * @return a monero address (as a string) or an empty string + */ +std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid) +{ + std::vector<std::string> addresses; + // get txt records + bool dnssec_available, dnssec_isvalid; + std::string oa_addr = DNSResolver::instance().get_dns_format_from_oa_address(url); + auto records = DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid); + + // TODO: update this to allow for conveying that dnssec was not available + if (dnssec_available && dnssec_isvalid) + { + dnssec_valid = true; + } + else dnssec_valid = false; + + // for each txt record, try to find a monero address in it. + for (auto& rec : records) + { + std::string addr = address_from_txt_record(rec); + if (addr.size()) + { + addresses.push_back(addr); + } + } + return addresses; +} + +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, bool cli_confirm) +{ + // attempt to get address from dns query + auto addresses = addresses_from_url(url, dnssec_valid); + if (addresses.empty()) + { + LOG_ERROR("wrong address: " << url); + return {}; + } + // for now, move on only if one address found + if (addresses.size() > 1) + { + LOG_ERROR("not yet supported: Multiple Monero addresses found for given URL: " << url); + return {}; + } + if (!cli_confirm) + return addresses[0]; + // prompt user for confirmation. + // inform user of DNSSEC validation status as well. + std::string dnssec_str; + if (dnssec_valid) + { + dnssec_str = tr("DNSSEC validation passed"); + } + else + { + dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); + } + std::stringstream prompt; + prompt << tr("For URL: ") << url + << ", " << dnssec_str << std::endl + << tr(" Monero Address = ") << addresses[0] + << std::endl + << tr("Is this OK? (Y/n) ") + ; + // prompt the user for confirmation given the dns query and dnssec status + std::string confirm_dns_ok = command_line::input_line(prompt.str()); + if (std::cin.eof()) + { + return {}; + } + if (!command_line::is_yes(confirm_dns_ok)) + { + std::cout << tr("you have cancelled the transfer request") << std::endl; + return {}; + } + return addresses[0]; +} + +namespace +{ + bool dns_records_match(const std::vector<std::string>& a, const std::vector<std::string>& b) + { + if (a.size() != b.size()) return false; + + for (const auto& record_in_a : a) + { + bool ok = false; + for (const auto& record_in_b : b) + { + if (record_in_a == record_in_b) + { + ok = true; + break; + } + } + if (!ok) return false; + } + + return true; + } +} + +bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std::vector<std::string> &dns_urls) +{ + // Prevent infinite recursion when distributing + if (dns_urls.empty()) return false; + + std::vector<std::vector<std::string> > records; + records.resize(dns_urls.size()); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1); + size_t first_index = dis(gen); + + bool avail, valid; + size_t cur_index = first_index; + do + { + std::string url = dns_urls[cur_index]; + + records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); + if (!avail) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); + } + if (!valid) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); + } + + cur_index++; + if (cur_index == dns_urls.size()) + { + cur_index = 0; + } + } while (cur_index != first_index); + + size_t num_valid_records = 0; + + for( const auto& record_set : records) + { + if (record_set.size() != 0) + { + num_valid_records++; + } + } + + if (num_valid_records < 2) + { + LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); + return false; + } + + int good_records_index = -1; + for (size_t i = 0; i < records.size() - 1; ++i) + { + if (records[i].size() == 0) continue; + + for (size_t j = i + 1; j < records.size(); ++j) + { + if (dns_records_match(records[i], records[j])) + { + good_records_index = i; + break; + } + } + if (good_records_index >= 0) break; + } + + if (good_records_index < 0) + { + LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); + return false; + } + + good_records = records[good_records_index]; + return true; +} + +} // namespace tools::dns_utils + } // namespace tools diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 2e87fb01f..53c0c1c7b 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -25,9 +25,11 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once #include <vector> #include <string> +#include "cryptonote_basic/cryptonote_basic.h" namespace tools { @@ -155,4 +157,16 @@ private: DNSResolverData *m_data; }; // class DNSResolver +namespace dns_utils +{ + +std::string address_from_txt_record(const std::string& s); +std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid); + +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, bool cli_confirm = true); + +bool load_txt_records_from_dns(std::vector<std::string> &records, const std::vector<std::string> &dns_urls); + +} // namespace tools::dns_utils + } // namespace tools diff --git a/src/common/download.cpp b/src/common/download.cpp new file mode 100644 index 000000000..28aac5a59 --- /dev/null +++ b/src/common/download.cpp @@ -0,0 +1,270 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <string> +#include <atomic> +#include <boost/filesystem.hpp> +#include <boost/asio.hpp> +#include <boost/thread/thread.hpp> +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "net/http_client.h" +#include "download.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.dl" + +namespace tools +{ + struct download_thread_control + { + const std::string path; + const std::string uri; + std::function<void(const std::string&, const std::string&, bool)> result_cb; + std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress_cb; + bool stop; + bool stopped; + bool success; + boost::thread thread; + boost::mutex mutex; + + download_thread_control(const std::string &path, const std::string &uri, std::function<void(const std::string&, const std::string&, bool)> result_cb, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress_cb): + path(path), uri(uri), result_cb(result_cb), progress_cb(progress_cb), stop(false), stopped(false), success(false) {} + ~download_thread_control() { if (thread.joinable()) thread.detach(); } + }; + + static void download_thread(download_async_handle control) + { + static std::atomic<unsigned int> thread_id(0); + + MLOG_SET_THREAD_NAME("DL" + std::to_string(thread_id++)); + + struct stopped_setter + { + stopped_setter(const download_async_handle &control): control(control) {} + ~stopped_setter() { control->stopped = true; } + download_async_handle control; + } stopped_setter(control); + + try + { + boost::unique_lock<boost::mutex> lock(control->mutex); + MINFO("Downloading " << control->uri << " to " << control->path); + std::ofstream f; + f.open(control->path, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + if (!f.good()) { + MERROR("Failed to open file " << control->path); + control->result_cb(control->path, control->uri, control->success); + return; + } + class download_client: public epee::net_utils::http::http_simple_client + { + public: + download_client(download_async_handle control, std::ofstream &f): + control(control), f(f), content_length(-1), total(0) {} + virtual ~download_client() { f.close(); } + virtual bool on_header(const epee::net_utils::http::http_response_info &headers) + { + ssize_t length; + if (epee::string_tools::get_xtype_from_string(length, headers.m_header_info.m_content_length) && length >= 0) + { + MINFO("Content-Length: " << length); + content_length = length; + boost::filesystem::path path(control->path); + boost::filesystem::space_info si = boost::filesystem::space(path); + if (si.available < (size_t)content_length) + { + const uint64_t avail = (si.available + 1023) / 1024, needed = (content_length + 1023) / 1024; + MERROR("Not enough space to download " << needed << " kB to " << path << " (" << avail << " kB available)"); + return false; + } + } + return true; + } + virtual bool handle_target_data(std::string &piece_of_transfer) + { + try + { + boost::lock_guard<boost::mutex> lock(control->mutex); + if (control->stop) + return false; + f << piece_of_transfer; + total += piece_of_transfer.size(); + if (control->progress_cb && !control->progress_cb(control->path, control->uri, total, content_length)) + return false; + return f.good(); + } + catch (const std::exception &e) + { + MERROR("Error writing data: " << e.what()); + return false; + } + } + private: + download_async_handle control; + std::ofstream &f; + ssize_t content_length; + size_t total; + } client(control, f); + epee::net_utils::http::url_content u_c; + if (!epee::net_utils::parse_url(control->uri, u_c)) + { + MERROR("Failed to parse URL " << control->uri); + control->result_cb(control->path, control->uri, control->success); + return; + } + if (u_c.host.empty()) + { + MERROR("Failed to determine address from URL " << control->uri); + control->result_cb(control->path, control->uri, control->success); + return; + } + + lock.unlock(); + + uint16_t port = u_c.port ? u_c.port : 80; + MDEBUG("Connecting to " << u_c.host << ":" << port); + client.set_server(u_c.host, std::to_string(port), boost::none); + if (!client.connect(std::chrono::seconds(30))) + { + boost::lock_guard<boost::mutex> lock(control->mutex); + MERROR("Failed to connect to " << control->uri); + control->result_cb(control->path, control->uri, control->success); + return; + } + MDEBUG("GETting " << u_c.uri); + const epee::net_utils::http::http_response_info *info = NULL; + if (!client.invoke_get(u_c.uri, std::chrono::seconds(30), "", &info)) + { + boost::lock_guard<boost::mutex> lock(control->mutex); + MERROR("Failed to connect to " << control->uri); + client.disconnect(); + control->result_cb(control->path, control->uri, control->success); + return; + } + if (control->stop) + { + boost::lock_guard<boost::mutex> lock(control->mutex); + MDEBUG("Download cancelled"); + client.disconnect(); + control->result_cb(control->path, control->uri, control->success); + return; + } + if (!info) + { + boost::lock_guard<boost::mutex> lock(control->mutex); + MERROR("Failed invoking GET command to " << control->uri << ", no status info returned"); + client.disconnect(); + control->result_cb(control->path, control->uri, control->success); + return; + } + MDEBUG("response code: " << info->m_response_code); + MDEBUG("response length: " << info->m_header_info.m_content_length); + MDEBUG("response comment: " << info->m_response_comment); + MDEBUG("response body: " << info->m_body); + for (const auto &f: info->m_additional_fields) + MDEBUG("additional field: " << f.first << ": " << f.second); + if (info->m_response_code != 200) + { + boost::lock_guard<boost::mutex> lock(control->mutex); + MERROR("Status code " << info->m_response_code); + client.disconnect(); + control->result_cb(control->path, control->uri, control->success); + return; + } + client.disconnect(); + f.close(); + MDEBUG("Download complete"); + lock.lock(); + control->success = true; + control->result_cb(control->path, control->uri, control->success); + return; + } + catch (const std::exception &e) + { + MERROR("Exception in download thread: " << e.what()); + // fall through and call result_cb not from the catch block to avoid another exception + } + boost::lock_guard<boost::mutex> lock(control->mutex); + control->result_cb(control->path, control->uri, control->success); + } + + bool download(const std::string &path, const std::string &url, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> cb) + { + bool success = false; + download_async_handle handle = download_async(path, url, [&success](const std::string&, const std::string&, bool result) {success = result;}, cb); + download_wait(handle); + return success; + } + + download_async_handle download_async(const std::string &path, const std::string &url, std::function<void(const std::string&, const std::string&, bool)> result, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress) + { + download_async_handle control = std::make_shared<download_thread_control>(path, url, result, progress); + control->thread = boost::thread([control](){ download_thread(control); }); + return control; + } + + bool download_finished(const download_async_handle &control) + { + CHECK_AND_ASSERT_MES(control != 0, false, "NULL async download handle"); + boost::lock_guard<boost::mutex> lock(control->mutex); + return control->stopped; + } + + bool download_error(const download_async_handle &control) + { + CHECK_AND_ASSERT_MES(control != 0, false, "NULL async download handle"); + boost::lock_guard<boost::mutex> lock(control->mutex); + return !control->success; + } + + bool download_wait(const download_async_handle &control) + { + CHECK_AND_ASSERT_MES(control != 0, false, "NULL async download handle"); + { + boost::lock_guard<boost::mutex> lock(control->mutex); + if (control->stopped) + return true; + } + control->thread.join(); + return true; + } + + bool download_cancel(const download_async_handle &control) + { + CHECK_AND_ASSERT_MES(control != 0, false, "NULL async download handle"); + { + boost::lock_guard<boost::mutex> lock(control->mutex); + if (control->stopped) + return true; + control->stop = true; + } + control->thread.join(); + return true; + } +} diff --git a/src/common/download.h b/src/common/download.h new file mode 100644 index 000000000..917cb2278 --- /dev/null +++ b/src/common/download.h @@ -0,0 +1,44 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> + +namespace tools +{ + struct download_thread_control; + typedef std::shared_ptr<download_thread_control> download_async_handle; + + bool download(const std::string &path, const std::string &url, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress = NULL); + download_async_handle download_async(const std::string &path, const std::string &url, std::function<void(const std::string&, const std::string&, bool)> result, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress = NULL); + bool download_error(const download_async_handle &h); + bool download_finished(const download_async_handle &h); + bool download_wait(const download_async_handle &h); + bool download_cancel(const download_async_handle &h); +} diff --git a/src/common/http_connection.h b/src/common/http_connection.h index 156474cdd..0357a90a0 100644 --- a/src/common/http_connection.h +++ b/src/common/http_connection.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,6 +28,7 @@ #pragma once +#include <chrono> #include "string_tools.h" #include "net/http_client.h" @@ -38,20 +39,16 @@ private: epee::net_utils::http::http_simple_client * mp_http_client; bool m_ok; public: - static unsigned int const TIMEOUT = 200000; + static constexpr std::chrono::seconds TIMEOUT() + { + return std::chrono::minutes(3) + std::chrono::seconds(30); + } - t_http_connection( - epee::net_utils::http::http_simple_client * p_http_client - , uint32_t ip - , uint16_t port - ) + t_http_connection(epee::net_utils::http::http_simple_client* p_http_client) : mp_http_client(p_http_client) , m_ok(false) { - // TODO fix http client so that it accepts properly typed arguments - std::string ip_str = epee::string_tools::get_ip_string_from_int32(ip); - std::string port_str = boost::lexical_cast<std::string>(port); - m_ok = mp_http_client->connect(ip_str, port_str, TIMEOUT); + m_ok = mp_http_client->connect(TIMEOUT()); } ~t_http_connection() diff --git a/src/common/i18n.cpp b/src/common/i18n.cpp index c04790ce6..4a76e76fc 100644 --- a/src/common/i18n.cpp +++ b/src/common/i18n.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -36,6 +36,9 @@ #include "common/util.h" #include "common/i18n.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "i18n" + static const unsigned char qm_magic[16] = {0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95, 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd}; static std::map<std::string,std::string> i18n_entries; diff --git a/src/common/i18n.h b/src/common/i18n.h index 332602185..5169cf9f7 100644 --- a/src/common/i18n.h +++ b/src/common/i18n.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/common/int-util.h b/src/common/int-util.h index e9eddee9c..34288805a 100644 --- a/src/common/int-util.h +++ b/src/common/int-util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -36,6 +36,10 @@ #include <string.h> #include <sys/param.h> +#if defined(__ANDROID__) +#include <byteswap.h> +#endif + #if defined(_MSC_VER) #include <stdlib.h> @@ -138,16 +142,24 @@ static inline uint32_t ident32(uint32_t x) { return x; } static inline uint64_t ident64(uint64_t x) { return x; } #ifndef __OpenBSD__ +# if defined(__ANDROID__) && defined(__swap32) && !defined(swap32) +# define swap32 __swap32 +# elif !defined(swap32) static inline uint32_t swap32(uint32_t x) { x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8); return (x << 16) | (x >> 16); } +# endif +# if defined(__ANDROID__) && defined(__swap64) && !defined(swap64) +# define swap64 __swap64 +# elif !defined(swap64) static inline uint64_t swap64(uint64_t x) { x = ((x & 0x00ff00ff00ff00ff) << 8) | ((x & 0xff00ff00ff00ff00) >> 8); x = ((x & 0x0000ffff0000ffff) << 16) | ((x & 0xffff0000ffff0000) >> 16); return (x << 32) | (x >> 32); } -#endif +# endif +#endif /* __OpenBSD__ */ #if defined(__GNUC__) #define UNUSED __attribute__((unused)) diff --git a/src/simplewallet/password_container.cpp b/src/common/password.cpp index 480d132e7..bdc9c69c0 100644 --- a/src/simplewallet/password_container.cpp +++ b/src/common/password.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "password_container.h" +#include "password.h" #include <iostream> #include <memory.h> @@ -42,153 +42,17 @@ #include <unistd.h> #endif -namespace tools +namespace { - namespace - { - bool is_cin_tty(); - } - // deleted via private member - password_container::password_container() - : m_empty(true),m_verify(true) - { - - } - password_container::password_container(bool verify) - : m_empty(true),m_verify(verify) - { - - } - - password_container::password_container(std::string&& password) - : m_empty(false) - , m_password(std::move(password)) - , m_verify(false) - { - - } - - - password_container::password_container(password_container&& rhs) - : m_empty(std::move(rhs.m_empty)) - , m_password(std::move(rhs.m_password)) - , m_verify(std::move(rhs.m_verify)) - { - } - password_container::~password_container() - { - clear(); - } - - void password_container::clear() - { - if (0 < m_password.capacity()) - { - m_password.replace(0, m_password.capacity(), m_password.capacity(), '\0'); - m_password.resize(0); - } - m_empty = true; - } - - bool password_container::read_password(const char *message) - { - clear(); - - bool r; - if (is_cin_tty()) - { - r = read_from_tty_double_check(message); - } - else - { - r = read_from_file(); - } - - if (r) - { - m_empty = false; - } - else - { - clear(); - } - - return r; - } - - bool password_container::read_from_file() - { - m_password.reserve(max_password_size); - for (size_t i = 0; i < max_password_size; ++i) - { - char ch = static_cast<char>(std::cin.get()); - if (std::cin.eof() || ch == '\n' || ch == '\r') - { - break; - } - else if (std::cin.fail()) - { - return false; - } - else - { - m_password.push_back(ch); - } - } - - return true; - } - -bool password_container::read_from_tty_double_check(const char *message) { - std::string pass1; - std::string pass2; - bool match=false; - bool doNotVerifyEntry=false; - do{ - if (message) - std::cout << message <<": "; - if (!password_container::read_from_tty(pass1)) - return false; - if (m_verify==true){//double check password; - if (message) - std::cout << message << ": "; - if (!password_container::read_from_tty(pass2)) - return false; - if(pass1!=pass2){ //new password entered did not match - - std::cout << "Passwords do not match" << std::endl; - pass1=""; - pass2=""; - match=false; - } - else{//new password matches - match=true; - } - } - else - doNotVerifyEntry=true; //do not verify - //No need to verify password entered at this point in the code - - }while(match==false && doNotVerifyEntry==false); - - m_password=pass1; - return true; - } - - #if defined(_WIN32) - - namespace + bool is_cin_tty() noexcept { - bool is_cin_tty() - { - return 0 != _isatty(_fileno(stdin)); - } + return 0 != _isatty(_fileno(stdin)); } - bool password_container::read_from_tty(std::string & pass) + bool read_from_tty(std::string& pass) { - const char BACKSPACE = 8; + static constexpr const char BACKSPACE = 8; HANDLE h_cin = ::GetStdHandle(STD_INPUT_HANDLE); @@ -198,8 +62,8 @@ bool password_container::read_from_tty_double_check(const char *message) { ::SetConsoleMode(h_cin, mode_new); bool r = true; - pass.reserve(max_password_size); - while (pass.size() < max_password_size) + pass.reserve(tools::password_container::max_password_size); + while (pass.size() < tools::password_container::max_password_size) { DWORD read; char ch; @@ -235,38 +99,36 @@ bool password_container::read_from_tty_double_check(const char *message) { return r; } -#else +#else // end WIN32 - namespace + bool is_cin_tty() noexcept { - bool is_cin_tty() - { - return 0 != isatty(fileno(stdin)); - } + return 0 != isatty(fileno(stdin)); + } - int getch() - { - struct termios tty_old; - tcgetattr(STDIN_FILENO, &tty_old); + int getch() noexcept + { + struct termios tty_old; + tcgetattr(STDIN_FILENO, &tty_old); - struct termios tty_new; - tty_new = tty_old; - tty_new.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &tty_new); + struct termios tty_new; + tty_new = tty_old; + tty_new.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &tty_new); - int ch = getchar(); + int ch = getchar(); - tcsetattr(STDIN_FILENO, TCSANOW, &tty_old); + tcsetattr(STDIN_FILENO, TCSANOW, &tty_old); - return ch; - } + return ch; } - bool password_container::read_from_tty(std::string &aPass) + + bool read_from_tty(std::string& aPass) { - const char BACKSPACE = 127; + static constexpr const char BACKSPACE = 127; - aPass.reserve(max_password_size); - while (aPass.size() < max_password_size) + aPass.reserve(tools::password_container::max_password_size); + while (aPass.size() < tools::password_container::max_password_size) { int ch = getch(); if (EOF == ch) @@ -297,5 +159,113 @@ bool password_container::read_from_tty_double_check(const char *message) { return true; } -#endif -} +#endif // end !WIN32 + + void clear(std::string& pass) noexcept + { + //! TODO Call a memory wipe function that hopefully is not optimized out + pass.replace(0, pass.capacity(), pass.capacity(), '\0'); + pass.clear(); + } + + bool read_from_tty(const bool verify, const char *message, std::string& pass1, std::string& pass2) + { + while (true) + { + if (message) + std::cout << message <<": "; + if (!read_from_tty(pass1)) + return false; + if (verify) + { + std::cout << "Confirm Password: "; + if (!read_from_tty(pass2)) + return false; + if(pass1!=pass2) + { + std::cout << "Passwords do not match! Please try again." << std::endl; + clear(pass1); + clear(pass2); + } + else //new password matches + return true; + } + else + return true; + //No need to verify password entered at this point in the code + } + + return false; + } + + bool read_from_file(std::string& pass) + { + pass.reserve(tools::password_container::max_password_size); + for (size_t i = 0; i < tools::password_container::max_password_size; ++i) + { + char ch = static_cast<char>(std::cin.get()); + if (std::cin.eof() || ch == '\n' || ch == '\r') + { + break; + } + else if (std::cin.fail()) + { + return false; + } + else + { + pass.push_back(ch); + } + } + return true; + } + +} // anonymous namespace + +namespace tools +{ + // deleted via private member + password_container::password_container() noexcept : m_password() {} + password_container::password_container(std::string&& password) noexcept + : m_password(std::move(password)) + { + } + + password_container::~password_container() noexcept + { + clear(m_password); + } + + boost::optional<password_container> password_container::prompt(const bool verify, const char *message) + { + password_container pass1{}; + password_container pass2{}; + if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) + return {std::move(pass1)}; + + return boost::none; + } + + boost::optional<login> login::parse(std::string&& userpass, bool verify, const char* message) + { + login out{}; + password_container wipe{std::move(userpass)}; + + const auto loc = wipe.password().find(':'); + if (loc == std::string::npos) + { + auto result = tools::password_container::prompt(verify, message); + if (!result) + return boost::none; + + out.password = std::move(*result); + } + else + { + out.password = password_container{wipe.password().substr(loc + 1)}; + } + + out.username = wipe.password().substr(0, loc); + return {std::move(out)}; + } +} diff --git a/src/common/password.h b/src/common/password.h new file mode 100644 index 000000000..12f715df4 --- /dev/null +++ b/src/common/password.h @@ -0,0 +1,96 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <string> +#include <boost/optional/optional.hpp> + +namespace tools +{ + class password_container + { + public: + static constexpr const size_t max_password_size = 1024; + + //! Empty password + password_container() noexcept; + + //! `password` is used as password + password_container(std::string&& password) noexcept; + + //! \return A password from stdin TTY prompt or `std::cin` pipe. + static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password"); + + password_container(const password_container&) = delete; + password_container(password_container&& rhs) = default; + + //! Wipes internal password + ~password_container() noexcept; + + password_container& operator=(const password_container&) = delete; + password_container& operator=(password_container&&) = default; + + const std::string& password() const noexcept { return m_password; } + + private: + //! TODO Custom allocator that locks to RAM? + std::string m_password; + }; + + struct login + { + login() = default; + + /*! + Extracts username and password from the format `username:password`. A + blank username or password is allowed. If the `:` character is not + present, `password_container::prompt` will be called by forwarding the + `verify` and `message` arguments. + + \param userpass Is "consumed", and the memory contents are wiped. + \param verify is passed to `password_container::prompt` if necessary. + \param message is passed to `password_container::prompt` if necessary. + + \return The username and password, or boost::none if + `password_container::prompt` fails. + */ + static boost::optional<login> parse(std::string&& userpass, bool verify, const char* message = "Password"); + + login(const login&) = delete; + login(login&&) = default; + ~login() = default; + login& operator=(const login&) = delete; + login& operator=(login&&) = default; + + std::string username; + password_container password; + }; +} diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp new file mode 100644 index 000000000..3b68485d9 --- /dev/null +++ b/src/common/perf_timer.cpp @@ -0,0 +1,51 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "perf_timer.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "perf" + +namespace tools +{ + +el::Level performance_timer_log_level = el::Level::Debug; +__thread std::vector<PerformanceTimer*> *performance_timers = NULL; + +void set_performance_timer_log_level(el::Level level) +{ + if (level != el::Level::Debug && level != el::Level::Trace && level != el::Level::Info + && level != el::Level::Warning && level != el::Level::Error && level != el::Level::Fatal) + { + MERROR("Wrong log level: " << el::LevelHelper::convertToString(level) << ", using Debug"); + level = el::Level::Debug; + } + performance_timer_log_level = level; +} + +} diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h new file mode 100644 index 000000000..56662ff24 --- /dev/null +++ b/src/common/perf_timer.h @@ -0,0 +1,95 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> +#include <stdio.h> +#include "misc_log_ex.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "perf" + +namespace tools +{ + +class PerformanceTimer; + +extern el::Level performance_timer_log_level; +extern __thread std::vector<PerformanceTimer*> *performance_timers; + +class PerformanceTimer +{ +public: + PerformanceTimer(const std::string &s, el::Level l = el::Level::Debug): name(s), level(l), started(false) + { + ticks = epee::misc_utils::get_tick_count(); + if (!performance_timers) + { + MLOG(level, "PERF ----------"); + performance_timers = new std::vector<PerformanceTimer*>(); + } + else + { + PerformanceTimer *pt = performance_timers->back(); + if (!pt->started) + { + MLOG(pt->level, "PERF " << std::string((performance_timers->size()-1) * 2, ' ') << " " << pt->name); + pt->started = true; + } + } + performance_timers->push_back(this); + } + + ~PerformanceTimer() + { + performance_timers->pop_back(); + ticks = epee::misc_utils::get_tick_count() - ticks; + char s[12]; + snprintf(s, sizeof(s), "%8llu ", (unsigned long long)ticks); + MLOG(level, "PERF " << s << std::string(performance_timers->size() * 2, ' ') << " " << name); + if (performance_timers->empty()) + { + delete performance_timers; + performance_timers = NULL; + } + } + +private: + std::string name; + el::Level level; + uint64_t ticks; + bool started; +}; + +void set_performance_timer_log_level(el::Level level); + +#define PERF_TIMER(name) tools::PerformanceTimer pt_##name(#name, tools::performance_timer_log_level) +#define PERF_TIMER_L(name, l) tools::PerformanceTimer pt_##name(#name, l) + +} diff --git a/src/common/pod-class.h b/src/common/pod-class.h index f3f241552..3896d5c29 100644 --- a/src/common/pod-class.h +++ b/src/common/pod-class.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/common/rpc_client.h b/src/common/rpc_client.h index 9c0036198..8494b4a60 100644 --- a/src/common/rpc_client.h +++ b/src/common/rpc_client.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,13 +28,15 @@ #pragma once +#include <boost/optional/optional.hpp> + #include "common/http_connection.h" #include "common/scoped_message_writer.h" #include "rpc/core_rpc_server_commands_defs.h" #include "storages/http_abstract_invoke.h" +#include "net/http_auth.h" #include "net/http_client.h" #include "string_tools.h" -#include <boost/lexical_cast.hpp> namespace tools { @@ -42,27 +44,17 @@ namespace tools { private: epee::net_utils::http::http_simple_client m_http_client; - uint32_t m_ip; - uint16_t m_port; public: t_rpc_client( uint32_t ip , uint16_t port + , boost::optional<epee::net_utils::http::login> user ) : m_http_client{} - , m_ip{ip} - , m_port{port} - {} - - std::string build_url(std::string const & relative_url) const { - std::string result = - "http://" - + epee::string_tools::get_ip_string_from_int32(m_ip) - + ":" - + boost::lexical_cast<std::string>(m_port) - + relative_url; - return result; + m_http_client.set_server( + epee::string_tools::get_ip_string_from_int32(ip), std::to_string(port), std::move(user) + ); } template <typename T_req, typename T_res> @@ -72,8 +64,7 @@ namespace tools , std::string const & method_name ) { - std::string rpc_url = build_url("/json_rpc"); - t_http_connection connection(&m_http_client, m_ip, m_port); + t_http_connection connection(&m_http_client); bool ok = connection.is_open(); if (!ok) @@ -81,7 +72,7 @@ namespace tools fail_msg_writer() << "Couldn't connect to daemon"; return false; } - ok = ok && epee::net_utils::invoke_http_json_rpc(rpc_url, method_name, req, res, m_http_client); + ok = ok && epee::net_utils::invoke_http_json_rpc("/json_rpc", method_name, req, res, m_http_client, t_http_connection::TIMEOUT()); if (!ok) { fail_msg_writer() << "Daemon request failed"; @@ -101,11 +92,10 @@ namespace tools , std::string const & fail_msg ) { - std::string rpc_url = build_url("/json_rpc"); - t_http_connection connection(&m_http_client, m_ip, m_port); + t_http_connection connection(&m_http_client); bool ok = connection.is_open(); - ok = ok && epee::net_utils::invoke_http_json_rpc(rpc_url, method_name, req, res, m_http_client); + ok = ok && epee::net_utils::invoke_http_json_rpc("/json_rpc", method_name, req, res, m_http_client, t_http_connection::TIMEOUT()); if (!ok) { fail_msg_writer() << "Couldn't connect to daemon"; @@ -130,11 +120,10 @@ namespace tools , std::string const & fail_msg ) { - std::string rpc_url = build_url(relative_url); - t_http_connection connection(&m_http_client, m_ip, m_port); + t_http_connection connection(&m_http_client); bool ok = connection.is_open(); - ok = ok && epee::net_utils::invoke_http_json_remote_command2(rpc_url, req, res, m_http_client); + ok = ok && epee::net_utils::invoke_http_json(relative_url, req, res, m_http_client, t_http_connection::TIMEOUT()); if (!ok) { fail_msg_writer() << "Couldn't connect to daemon"; @@ -153,7 +142,7 @@ namespace tools bool check_connection() { - t_http_connection connection(&m_http_client, m_ip, m_port); + t_http_connection connection(&m_http_client); return connection.is_open(); } }; diff --git a/src/common/scoped_message_writer.h b/src/common/scoped_message_writer.h index 77dda7181..7ee4f1379 100644 --- a/src/common/scoped_message_writer.h +++ b/src/common/scoped_message_writer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,20 +34,23 @@ namespace tools { +/************************************************************************/ +/* */ +/************************************************************************/ class scoped_message_writer { private: bool m_flush; std::stringstream m_oss; - epee::log_space::console_colors m_color; + epee::console_colors m_color; bool m_bright; - int m_log_level; + el::Level m_log_level; public: scoped_message_writer( - epee::log_space::console_colors color = epee::log_space::console_color_default + epee::console_colors color = epee::console_color_default , bool bright = false , std::string&& prefix = std::string() - , int log_level = LOG_LEVEL_2 + , el::Level log_level = el::Level::Info ) : m_flush(true) , m_color(color) @@ -88,17 +91,17 @@ public: { m_flush = false; - LOG_PRINT(m_oss.str(), m_log_level); + MCLOG(m_log_level, "msgwriter", m_oss.str()); - if (epee::log_space::console_color_default == m_color) + if (epee::console_color_default == m_color) { std::cout << m_oss.str(); } else { - epee::log_space::set_console_color(m_color, m_bright); + set_console_color(m_color, m_bright); std::cout << m_oss.str(); - epee::log_space::reset_console_color(); + epee::reset_console_color(); } std::cout << std::endl; } @@ -107,17 +110,17 @@ public: inline scoped_message_writer success_msg_writer() { - return scoped_message_writer(epee::log_space::console_color_green, false, std::string(), LOG_LEVEL_2); + return scoped_message_writer(epee::console_color_green, false, std::string(), el::Level::Info); } -inline scoped_message_writer msg_writer(epee::log_space::console_colors color = epee::log_space::console_color_default) +inline scoped_message_writer msg_writer(epee::console_colors color = epee::console_color_default) { - return scoped_message_writer(color, false, std::string(), LOG_LEVEL_2); + return scoped_message_writer(color, false, std::string(), el::Level::Info); } inline scoped_message_writer fail_msg_writer() { - return scoped_message_writer(epee::log_space::console_color_red, true, "Error: ", LOG_LEVEL_0); + return scoped_message_writer(epee::console_color_red, true, "Error: ", el::Level::Error); } } // namespace tools diff --git a/src/common/stack_trace.cpp b/src/common/stack_trace.cpp index 4387d4ffd..ef64c20c5 100644 --- a/src/common/stack_trace.cpp +++ b/src/common/stack_trace.cpp @@ -26,15 +26,26 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#if !defined __GNUC__ || defined __MINGW32__ || defined __MINGW64__ || defined __ANDROID__ +#define USE_UNWIND +#endif + #include "common/stack_trace.h" #include "misc_log_ex.h" +#ifdef USE_UNWIND #define UNW_LOCAL_ONLY #include <libunwind.h> #include <cxxabi.h> +#endif #ifndef STATICLIB #include <dlfcn.h> #endif +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "stacktrace" + +#define ST_LOG(x) CINFO(el::base::Writer,el::base::DispatchAction::FileOnlyLog,MONERO_DEFAULT_LOG_CATEGORY) << x + // from http://stackoverflow.com/questions/11665829/how-can-i-print-stack-trace-for-caught-exceptions-in-c-code-injection-in-c // The decl of __cxa_throw in /usr/include/.../cxxabi.h uses @@ -94,6 +105,7 @@ void set_stack_trace_log(const std::string &log) void log_stack_trace(const char *msg) { +#ifdef USE_UNWIND unw_context_t ctx; unw_cursor_t cur; unw_word_t ip, off; @@ -101,38 +113,50 @@ void log_stack_trace(const char *msg) char sym[512], *dsym; int status; const char *log = stack_trace_log.empty() ? NULL : stack_trace_log.c_str(); +#endif if (msg) - LOG_PRINT2(log, msg, LOG_LEVEL_0); - LOG_PRINT2(log, "Unwinded call stack:", LOG_LEVEL_0); + ST_LOG(msg); + ST_LOG("Unwound call stack:"); + +#ifdef USE_UNWIND if (unw_getcontext(&ctx) < 0) { - LOG_PRINT2(log, "Failed to create unwind context", LOG_LEVEL_0); + ST_LOG("Failed to create unwind context"); return; } if (unw_init_local(&cur, &ctx) < 0) { - LOG_PRINT2(log, "Failed to find the first unwind frame", LOG_LEVEL_0); + ST_LOG("Failed to find the first unwind frame"); return; } for (level = 1; level < 999; ++level) { // 999 for safety int ret = unw_step(&cur); if (ret < 0) { - LOG_PRINT2(log, "Failed to find the next frame", LOG_LEVEL_0); + ST_LOG("Failed to find the next frame"); return; } if (ret == 0) break; if (unw_get_reg(&cur, UNW_REG_IP, &ip) < 0) { - LOG_PRINT2(log, " " << std::setw(4) << level, LOG_LEVEL_0); + ST_LOG(" " << std::setw(4) << level); continue; } if (unw_get_proc_name(&cur, sym, sizeof(sym), &off) < 0) { - LOG_PRINT2(log, " " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip, LOG_LEVEL_0); + ST_LOG(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip); continue; } dsym = abi::__cxa_demangle(sym, NULL, NULL, &status); - LOG_PRINT2(log, " " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip << " " << (!status && dsym ? dsym : sym) << " + " << "0x" << off, LOG_LEVEL_0); + ST_LOG(" " << std::setw(4) << level << std::setbase(16) << std::setw(20) << "0x" << ip << " " << (!status && dsym ? dsym : sym) << " + " << "0x" << off); free(dsym); } +#else + std::stringstream ss; + ss << el::base::debug::StackTrace(); + std::vector<std::string> lines; + std::string s = ss.str(); + boost::split(lines, s, boost::is_any_of("\n")); + for (const auto &line: lines) + ST_LOG(line); +#endif } } // namespace tools diff --git a/src/common/task_region.cpp b/src/common/task_region.cpp new file mode 100644 index 000000000..9b4620c6e --- /dev/null +++ b/src/common/task_region.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/task_region.h" + +#include <boost/thread/locks.hpp> +#include <cassert> + +/* `mark_completed` and `wait` can throw in the lock call, but its difficult to +recover from either. An exception in `wait` means the post condition of joining +all threads cannot be achieved, and an exception in `mark_completed` means +certain deadlock. `noexcept` qualifier will force a call to `std::terminate` if +locking throws an exception, which should only happen if a recursive lock +attempt is made (which is not possible since no external function is called +while holding the lock). */ + +namespace tools +{ +void task_region_handle::state::mark_completed(id task_id) noexcept { + assert(task_id != 0 && (task_id & (task_id - 1)) == 0); // power of 2 check + if (pending.fetch_and(~task_id) == task_id) { + // synchronize with wait call, but do not need to hold + boost::unique_lock<boost::mutex>{sync_on_complete}; + all_complete.notify_all(); + } +} + +void task_region_handle::state::abort() noexcept { + state* current = this; + while (current) { + current->ready = 0; + current = current->next.get(); + } +} + +void task_region_handle::state::wait() noexcept { + state* current = this; + while (current) { + { + boost::unique_lock<boost::mutex> lock{current->sync_on_complete}; + current->all_complete.wait(lock, [current] { return current->pending == 0; }); + } + current = current->next.get(); + } +} + +void task_region_handle::state::wait(thread_group& threads) noexcept { + state* current = this; + while (current) { + while (current->pending != 0) { + if (!threads.try_run_one()) { + current->wait(); + return; + } + } + current = current->next.get(); + } +} + +void task_region_handle::create_state() { + st = std::make_shared<state>(std::move(st)); + next_id = 1; +} + +void task_region_handle::do_wait() noexcept { + assert(st); + const std::shared_ptr<state> temp = std::move(st); + temp->wait(threads); +} +} diff --git a/src/common/task_region.h b/src/common/task_region.h new file mode 100644 index 000000000..30972cce3 --- /dev/null +++ b/src/common/task_region.h @@ -0,0 +1,223 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once + +#include <atomic> +#include <boost/thread/condition_variable.hpp> +#include <boost/thread/mutex.hpp> +#include <memory> +#include <type_traits> +#include <utility> + +#include "common/thread_group.h" + +namespace tools +{ + +/*! A model of the fork-join concept. `run(...)` "forks" (i.e. spawns new +tasks), and `~task_region_handle()` or `wait()` "joins" the spawned tasks. +`wait` will block until all tasks have completed, while `~task_region_handle()` +blocks until all tasks have completed or aborted. + +Do _NOT_ give this object to separate thread of execution (which includes +`task_region_handle::run(...)`) because joining on a different thread is +undesireable (potential deadlock). + +This class cannot be constructed directly, use the function +`task_region(...)` instead. +*/ +class task_region_handle +{ + struct state + { + using id = unsigned; + + explicit state(std::shared_ptr<state> next_src) noexcept + : next(std::move(next_src)) + , ready(0) + , pending(0) + , sync_on_complete() + , all_complete() { + } + + state(const state&) = default; + state(state&&) = default; + ~state() = default; + state& operator=(const state&) = default; + state& operator=(state&&) = default; + + void track_id(id task_id) noexcept { + pending |= task_id; + ready |= task_id; + } + + //! \return True only once whether a given id can execute + bool can_run(id task_id) noexcept { + return (ready.fetch_and(~task_id) & task_id); + } + + //! Mark id as completed, and synchronize with waiting threads + void mark_completed(id task_id) noexcept; + + //! Tell all unstarted functions in region to return immediately + void abort() noexcept; + + //! Blocks until all functions in region have aborted or completed. + void wait() noexcept; + + //! Same as `wait()`, except `this_thread` runs tasks while waiting. + void wait(thread_group& threads) noexcept; + + private: + /* This implementation is a bit pessimistic, it ensures that all copies + of a wrapped task can only be executed once. `thread_group` should never + do this, but some variable needs to track whether an abort should be done + anyway... */ + std::shared_ptr<state> next; + std::atomic<id> ready; //!< Tracks whether a task has been invoked + std::atomic<id> pending; //!< Tracks when a task has completed or aborted + boost::mutex sync_on_complete; + boost::condition_variable all_complete; + }; + + template<typename F> + struct wrapper + { + wrapper(state::id id_src, std::shared_ptr<state> st_src, F f_src) + : task_id(id_src), st(std::move(st_src)), f(std::move(f_src)) { + } + + wrapper(const wrapper&) = default; + wrapper(wrapper&&) = default; + wrapper& operator=(const wrapper&) = default; + wrapper& operator=(wrapper&&) = default; + + void operator()() { + if (st) { + if (st->can_run(task_id)) { + f(); + } + st->mark_completed(task_id); + } + } + + private: + const state::id task_id; + std::shared_ptr<state> st; + F f; + }; + +public: + friend struct task_region_; + + task_region_handle() = delete; + task_region_handle(const task_region_handle&) = delete; + task_region_handle(task_region_handle&&) = delete; + + //! Cancels unstarted pending tasks, and waits for them to respond. + ~task_region_handle() noexcept { + if (st) { + st->abort(); + st->wait(threads); + } + } + + task_region_handle& operator=(const task_region_handle&) = delete; + task_region_handle& operator=(task_region_handle&&) = delete; + + /*! If the group has no threads, `f` is immediately run before returning. + Otherwise, `f` is dispatched to the thread_group associated with `this` + region. If `f` is dispatched to another thread, and it throws, the process + will immediately terminate. See std::packaged_task for getting exceptions on + functions executed on other threads. */ + template<typename F> + void run(F&& f) { + if (threads.count() == 0) { + f(); + } else { + if (!st || next_id == 0) { + create_state(); + } + const state::id this_id = next_id; + next_id <<= 1; + + st->track_id(this_id); + threads.dispatch(wrapper<F>{this_id, st, std::move(f)}); + } + } + + //! Wait until all functions provided to `run` have completed. + void wait() noexcept { + if (st) { + do_wait(); + } + } + +private: + explicit task_region_handle(thread_group& threads_src) + : st(nullptr), threads(threads_src), next_id(0) { + } + + void create_state(); + void do_wait() noexcept; + + std::shared_ptr<state> st; + thread_group& threads; + state::id next_id; +}; + +/*! Function for creating a `task_region_handle`, which automatically calls +`task_region_handle::wait()` before returning. If a `thread_group` is not +provided, one is created with an optimal number of threads. The callback `f` +must have the signature `void(task_region_handle&)`. */ +struct task_region_ { + template<typename F> + void operator()(thread_group& threads, F&& f) const { + static_assert( + std::is_same<void, typename std::result_of<F(task_region_handle&)>::type>::value, + "f cannot have a return value" + ); + task_region_handle region{threads}; + f(region); + region.wait(); + } + + template<typename F> + void operator()(thread_group&& threads, F&& f) const { + (*this)(threads, std::forward<F>(f)); + } + + template<typename F> + void operator()(F&& f) const { + thread_group threads; + (*this)(threads, std::forward<F>(f)); + } +}; + +constexpr const task_region_ task_region{}; +} diff --git a/src/common/thread_group.cpp b/src/common/thread_group.cpp new file mode 100644 index 000000000..860d0b732 --- /dev/null +++ b/src/common/thread_group.cpp @@ -0,0 +1,150 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/thread_group.h" + +#include <boost/thread/locks.hpp> +#include <cassert> +#include <limits> +#include <stdexcept> + +#include "common/util.h" + +namespace tools +{ +std::size_t thread_group::optimal() { + static_assert( + std::numeric_limits<unsigned>::max() <= std::numeric_limits<std::size_t>::max(), + "unexpected truncation" + ); + const std::size_t hardware = get_max_concurrency(); + return hardware ? (hardware - 1) : 0; +} + +std::size_t thread_group::optimal_with_max(std::size_t count) { + return count ? std::min(count - 1, optimal()) : 0; +} + +thread_group::thread_group(std::size_t count) : internal() { + if (count) { + internal.emplace(count); + } +} + +thread_group::data::data(std::size_t count) + : threads() + , head{nullptr} + , last(std::addressof(head)) + , mutex() + , has_work() + , stop(false) { + threads.reserve(count); + while (count--) { + threads.push_back(boost::thread(&thread_group::data::run, this)); + } +} + +thread_group::data::~data() noexcept { + { + const boost::unique_lock<boost::mutex> lock(mutex); + stop = true; + } + has_work.notify_all(); + for (auto& worker : threads) { + try { + worker.join(); + } + catch(...) {} + } +} + +std::unique_ptr<thread_group::data::work> thread_group::data::get_next() noexcept { + std::unique_ptr<work> rc = std::move(head.ptr); + if (rc != nullptr) { + head.ptr = std::move(rc->next.ptr); + if (head.ptr == nullptr) { + last = std::addressof(head); + } + } + return rc; +} + +bool thread_group::data::try_run_one() noexcept { + /* This function and `run()` can both throw when acquiring the lock, or in + dispatched function. It is tough to recover from either, particularly the + lock case. These functions are marked as noexcept so that if either call + throws, the entire process is terminated. Users of the `dispatch` call are + expected to make their functions noexcept, or use std::packaged_task to copy + exceptions so that the process will continue in all but the most pessimistic + cases (std::bad_alloc). This was the existing behavior; + `asio::io_service::run` propogates errors from dispatched calls, and uncaught + exceptions on threads result in process termination. */ + std::unique_ptr<work> next = nullptr; + { + const boost::unique_lock<boost::mutex> lock(mutex); + next = get_next(); + } + if (next) { + assert(next->f); + next->f(); + return true; + } + return false; +} + +void thread_group::data::run() noexcept { + // see `try_run_one()` source for additional information + while (true) { + std::unique_ptr<work> next = nullptr; + { + boost::unique_lock<boost::mutex> lock(mutex); + has_work.wait(lock, [this] { return head.ptr != nullptr || stop; }); + if (stop) { + return; + } + next = get_next(); + } + assert(next != nullptr); + assert(next->f); + next->f(); + } +} + +void thread_group::data::dispatch(std::function<void()> f) { + std::unique_ptr<work> latest(new work{std::move(f), node{nullptr}}); + node* const latest_node = std::addressof(latest->next); + { + const boost::unique_lock<boost::mutex> lock(mutex); + assert(last != nullptr); + assert(last->ptr == nullptr); + + last->ptr = std::move(latest); + last = latest_node; + } + has_work.notify_one(); +} +} diff --git a/src/common/thread_group.h b/src/common/thread_group.h new file mode 100644 index 000000000..48fd4cd56 --- /dev/null +++ b/src/common/thread_group.h @@ -0,0 +1,143 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once + +#include <boost/optional/optional.hpp> +#include <boost/thread/condition_variable.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <cstddef> +#include <functional> +#include <thread> +#include <utility> +#include <vector> + +namespace tools +{ +//! Manages zero or more threads for work dispatching. +class thread_group +{ +public: + + //! \return `get_max_concurrency() ? get_max_concurrency() - 1 : 0` + static std::size_t optimal(); + + //! \return `count ? min(count - 1, optimal()) : 0` + static std::size_t optimal_with_max(std::size_t count); + + //! Create an optimal number of threads. + explicit thread_group() : thread_group(optimal()) {} + + //! Create exactly `count` threads. + explicit thread_group(std::size_t count); + + thread_group(thread_group const&) = delete; + thread_group(thread_group&&) = delete; + + //! Joins threads, but does not necessarily run all dispatched functions. + ~thread_group() = default; + + thread_group& operator=(thread_group const&) = delete; + thread_group& operator=(thread_group&&) = delete; + + //! \return Number of threads owned by `this` group. + std::size_t count() const noexcept { + if (internal) { + return internal->count(); + } + return 0; + } + + //! \return True iff a function was available and executed (on `this_thread`). + bool try_run_one() noexcept { + if (internal) { + return internal->try_run_one(); + } + return false; + } + + /*! `f` is invoked immediately if `count() == 0`, otherwise execution of `f` + is queued for next available thread. If `f` is queued, any exception leaving + that function will result in process termination. Use std::packaged_task if + exceptions need to be handled. */ + template<typename F> + void dispatch(F&& f) { + if (internal) { + internal->dispatch(std::forward<F>(f)); + } + else { + f(); + } + } + +private: + class data { + public: + data(std::size_t count); + ~data() noexcept; + + std::size_t count() const noexcept { + return threads.size(); + } + + bool try_run_one() noexcept; + void dispatch(std::function<void()> f); + + private: + struct work; + + struct node { + std::unique_ptr<work> ptr; + }; + + struct work { + std::function<void()> f; + node next; + }; + + //! Requires lock on `mutex`. + std::unique_ptr<work> get_next() noexcept; + + //! Blocks until destructor is invoked, only call from thread. + void run() noexcept; + + private: + std::vector<boost::thread> threads; + node head; + node* last; + boost::condition_variable has_work; + boost::mutex mutex; + bool stop; + }; + +private: + // optionally construct elements, without separate heap allocation + boost::optional<data> internal; +}; + +} diff --git a/src/common/unordered_containers_boost_serialization.h b/src/common/unordered_containers_boost_serialization.h index 08b5d9ccf..4d82a1364 100644 --- a/src/common/unordered_containers_boost_serialization.h +++ b/src/common/unordered_containers_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -43,7 +43,7 @@ namespace boost { size_t s = x.size(); a << s; - BOOST_FOREACH(auto& v, x) + for(auto& v: x) { a << v.first; a << v.second; @@ -72,7 +72,7 @@ namespace boost { size_t s = x.size(); a << s; - BOOST_FOREACH(auto& v, x) + for(auto& v: x) { a << v.first; a << v.second; @@ -101,7 +101,7 @@ namespace boost { size_t s = x.size(); a << s; - BOOST_FOREACH(auto& v, x) + for(auto& v: x) { a << v; } diff --git a/src/common/updates.cpp b/src/common/updates.cpp new file mode 100644 index 000000000..5b1acf5fa --- /dev/null +++ b/src/common/updates.cpp @@ -0,0 +1,115 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "util.h" +#include "dns_utils.h" +#include "updates.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "updates" + +namespace tools +{ + bool check_updates(const std::string &software, const std::string &buildtag, std::string &version, std::string &hash) + { + std::vector<std::string> records; + bool found = false; + + MDEBUG("Checking updates for " << buildtag << " " << software); + + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector<std::string> dns_urls = { + "updates.moneropulse.org", + "updates.moneropulse.net", + "updates.moneropulse.co", + "updates.moneropulse.se" + }; + + if (!tools::dns_utils::load_txt_records_from_dns(records, dns_urls)) + return false; + + for (const auto& record : records) + { + std::vector<std::string> fields; + boost::split(fields, record, boost::is_any_of(":")); + if (fields.size() != 4) + { + MWARNING("Updates record does not have 4 fields: " << record); + continue; + } + + if (software != fields[0] || buildtag != fields[1]) + continue; + + bool alnum = true; + for (auto c: hash) + if (!isalnum(c)) + alnum = false; + if (hash.size() != 64 && !alnum) + { + MWARNING("Invalid hash: " << hash); + continue; + } + + // use highest version + if (found) + { + int cmp = vercmp(version.c_str(), fields[2].c_str()); + if (cmp > 0) + continue; + if (cmp == 0 && hash != fields[3]) + MWARNING("Two matches found for " << software << " version " << version << " on " << buildtag); + } + + version = fields[2]; + hash = fields[3]; + + MINFO("Found new version " << version << " with hash " << hash); + found = true; + } + return found; + } + + std::string get_update_url(const std::string &software, const std::string &subdir, const std::string &buildtag, const std::string &version, bool user) + { + const char *base = user ? "https://downloads.getmonero.org/" : "http://updates.getmonero.org/"; +#ifdef _WIN32 + static const char extension[] = ".zip"; +#else + static const char extension[] = ".tar.bz2"; +#endif + + std::string url; + + url = base; + if (!subdir.empty()) + url += subdir + "/"; + url = url + software + "-" + buildtag + "-v" + version + extension; + return url; + } +} diff --git a/src/common/updates.h b/src/common/updates.h new file mode 100644 index 000000000..e494ed7ac --- /dev/null +++ b/src/common/updates.h @@ -0,0 +1,37 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> + +namespace tools +{ + bool check_updates(const std::string &software, const std::string &buildtag, std::string &version, std::string &hash); + std::string get_update_url(const std::string &software, const std::string &subdir, const std::string &buildtag, const std::string &version, bool user); +} diff --git a/src/common/util.cpp b/src/common/util.cpp index a53a9be52..046961b06 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,10 +31,12 @@ #include <cstdio> #include "include_base_utils.h" +#include "file_io_utils.h" using namespace epee; #include "util.h" #include "cryptonote_config.h" +#include "net/http_client.h" // epee::net_utils::... #ifdef WIN32 #include <windows.h> @@ -44,12 +46,99 @@ using namespace epee; #include <sys/utsname.h> #endif #include <boost/filesystem.hpp> - +#include <boost/asio.hpp> +#include <openssl/sha.h> namespace tools { std::function<void(int)> signal_handler::m_handler; + std::unique_ptr<std::FILE, tools::close_file> create_private_file(const std::string& name) + { +#ifdef WIN32 + struct close_handle + { + void operator()(HANDLE handle) const noexcept + { + CloseHandle(handle); + } + }; + + std::unique_ptr<void, close_handle> process = nullptr; + { + HANDLE temp{}; + const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0; + process.reset(temp); + if (fail) + return nullptr; + } + + DWORD sid_size = 0; + GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size)); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return nullptr; + + std::unique_ptr<char[]> sid{new char[sid_size]}; + if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size))) + return nullptr; + + const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner; + const DWORD daclSize = + sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid) - sizeof(DWORD); + + const std::unique_ptr<char[]> dacl{new char[daclSize]}; + if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION)) + return nullptr; + + if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid)) + return nullptr; + + SECURITY_DESCRIPTOR descriptor{}; + if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION)) + return nullptr; + + if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false)) + return nullptr; + + SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false}; + std::unique_ptr<void, close_handle> file{ + CreateFile( + name.c_str(), + GENERIC_WRITE, FILE_SHARE_READ, + std::addressof(attributes), + CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, + nullptr + ) + }; + if (file) + { + const int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file.get()), 0); + if (0 <= fd) + { + file.release(); + std::FILE* real_file = _fdopen(fd, "w"); + if (!real_file) + { + _close(fd); + } + return {real_file, tools::close_file{}}; + } + } +#else + const int fd = open(name.c_str(), (O_RDWR | O_EXCL | O_CREAT), S_IRUSR); + if (0 <= fd) + { + std::FILE* file = fdopen(fd, "w"); + if (!file) + { + close(fd); + } + return {file, tools::close_file{}}; + } +#endif + return nullptr; + } + #ifdef WIN32 std::string get_windows_version_display_string() { @@ -445,4 +534,100 @@ std::string get_nix_version_display_string() boost::lock_guard<boost::mutex> lock(max_concurrency_lock); return max_concurrency; } + + bool is_local_address(const std::string &address) + { + // extract host + epee::net_utils::http::url_content u_c; + if (!epee::net_utils::parse_url(address, u_c)) + { + MWARNING("Failed to determine whether address '" << address << "' is local, assuming not"); + return false; + } + if (u_c.host.empty()) + { + MWARNING("Failed to determine whether address '" << address << "' is local, assuming not"); + return false; + } + + // resolve to IP + boost::asio::io_service io_service; + boost::asio::ip::tcp::resolver resolver(io_service); + boost::asio::ip::tcp::resolver::query query(u_c.host, ""); + boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); + while (i != boost::asio::ip::tcp::resolver::iterator()) + { + const boost::asio::ip::tcp::endpoint &ep = *i; + if (ep.address().is_loopback()) + { + MDEBUG("Address '" << address << "' is local"); + return true; + } + ++i; + } + + MDEBUG("Address '" << address << "' is not local"); + return false; + } + int vercmp(const char *v0, const char *v1) + { + std::vector<std::string> f0, f1; + boost::split(f0, v0, boost::is_any_of(".")); + boost::split(f1, v1, boost::is_any_of(".")); + while (f0.size() < f1.size()) + f0.push_back("0"); + while (f1.size() < f0.size()) + f1.push_back("0"); + for (size_t i = 0; i < f0.size(); ++i) { + int f0i = atoi(f0[i].c_str()), f1i = atoi(f1[i].c_str()); + int n = f0i - f1i; + if (n) + return n; + } + return 0; + } + + bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash) + { + SHA256_CTX ctx; + if (!SHA256_Init(&ctx)) + return false; + if (!SHA256_Update(&ctx, data, len)) + return false; + if (!SHA256_Final((unsigned char*)hash.data, &ctx)) + return false; + return true; + } + + bool sha256sum(const std::string &filename, crypto::hash &hash) + { + if (!epee::file_io_utils::is_file_exist(filename)) + return false; + std::ifstream f; + f.exceptions(std::ifstream::failbit | std::ifstream::badbit); + f.open(filename, std::ios_base::binary | std::ios_base::in | std::ios::ate); + if (!f) + return false; + std::ifstream::pos_type file_size = f.tellg(); + SHA256_CTX ctx; + if (!SHA256_Init(&ctx)) + return false; + size_t size_left = file_size; + f.seekg(0, std::ios::beg); + while (size_left) + { + char buf[4096]; + std::ifstream::pos_type read_size = size_left > sizeof(buf) ? sizeof(buf) : size_left; + f.read(buf, read_size); + if (!f || !f.good()) + return false; + if (!SHA256_Update(&ctx, buf, read_size)) + return false; + size_left -= read_size; + } + f.close(); + if (!SHA256_Final((unsigned char*)hash.data, &ctx)) + return false; + return true; + } } diff --git a/src/common/util.h b/src/common/util.h index 4fcf66b8f..4291d7e18 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,13 +30,16 @@ #pragma once -#include <mutex> +#include <boost/thread/locks.hpp> +#include <boost/thread/mutex.hpp> #include <system_error> -#include <boost/filesystem.hpp> +#include <csignal> +#include <cstdio> +#include <functional> +#include <memory> +#include <string> -#include "crypto/crypto.h" #include "crypto/hash.h" -#include "misc_language.h" #include "p2p/p2p_protocol_defs.h" /*! \brief Various Tools @@ -46,6 +49,21 @@ */ namespace tools { + //! Functional class for closing C file handles. + struct close_file + { + void operator()(std::FILE* handle) const noexcept + { + if (handle) + { + std::fclose(handle); + } + } + }; + + //! \return File only readable by owner. nullptr if `filename` exists. + std::unique_ptr<std::FILE, close_file> create_private_file(const std::string& filename); + /*! \brief Returns the default data directory. * * \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME @@ -134,7 +152,7 @@ namespace tools } else { - LOG_PRINT_RED_L0("Got control signal " << type << ". Exiting without saving..."); + MGINFO_RED("Got control signal " << type << ". Exiting without saving..."); return FALSE; } return TRUE; @@ -163,4 +181,10 @@ namespace tools void set_max_concurrency(unsigned n); unsigned get_max_concurrency(); + + bool is_local_address(const std::string &address); + int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate + + bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash); + bool sha256sum(const std::string &filename, crypto::hash &hash); } diff --git a/src/common/varint.h b/src/common/varint.h index ffaa682c5..cb785e61a 100644 --- a/src/common/varint.h +++ b/src/common/varint.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 28f845d47..277ee64c2 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -68,9 +68,9 @@ set(crypto_private_headers skein.h skein_port.h) -bitmonero_private_headers(crypto +monero_private_headers(crypto ${crypto_private_headers}) -bitmonero_add_library(crypto +monero_add_library(crypto ${crypto_sources} ${crypto_headers} ${crypto_private_headers}) @@ -89,3 +89,14 @@ if (ARM) PROPERTY COMPILE_DEFINITIONS "NO_OPTIMIZED_MULTIPLY_ON_ARM") endif() endif() + +# Because of the way Qt works on android with JNI, the code does not live in the main android thread +# So this code runs with a 1 MB default stack size. +# This will force the use of the heap for the allocation of the scratchpad +if (ANDROID OR IOS) + if( BUILD_GUI_DEPS ) + add_definitions(-DFORCE_USE_HEAP=1) + endif() +endif() + + diff --git a/src/crypto/blake256.c b/src/crypto/blake256.c index 1cb1cf344..1e43f9c4d 100644 --- a/src/crypto/blake256.c +++ b/src/crypto/blake256.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/blake256.h b/src/crypto/blake256.h index e262d1b4b..921fcd2fd 100644 --- a/src/crypto/blake256.h +++ b/src/crypto/blake256.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/chacha8.h b/src/crypto/chacha8.h index 94c0ba721..80557e9f5 100644 --- a/src/crypto/chacha8.h +++ b/src/crypto/chacha8.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops-data.c b/src/crypto/crypto-ops-data.c index 60ee38096..4bd75b77c 100644 --- a/src/crypto/crypto-ops-data.c +++ b/src/crypto/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 1b390e402..4edfee0ce 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 4986499f4..37edf5b6d 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 250779ac3..98da466cc 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -42,7 +42,7 @@ #include "crypto.h" #include "hash.h" -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) #include <alloca.h> #else #include <stdlib.h> diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index b396fc7db..3b8c7996b 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -236,6 +236,6 @@ namespace crypto { } } -CRYPTO_MAKE_COMPARABLE(public_key) +CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/crypto/crypto_ops_builder/README.md b/src/crypto/crypto_ops_builder/README.md index eec3e21e7..3b87966f5 100644 --- a/src/crypto/crypto_ops_builder/README.md +++ b/src/crypto/crypto_ops_builder/README.md @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2016, The Monero Project +Copyright (c) 2014-2017, The Monero Project ## Crypto Ops Builder diff --git a/src/crypto/crypto_ops_builder/crypto-ops-data.c b/src/crypto/crypto_ops_builder/crypto-ops-data.c index 60ee38096..4bd75b77c 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-data.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops-old.c b/src/crypto/crypto_ops_builder/crypto-ops-old.c index 910801c57..b7a290b4a 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-old.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-old.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops.h b/src/crypto/crypto_ops_builder/crypto-ops.h index 84ef12ae2..47d5b46ae 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py index 9d996f9d1..5f8776a49 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py @@ -15,7 +15,7 @@ print("maybe someone smart can replace the sed with perl..") a = "" license = textwrap.dedent("""\ - // Copyright (c) 2014-2016, The Monero Project + // Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h index cdc5ac1ee..b432efade 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/generic-ops.h b/src/crypto/generic-ops.h index a8d8fdb19..1a135ffcf 100644 --- a/src/crypto/generic-ops.h +++ b/src/crypto/generic-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl.h b/src/crypto/groestl.h index ac749d1d8..89a073a4c 100644 --- a/src/crypto/groestl.h +++ b/src/crypto/groestl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl_tables.h b/src/crypto/groestl_tables.h index c271fd367..8fa6d7a83 100644 --- a/src/crypto/groestl_tables.h +++ b/src/crypto/groestl_tables.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-blake.c b/src/crypto/hash-extra-blake.c index 15357dc7a..236479880 100644 --- a/src/crypto/hash-extra-blake.c +++ b/src/crypto/hash-extra-blake.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-groestl.c b/src/crypto/hash-extra-groestl.c index 69546ee44..b15075306 100644 --- a/src/crypto/hash-extra-groestl.c +++ b/src/crypto/hash-extra-groestl.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c index 5da0894f6..8950687d3 100644 --- a/src/crypto/hash-extra-jh.c +++ b/src/crypto/hash-extra-jh.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c index babf5006f..e63e7da20 100644 --- a/src/crypto/hash-extra-skein.c +++ b/src/crypto/hash-extra-skein.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index 612d94efe..6e3a5c6c9 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash.c b/src/crypto/hash.c index 93f7353e4..ed95391d8 100644 --- a/src/crypto/hash.c +++ b/src/crypto/hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 803992078..22991e513 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/initializer.h b/src/crypto/initializer.h index fefc3b7c3..619038ae6 100644 --- a/src/crypto/initializer.h +++ b/src/crypto/initializer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/oaes_lib.c b/src/crypto/oaes_lib.c index 4c1446898..0afec6212 100644 --- a/src/crypto/oaes_lib.c +++ b/src/crypto/oaes_lib.c @@ -34,12 +34,13 @@ #include <stdio.h> // OS X, FreeBSD, and OpenBSD don't need malloc.h -#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) +#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) \ + && !defined(__DragonFly__) #include <malloc.h> #endif -// FreeBSD, and OpenBSD also don't need timeb.h -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) +// ANDROID, FreeBSD, and OpenBSD also don't need timeb.h +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) #include <sys/timeb.h> #else #include <sys/time.h> @@ -498,7 +499,7 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] ) #else static uint32_t oaes_get_seed(void) { - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) + #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) struct timeb timer; struct tm *gmTimer; char * _test = NULL; @@ -639,7 +640,10 @@ static OAES_RET oaes_key_gen( OAES_CTX * ctx, size_t key_size ) _key->data = (uint8_t *) calloc( key_size, sizeof( uint8_t )); if( NULL == _key->data ) + { + free( _key ); return OAES_RET_MEM; + } for( _i = 0; _i < key_size; _i++ ) #ifdef OAES_HAVE_ISAAC diff --git a/src/crypto/random.c b/src/crypto/random.c index 6a9f63c12..691c31f62 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/random.h b/src/crypto/random.h index b0d2303b6..75d23fd04 100644 --- a/src/crypto/random.h +++ b/src/crypto/random.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/skein.c b/src/crypto/skein.c index 9c8ac288d..65e4525c3 100644 --- a/src/crypto/skein.c +++ b/src/crypto/skein.c @@ -77,7 +77,7 @@ typedef struct /* 1024-bit Skein hash context stru } Skein1024_Ctxt_t; /* Skein APIs for (incremental) "straight hashing" */ -#if SKEIN_256_NIST_MAX_HASH_BITS +#if SKEIN_256_NIST_MAX_HASHBITS static int Skein_256_Init (Skein_256_Ctxt_t *ctx, size_t hashBitLen); #endif static int Skein_512_Init (Skein_512_Ctxt_t *ctx, size_t hashBitLen); @@ -1941,7 +1941,7 @@ static HashReturn Final (hashState *state, BitSequence *hashval); /* select the context size and init the context */ static HashReturn Init(hashState *state, int hashbitlen) { -#if SKEIN_256_NIST_MAX_HASH_BITS +#if SKEIN_256_NIST_MAX_HASHBITS if (hashbitlen <= SKEIN_256_NIST_MAX_HASHBITS) { Skein_Assert(hashbitlen > 0,BAD_HASHLEN); diff --git a/src/crypto/skein_port.h b/src/crypto/skein_port.h index 0b3d071ee..a06ef30a2 100644 --- a/src/crypto/skein_port.h +++ b/src/crypto/skein_port.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 2ac303a36..6afa28934 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -44,6 +44,9 @@ #define INIT_SIZE_BLK 8 #define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) +extern int aesb_single_round(const uint8_t *in, uint8_t*out, const uint8_t *expandedKey); +extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); + #if defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64)) // Optimised code below, uses x86-specific intrinsics, SSE2, AES-NI // Fall back to more portable code is down at the bottom @@ -138,9 +141,6 @@ #define THREADV __thread #endif -extern int aesb_single_round(const uint8_t *in, uint8_t*out, const uint8_t *expandedKey); -extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); - #pragma pack(push, 1) union cn_slow_hash_state { @@ -442,7 +442,8 @@ void slow_hash_allocate_state(void) hp_state = (uint8_t *) VirtualAlloc(hp_state, MEMORY, MEM_LARGE_PAGES | MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #else -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__) hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0); #else @@ -493,7 +494,7 @@ void slow_hash_free_state(void) * buffer of pseudorandom data by hashing the supplied data. It then uses this * random data to fill a large 2MB buffer with pseudorandom data by iteratively * encrypting it using 10 rounds of AES per entry. After this initialization, - * it executes 500,000 rounds of mixing through the random 2MB buffer using + * it executes 524,288 rounds of mixing through the random 2MB buffer using * AES (typically provided in hardware on modern CPUs) and a 64 bit multiply. * Finally, it re-mixes this large buffer back into * the 200 byte "text" buffer, and then hashes this buffer using one of four @@ -529,7 +530,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) size_t i, j; uint64_t *p = NULL; - oaes_ctx *aes_ctx; + oaes_ctx *aes_ctx = NULL; int useAes = !force_software_aes() && check_aes_hw(); static void (*const extra_hashes[4])(const void *, size_t, char *) = @@ -577,8 +578,8 @@ void cn_slow_hash(const void *data, size_t length, char *hash) U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - /* CryptoNight Step 3: Bounce randomly 1 million times through the mixing buffer, - * using 500,000 iterations of the following mixing function. Each execution + /* CryptoNight Step 3: Bounce randomly 1,048,576 times (1<<20) through the mixing buffer, + * using 524,288 iterations of the following mixing function. Each execution * performs two reads and writes from the mixing buffer. */ @@ -721,32 +722,24 @@ union cn_slow_hash_state * key schedule. Don't try to use this for vanilla AES. */ static void aes_expand_key(const uint8_t *key, uint8_t *expandedKey) { -__asm__("mov x2, %1\n\t" : : "r"(key), "r"(expandedKey)); +static const int rcon[] = { + 0x01,0x01,0x01,0x01, + 0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d, // rotate-n-splat + 0x1b,0x1b,0x1b,0x1b }; __asm__( -" adr x3,Lrcon\n" -"\n" " eor v0.16b,v0.16b,v0.16b\n" -" ld1 {v3.16b},[x0],#16\n" -" ld1 {v1.4s,v2.4s},[x3],#32\n" -" b L256\n" -".align 5\n" -"Lrcon:\n" -".long 0x01,0x01,0x01,0x01\n" -".long 0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d // rotate-n-splat\n" -".long 0x1b,0x1b,0x1b,0x1b\n" +" ld1 {v3.16b},[%0],#16\n" +" ld1 {v1.4s,v2.4s},[%2],#32\n" +" ld1 {v4.16b},[%0]\n" +" mov w2,#5\n" +" st1 {v3.4s},[%1],#16\n" "\n" -".align 4\n" -"L256:\n" -" ld1 {v4.16b},[x0]\n" -" mov w1,#5\n" -" st1 {v3.4s},[x2],#16\n" -"\n" -"Loop256:\n" +"1:\n" " tbl v6.16b,{v4.16b},v2.16b\n" " ext v5.16b,v0.16b,v3.16b,#12\n" -" st1 {v4.4s},[x2],#16\n" +" st1 {v4.4s},[%1],#16\n" " aese v6.16b,v0.16b\n" -" subs w1,w1,#1\n" +" subs w2,w2,#1\n" "\n" " eor v3.16b,v3.16b,v5.16b\n" " ext v5.16b,v0.16b,v5.16b,#12\n" @@ -756,8 +749,8 @@ __asm__( " eor v3.16b,v3.16b,v5.16b\n" " shl v1.16b,v1.16b,#1\n" " eor v3.16b,v3.16b,v6.16b\n" -" st1 {v3.4s},[x2],#16\n" -" b.eq Ldone\n" +" st1 {v3.4s},[%1],#16\n" +" b.eq 2f\n" "\n" " dup v6.4s,v3.s[3] // just splat\n" " ext v5.16b,v0.16b,v4.16b,#12\n" @@ -770,9 +763,9 @@ __asm__( " eor v4.16b,v4.16b,v5.16b\n" "\n" " eor v4.16b,v4.16b,v6.16b\n" -" b Loop256\n" +" b 1b\n" "\n" -"Ldone:\n"); +"2:\n" : : "r"(key), "r"(expandedKey), "r"(rcon)); } /* An ordinary AES round is a sequence of SubBytes, ShiftRows, MixColumns, AddRoundKey. There @@ -894,8 +887,8 @@ void cn_slow_hash(const void *data, size_t length, char *hash) U64(b)[0] = U64(&state.k[16])[0] ^ U64(&state.k[48])[0]; U64(b)[1] = U64(&state.k[16])[1] ^ U64(&state.k[48])[1]; - /* CryptoNight Step 3: Bounce randomly 1 million times through the mixing buffer, - * using 500,000 iterations of the following mixing function. Each execution + /* CryptoNight Step 3: Bounce randomly 1,048,576 times (1<<20) through the mixing buffer, + * using 524,288 iterations of the following mixing function. Each execution * performs two reads and writes from the mixing buffer. */ @@ -1051,7 +1044,6 @@ STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) void cn_slow_hash(const void *data, size_t length, char *hash) { - uint8_t long_state[MEMORY]; uint8_t text[INIT_SIZE_BYTE]; uint8_t a[AES_BLOCK_SIZE]; uint8_t b[AES_BLOCK_SIZE]; @@ -1069,6 +1061,13 @@ void cn_slow_hash(const void *data, size_t length, char *hash) hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein }; +#ifndef FORCE_USE_HEAP + uint8_t long_state[MEMORY]; +#else + uint8_t *long_state = NULL; + long_state = (uint8_t *)malloc(MEMORY); +#endif + hash_process(&state.hs, data, length); memcpy(text, state.init, INIT_SIZE_BYTE); @@ -1128,6 +1127,9 @@ void cn_slow_hash(const void *data, size_t length, char *hash) memcpy(state.init, text, INIT_SIZE_BYTE); hash_permutation(&state.hs); extra_hashes[state.hs.b[0] & 3](&state, 200, hash); +#ifdef FORCE_USE_HEAP + free(long_state); +#endif } #endif /* !aarch64 || !crypto */ diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 7a128e4b0..eb98c31b7 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,33 +34,34 @@ #include "hash-ops.h" -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) #include <alloca.h> #else #include <stdlib.h> #endif -/// Quick check if this is power of two (use on unsigned types; in this case for size_t only) -bool ispowerof2_size_t(size_t x) { - return x && !(x & (x - 1)); -} - /*** * Round to power of two, for count>=3 and for count being not too large (as reasonable for tree hash calculations) */ size_t tree_hash_cnt(size_t count) { - assert( count >= 3); // cases for 0,1,2 are handled elsewhere - // Round down the count size: fun(2**n)= 2**(n-1) to round down to power of two - size_t tmp = count - 1; - size_t jj = 1; - for (jj=1 ; tmp != 0 ; ++jj) { - tmp /= 2; // dividing by 2 until to get how many powers of 2 fits size_to tmp - } - size_t cnt = 1 << (jj-2); // cnt is the count, but rounded down to power of two - // printf("count=%zu cnt=%zu jj=%zu tmp=%zu \n" , count,cnt,jj,tmp); - assert( cnt > 0 ); assert( cnt >= count/2 ); assert( cnt <= count ); - assert( ispowerof2_size_t( cnt )); - return cnt; + // This algo has some bad history but all we are doing is 1 << floor(log2(count)) + // There are _many_ ways to do log2, for some reason the one selected was the most obscure one, + // and fixing it made it even more obscure. + // + // Iterative method implemented below aims for clarity over speed, if performance is needed + // then my advice is to use the BSR instruction on x86 + // + // All the paranoid asserts have been removed since it is trivial to mathematically prove that + // the return will always be a power of 2. + // Problem space has been defined as 3 <= count <= 2^28. Of course quarter of a billion transactions + // is not a sane upper limit for a block, so there will be tighter limits in other parts of the code + + assert( count >= 3 ); // cases for 0,1,2 are handled elsewhere + assert( count <= 0x10000000 ); // sanity limit to 2^28, MSB=1 will cause an inf loop + + size_t pow = 2; + while(pow < count) pow <<= 1; + return pow >> 1; } void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { @@ -86,9 +87,6 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { size_t i, j; size_t cnt = tree_hash_cnt( count ); - size_t max_size_t = (size_t) -1; // max allowed value of size_t - assert( cnt < max_size_t/2 ); // reasonable size to avoid any overflows. /2 is extra; Anyway should be limited much stronger by logical code - // as we have sane limits on transactions counts in blockchain rules char (*ints)[HASH_SIZE]; size_t ints_size = cnt * HASH_SIZE; diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt new file mode 100644 index 000000000..2b8ad365a --- /dev/null +++ b/src/cryptonote_basic/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (c) 2014-2017, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(cryptonote_basic_sources + account.cpp + checkpoints.cpp + cryptonote_basic_impl.cpp + cryptonote_format_utils.cpp + difficulty.cpp + hardfork.cpp + miner.cpp) + +set(cryptonote_basic_headers) + +set(cryptonote_basic_private_headers + account.h + account_boost_serialization.h + checkpoints.h + connection_context.h + cryptonote_basic.h + cryptonote_basic_impl.h + cryptonote_boost_serialization.h + cryptonote_format_utils.h + cryptonote_stat_info.h + difficulty.h + hardfork.h + miner.h + tx_extra.h + verification_context.h) + +monero_private_headers(cryptonote_basic + ${crypto_private_headers}) +monero_add_library(cryptonote_basic + ${cryptonote_basic_sources} + ${cryptonote_basic_headers} + ${cryptonote_basic_private_headers}) +target_link_libraries(cryptonote_basic + PUBLIC + common + crypto + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/cryptonote_core/account.cpp b/src/cryptonote_basic/account.cpp index c3f2b4446..dd875402f 100644 --- a/src/cryptonote_core/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,8 +28,6 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include <boost/archive/binary_oarchive.hpp> -#include <boost/archive/binary_iarchive.hpp> #include <fstream> #include "include_base_utils.h" @@ -40,8 +38,12 @@ extern "C" { #include "crypto/keccak.h" } -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic_impl.h" +#include "cryptonote_format_utils.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "account" + using namespace std; DISABLE_VS_WARNINGS(4244 4345) @@ -74,7 +76,7 @@ DISABLE_VS_WARNINGS(4244 4345) generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key, second, two_random ? false : true); - struct tm timestamp; + struct tm timestamp = {0}; timestamp.tm_year = 2014 - 1900; // year 2014 timestamp.tm_mon = 6 - 1; // month june timestamp.tm_mday = 8; // 8th of june @@ -85,6 +87,8 @@ DISABLE_VS_WARNINGS(4244 4345) if (recover) { m_creation_timestamp = mktime(×tamp); + if (m_creation_timestamp == (uint64_t)-1) // failure + m_creation_timestamp = 0; // lowest value } else { @@ -99,7 +103,7 @@ DISABLE_VS_WARNINGS(4244 4345) m_keys.m_spend_secret_key = spendkey; m_keys.m_view_secret_key = viewkey; - struct tm timestamp; + struct tm timestamp = {0}; timestamp.tm_year = 2014 - 1900; // year 2014 timestamp.tm_mon = 4 - 1; // month april timestamp.tm_mday = 15; // 15th of april @@ -108,6 +112,8 @@ DISABLE_VS_WARNINGS(4244 4345) timestamp.tm_sec = 0; m_creation_timestamp = mktime(×tamp); + if (m_creation_timestamp == (uint64_t)-1) // failure + m_creation_timestamp = 0; // lowest value } //----------------------------------------------------------------- void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey) diff --git a/src/cryptonote_core/account.h b/src/cryptonote_basic/account.h index 41a119b07..e0d5447a2 100644 --- a/src/cryptonote_core/account.h +++ b/src/cryptonote_basic/account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,7 +30,7 @@ #pragma once -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic.h" #include "crypto/crypto.h" #include "serialization/keyvalue_serialization.h" diff --git a/src/cryptonote_core/account_boost_serialization.h b/src/cryptonote_basic/account_boost_serialization.h index 4151d9b50..d2f541638 100644 --- a/src/cryptonote_core/account_boost_serialization.h +++ b/src/cryptonote_basic/account_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,7 +31,7 @@ #pragma once #include "account.h" -#include "cryptonote_core/cryptonote_boost_serialization.h" +#include "cryptonote_boost_serialization.h" //namespace cryptonote { namespace boost diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_basic/checkpoints.cpp index 1d5959c46..103a4a33e 100644 --- a/src/cryptonote_core/checkpoints.cpp +++ b/src/cryptonote_basic/checkpoints.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -39,29 +39,8 @@ using namespace epee; #include <sstream> #include <random> -namespace -{ - bool dns_records_match(const std::vector<std::string>& a, const std::vector<std::string>& b) - { - if (a.size() != b.size()) return false; - - for (const auto& record_in_a : a) - { - bool ok = false; - for (const auto& record_in_b : b) - { - if (record_in_a == record_in_b) - { - ok = true; - break; - } - } - if (!ok) return false; - } - - return true; - } -} // anonymous namespace +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "checkpoints" namespace cryptonote { @@ -99,11 +78,11 @@ namespace cryptonote if(it->second == h) { - LOG_PRINT_GREEN("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h, LOG_LEVEL_1); + MINFO("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h); return true; }else { - LOG_ERROR("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h); + MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h); return false; } } @@ -186,6 +165,9 @@ namespace cryptonote ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); ADD_CHECKPOINT(1000000, "a886ef5149902d8342475fee9bb296341b891ac67c4842f47a833f23c00ed721"); ADD_CHECKPOINT(1100000, "3fd720c5c8b3072fc1ccda922dec1ef25f9ed88a1e6ad4103d0fe00b180a5903"); + ADD_CHECKPOINT(1150000, "1dd16f626d18e1e988490dfd06de5920e22629c972c58b4d8daddea0038627b2"); + ADD_CHECKPOINT(1200000, "fa7d13a90850882060479d100141ff84286599ae39c3277c8ea784393f882d1f"); + return true; } @@ -224,6 +206,8 @@ namespace cryptonote bool checkpoints::load_checkpoints_from_dns(bool testnet) { + std::vector<std::string> records; + // All four MoneroPulse domains have DNSSEC on and valid static const std::vector<std::string> dns_urls = { "checkpoints.moneropulse.se" , "checkpoints.moneropulse.org" @@ -237,87 +221,10 @@ namespace cryptonote , "testpoints.moneropulse.co" }; - std::vector<std::vector<std::string> > records; - records.resize(dns_urls.size()); - - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1); - size_t first_index = dis(gen); - - bool avail, valid; - size_t cur_index = first_index; - do - { - std::string url; - if (testnet) - { - url = testnet_dns_urls[cur_index]; - } - else - { - url = dns_urls[cur_index]; - } - - records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); - if (!avail) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); - } - if (!valid) - { - records[cur_index].clear(); - LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); - } - - cur_index++; - if (cur_index == dns_urls.size()) - { - cur_index = 0; - } - records[cur_index].clear(); - } while (cur_index != first_index); - - size_t num_valid_records = 0; - - for( const auto& record_set : records) - { - if (record_set.size() != 0) - { - num_valid_records++; - } - } - - if (num_valid_records < 2) - { - LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); - return true; - } - - int good_records_index = -1; - for (size_t i = 0; i < records.size() - 1; ++i) - { - if (records[i].size() == 0) continue; - - for (size_t j = i + 1; j < records.size(); ++j) - { - if (dns_records_match(records[i], records[j])) - { - good_records_index = i; - break; - } - } - if (good_records_index >= 0) break; - } - - if (good_records_index < 0) - { - LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); - return true; - } + if (!tools::dns_utils::load_txt_records_from_dns(records, testnet ? testnet_dns_urls : dns_urls)) + return true; // why true ? - for (auto& record : records[good_records_index]) + for (const auto& record : records) { auto pos = record.find(":"); if (pos != std::string::npos) diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_basic/checkpoints.h index 71727753e..3a74d8a69 100644 --- a/src/cryptonote_core/checkpoints.h +++ b/src/cryptonote_basic/checkpoints.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/connection_context.h b/src/cryptonote_basic/connection_context.h index 7e62e77b9..8d739900e 100644 --- a/src/cryptonote_core/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index da069a21a..c4adf1fcb 100644 --- a/src/cryptonote_core/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -35,6 +35,7 @@ #include <vector> #include <cstring> // memcmp #include <sstream> +#include <atomic> #include "serialization/serialization.h" #include "serialization/variant.h" #include "serialization/vector.h" @@ -43,7 +44,6 @@ #include "serialization/debug_archive.h" #include "serialization/crypto.h" #include "serialization/keyvalue_serialization.h" // eepe named serialization -#include "string_tools.h" #include "cryptonote_config.h" #include "crypto/crypto.h" #include "crypto/hash.h" @@ -186,15 +186,37 @@ namespace cryptonote class transaction: public transaction_prefix { + private: + // hash cash + mutable std::atomic<bool> hash_valid; + mutable std::atomic<bool> blob_size_valid; + public: std::vector<std::vector<crypto::signature> > signatures; //count signatures always the same as inputs count rct::rctSig rct_signatures; + // hash cash + mutable crypto::hash hash; + mutable size_t blob_size; + transaction(); + transaction(const transaction &t): transaction_prefix(t), hash_valid(false), blob_size_valid(false), signatures(t.signatures), rct_signatures(t.rct_signatures) { if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } } + transaction &operator=(const transaction &t) { transaction_prefix::operator=(t); set_hash_valid(false); set_blob_size_valid(false); signatures = t.signatures; rct_signatures = t.rct_signatures; if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } return *this; } virtual ~transaction(); void set_null(); + void invalidate_hashes(); + bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } + void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + bool is_blob_size_valid() const { return blob_size_valid.load(std::memory_order_acquire); } + void set_blob_size_valid(bool v) const { blob_size_valid.store(v,std::memory_order_release); } BEGIN_SERIALIZE_OBJECT() + if (!typename Archive<W>::is_saving()) + { + set_hash_valid(false); + set_blob_size_valid(false); + } + FIELDS(*static_cast<transaction_prefix *>(this)) if (version == 1) @@ -250,6 +272,28 @@ namespace cryptonote } END_SERIALIZE() + template<bool W, template <bool> class Archive> + bool serialize_base(Archive<W> &ar) + { + FIELDS(*static_cast<transaction_prefix *>(this)) + + if (version == 1) + { + } + else + { + ar.tag("rct_signatures"); + if (!vin.empty()) + { + ar.begin_object(); + bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); + if (!r || !ar.stream().good()) return false; + ar.end_object(); + } + } + return true; + } + private: static size_t get_signature_size(const txin_v& tx_in); }; @@ -277,6 +321,15 @@ namespace cryptonote extra.clear(); signatures.clear(); rct_signatures.type = rct::RCTTypeNull; + set_hash_valid(false); + set_blob_size_valid(false); + } + + inline + void transaction::invalidate_hashes() + { + set_hash_valid(false); + set_blob_size_valid(false); } inline @@ -317,10 +370,28 @@ namespace cryptonote struct block: public block_header { + private: + // hash cash + mutable std::atomic<bool> hash_valid; + + public: + block(): block_header(), hash_valid(false) {} + block(const block &b): block_header(b), hash_valid(false), miner_tx(b.miner_tx), tx_hashes(b.tx_hashes) { if (b.is_hash_valid()) { hash = b.hash; set_hash_valid(true); } } + block &operator=(const block &b) { block_header::operator=(b); hash_valid = false; miner_tx = b.miner_tx; tx_hashes = b.tx_hashes; if (b.is_hash_valid()) { hash = b.hash; set_hash_valid(true); } return *this; } + void invalidate_hashes() { set_hash_valid(false); } + bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } + void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + transaction miner_tx; std::vector<crypto::hash> tx_hashes; + // hash cash + mutable crypto::hash hash; + BEGIN_SERIALIZE_OBJECT() + if (!typename Archive<W>::is_saving()) + set_hash_valid(false); + FIELDS(*static_cast<block_header *>(this)) FIELD(miner_tx) FIELD(tx_hashes) diff --git a/src/cryptonote_core/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 74f44e2af..edd67793f 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -41,6 +41,10 @@ using namespace epee; #include "common/base58.h" #include "crypto/hash.h" #include "common/int-util.h" +#include "common/dns_utils.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" namespace cryptonote { @@ -63,6 +67,15 @@ namespace cryptonote { /* Cryptonote helper functions */ /************************************************************************/ //----------------------------------------------------------------------------------------------- + size_t get_min_block_size(uint8_t version) + { + if (version < 2) + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; + if (version < 5) + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; + } + //----------------------------------------------------------------------------------------------- size_t get_max_block_size() { return CRYPTONOTE_MAX_BLOCK_SIZE; @@ -85,7 +98,7 @@ namespace cryptonote { base_reward = FINAL_SUBSIDY_PER_MINUTE*target_minutes; } - uint64_t full_reward_zone = version < 2 ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + uint64_t full_reward_zone = get_min_block_size(version); //make it soft if (median_size < full_reward_zone) { @@ -98,7 +111,7 @@ namespace cryptonote { } 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); + MERROR("Block cumulative size is too big: " << current_block_size << ", expected less than " << 2 * median_size); return false; } @@ -199,7 +212,7 @@ namespace cryptonote { uint64_t prefix; if (!tools::base58::decode_addr(str, prefix, data)) { - LOG_PRINT_L1("Invalid address format"); + LOG_PRINT_L2("Invalid address format"); return false; } @@ -288,7 +301,36 @@ namespace cryptonote { crypto::hash8 payment_id; return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str); } - + //-------------------------------------------------------------------------------- + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + , bool cli_confirm + ) + { + if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) + return true; + bool dnssec_valid; + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, cli_confirm); + return !address_str.empty() && + get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); + } + //-------------------------------------------------------------------------------- + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool testnet + , const std::string& str_or_url + , bool cli_confirm + ) + { + bool has_payment_id; + crypto::hash8 payment_id; + return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, cli_confirm); + } + //-------------------------------------------------------------------------------- bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { return cryptonote::get_transaction_hash(a) == cryptonote::get_transaction_hash(b); } diff --git a/src/cryptonote_core/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 147bc89ba..14c03ac4c 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -33,6 +33,8 @@ #include "cryptonote_basic.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "hex.h" +#include "span.h" namespace cryptonote { @@ -69,6 +71,7 @@ namespace cryptonote { /************************************************************************/ /* Cryptonote helper functions */ /************************************************************************/ + size_t get_min_block_size(uint8_t version); size_t get_max_block_size(); size_t get_max_tx_size(); bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward, uint8_t version); @@ -100,29 +103,50 @@ namespace cryptonote { , const std::string& str ); + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + , bool cli_confirm = true + ); + + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool testnet + , const std::string& str_or_url + , bool cli_confirm = true + ); + bool is_coinbase(const transaction& tx); bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b); bool operator ==(const cryptonote::block& a, const cryptonote::block& b); } -template <class T> -std::ostream &print256(std::ostream &o, const T &v) { - return o << "<" << epee::string_tools::pod_to_hex(v) << ">"; -} -template <class T> -std::ostream &print64(std::ostream &o, const T &v) { - return o << "<" << epee::string_tools::pod_to_hex(v) << ">"; -} - bool parse_hash256(const std::string str_hash, crypto::hash& hash); namespace crypto { - inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { return print256(o, v); } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { return print64(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } } diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 19b1a687e..6e4ac9b72 100644 --- a/src/cryptonote_core/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -35,10 +35,10 @@ #include <boost/serialization/variant.hpp> #include <boost/serialization/set.hpp> #include <boost/serialization/map.hpp> -#include <boost/foreach.hpp> #include <boost/serialization/is_bitwise_serializable.hpp> #include <boost/archive/binary_iarchive.hpp> -#include <boost/archive/binary_oarchive.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> #include "cryptonote_basic.h" #include "common/unordered_containers_boost_serialization.h" #include "crypto/crypto.h" @@ -207,11 +207,11 @@ namespace boost } template <class Archive> - inline void serialize(Archive &a, rct::asnlSig &x, const boost::serialization::version_type ver) + inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { - a & x.L1; - a & x.s2; - a & x.s; + a & x.s0; + a & x.s1; + a & x.ee; } template <class Archive> @@ -230,7 +230,8 @@ namespace boost // a & x.senderPk; // not serialized, as we do not use it in monero currently } - inline void serializeOutPk(boost::archive::binary_iarchive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + template <class Archive> + inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) { rct::keyV outPk; a & outPk; @@ -242,7 +243,8 @@ namespace boost } } - inline void serializeOutPk(boost::archive::binary_oarchive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + template <class Archive> + inline typename std::enable_if<Archive::is_saving::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) { rct::keyV outPk(outPk_.size()); for (size_t n = 0; n < outPk_.size(); ++n) diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index d1ccfc7d1..745dfb72e 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,16 +31,20 @@ #include "include_base_utils.h" using namespace epee; +#include <atomic> #include "cryptonote_format_utils.h" -#include <boost/foreach.hpp> #include "cryptonote_config.h" -#include "miner.h" #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" + #define ENCRYPTED_PAYMENT_ID_TAIL 0x8d +// #define ENABLE_HASH_CASH_INTEGRITY_CHECK + static const uint64_t valid_decomposed_outputs[] = { (uint64_t)1, (uint64_t)2, (uint64_t)3, (uint64_t)4, (uint64_t)5, (uint64_t)6, (uint64_t)7, (uint64_t)8, (uint64_t)9, // 1 piconero (uint64_t)10, (uint64_t)20, (uint64_t)30, (uint64_t)40, (uint64_t)50, (uint64_t)60, (uint64_t)70, (uint64_t)80, (uint64_t)90, @@ -64,6 +68,13 @@ static const uint64_t valid_decomposed_outputs[] = { (uint64_t)10000000000000000000ull }; +static std::atomic<unsigned int> default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); + +static std::atomic<uint64_t> tx_hashes_calculated_count(0); +static std::atomic<uint64_t> tx_hashes_cached_count(0); +static std::atomic<uint64_t> block_hashes_calculated_count(0); +static std::atomic<uint64_t> block_hashes_cached_count(0); + namespace cryptonote { //--------------------------------------------------------------- @@ -89,6 +100,17 @@ namespace cryptonote binary_archive<false> ba(ss); bool r = ::serialization::serialize(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + tx.invalidate_hashes(); + return true; + } + //--------------------------------------------------------------- + bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx) + { + std::stringstream ss; + ss << tx_blob; + binary_archive<false> ba(ss); + bool r = tx.serialize_base(ba); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); return true; } //--------------------------------------------------------------- @@ -99,6 +121,7 @@ namespace cryptonote binary_archive<false> ba(ss); bool r = ::serialization::serialize(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + tx.invalidate_hashes(); //TODO: validate tx get_transaction_hash(tx, tx_hash); @@ -106,102 +129,6 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - 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, uint8_t hard_fork_version) { - tx.vin.clear(); - tx.vout.clear(); - tx.extra.clear(); - - keypair txkey = keypair::generate(); - add_tx_pub_key_to_extra(tx, txkey.pub); - if(!extra_nonce.empty()) - if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) - return false; - - txin_gen in; - in.height = height; - - uint64_t block_reward; - if(!get_block_reward(median_size, current_block_size, already_generated_coins, block_reward, hard_fork_version)) - { - LOG_PRINT_L0("Block is too big"); - return false; - } - -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: reward " << block_reward << - ", fee " << fee); -#endif - block_reward += fee; - - // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and - // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the - // emission schedule - // from hard fork 4, we use a single "dusty" output. This makes the tx even smaller, - // and avoids the quantization. These outputs will be added as rct outputs with identity - // masks, to they can be used as rct inputs. - if (hard_fork_version >= 2 && hard_fork_version < 4) { - block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; - } - - std::vector<uint64_t> out_amounts; - decompose_amount_into_digits(block_reward, hard_fork_version >= 2 ? 0 : ::config::DEFAULT_DUST_THRESHOLD, - [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, - [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); - - CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); - if (height == 0 || hard_fork_version >= 4) - { - // the genesis block was not decomposed, for unknown reasons - while (max_outs < out_amounts.size()) - { - //out_amounts[out_amounts.size() - 2] += out_amounts.back(); - //out_amounts.resize(out_amounts.size() - 1); - out_amounts[1] += out_amounts[0]; - for (size_t n = 1; n < out_amounts.size(); ++n) - out_amounts[n - 1] = out_amounts[n]; - out_amounts.resize(out_amounts.size() - 1); - } - } - else - { - CHECK_AND_ASSERT_MES(max_outs >= out_amounts.size(), false, "max_out exceeded"); - } - - uint64_t summary_amounts = 0; - for (size_t no = 0; no < out_amounts.size(); no++) - { - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; - crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); - bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); - - r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); - CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); - - txout_to_key tk; - tk.key = out_eph_public_key; - - tx_out out; - summary_amounts += out.amount = out_amounts[no]; - out.target = tk; - tx.vout.push_back(out); - } - - CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); - - if (hard_fork_version >= 4) - tx.version = 2; - else - tx.version = 1; - - //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) - // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); - return true; - } - //--------------------------------------------------------------- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); @@ -237,12 +164,12 @@ namespace cryptonote if (std::string::npos != point_index) { fraction_size = str_amount.size() - point_index - 1; - while (CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size && '0' == str_amount.back()) + while (default_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) + if (default_decimal_point < fraction_size) return false; str_amount.erase(point_index, 1); } @@ -254,9 +181,9 @@ namespace cryptonote if (str_amount.empty()) return false; - if (fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT) + if (fraction_size < default_decimal_point) { - str_amount.append(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size, '0'); + str_amount.append(default_decimal_point - fraction_size, '0'); } return string_tools::get_xtype_from_string(amount, str_amount); @@ -271,12 +198,12 @@ namespace cryptonote } uint64_t amount_in = 0; uint64_t amount_out = 0; - BOOST_FOREACH(auto& in, tx.vin) + for(auto& in: tx.vin) { CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), 0, "unexpected type id in transaction"); amount_in += boost::get<txin_to_key>(in).amount; } - BOOST_FOREACH(auto& o, tx.vout) + for(auto& o: tx.vout) amount_out += o.amount; CHECK_AND_ASSERT_MES(amount_in >= amount_out, false, "transaction spend (" <<amount_in << ") more than it has (" << amount_out << ")"); @@ -320,26 +247,26 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra) + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index) { std::vector<tx_extra_field> tx_extra_fields; parse_tx_extra(tx_extra, tx_extra_fields); tx_extra_pub_key pub_key_field; - if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field)) + if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index)) return null_pkey; return pub_key_field.pub_key; } //--------------------------------------------------------------- - crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx_prefix) + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx_prefix, size_t pk_index) { - return get_tx_pub_key_from_extra(tx_prefix.extra); + return get_tx_pub_key_from_extra(tx_prefix.extra, pk_index); } //--------------------------------------------------------------- - crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index) { - return get_tx_pub_key_from_extra(tx.extra); + return get_tx_pub_key_from_extra(tx.extra, pk_index); } //--------------------------------------------------------------- bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key) @@ -366,8 +293,10 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool remove_extra_nonce_tx_extra(std::vector<uint8_t>& tx_extra) + bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type) { + if (tx_extra.empty()) + return true; std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); std::istringstream iss(extra_str); binary_archive<false> ar(iss); @@ -380,7 +309,7 @@ namespace cryptonote tx_extra_field field; bool r = ::do_serialize(ar, field); CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); - if (field.type() != typeid(tx_extra_nonce)) + if (field.type() != type) ::do_serialize(newar, field); std::ios_base::iostate state = iss.rdstate(); @@ -431,20 +360,6 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys) - { - if (destinations.empty()) - return null_pkey; - for (size_t n = 1; n < destinations.size(); ++n) - { - if (!memcmp(&destinations[n].addr, &sender_keys.m_account_address, sizeof(destinations[0].addr))) - continue; - if (memcmp(&destinations[n].addr, &destinations[0].addr, sizeof(destinations[0].addr))) - return null_pkey; - } - return destinations[0].addr.m_view_public_key; - } - //--------------------------------------------------------------- bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) { crypto::key_derivation derivation; @@ -469,309 +384,10 @@ namespace cryptonote return encrypt_payment_id(payment_id, public_key, secret_key); } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) - { - std::vector<rct::key> amount_keys; - tx.vin.clear(); - tx.vout.clear(); - tx.signatures.clear(); - tx.rct_signatures.type = rct::RCTTypeNull; - amount_keys.clear(); - - tx.version = rct ? 2 : 1; - tx.unlock_time = unlock_time; - - tx.extra = extra; - keypair txkey = keypair::generate(); - add_tx_pub_key_to_extra(tx, txkey.pub); - tx_key = txkey.sec; - - // if we have a stealth payment id, find it and encrypt it with the tx key now - std::vector<tx_extra_field> tx_extra_fields; - if (parse_tx_extra(tx.extra, tx_extra_fields)) - { - tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - crypto::hash8 payment_id = null_hash8; - if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) - { - LOG_PRINT_L2("Encrypting payment id " << payment_id); - crypto::public_key view_key_pub = get_destination_view_key_pub(destinations, sender_account_keys); - if (view_key_pub == null_pkey) - { - LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); - return false; - } - - if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec)) - { - LOG_ERROR("Failed to encrypt payment id"); - return false; - } - - std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - remove_extra_nonce_tx_extra(tx.extra); - if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) - { - LOG_ERROR("Failed to add encrypted payment id to tx extra"); - return false; - } - LOG_PRINT_L1("Encrypted payment ID: " << payment_id); - } - } - } - else - { - LOG_ERROR("Failed to parse tx extra"); - return false; - } - - struct input_generation_context_data - { - keypair in_ephemeral; - }; - std::vector<input_generation_context_data> in_contexts; - - uint64_t summary_inputs_money = 0; - //fill inputs - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - { - if(src_entr.real_output >= src_entr.outputs.size()) - { - LOG_ERROR("real_output index (" << src_entr.real_output << ")bigger than output_keys.size()=" << src_entr.outputs.size()); - return false; - } - summary_inputs_money += src_entr.amount; - - //key_derivation recv_derivation; - in_contexts.push_back(input_generation_context_data()); - keypair& in_ephemeral = in_contexts.back().in_ephemeral; - crypto::key_image img; - if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img)) - return false; - - //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) - { - LOG_ERROR("derived public key missmatch with output public key! "<< ENDL << "derived_key:" - << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" - << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second) ); - return false; - } - - //put key image into tx input - txin_to_key input_to_key; - input_to_key.amount = src_entr.amount; - input_to_key.k_image = img; - - //fill outputs array and use relative offsets - BOOST_FOREACH(const tx_source_entry::output_entry& out_entry, src_entr.outputs) - input_to_key.key_offsets.push_back(out_entry.first); - - input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); - tx.vin.push_back(input_to_key); - } - - // "Shuffle" outs - std::vector<tx_destination_entry> shuffled_dsts(destinations); - std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; } ); - - uint64_t summary_outs_money = 0; - //fill outputs - size_t output_index = 0; - BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) - { - CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); - crypto::key_derivation derivation; - crypto::public_key out_eph_public_key; - bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")"); - - if (tx.version > 1) - { - crypto::secret_key scalar1; - crypto::derivation_to_scalar(derivation, output_index, scalar1); - amount_keys.push_back(rct::sk2rct(scalar1)); - } - r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); - - tx_out out; - out.amount = dst_entr.amount; - txout_to_key tk; - tk.key = out_eph_public_key; - out.target = tk; - tx.vout.push_back(out); - output_index++; - summary_outs_money += dst_entr.amount; - } - - //check money - if(summary_outs_money > summary_inputs_money ) - { - LOG_ERROR("Transaction inputs money ("<< summary_inputs_money << ") less than outputs money (" << summary_outs_money << ")"); - return false; - } - - - if (tx.version == 1) - { - //generate ring signatures - crypto::hash tx_prefix_hash; - get_transaction_prefix_hash(tx, tx_prefix_hash); - - std::stringstream ss_ring_s; - size_t i = 0; - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - { - ss_ring_s << "pub_keys:" << ENDL; - std::vector<const crypto::public_key*> keys_ptrs; - std::vector<crypto::public_key> keys(src_entr.outputs.size()); - size_t ii = 0; - BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) - { - keys[ii] = rct2pk(o.second.dest); - keys_ptrs.push_back(&keys[ii]); - ss_ring_s << o.second.dest << ENDL; - ++ii; - } - - tx.signatures.push_back(std::vector<crypto::signature>()); - std::vector<crypto::signature>& sigs = tx.signatures.back(); - sigs.resize(src_entr.outputs.size()); - crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); - ss_ring_s << "signatures:" << ENDL; - std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); - ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; - i++; - } - - LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); - } - else - { - bool all_rct_inputs = true; - size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - all_rct_inputs &= !(src_entr.mask == rct::identity()); - - // the non-simple version is slightly smaller, but assumes all real inputs - // are on the same index, so can only be used if there just one ring. - bool use_simple_rct = sources.size() > 1; - - if (!use_simple_rct) - { - // non simple ringct requires all real inputs to be at the same index for all inputs - BOOST_FOREACH(const tx_source_entry& src_entr, sources) - { - if(src_entr.real_output != sources.begin()->real_output) - { - LOG_ERROR("All inputs must have the same index for non-simple ringct"); - return false; - } - } - - // enforce same mixin for all outputs - for (size_t i = 1; i < sources.size(); ++i) { - if (n_total_outs != sources[i].outputs.size()) { - LOG_ERROR("Non-simple ringct transaction has varying mixin"); - return false; - } - } - } - - uint64_t amount_in = 0, amount_out = 0; - rct::ctkeyV inSk; - // mixRing indexing is done the other way round for simple - rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); - rct::keyV destinations; - std::vector<uint64_t> inamounts, outamounts; - std::vector<unsigned int> index; - for (size_t i = 0; i < sources.size(); ++i) - { - rct::ctkey ctkey; - amount_in += sources[i].amount; - inamounts.push_back(sources[i].amount); - index.push_back(sources[i].real_output); - // inSk: (secret key, mask) - ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); - ctkey.mask = sources[i].mask; - inSk.push_back(ctkey); - // inPk: (public key, commitment) - // will be done when filling in mixRing - } - for (size_t i = 0; i < tx.vout.size(); ++i) - { - destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); - outamounts.push_back(tx.vout[i].amount); - amount_out += tx.vout[i].amount; - } - - if (use_simple_rct) - { - // mixRing indexing is done the other way round for simple - for (size_t i = 0; i < sources.size(); ++i) - { - mixRing[i].resize(sources[i].outputs.size()); - for (size_t n = 0; n < sources[i].outputs.size(); ++n) - { - mixRing[i][n] = sources[i].outputs[n].second; - } - } - } - else - { - for (size_t i = 0; i < n_total_outs; ++i) // same index assumption - { - mixRing[i].resize(sources.size()); - for (size_t n = 0; n < sources.size(); ++n) - { - mixRing[i][n] = sources[n].outputs[i].second; - } - } - } - - // fee - if (!use_simple_rct && amount_in > amount_out) - outamounts.push_back(amount_in - amount_out); - - // zero out all amounts to mask rct outputs, real amounts are now encrypted - for (size_t i = 0; i < tx.vin.size(); ++i) - { - if (sources[i].rct) - boost::get<txin_to_key>(tx.vin[i]).amount = 0; - } - for (size_t i = 0; i < tx.vout.size(); ++i) - tx.vout[i].amount = 0; - - crypto::hash tx_prefix_hash; - get_transaction_prefix_hash(tx, tx_prefix_hash); - rct::ctkeyV outSk; - if (use_simple_rct) - tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk); - else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk); // same index assumption - - CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); - - LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL, LOG_LEVEL_3); - } - - return true; - } - //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time) - { - crypto::secret_key tx_key; - return construct_tx_and_get_tx_key(sender_account_keys, sources, destinations, extra, tx, unlock_time, tx_key); - } - //--------------------------------------------------------------- bool get_inputs_money_amount(const transaction& tx, uint64_t& money) { money = 0; - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); money += tokey_in.amount; @@ -788,7 +404,7 @@ namespace cryptonote //--------------------------------------------------------------- bool check_inputs_types_supported(const transaction& tx) { - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), false, "wrong variant type: " << in.type().name() << ", expected " << typeid(txin_to_key).name() @@ -800,7 +416,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool check_outs_valid(const transaction& tx) { - BOOST_FOREACH(const tx_out& out, tx.vout) + for(const tx_out& out: tx.vout) { CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: " << out.target.type().name() << ", expected " << typeid(txout_to_key).name() @@ -825,7 +441,7 @@ namespace cryptonote bool check_inputs_overflow(const transaction& tx) { uint64_t money = 0; - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); if(money > tokey_in.amount + money) @@ -838,7 +454,7 @@ namespace cryptonote bool check_outs_overflow(const transaction& tx) { uint64_t money = 0; - BOOST_FOREACH(const auto& o, tx.vout) + for(const auto& o: tx.vout) { if(money > o.amount + money) return false; @@ -850,7 +466,7 @@ namespace cryptonote uint64_t get_outs_money_amount(const transaction& tx) { uint64_t outputs_amount = 0; - BOOST_FOREACH(const auto& o, tx.vout) + for(const auto& o: tx.vout) outputs_amount += o.amount; return outputs_amount; } @@ -873,6 +489,13 @@ namespace cryptonote return pk == out_key.key; } //--------------------------------------------------------------- + bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) + { + crypto::public_key pk; + derive_public_key(derivation, output_index, spend_public_key, pk); + return pk == out_key.key; + } + //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered) { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); @@ -885,7 +508,7 @@ namespace cryptonote { money_transfered = 0; size_t i = 0; - BOOST_FOREACH(const tx_out& o, tx.vout) + for(const tx_out& o: tx.vout) { CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) @@ -903,14 +526,59 @@ namespace cryptonote cn_fast_hash(blob.data(), blob.size(), res); } //--------------------------------------------------------------- - std::string print_money(uint64_t amount) + void set_default_decimal_point(unsigned int decimal_point) { + switch (decimal_point) + { + case 12: + case 9: + case 6: + case 3: + case 0: + default_decimal_point = decimal_point; + break; + default: + ASSERT_MES_AND_THROW("Invalid decimal point specification: " << decimal_point); + } + } + //--------------------------------------------------------------- + unsigned int get_default_decimal_point() + { + return default_decimal_point; + } + //--------------------------------------------------------------- + std::string get_unit(unsigned int decimal_point) + { + if (decimal_point == (unsigned int)-1) + decimal_point = default_decimal_point; + switch (std::atomic_load(&default_decimal_point)) + { + case 12: + return "monero"; + case 9: + return "millinero"; + case 6: + return "micronero"; + case 3: + return "nanonero"; + case 0: + return "piconero"; + default: + ASSERT_MES_AND_THROW("Invalid decimal point specification: " << default_decimal_point); + } + } + //--------------------------------------------------------------- + std::string print_money(uint64_t amount, unsigned int decimal_point) + { + if (decimal_point == (unsigned int)-1) + decimal_point = default_decimal_point; std::string s = std::to_string(amount); - if(s.size() < CRYPTONOTE_DISPLAY_DECIMAL_POINT+1) + if(s.size() < decimal_point+1) { - s.insert(0, CRYPTONOTE_DISPLAY_DECIMAL_POINT+1 - s.size(), '0'); + s.insert(0, decimal_point+1 - s.size(), '0'); } - s.insert(s.size() - CRYPTONOTE_DISPLAY_DECIMAL_POINT, "."); + if (decimal_point > 0) + s.insert(s.size() - decimal_point, "."); return s; } //--------------------------------------------------------------- @@ -933,7 +601,7 @@ namespace cryptonote return get_transaction_hash(t, res, NULL); } //--------------------------------------------------------------- - bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) + bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) { // v1 transactions hash the entire blob if (t.version == 1) @@ -988,6 +656,40 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) + { + if (t.is_hash_valid()) + { +#ifdef ENABLE_HASH_CASH_INTEGRITY_CHECK + CHECK_AND_ASSERT_THROW_MES(!calculate_transaction_hash(t, res, blob_size) || t.hash == res, "tx hash cash integrity failure"); +#endif + res = t.hash; + if (blob_size) + { + if (!t.is_blob_size_valid()) + { + t.blob_size = get_object_blobsize(t); + t.set_blob_size_valid(true); + } + *blob_size = t.blob_size; + } + ++tx_hashes_cached_count; + return true; + } + ++tx_hashes_calculated_count; + bool ret = calculate_transaction_hash(t, res, blob_size); + if (!ret) + return false; + t.hash = res; + t.set_hash_valid(true); + if (blob_size) + { + t.blob_size = *blob_size; + t.set_blob_size_valid(true); + } + return true; + } + //--------------------------------------------------------------- bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size) { return get_transaction_hash(t, res, &blob_size); @@ -1002,7 +704,7 @@ namespace cryptonote return blob; } //--------------------------------------------------------------- - bool get_block_hash(const block& b, crypto::hash& res) + bool calculate_block_hash(const block& b, crypto::hash& res) { // EXCEPTION FOR BLOCK 202612 const std::string correct_blob_hash_202612 = "3a8a2b3a29b50fc86ff73dd087ea43c6f0d6b8f936c849194d5c84c737903966"; @@ -1029,6 +731,26 @@ namespace cryptonote return hash_result; } //--------------------------------------------------------------- + bool get_block_hash(const block& b, crypto::hash& res) + { + if (b.is_hash_valid()) + { +#ifdef ENABLE_HASH_CASH_INTEGRITY_CHECK + CHECK_AND_ASSERT_THROW_MES(!calculate_block_hash(b, res) || b.hash == res, "block hash cash integrity failure"); +#endif + res = b.hash; + ++block_hashes_cached_count; + return true; + } + ++block_hashes_calculated_count; + bool ret = calculate_block_hash(b, res); + if (!ret) + return false; + b.hash = res; + b.set_hash_valid(true); + return true; + } + //--------------------------------------------------------------- crypto::hash get_block_hash(const block& b) { crypto::hash p = null_hash; @@ -1036,36 +758,6 @@ namespace cryptonote return p; } //--------------------------------------------------------------- - bool generate_genesis_block( - block& bl - , std::string const & genesis_tx - , uint32_t nonce - ) - { - //genesis block - bl = boost::value_initialized<block>(); - - - account_public_address ac = boost::value_initialized<account_public_address>(); - std::vector<size_t> sz; - 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); - - std::string genesis_coinbase_tx_hex = config::GENESIS_TX; - - blobdata tx_bl; - string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); - bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); - CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); - bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; - bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; - bl.timestamp = 0; - bl.nonce = nonce; - miner::find_nonce_for_given_block(bl, 1, 0); - return true; - } - //--------------------------------------------------------------- bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) { // block 202612 bug workaround @@ -1075,7 +767,6 @@ namespace cryptonote string_tools::hex_to_pod(longhash_202612, res); return true; } - block b_local = b; //workaround to avoid const errors with do_serialize blobdata bd = get_block_hashing_blob(b); crypto::cn_slow_hash(bd.data(), bd.size(), res); return true; @@ -1115,6 +806,8 @@ namespace cryptonote binary_archive<false> ba(ss); bool r = ::serialization::serialize(ba, b); CHECK_AND_ASSERT_MES(r, false, "Failed to parse block from blob"); + b.invalidate_hashes(); + b.miner_tx.invalidate_hashes(); return true; } //--------------------------------------------------------------- @@ -1157,7 +850,7 @@ namespace cryptonote size_t bl_sz = 0; get_transaction_hash(b.miner_tx, h, bl_sz); txs_ids.push_back(h); - BOOST_FOREACH(auto& th, b.tx_hashes) + for(auto& th: b.tx_hashes) txs_ids.push_back(th); return get_tx_tree_hash(txs_ids); } @@ -1169,4 +862,11 @@ namespace cryptonote return std::binary_search(begin, end, amount); } //--------------------------------------------------------------- + void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached) + { + tx_hashes_calculated = tx_hashes_calculated_count; + tx_hashes_cached = tx_hashes_cached_count; + block_hashes_calculated = block_hashes_calculated_count; + block_hashes_cached = block_hashes_cached_count; + } } diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index e6a3bfba4..5c10907fd 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,14 +30,13 @@ #pragma once #include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_basic_impl.h" #include "account.h" #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctOps.h" - namespace cryptonote { //--------------------------------------------------------------- @@ -45,42 +44,14 @@ namespace cryptonote 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(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 = 999, uint8_t hard_fork_version = 1); + bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx); bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); - struct tx_source_entry - { - typedef std::pair<uint64_t, rct::ctkey> output_entry; - - std::vector<output_entry> outputs; //index + key + optional ringct commitment - size_t real_output; //index in outputs vector of real output_entry - crypto::public_key real_out_tx_key; //incoming real tx public key - size_t real_output_in_tx_index; //index in transaction outputs vector - uint64_t amount; //money - bool rct; //true if the output is rct - rct::key mask; //ringct amount mask - - void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } - }; - - struct tx_destination_entry - { - uint64_t amount; //money - account_public_address addr; //destination address - - tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { } - tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } - }; - - //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); - template<typename T> - bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field) + bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field, size_t index = 0) { - auto it = std::find_if(tx_extra_fields.begin(), tx_extra_fields.end(), [](const tx_extra_field& f) { return typeid(T) == f.type(); }); + auto it = std::find_if(tx_extra_fields.begin(), tx_extra_fields.end(), [&index](const tx_extra_field& f) { return typeid(T) == f.type() && !index--; }); if(tx_extra_fields.end() == it) return false; @@ -89,17 +60,18 @@ namespace cryptonote } bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields); - crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra); - crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx); - crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0); + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); - bool remove_extra_nonce_tx_extra(std::vector<uint8_t>& tx_extra); + bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); + bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); @@ -113,16 +85,13 @@ namespace cryptonote bool get_transaction_hash(const transaction& t, crypto::hash& res); bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size); bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); + bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); blobdata get_block_hashing_blob(const block& b); + bool calculate_block_hash(const block& b, crypto::hash& res); bool get_block_hash(const block& b, crypto::hash& res); crypto::hash get_block_hash(const block& b); bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height); crypto::hash get_block_longhash(const block& b, uint64_t height); - bool generate_genesis_block( - block& bl - , std::string const & genesis_tx - , uint32_t nonce - ); bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b); bool get_inputs_money_amount(const transaction& tx, uint64_t& money); uint64_t get_outs_money_amount(const transaction& tx); @@ -136,7 +105,10 @@ namespace cryptonote uint64_t get_block_height(const block& b); std::vector<uint64_t> relative_output_offsets_to_absolute(const std::vector<uint64_t>& off); std::vector<uint64_t> absolute_output_offsets_to_relative(const std::vector<uint64_t>& off); - std::string print_money(uint64_t amount); + void set_default_decimal_point(unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT); + unsigned int get_default_decimal_point(); + std::string get_unit(unsigned int decimal_point = -1); + std::string print_money(uint64_t amount, unsigned int decimal_point = -1); //--------------------------------------------------------------- template<class t_object> bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) @@ -239,6 +211,7 @@ namespace cryptonote crypto::hash get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes); crypto::hash get_tx_tree_hash(const block& b); bool is_valid_decomposed_amount(uint64_t amount); + void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ diff --git a/src/cryptonote_core/cryptonote_stat_info.h b/src/cryptonote_basic/cryptonote_stat_info.h index d44904b6d..7ebf86878 100644 --- a/src/cryptonote_core/cryptonote_stat_info.h +++ b/src/cryptonote_basic/cryptonote_stat_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 54da77392..863aa4359 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -39,6 +39,9 @@ #include "cryptonote_config.h" #include "difficulty.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "difficulty" + namespace cryptonote { using std::size_t; diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_basic/difficulty.h index 910f97035..aed6cb289 100644 --- a/src/cryptonote_core/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index c63ca36ef..546af2076 100644 --- a/src/cryptonote_core/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,10 +29,13 @@ #include <algorithm> #include <cstdio> -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "blockchain_db/blockchain_db.h" #include "hardfork.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "hardfork" + using namespace cryptonote; static uint8_t get_block_vote(const cryptonote::block &b) @@ -114,6 +117,19 @@ bool HardFork::check(const cryptonote::block &block) const return do_check(::get_block_version(block), ::get_block_vote(block)); } +bool HardFork::do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const +{ + int fork_index = get_voted_fork_index(height); + return block_version == heights[fork_index].version + && voting_version >= heights[fork_index].version; +} + +bool HardFork::check_for_height(const cryptonote::block &block, uint64_t height) const +{ + CRITICAL_REGION_LOCAL(lock); + return do_check_for_height(::get_block_version(block), ::get_block_vote(block), height); +} + bool HardFork::add(uint8_t block_version, uint8_t voting_version, uint64_t height) { CRITICAL_REGION_LOCAL(lock); @@ -175,10 +191,10 @@ void HardFork::init() } catch (...) { populate = true; } if (populate) { - LOG_PRINT_L0("The DB has no hard fork info, reparsing from start"); + MINFO("The DB has no hard fork info, reparsing from start"); height = 1; } - LOG_PRINT_L1("reorganizing from " << height); + MDEBUG("reorganizing from " << height); if (populate) { reorganize_from_chain_height(height); // reorg will not touch the genesis block, use this as a flag for populating done @@ -187,7 +203,7 @@ void HardFork::init() else { rescan_from_chain_height(height); } - LOG_PRINT_L1("reorganization done"); + MDEBUG("reorganization done"); } uint8_t HardFork::get_block_version(uint64_t height) const @@ -206,7 +222,7 @@ bool HardFork::reorganize_from_block_height(uint64_t height) return false; db.set_batch_transactions(true); - db.batch_start(); + bool stop_batch = db.batch_start(); versions.clear(); @@ -234,7 +250,8 @@ bool HardFork::reorganize_from_block_height(uint64_t height) add(db.get_block_from_height(h), h); } - db.batch_stop(); + if (stop_batch) + db.batch_stop(); return true; } diff --git a/src/cryptonote_core/hardfork.h b/src/cryptonote_basic/hardfork.h index 33b958f9c..6c6fbcb84 100644 --- a/src/cryptonote_core/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,7 +29,7 @@ #pragma once #include "syncobj.h" -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" namespace cryptonote { @@ -114,6 +114,20 @@ namespace cryptonote bool check(const cryptonote::block &block) const; /** + * @brief same as check, but for a particular height, rather than the top + * + * NOTE: this does not play well with voting, and relies on voting to be + * disabled (that is, forks happen on the scheduled date, whether or not + * enough blocks have voted for the fork). + * + * returns true if no error, false otherwise + * + * @param block the new block + * @param height which height to check for + */ + bool check_for_height(const cryptonote::block &block, uint64_t height) const; + + /** * @brief add a new block * * returns true if no error, false otherwise @@ -211,6 +225,7 @@ namespace cryptonote uint8_t get_block_version(uint64_t height) const; bool do_check(uint8_t block_version, uint8_t voting_version) const; + bool do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const; int get_voted_fork_index(uint64_t height) const; uint8_t get_effective_version(uint8_t voting_version) const; bool add(uint8_t block_version, uint8_t voting_version, uint64_t height); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp new file mode 100644 index 000000000..eeb7b6094 --- /dev/null +++ b/src/cryptonote_basic/miner.cpp @@ -0,0 +1,853 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <sstream> +#include <numeric> +#include <boost/utility/value_init.hpp> +#include <boost/interprocess/detail/atomic.hpp> +#include <boost/limits.hpp> +#include "misc_language.h" +#include "include_base_utils.h" +#include "cryptonote_basic_impl.h" +#include "cryptonote_format_utils.h" +#include "file_io_utils.h" +#include "common/command_line.h" +#include "string_coding.h" +#include "storages/portable_storage_template_helper.h" +#include "boost/logic/tribool.hpp" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "miner" + +using namespace epee; + +#include "miner.h" + + +extern "C" void slow_hash_allocate_state(); +extern "C" void slow_hash_free_state(); +namespace cryptonote +{ + + namespace + { + const command_line::arg_descriptor<std::string> arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; + const command_line::arg_descriptor<std::string> arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; + const command_line::arg_descriptor<uint32_t> arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; + const command_line::arg_descriptor<bool> arg_bg_mining_enable = {"bg-mining-enable", "enable/disable background mining", true, true}; + const command_line::arg_descriptor<bool> arg_bg_mining_ignore_battery = {"bg-mining-ignore-battery", "if true, assumes plugged in when unable to query system power status", false, true}; + const command_line::arg_descriptor<uint64_t> arg_bg_mining_min_idle_interval_seconds = {"bg-mining-min-idle-interval", "Specify min lookback interval in seconds for determining idle state", miner::BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS, true}; + const command_line::arg_descriptor<uint8_t> arg_bg_mining_idle_threshold_percentage = {"bg-mining-idle-threshold", "Specify minimum avg idle percentage over lookback interval", miner::BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE, true}; + const command_line::arg_descriptor<uint8_t> arg_bg_mining_miner_target_percentage = {"bg-mining-miner-target", "Specificy maximum percentage cpu use by miner(s)", miner::BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE, true}; + } + + + miner::miner(i_miner_handler* phandler):m_stop(1), + m_template(boost::value_initialized<block>()), + m_template_no(0), + m_diffic(0), + m_thread_index(0), + m_phandler(phandler), + m_height(0), + m_pausers_count(0), + m_threads_total(0), + m_starter_nonce(0), + m_last_hr_merge_time(0), + m_hashes(0), + m_do_print_hashrate(false), + m_do_mining(false), + m_current_hash_rate(0), + m_is_background_mining_enabled(false), + m_min_idle_seconds(BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS), + m_idle_threshold(BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE), + m_mining_target(BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE), + m_miner_extra_sleep(BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS) + { + + } + //----------------------------------------------------------------------------------------------------- + miner::~miner() + { + stop(); + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height) + { + CRITICAL_REGION_LOCAL(m_template_lock); + m_template = bl; + m_diffic = di; + m_height = height; + ++m_template_no; + m_starter_nonce = crypto::rand<uint32_t>(); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::on_block_chain_update() + { + if(!is_mining()) + return true; + + return request_block_template(); + } + //----------------------------------------------------------------------------------------------------- + bool miner::request_block_template() + { + block bl = AUTO_VAL_INIT(bl); + difficulty_type di = AUTO_VAL_INIT(di); + uint64_t height = AUTO_VAL_INIT(height); + uint64_t expected_reward; //only used for RPC calls - could possibly be useful here too? + + cryptonote::blobdata extra_nonce; + if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) + { + extra_nonce = m_extra_messages[m_config.current_extra_message_index]; + } + + if(!m_phandler->get_block_template(bl, m_mine_address, di, height, expected_reward, extra_nonce)) + { + LOG_ERROR("Failed to get_block_template(), stopping mining"); + return false; + } + set_block_template(bl, di, height); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::on_idle() + { + m_update_block_template_interval.do_call([&](){ + if(is_mining())request_block_template(); + return true; + }); + + m_update_merge_hr_interval.do_call([&](){ + merge_hr(); + return true; + }); + + return true; + } + //----------------------------------------------------------------------------------------------------- + void miner::do_print_hashrate(bool do_hr) + { + m_do_print_hashrate = do_hr; + } + //----------------------------------------------------------------------------------------------------- + void miner::merge_hr() + { + if(m_last_hr_merge_time && is_mining()) + { + m_current_hash_rate = m_hashes * 1000 / ((misc_utils::get_tick_count() - m_last_hr_merge_time + 1)); + CRITICAL_REGION_LOCAL(m_last_hash_rates_lock); + m_last_hash_rates.push_back(m_current_hash_rate); + if(m_last_hash_rates.size() > 19) + m_last_hash_rates.pop_front(); + if(m_do_print_hashrate) + { + uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0); + float hr = static_cast<float>(total_hr)/static_cast<float>(m_last_hash_rates.size()); + std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << ENDL; + } + } + m_last_hr_merge_time = misc_utils::get_tick_count(); + m_hashes = 0; + } + //----------------------------------------------------------------------------------------------------- + void miner::init_options(boost::program_options::options_description& desc) + { + command_line::add_arg(desc, arg_extra_messages); + command_line::add_arg(desc, arg_start_mining); + command_line::add_arg(desc, arg_mining_threads); + command_line::add_arg(desc, arg_bg_mining_enable); + command_line::add_arg(desc, arg_bg_mining_ignore_battery); + command_line::add_arg(desc, arg_bg_mining_min_idle_interval_seconds); + command_line::add_arg(desc, arg_bg_mining_idle_threshold_percentage); + command_line::add_arg(desc, arg_bg_mining_miner_target_percentage); + } + //----------------------------------------------------------------------------------------------------- + bool miner::init(const boost::program_options::variables_map& vm, bool testnet) + { + if(command_line::has_arg(vm, arg_extra_messages)) + { + std::string buff; + bool r = file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_extra_messages), buff); + CHECK_AND_ASSERT_MES(r, false, "Failed to load file with extra messages: " << command_line::get_arg(vm, arg_extra_messages)); + std::vector<std::string> extra_vec; + boost::split(extra_vec, buff, boost::is_any_of("\n"), boost::token_compress_on ); + m_extra_messages.resize(extra_vec.size()); + for(size_t i = 0; i != extra_vec.size(); i++) + { + string_tools::trim(extra_vec[i]); + if(!extra_vec[i].size()) + continue; + std::string buff = string_encoding::base64_decode(extra_vec[i]); + if(buff != "0") + m_extra_messages[i] = buff; + } + m_config_folder_path = boost::filesystem::path(command_line::get_arg(vm, arg_extra_messages)).parent_path().string(); + m_config = AUTO_VAL_INIT(m_config); + epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + MINFO("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); + } + + if(command_line::has_arg(vm, arg_start_mining)) + { + if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining))) + { + LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); + return false; + } + m_threads_total = 1; + m_do_mining = true; + if(command_line::has_arg(vm, arg_mining_threads)) + { + m_threads_total = command_line::get_arg(vm, arg_mining_threads); + } + } + + // Background mining parameters + // Let init set all parameters even if background mining is not enabled, they can start later with params set + if(command_line::has_arg(vm, arg_bg_mining_enable)) + set_is_background_mining_enabled( command_line::get_arg(vm, arg_bg_mining_enable) ); + if(command_line::has_arg(vm, arg_bg_mining_ignore_battery)) + set_ignore_battery( command_line::get_arg(vm, arg_bg_mining_ignore_battery) ); + if(command_line::has_arg(vm, arg_bg_mining_min_idle_interval_seconds)) + set_min_idle_seconds( command_line::get_arg(vm, arg_bg_mining_min_idle_interval_seconds) ); + if(command_line::has_arg(vm, arg_bg_mining_idle_threshold_percentage)) + set_idle_threshold( command_line::get_arg(vm, arg_bg_mining_idle_threshold_percentage) ); + if(command_line::has_arg(vm, arg_bg_mining_miner_target_percentage)) + set_mining_target( command_line::get_arg(vm, arg_bg_mining_miner_target_percentage) ); + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::is_mining() const + { + return !m_stop; + } + //----------------------------------------------------------------------------------------------------- + const account_public_address& miner::get_mining_address() const + { + return m_mine_address; + } + //----------------------------------------------------------------------------------------------------- + uint32_t miner::get_threads_count() const { + return m_threads_total; + } + //----------------------------------------------------------------------------------------------------- + bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs, bool do_background, bool ignore_battery) + { + m_mine_address = adr; + m_threads_total = static_cast<uint32_t>(threads_count); + m_starter_nonce = crypto::rand<uint32_t>(); + CRITICAL_REGION_LOCAL(m_threads_lock); + if(is_mining()) + { + LOG_ERROR("Starting miner but it's already started"); + return false; + } + + if(!m_threads.empty()) + { + LOG_ERROR("Unable to start miner because there are active mining threads"); + return false; + } + + if(!m_template_no) + request_block_template();//lets update block template + + boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); + boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); + set_is_background_mining_enabled(do_background); + set_ignore_battery(ignore_battery); + + for(size_t i = 0; i != threads_count; i++) + { + m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this))); + } + + LOG_PRINT_L0("Mining has started with " << threads_count << " threads, good luck!" ); + + if( get_is_background_mining_enabled() ) + { + m_background_mining_thread = boost::thread(attrs, boost::bind(&miner::background_worker_thread, this)); + LOG_PRINT_L0("Background mining controller thread started" ); + } + + return true; + } + //----------------------------------------------------------------------------------------------------- + uint64_t miner::get_speed() const + { + if(is_mining()) { + return m_current_hash_rate; + } + else { + return 0; + } + } + //----------------------------------------------------------------------------------------------------- + void miner::send_stop_signal() + { + boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); + } + //----------------------------------------------------------------------------------------------------- + bool miner::stop() + { + MTRACE("Miner has received stop signal"); + + if (!is_mining()) + { + MDEBUG("Not mining - nothing to stop" ); + return true; + } + + send_stop_signal(); + CRITICAL_REGION_LOCAL(m_threads_lock); + + // In case background mining was active and the miner threads are waiting + // on the background miner to signal start. + m_is_background_mining_started_cond.notify_all(); + + for(boost::thread& th: m_threads) + th.join(); + + // The background mining thread could be sleeping for a long time, so we + // interrupt it just in case + m_background_mining_thread.interrupt(); + m_background_mining_thread.join(); + + MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); + m_threads.clear(); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height) + { + for(; bl.nonce != std::numeric_limits<uint32_t>::max(); bl.nonce++) + { + crypto::hash h; + get_block_longhash(bl, h, height); + + if(check_hash(h, diffic)) + { + bl.invalidate_hashes(); + return true; + } + } + bl.invalidate_hashes(); + return false; + } + //----------------------------------------------------------------------------------------------------- + void miner::on_synchronized() + { + if(m_do_mining) + { + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + + start(m_mine_address, m_threads_total, attrs, get_is_background_mining_enabled()); + } + } + //----------------------------------------------------------------------------------------------------- + void miner::pause() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + MDEBUG("miner::pause: " << m_pausers_count << " -> " << (m_pausers_count + 1)); + ++m_pausers_count; + if(m_pausers_count == 1 && is_mining()) + MDEBUG("MINING PAUSED"); + } + //----------------------------------------------------------------------------------------------------- + void miner::resume() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + MDEBUG("miner::resume: " << m_pausers_count << " -> " << (m_pausers_count - 1)); + --m_pausers_count; + if(m_pausers_count < 0) + { + m_pausers_count = 0; + MERROR("Unexpected miner::resume() called"); + } + if(!m_pausers_count && is_mining()) + MDEBUG("MINING RESUMED"); + } + //----------------------------------------------------------------------------------------------------- + bool miner::worker_thread() + { + uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); + MGINFO("Miner thread was started ["<< th_local_index << "]"); + MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]"); + uint32_t nonce = m_starter_nonce + th_local_index; + uint64_t height = 0; + difficulty_type local_diff = 0; + uint32_t local_template_ver = 0; + block b; + slow_hash_allocate_state(); + while(!m_stop) + { + if(m_pausers_count)//anti split workaround + { + misc_utils::sleep_no_w(100); + continue; + } + else if( m_is_background_mining_enabled ) + { + misc_utils::sleep_no_w(m_miner_extra_sleep); + while( !m_is_background_mining_started ) + { + MGINFO("background mining is enabled, but not started, waiting until start triggers"); + boost::unique_lock<boost::mutex> started_lock( m_is_background_mining_started_mutex ); + m_is_background_mining_started_cond.wait( started_lock ); + if( m_stop ) break; + } + + if( m_stop ) continue; + } + + if(local_template_ver != m_template_no) + { + CRITICAL_REGION_BEGIN(m_template_lock); + b = m_template; + local_diff = m_diffic; + height = m_height; + CRITICAL_REGION_END(); + local_template_ver = m_template_no; + nonce = m_starter_nonce + th_local_index; + } + + if(!local_template_ver)//no any set_block_template call + { + LOG_PRINT_L2("Block template not set yet"); + epee::misc_utils::sleep_no_w(1000); + continue; + } + + b.nonce = nonce; + crypto::hash h; + get_block_longhash(b, h, height); + + if(check_hash(h, local_diff)) + { + //we lucky! + ++m_config.current_extra_message_index; + MGINFO_GREEN("Found block for difficulty: " << local_diff); + if(!m_phandler->handle_block_found(b)) + { + --m_config.current_extra_message_index; + }else + { + //success update, lets update config + if (!m_config_folder_path.empty()) + epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + } + } + nonce+=m_threads_total; + ++m_hashes; + } + slow_hash_free_state(); + MGINFO("Miner thread stopped ["<< th_local_index << "]"); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_is_background_mining_enabled() const + { + return m_is_background_mining_enabled; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_ignore_battery() const + { + return m_ignore_battery; + } + //----------------------------------------------------------------------------------------------------- + /** + * This has differing behaviour depending on if mining has been started/etc. + * Note: add documentation + */ + bool miner::set_is_background_mining_enabled(bool is_background_mining_enabled) + { + m_is_background_mining_enabled = is_background_mining_enabled; + // Extra logic will be required if we make this function public in the future + // and allow toggling smart mining without start/stop + //m_is_background_mining_enabled_cond.notify_one(); + return true; + } + //----------------------------------------------------------------------------------------------------- + void miner::set_ignore_battery(bool ignore_battery) + { + m_ignore_battery = ignore_battery; + } + //----------------------------------------------------------------------------------------------------- + uint64_t miner::get_min_idle_seconds() const + { + return m_min_idle_seconds; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_min_idle_seconds(uint64_t min_idle_seconds) + { + if(min_idle_seconds > BACKGROUND_MINING_MAX_MIN_IDLE_INTERVAL_IN_SECONDS) return false; + if(min_idle_seconds < BACKGROUND_MINING_MIN_MIN_IDLE_INTERVAL_IN_SECONDS) return false; + m_min_idle_seconds = min_idle_seconds; + return true; + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_idle_threshold() const + { + return m_idle_threshold; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_idle_threshold(uint8_t idle_threshold) + { + if(idle_threshold > BACKGROUND_MINING_MAX_IDLE_THRESHOLD_PERCENTAGE) return false; + if(idle_threshold < BACKGROUND_MINING_MIN_IDLE_THRESHOLD_PERCENTAGE) return false; + m_idle_threshold = idle_threshold; + return true; + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_mining_target() const + { + return m_mining_target; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_mining_target(uint8_t mining_target) + { + if(mining_target > BACKGROUND_MINING_MAX_MINING_TARGET_PERCENTAGE) return false; + if(mining_target < BACKGROUND_MINING_MIN_MINING_TARGET_PERCENTAGE) return false; + m_mining_target = mining_target; + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::background_worker_thread() + { + uint64_t prev_total_time, current_total_time; + uint64_t prev_idle_time, current_idle_time; + uint64_t previous_process_time = 0, current_process_time = 0; + m_is_background_mining_started = false; + + if(!get_system_times(prev_total_time, prev_idle_time)) + { + LOG_ERROR("get_system_times call failed, background mining will NOT work!"); + return false; + } + + while(!m_stop) + { + + try + { + // Commenting out the below since we're going with privatizing the bg mining enabled + // function, but I'll leave the code/comments here for anyone that wants to modify the + // patch in the future + // ------------------------------------------------------------------------------------- + // All of this might be overkill if we just enforced some simple requirements + // about changing this variable before/after the miner starts, but I envision + // in the future a checkbox that you can tick on/off for background mining after + // you've clicked "start mining". There's still an issue here where if background + // mining is disabled when start is called, this thread is never created, and so + // enabling after does nothing, something I have to fix in the future. However, + // this should take care of the case where mining is started with bg-enabled, + // and then the user decides to un-check background mining, and just do + // regular full-speed mining. I might just be over-doing it and thinking up + // non-existant use-cases, so if the concensus is to simplify, we can remove all this fluff. + /* + while( !m_is_background_mining_enabled ) + { + MGINFO("background mining is disabled, waiting until enabled!"); + boost::unique_lock<boost::mutex> enabled_lock( m_is_background_mining_enabled_mutex ); + m_is_background_mining_enabled_cond.wait( enabled_lock ); + } + */ + + // If we're already mining, then sleep for the miner monitor interval. + // If we're NOT mining, then sleep for the idle monitor interval + uint64_t sleep_for_seconds = BACKGROUND_MINING_MINER_MONITOR_INVERVAL_IN_SECONDS; + if( !m_is_background_mining_started ) sleep_for_seconds = get_min_idle_seconds(); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_for_seconds)); + } + catch(const boost::thread_interrupted&) + { + MDEBUG("background miner thread interrupted "); + continue; // if interrupted because stop called, loop should end .. + } + + boost::tribool battery_powered(on_battery_power()); + bool on_ac_power = false; + if(indeterminate( battery_powered )) + { + // name could be better, only ignores battery requirement if we failed + // to get the status of the system + if( m_ignore_battery ) + { + on_ac_power = true; + } + } + else + { + on_ac_power = !battery_powered; + } + + if( m_is_background_mining_started ) + { + // figure out if we need to stop, and monitor mining usage + + // If we get here, then previous values are initialized. + // Let's get some current data for comparison. + + if(!get_system_times(current_total_time, current_idle_time)) + { + MERROR("get_system_times call failed"); + continue; + } + + if(!get_process_time(current_process_time)) + { + MERROR("get_process_time call failed!"); + continue; + } + + uint64_t total_diff = (current_total_time - prev_total_time); + uint64_t idle_diff = (current_idle_time - prev_idle_time); + uint64_t process_diff = (current_process_time - previous_process_time); + uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); + uint8_t process_percentage = get_percent_of_total(process_diff, total_diff); + + MGINFO("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); + if( idle_percentage + process_percentage < get_idle_threshold() || !on_ac_power ) + { + MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); + m_is_background_mining_started = false; + + // reset process times + previous_process_time = 0; + current_process_time = 0; + } + else + { + previous_process_time = current_process_time; + + // adjust the miner extra sleep variable + int64_t miner_extra_sleep_change = (-1 * (get_mining_target() - process_percentage) ); + int64_t new_miner_extra_sleep = m_miner_extra_sleep + miner_extra_sleep_change; + // if you start the miner with few threads on a multicore system, this could + // fall below zero because all the time functions aggregate across all processors. + // I'm just hard limiting to 5 millis min sleep here, other options? + m_miner_extra_sleep = std::max( new_miner_extra_sleep , (int64_t)5 ); + MDEBUG("m_miner_extra_sleep " << m_miner_extra_sleep); + } + + prev_total_time = current_total_time; + prev_idle_time = current_idle_time; + } + else if( on_ac_power ) + { + // figure out if we need to start + + if(!get_system_times(current_total_time, current_idle_time)) + { + MERROR("get_system_times call failed"); + continue; + } + + uint64_t total_diff = (current_total_time - prev_total_time); + uint64_t idle_diff = (current_idle_time - prev_idle_time); + uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); + + MGINFO("idle percentage is " << unsigned(idle_percentage)); + if( idle_percentage >= get_idle_threshold() && on_ac_power ) + { + MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); + m_is_background_mining_started = true; + m_is_background_mining_started_cond.notify_all(); + + // Wait for a little mining to happen .. + boost::this_thread::sleep_for(boost::chrono::seconds( 1 )); + + // Starting data ... + if(!get_process_time(previous_process_time)) + { + m_is_background_mining_started = false; + MERROR("get_process_time call failed!"); + } + } + + prev_total_time = current_total_time; + prev_idle_time = current_idle_time; + } + } + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_system_times(uint64_t& total_time, uint64_t& idle_time) + { + #ifdef _WIN32 + + FILETIME idleTime; + FILETIME kernelTime; + FILETIME userTime; + if ( GetSystemTimes( &idleTime, &kernelTime, &userTime ) != -1 ) + { + total_time = + ( (((uint64_t)(kernelTime.dwHighDateTime)) << 32) | ((uint64_t)kernelTime.dwLowDateTime) ) + + ( (((uint64_t)(userTime.dwHighDateTime)) << 32) | ((uint64_t)userTime.dwLowDateTime) ); + + idle_time = ( (((uint64_t)(idleTime.dwHighDateTime)) << 32) | ((uint64_t)idleTime.dwLowDateTime) ); + + return true; + } + + #elif defined(__linux__) + + const std::string STR_CPU("cpu"); + const std::size_t STR_CPU_LEN = STR_CPU.size(); + const std::string STAT_FILE_PATH = "/proc/stat"; + + if( !epee::file_io_utils::is_file_exist(STAT_FILE_PATH) ) + { + LOG_ERROR("'" << STAT_FILE_PATH << "' file does not exist"); + return false; + } + + std::ifstream stat_file_stream(STAT_FILE_PATH); + if( stat_file_stream.fail() ) + { + LOG_ERROR("failed to open '" << STAT_FILE_PATH << "'"); + return false; + } + + std::string line; + std::getline(stat_file_stream, line); + std::istringstream stat_file_iss(line); + stat_file_iss.ignore(65536, ' '); // skip cpu label ... + uint64_t utime, ntime, stime, itime; + if( !(stat_file_iss >> utime && stat_file_iss >> ntime && stat_file_iss >> stime && stat_file_iss >> itime) ) + { + LOG_ERROR("failed to read '" << STAT_FILE_PATH << "'"); + return false; + } + + idle_time = itime; + total_time = utime + ntime + stime + itime; + + return true; + + #endif + + return false; // unsupported systemm.. + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_process_time(uint64_t& total_time) + { + #ifdef _WIN32 + + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + if ( GetProcessTimes( GetCurrentProcess(), &createTime, &exitTime, &kernelTime, &userTime ) != -1 ) + { + total_time = + ( (((uint64_t)(kernelTime.dwHighDateTime)) << 32) | ((uint64_t)kernelTime.dwLowDateTime) ) + + ( (((uint64_t)(userTime.dwHighDateTime)) << 32) | ((uint64_t)userTime.dwLowDateTime) ); + + return true; + } + + #elif defined(__linux__) && defined(_SC_CLK_TCK) + + struct tms tms; + if ( times(&tms) != (clock_t)-1 ) + { + total_time = tms.tms_utime + tms.tms_stime; + return true; + } + + #endif + + return false; // unsupported system.. + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_percent_of_total(uint64_t other, uint64_t total) + { + return (uint8_t)( ceil( (other * 1.f / total * 1.f) * 100) ); + } + //----------------------------------------------------------------------------------------------------- + boost::logic::tribool miner::on_battery_power() + { + #ifdef _WIN32 + + SYSTEM_POWER_STATUS power_status; + if ( GetSystemPowerStatus( &power_status ) != 0 ) + { + return boost::logic::tribool(power_status.ACLineStatus != 1); + } + + #elif defined(__linux__) + + // i've only tested on UBUNTU, these paths might be different on other systems + // need to figure out a way to make this more flexible + std::string power_supply_path = ""; + const std::string POWER_SUPPLY_STATUS_PATHS[] = + { + "/sys/class/power_supply/ACAD/online", + "/sys/class/power_supply/AC/online", + "/sys/class/power_supply/AC0/online", + "/sys/class/power_supply/ADP0/online" + }; + + for(const std::string& path : POWER_SUPPLY_STATUS_PATHS) + { + if( epee::file_io_utils::is_file_exist(path) ) + { + power_supply_path = path; + break; + } + } + + if( power_supply_path.empty() ) + { + LOG_ERROR("Couldn't find battery/power status file, can't determine if plugged in!"); + return boost::logic::tribool(boost::logic::indeterminate);; + } + + std::ifstream power_stream(power_supply_path); + if( power_stream.fail() ) + { + LOG_ERROR("failed to open '" << power_supply_path << "'"); + return boost::logic::tribool(boost::logic::indeterminate);; + } + + return boost::logic::tribool( (power_stream.get() != '1') ); + + #endif + + LOG_ERROR("couldn't query power status"); + return boost::logic::tribool(boost::logic::indeterminate); + } +} diff --git a/src/cryptonote_core/miner.h b/src/cryptonote_basic/miner.h index f24f6e960..964ee6a36 100644 --- a/src/cryptonote_core/miner.h +++ b/src/cryptonote_basic/miner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,11 +31,19 @@ #pragma once #include <boost/program_options.hpp> +#include <boost/logic/tribool_fwd.hpp> #include <atomic> #include "cryptonote_basic.h" #include "difficulty.h" #include "math_helper.h" - +#ifdef _WIN32 +#include <windows.h> +#elif defined(__linux__) +#include <unistd.h> +#include <sys/resource.h> +#include <sys/times.h> +#include <time.h> +#endif namespace cryptonote { @@ -43,7 +51,7 @@ namespace cryptonote struct i_miner_handler { virtual bool handle_block_found(block& b) = 0; - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) = 0; + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0; protected: ~i_miner_handler(){}; }; @@ -60,7 +68,7 @@ namespace cryptonote static void init_options(boost::program_options::options_description& desc); bool set_block_template(const block& bl, const difficulty_type& diffic, uint64_t height); bool on_block_chain_update(); - bool start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs); + bool start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs, bool do_background = false, bool ignore_battery = false); uint64_t get_speed() const; uint32_t get_threads_count() const; void send_stop_signal(); @@ -74,6 +82,27 @@ namespace cryptonote void pause(); void resume(); void do_print_hashrate(bool do_hr); + bool get_is_background_mining_enabled() const; + bool get_ignore_battery() const; + uint64_t get_min_idle_seconds() const; + bool set_min_idle_seconds(uint64_t min_idle_seconds); + uint8_t get_idle_threshold() const; + bool set_idle_threshold(uint8_t idle_threshold); + uint8_t get_mining_target() const; + bool set_mining_target(uint8_t mining_target); + + static constexpr uint8_t BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE = 90; + static constexpr uint8_t BACKGROUND_MINING_MIN_IDLE_THRESHOLD_PERCENTAGE = 50; + static constexpr uint8_t BACKGROUND_MINING_MAX_IDLE_THRESHOLD_PERCENTAGE = 99; + static constexpr uint16_t BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS = 10; + static constexpr uint16_t BACKGROUND_MINING_MIN_MIN_IDLE_INTERVAL_IN_SECONDS = 10; + static constexpr uint16_t BACKGROUND_MINING_MAX_MIN_IDLE_INTERVAL_IN_SECONDS = 3600; + static constexpr uint8_t BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE = 40; + static constexpr uint8_t BACKGROUND_MINING_MIN_MINING_TARGET_PERCENTAGE = 5; + static constexpr uint8_t BACKGROUND_MINING_MAX_MINING_TARGET_PERCENTAGE = 50; + static constexpr uint8_t BACKGROUND_MINING_MINER_MONITOR_INVERVAL_IN_SECONDS = 10; + static constexpr uint64_t BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS = 400; // ramp up + static constexpr uint64_t BACKGROUND_MINING_MIN_MINER_EXTRA_SLEEP_MILLIS = 5; private: bool worker_thread(); @@ -119,8 +148,26 @@ namespace cryptonote bool m_do_print_hashrate; bool m_do_mining; + // background mining stuffs .. + + bool set_is_background_mining_enabled(bool is_background_mining_enabled); + void set_ignore_battery(bool ignore_battery); + bool background_worker_thread(); + std::atomic<bool> m_is_background_mining_enabled; + bool m_ignore_battery; + boost::mutex m_is_background_mining_enabled_mutex; + boost::condition_variable m_is_background_mining_enabled_cond; + std::atomic<bool> m_is_background_mining_started; + boost::mutex m_is_background_mining_started_mutex; + boost::condition_variable m_is_background_mining_started_cond; + boost::thread m_background_mining_thread; + uint64_t m_min_idle_seconds; + uint8_t m_idle_threshold; + uint8_t m_mining_target; + std::atomic<uint64_t> m_miner_extra_sleep; + static bool get_system_times(uint64_t& total_time, uint64_t& idle_time); + static bool get_process_time(uint64_t& total_time); + static uint8_t get_percent_of_total(uint64_t some_time, uint64_t total_time); + static boost::logic::tribool on_battery_power(); }; } - - - diff --git a/src/cryptonote_core/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 6f5fbe466..5a6c3176d 100644 --- a/src/cryptonote_core/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/verification_context.h b/src/cryptonote_basic/verification_context.h index 0bb635e84..ce885ec1d 100644 --- a/src/cryptonote_core/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 319e18808..8dbf1b53b 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -57,6 +57,7 @@ #define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 #define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 60000 //size of block (bytes) after which reward for block calculated using block size #define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 20000 //size of block (bytes) after which reward for block calculated using block size - before first fork +#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 300000 //size of block (bytes) after which reward for block calculated using block size - second change, from v5 #define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 #define CRYPTONOTE_DISPLAY_DECIMAL_POINT 12 // COIN - number of smallest units in one coin @@ -64,6 +65,9 @@ #define FEE_PER_KB_OLD ((uint64_t)10000000000) // pow(10, 10) #define FEE_PER_KB ((uint64_t)2000000000) // 2 * pow(10, 9) +#define DYNAMIC_FEE_PER_KB_BASE_FEE ((uint64_t)2000000000) // 2 * pow(10,9) +#define DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD ((uint64_t)10000000000000) // 10 * pow(10,12) +#define DYNAMIC_FEE_PER_KB_BASE_FEE_V5 ((uint64_t)2000000000 * (uint64_t)CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5) #define ORPHANED_BLOCKS_MAX_COUNT 100 @@ -105,29 +109,39 @@ #define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes #define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds #define P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70 +#define P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT 2 #define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour #define P2P_IP_BLOCKTIME (60*60*24) //24 hour #define P2P_IP_FAILS_BEFORE_BLOCK 10 #define P2P_IDLE_CONNECTION_KILL_INTERVAL (5*60) //5 minutes +#define P2P_SUPPORT_FLAG_FLUFFY_BLOCKS 0x01 +#define P2P_SUPPORT_FLAGS P2P_SUPPORT_FLAG_FLUFFY_BLOCKS + #define ALLOW_DEBUG_COMMANDS #define CRYPTONOTE_NAME "bitmonero" #define CRYPTONOTE_POOLDATA_FILENAME "poolstate.bin" -#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "blockchain.bin" -#define CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME "blockchain.bin.tmp" +#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb" +#define CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME "lock.mdb" #define P2P_NET_DATA_FILENAME "p2pstate.bin" #define MINER_CONFIG_FILE_NAME "miner_conf.json" #define THREAD_STACK_SIZE 5 * 1024 * 1024 +#define HF_VERSION_DYNAMIC_FEE 4 +#define HF_VERSION_MIN_MIXIN_4 6 +#define HF_VERSION_ENFORCE_RCT 6 + +#define PER_KB_FEE_QUANTIZATION_DECIMALS 8 + // New constants are intended to go here namespace config { uint64_t const DEFAULT_FEE_ATOMIC_XMR_PER_KB = 500; // Just a placeholder! Change me! uint8_t const FEE_CALCULATION_MAX_RETRIES = 10; - uint64_t const DEFAULT_DUST_THRESHOLD = ((uint64_t)10000000000); // pow(10, 10) + uint64_t const DEFAULT_DUST_THRESHOLD = ((uint64_t)2000000000); // 2 * pow(10, 9) uint64_t const BASE_REWARD_CLAMP_THRESHOLD = ((uint64_t)100000000); // pow(10, 8) std::string const P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY = "0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 89bf2f682..5944ddcd1 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -27,38 +27,19 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(cryptonote_core_sources - account.cpp blockchain.cpp - checkpoints.cpp - cryptonote_basic_impl.cpp cryptonote_core.cpp - cryptonote_format_utils.cpp - difficulty.cpp - miner.cpp tx_pool.cpp - hardfork.cpp) + cryptonote_tx_utils.cpp) set(cryptonote_core_headers) set(cryptonote_core_private_headers - account.h - account_boost_serialization.h blockchain_storage_boost_serialization.h blockchain.h - checkpoints.h - connection_context.h - cryptonote_basic.h - cryptonote_basic_impl.h - cryptonote_boost_serialization.h cryptonote_core.h - cryptonote_format_utils.h - cryptonote_stat_info.h - difficulty.h - miner.h - tx_extra.h tx_pool.h - verification_context.h - hardfork.h) + cryptonote_tx_utils.h) if(PER_BLOCK_CHECKPOINT) set(Blocks "blocks") @@ -66,9 +47,9 @@ else() set(Blocks "") endif() -bitmonero_private_headers(cryptonote_core +monero_private_headers(cryptonote_core ${crypto_private_headers}) -bitmonero_add_library(cryptonote_core +monero_add_library(cryptonote_core ${cryptonote_core_sources} ${cryptonote_core_headers} ${cryptonote_core_private_headers}) @@ -76,7 +57,6 @@ target_link_libraries(cryptonote_core PUBLIC common crypto - otshell_utils blockchain_db ringct ${Boost_DATE_TIME_LIBRARY} diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index badb1a335..74c5b3b83 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,32 +30,35 @@ #include <algorithm> #include <cstdio> -#include <boost/archive/binary_oarchive.hpp> -#include <boost/archive/binary_iarchive.hpp> #include <boost/filesystem.hpp> +#include <boost/range/adaptor/reversed.hpp> #include "include_base_utils.h" -#include "cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "tx_pool.h" #include "blockchain.h" #include "blockchain_db/blockchain_db.h" -#include "cryptonote_format_utils.h" -#include "cryptonote_boost_serialization.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_config.h" -#include "miner.h" +#include "cryptonote_basic/miner.h" #include "misc_language.h" #include "profile_tools.h" #include "file_io_utils.h" +#include "common/int-util.h" #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_core/checkpoints.h" -#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_basic/checkpoints.h" +#include "cryptonote_core.h" #include "ringct/rctSigs.h" +#include "common/perf_timer.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" #endif +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "blockchain" + //#include "serialization/json_archive.h" /* TODO: @@ -71,6 +74,11 @@ extern "C" void slow_hash_free_state(); DISABLE_VS_WARNINGS(4267) +#define MERROR_VER(x) MCERROR("verify", x) + +// used to overestimate the block reward when estimating a per kB to use +#define BLOCK_REWARD_OVERESTIMATE (10 * 1000000000000) + static const struct { uint8_t version; uint64_t height; @@ -86,11 +94,11 @@ static const struct { // version 3 starts from block 1141317, which is on or around the 24th of September, 2016. Fork time finalised on 2016-03-21. { 3, 1141317, 0, 1458558528 }, - // version 4 starts from block 1220517, which is on or around the 5th of January, 2017. Fork time finalised on 2016-09-18. - { 4, 1220517, 0, 1483574400 }, + // version 4 starts from block 1220516, which is on or around the 5th of January, 2017. Fork time finalised on 2016-09-18. + { 4, 1220516, 0, 1483574400 }, - // version 5 starts from block 1406997, which is on or around the 20th of September, 2017. Fork time finalised on 2016-09-18. - { 5, 1406997, 0, 1505865600 }, + // version 5 starts from block 1288616, which is on or around the 15th of April, 2017. Fork time finalised on 2017-03-14. + { 5, 1288616, 0, 1489520158 }, }; static const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -108,15 +116,15 @@ static const struct { // versions 3-5 were passed in rapid succession from September 18th, 2016 { 3, 800500, 0, 1472415034 }, - { 4, 801220, 0, 1472415035 }, - { 5, 802660, 0, 1472415036 }, + { 4, 801219, 0, 1472415035 }, + { 5, 802660, 0, 1472415036 + 86400*180 }, // add 5 months on testnet to shut the update warning up since there's a large gap to v6 }; static const uint64_t testnet_hard_fork_version_1_till = 624633; //------------------------------------------------------------------ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), - m_is_blockchain_storing(false), m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0) + m_is_blockchain_storing(false), m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_cancel(false) { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -177,7 +185,7 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke } catch (...) { - LOG_PRINT_L0("Output does not exist! amount = " << tx_in_to_key.amount); + MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); return false; } } @@ -186,7 +194,7 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke // check for partial results and add the rest if needed; if (outputs.size() < absolute_offsets.size() && outputs.size() > 0) { - LOG_PRINT_L1("Additional outputs needed: " << absolute_offsets.size() - outputs.size()); + MDEBUG("Additional outputs needed: " << absolute_offsets.size() - outputs.size()); std::vector < uint64_t > add_offsets; std::vector<output_data_t> add_outputs; for (size_t i = outputs.size(); i < absolute_offsets.size(); i++) @@ -197,7 +205,7 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke } catch (...) { - LOG_PRINT_L0("Output does not exist! amount = " << tx_in_to_key.amount); + MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); return false; } outputs.insert(outputs.end(), add_outputs.begin(), add_outputs.end()); @@ -221,13 +229,13 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke // call to the passed boost visitor to grab the public key for the output if (!vis.handle_output(output_index.unlock_time, output_index.pubkey, output_index.commitment)) { - LOG_PRINT_L0("Failed to handle_output for output no = " << count << ", with absolute offset " << i); + MERROR_VER("Failed to handle_output for output no = " << count << ", with absolute offset " << i); return false; } } catch (...) { - LOG_PRINT_L0("Output does not exist! amount = " << tx_in_to_key.amount << ", absolute_offset = " << i); + MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount << ", absolute_offset = " << i); return false; } @@ -245,12 +253,12 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke } catch (const OUTPUT_DNE& e) { - LOG_PRINT_L0("Output does not exist: " << e.what()); + MERROR_VER("Output does not exist: " << e.what()); return false; } catch (const TX_DNE& e) { - LOG_PRINT_L0("Transaction does not exist: " << e.what()); + MERROR_VER("Transaction does not exist: " << e.what()); return false; } @@ -323,7 +331,7 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::te // taking testnet into account if(!m_db->height()) { - LOG_PRINT_L0("Blockchain not loaded, generating genesis block."); + MINFO("Blockchain not loaded, generating genesis block."); block bl = boost::value_initialized<block>(); block_verification_context bvc = boost::value_initialized<block_verification_context>(); if (m_testnet) @@ -369,9 +377,10 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::te load_compiled_in_block_hashes(); #endif - LOG_PRINT_GREEN("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block(), LOG_LEVEL_0); + MINFO("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block()); m_db->block_txn_stop(); + update_next_cumulative_size_limit(); return true; } //------------------------------------------------------------------ @@ -387,7 +396,7 @@ bool Blockchain::init(BlockchainDB* db, HardFork*& hf, const bool testnet) //------------------------------------------------------------------ bool Blockchain::store_blockchain() { - LOG_PRINT_YELLOW("Blockchain::" << __func__, LOG_LEVEL_3); + LOG_PRINT_L3("Blockchain::" << __func__); // lock because the rpc_thread command handler also calls this CRITICAL_REGION_LOCAL(m_db->m_synchronization_lock); @@ -400,18 +409,18 @@ bool Blockchain::store_blockchain() } catch (const std::exception& e) { - LOG_PRINT_L0(std::string("Error syncing blockchain db: ") + e.what() + "-- shutting down now to prevent issues!"); + MERROR(std::string("Error syncing blockchain db: ") + e.what() + "-- shutting down now to prevent issues!"); throw; } catch (...) { - LOG_PRINT_L0("There was an issue storing the blockchain, shutting down now to prevent issues!"); + MERROR("There was an issue storing the blockchain, shutting down now to prevent issues!"); throw; } TIME_MEASURE_FINISH(save); if(m_show_time_stats) - LOG_PRINT_L0("Blockchain stored OK, took: " << save << " ms"); + MINFO("Blockchain stored OK, took: " << save << " ms"); return true; } //------------------------------------------------------------------ @@ -419,9 +428,10 @@ bool Blockchain::deinit() { LOG_PRINT_L3("Blockchain::" << __func__); - LOG_PRINT_L0("Closing IO Service."); - // stop async service - m_async_work_idle.reset(); + MTRACE("Stopping blockchain read/write activity"); + + // stop async service + m_async_work_idle.reset(); m_async_pool.join_all(); m_async_service.stop(); @@ -436,14 +446,15 @@ bool Blockchain::deinit() try { m_db->close(); + MTRACE("Local blockchain read/write activity stopped successfully"); } catch (const std::exception& e) { - LOG_PRINT_L0(std::string("Error closing blockchain db: ") + e.what()); + LOG_ERROR(std::string("Error closing blockchain db: ") + e.what()); } catch (...) { - LOG_PRINT_L0("There was an issue closing/storing the blockchain, shutting down now to prevent issues!"); + LOG_ERROR("There was an issue closing/storing the blockchain, shutting down now to prevent issues!"); } delete m_hardfork; @@ -503,7 +514,7 @@ block Blockchain::pop_block_from_blockchain() // that might not be always true. Unlikely though, and always relaying // these again might cause a spike of traffic as many nodes re-relay // all the transactions in a popped block when a reorg happens. - bool r = m_tx_pool.add_tx(tx, tvc, true, true, version); + bool r = m_tx_pool.add_tx(tx, tvc, true, true, false, version); if (!r) { LOG_ERROR("Error returning transaction to tx_pool"); @@ -520,12 +531,14 @@ bool Blockchain::reset_and_set_genesis_block(const block& b) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_timestamps_and_difficulties_height = 0; m_alternative_chains.clear(); m_db->reset(); m_hardfork->init(); block_verification_context bvc = boost::value_initialized<block_verification_context>(); add_new_block(b, bvc); + update_next_cumulative_size_limit(); return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; } //------------------------------------------------------------------ @@ -612,18 +625,18 @@ crypto::hash Blockchain::get_block_id_by_height(uint64_t height) const } catch (const std::exception& e) { - LOG_PRINT_L0(std::string("Something went wrong fetching block hash by height: ") + e.what()); + MERROR(std::string("Something went wrong fetching block hash by height: ") + e.what()); throw; } catch (...) { - LOG_PRINT_L0(std::string("Something went wrong fetching block hash by height")); + MERROR(std::string("Something went wrong fetching block hash by height")); throw; } return null_hash; } //------------------------------------------------------------------ -bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk) const +bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -632,6 +645,8 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk) const try { blk = m_db->get_block(h); + if (orphan) + *orphan = false; return true; } // try to find block in alternative chain @@ -641,17 +656,19 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk) const if (m_alternative_chains.end() != it_alt) { blk = it_alt->second.bl; + if (orphan) + *orphan = true; return true; } } catch (const std::exception& e) { - LOG_PRINT_L0(std::string("Something went wrong fetching block by hash: ") + e.what()); + MERROR(std::string("Something went wrong fetching block by hash: ") + e.what()); throw; } catch (...) { - LOG_PRINT_L0(std::string("Something went wrong fetching block hash by hash")); + MERROR(std::string("Something went wrong fetching block hash by hash")); throw; } @@ -752,6 +769,9 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, pop_block_from_blockchain(); } + // make sure the hard fork object updates its current version + m_hardfork->reorganize_from_chain_height(rollback_height); + //return back original chain for (auto& bl : original_chain) { @@ -762,10 +782,10 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, m_hardfork->reorganize_from_chain_height(rollback_height); - LOG_PRINT_L1("Rollback to height " << rollback_height << " was successful."); + MINFO("Rollback to height " << rollback_height << " was successful."); if (original_chain.size()) { - LOG_PRINT_L1("Restoration to previous blockchain successful as well."); + MINFO("Restoration to previous blockchain successful as well."); } return true; } @@ -813,7 +833,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: // return false if(!r || !bvc.m_added_to_main_chain) { - LOG_PRINT_L1("Failed to switch to alternative blockchain"); + MERROR("Failed to switch to alternative blockchain"); // rollback_blockchain_switching should be moved to two different // functions: rollback and apply_chain, but for now we pretend it is @@ -824,13 +844,13 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: // about them again so we can immediately dismiss them, but needs some // looking into. add_block_as_invalid(ch_ent->second, get_block_hash(ch_ent->second.bl)); - LOG_PRINT_L1("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl)); - m_alternative_chains.erase(ch_ent); + MERROR("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl)); + m_alternative_chains.erase(*alt_ch_iter++); - for(auto alt_ch_to_orph_iter = ++alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); alt_ch_to_orph_iter++) + for(auto alt_ch_to_orph_iter = alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); ) { - add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first); - m_alternative_chains.erase(*alt_ch_to_orph_iter); + add_block_as_invalid((*alt_ch_to_orph_iter)->second, (*alt_ch_to_orph_iter)->first); + m_alternative_chains.erase(*alt_ch_to_orph_iter++); } return false; } @@ -846,7 +866,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc); if(!r) { - LOG_PRINT_L1("Failed to push ex-main chain blocks to alternative chain "); + MERROR("Failed to push ex-main chain blocks to alternative chain "); // previously this would fail the blockchain switching, but I don't // think this is bad enough to warrant that. } @@ -861,7 +881,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: m_hardfork->reorganize_from_chain_height(split_height); - LOG_PRINT_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height(), LOG_LEVEL_0); + MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); return true; } //------------------------------------------------------------------ @@ -913,7 +933,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: size_t count = 0; size_t max_i = timestamps.size()-1; // get difficulties and timestamps from most recent blocks in alt chain - BOOST_REVERSE_FOREACH(auto it, alt_chain) + for(auto it: boost::adaptors::reverse(alt_chain)) { timestamps[max_i - count] = it->second.bl.timestamp; cumulative_difficulties[max_i - count] = it->second.cumulative_difficulty; @@ -942,9 +962,10 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height) CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type"); if(boost::get<txin_gen>(b.miner_tx.vin[0]).height != height) { - LOG_PRINT_RED_L1("The miner transaction in block has invalid height: " << boost::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height); + MWARNING("The miner transaction in block has invalid height: " << boost::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height); return false; } + MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); //check outs overflow @@ -953,7 +974,7 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height) // does not overflow a uint64_t, and this transaction *is* a uint64_t... if(!check_outs_overflow(b.miner_tx)) { - LOG_PRINT_RED_L1("miner transaction has money overflow in block " << get_block_hash(b)); + MERROR("miner transaction has money overflow in block " << get_block_hash(b)); return false; } @@ -973,7 +994,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl if (version == 3) { for (auto &o: b.miner_tx.vout) { if (!is_valid_decomposed_amount(o.amount)) { - LOG_PRINT_L1("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount"); + MERROR_VER("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount"); return false; } } @@ -983,12 +1004,12 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward, version)) { - LOG_PRINT_L1("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); + MERROR_VER("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); return false; } if(base_reward + fee < money_in_use) { - LOG_PRINT_L1("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); return false; } // From hard fork 2, we allow a miner to claim less block reward than is allowed, in case a miner wants less dust @@ -996,7 +1017,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl { if(base_reward + fee != money_in_use) { - LOG_PRINT_L1("coinbase transaction doesn't use full amount of block reward: spent: " << money_in_use << ", block reward " << base_reward + fee << "(" << base_reward << "+" << fee << ")"); + MDEBUG("coinbase transaction doesn't use full amount of block reward: spent: " << money_in_use << ", block reward " << base_reward + fee << "(" << base_reward << "+" << fee << ")"); return false; } } @@ -1051,7 +1072,7 @@ uint64_t Blockchain::get_current_cumulative_blocksize_limit() const // in a lot of places. That flag is not referenced in any of the code // nor any of the makefiles, howeve. Need to look into whether or not it's // necessary at all. -bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) +bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) { LOG_PRINT_L3("Blockchain::" << __func__); size_t median_size; @@ -1075,7 +1096,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m size_t txs_size; uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) + if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee, expected_reward, m_hardfork->get_current_version())) { return false; } @@ -1127,7 +1148,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m LOG_ERROR("Creating block template: error: wrongly calculated fee"); } CRITICAL_REGION_END(); - LOG_PRINT_L1("Creating block template: height " << height << + MDEBUG("Creating block template: height " << height << ", median size " << median_size << ", already generated coins " << already_generated_coins << ", transaction size " << txs_size << @@ -1142,23 +1163,23 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m uint8_t hf_version = m_hardfork->get_current_version(); size_t max_outs = hf_version >= 4 ? 1 : 11; bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); - CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); + CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << get_object_blobsize(b.miner_tx) << + MDEBUG("Creating block template: miner tx size " << get_object_blobsize(b.miner_tx) << ", cumulative size " << cumulative_size); #endif 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, max_outs, hf_version); - CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); + CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); if (coinbase_blob_size > cumulative_size - txs_size) { cumulative_size = txs_size + coinbase_blob_size; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + MDEBUG("Creating block template: miner tx size " << coinbase_blob_size << ", cumulative size " << cumulative_size << " is greater then before"); #endif continue; @@ -1168,7 +1189,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m { size_t delta = cumulative_size - txs_size - coinbase_blob_size; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + MDEBUG("Creating block template: miner tx size " << coinbase_blob_size << ", cumulative size " << txs_size + coinbase_blob_size << " is less then before, adding " << delta << " zero bytes"); #endif @@ -1181,16 +1202,16 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m 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 has no luck with delta_extra size = " << delta << " and " << delta - 1 , LOG_LEVEL_2); + MDEBUG("Miner tx creation has no luck with delta_extra size = " << delta << " and " << delta - 1); cumulative_size += delta - 1; continue; } - LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); + MDEBUG("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count); } } 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)); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + MDEBUG("Creating block template: miner tx size " << coinbase_blob_size << ", cumulative size " << cumulative_size << " is now good"); #endif return true; @@ -1234,7 +1255,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id uint64_t block_height = get_block_height(b); if(0 == block_height) { - LOG_PRINT_L1("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative), but miner tx says height is 0."); + MERROR_VER("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative), but miner tx says height is 0."); bvc.m_verifivation_failed = true; return false; } @@ -1244,7 +1265,15 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id // the block to be added, then this is fine. if (!m_checkpoints.is_alternative_block_allowed(get_current_blockchain_height(), block_height)) { - LOG_PRINT_RED_L1("Block with id: " << id << std::endl << " can't be accepted for alternative chain, block height: " << block_height << std::endl << " blockchain height: " << get_current_blockchain_height()); + MERROR_VER("Block with id: " << id << std::endl << " can't be accepted for alternative chain, block height: " << block_height << std::endl << " blockchain height: " << get_current_blockchain_height()); + bvc.m_verifivation_failed = true; + return false; + } + + // this is a cheap test + if (!m_hardfork->check_for_height(b, block_height)) + { + LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version for height " << block_height); bvc.m_verifivation_failed = true; return false; } @@ -1279,7 +1308,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id // this alternate chain with it. if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) { - LOG_PRINT_L1("alternate chain does not appear to connect to main chain..."); + MERROR("alternate chain does not appear to connect to main chain..."); return false; } @@ -1302,7 +1331,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id // (not earlier than the median of the last X blocks) if(!check_block_timestamp(timestamps, b)) { - LOG_PRINT_RED_L1("Block with id: " << id << std::endl << " for alternative chain, has invalid timestamp: " << b.timestamp); + MERROR_VER("Block with id: " << id << std::endl << " for alternative chain, has invalid timestamp: " << b.timestamp); bvc.m_verifivation_failed = true; return false; } @@ -1328,14 +1357,14 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id get_block_longhash(bei.bl, proof_of_work, bei.height); if(!check_hash(proof_of_work, current_diff)) { - LOG_PRINT_RED_L1("Block with id: " << id << std::endl << " for alternative chain, does not have enough proof of work: " << proof_of_work << std::endl << " expected difficulty: " << current_diff); + MERROR_VER("Block with id: " << id << std::endl << " for alternative chain, does not have enough proof of work: " << proof_of_work << std::endl << " expected difficulty: " << current_diff); bvc.m_verifivation_failed = true; return false; } if(!prevalidate_miner_transaction(b, bei.height)) { - LOG_PRINT_RED_L1("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative) has incorrect miner transaction."); + MERROR_VER("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative) has incorrect miner transaction."); bvc.m_verifivation_failed = true; return false; } @@ -1365,7 +1394,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id if(is_a_checkpoint) { //do reorganize! - LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << ", checkpoint is found in alternative chain on height " << bei.height, LOG_LEVEL_0); + MGINFO_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << ", checkpoint is found in alternative chain on height " << bei.height); bool r = switch_to_alternative_blockchain(alt_chain, true); @@ -1377,7 +1406,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id else if(main_chain_cumulative_difficulty < bei.cumulative_difficulty) //check if difficulty bigger then in main chain { //do reorganize! - LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << " with cum_difficulty " << m_db->get_block_cumulative_difficulty(m_db->height() - 1) << std::endl << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty, LOG_LEVEL_0); + MGINFO_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << " with cum_difficulty " << m_db->get_block_cumulative_difficulty(m_db->height() - 1) << std::endl << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty); bool r = switch_to_alternative_blockchain(alt_chain, false); if (r) @@ -1388,7 +1417,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id } else { - LOG_PRINT_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "difficulty:\t" << current_diff, LOG_LEVEL_0); + MGINFO_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "difficulty:\t" << current_diff); return true; } } @@ -1396,13 +1425,13 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id { //block orphaned bvc.m_marked_as_orphaned = true; - LOG_PRINT_RED_L1("Block recognized as orphaned and rejected, id = " << id); + MERROR_VER("Block recognized as orphaned and rejected, id = " << id); } return true; } //------------------------------------------------------------------ -bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const +bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1414,17 +1443,17 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<block return false; } - for(const block& blk : blocks) + for(const auto& blk : blocks) { std::list<crypto::hash> missed_ids; - get_transactions(blk.tx_hashes, txs, missed_ids); + get_transactions_blobs(blk.second.tx_hashes, txs, missed_ids); CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain"); } return true; } //------------------------------------------------------------------ -bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const +bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1433,7 +1462,12 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<block for(size_t i = start_offset; i < start_offset + count && i < m_db->height();i++) { - blocks.push_back(m_db->get_block_from_height(i)); + blocks.push_back(std::make_pair(m_db->get_block_blob_from_height(i), block())); + if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) + { + LOG_ERROR("Invalid block"); + return false; + } } return true; } @@ -1451,22 +1485,22 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO CRITICAL_REGION_LOCAL(m_blockchain_lock); m_db->block_txn_start(true); rsp.current_blockchain_height = get_current_blockchain_height(); - std::list<block> blocks; + std::list<std::pair<cryptonote::blobdata,block>> blocks; get_blocks(arg.blocks, blocks, rsp.missed_ids); for (const auto& bl: blocks) { std::list<crypto::hash> missed_tx_ids; - std::list<transaction> txs; + std::list<cryptonote::blobdata> txs; // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids // is for missed blocks, not missed transactions as well. - get_transactions(bl.tx_hashes, txs, missed_tx_ids); + get_transactions_blobs(bl.second.tx_hashes, txs, missed_tx_ids); if (missed_tx_ids.size() != 0) { LOG_ERROR("Error retrieving blocks, missed " << missed_tx_ids.size() - << " transactions for block with hash: " << get_block_hash(bl) + << " transactions for block with hash: " << get_block_hash(bl.second) << std::endl ); @@ -1481,17 +1515,17 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO rsp.blocks.push_back(block_complete_entry()); block_complete_entry& e = rsp.blocks.back(); //pack block - e.block = t_serializable_object_to_blob(bl); + e.block = bl.first; //pack transactions - for (transaction& tx: txs) - e.txs.push_back(t_serializable_object_to_blob(tx)); + for (const cryptonote::blobdata& tx: txs) + e.txs.push_back(tx); } //get another transactions, if need - std::list<transaction> txs; - get_transactions(arg.txs, txs, rsp.missed_ids); + std::list<cryptonote::blobdata> txs; + get_transactions_blobs(arg.txs, txs, rsp.missed_ids); //pack aside transactions for (const auto& tx: txs) - rsp.txs.push_back(t_serializable_object_to_blob(tx)); + rsp.txs.push_back(tx); m_db->block_txn_stop(); return true; @@ -1754,7 +1788,7 @@ bool Blockchain::get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::r return true; } //------------------------------------------------------------------ -bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const +bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1764,11 +1798,11 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_R for (const auto &i: req.outputs) { // get tx_hash, tx_out_index from DB - const output_data_t &od = m_db->get_output_key(i.amount, i.index); + const output_data_t od = m_db->get_output_key(i.amount, i.index); tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index); bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); - res.outs.push_back({od.pubkey, od.commitment, unlocked}); + res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first}); } return true; } @@ -1785,7 +1819,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc // how can we expect to sync from the client that the block list came from? if(!qblock_ids.size() /*|| !req.m_total_height*/) { - LOG_PRINT_L1("Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << /*", m_height=" << req.m_total_height <<*/ ", dropping connection"); + MCERROR("net.p2p", "Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << /*", m_height=" << req.m_total_height <<*/ ", dropping connection"); return false; } @@ -1795,7 +1829,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc auto gen_hash = m_db->get_block_hash_from_height(0); if(qblock_ids.back() != gen_hash) { - LOG_PRINT_L1("Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block missmatch: " << std::endl << "id: " << qblock_ids.back() << ", " << std::endl << "expected: " << gen_hash << "," << std::endl << " dropping connection"); + MCERROR("net.p2p", "Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block mismatch: " << std::endl << "id: " << qblock_ids.back() << ", " << std::endl << "expected: " << gen_hash << "," << std::endl << " dropping connection"); m_db->block_txn_abort(); return false; } @@ -1813,7 +1847,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc } catch (const std::exception& e) { - LOG_PRINT_L1("Non-critical error trying to find block by hash in BlockchainDB, hash: " << *bl_it); + MWARNING("Non-critical error trying to find block by hash in BlockchainDB, hash: " << *bl_it); m_db->block_txn_abort(); return false; } @@ -1824,15 +1858,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc // but just in case... if(bl_it == qblock_ids.end()) { - LOG_PRINT_L1("Internal error handling connection, can't find split point"); - return false; - } - - // if split_height remains 0, we didn't have any but the genesis block in common - // which is only fine if the blocks just have the genesis block - if(split_height == 0 && qblock_ids.size() > 1) - { - LOG_ERROR("Ours and foreign blockchain have only genesis block in common... o.O"); + MERROR("Internal error handling connection, can't find split point"); return false; } @@ -1851,7 +1877,7 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const } catch (const BLOCK_DNE& e) { - LOG_PRINT_L0("Attempted to get block difficulty for height above blockchain height"); + MERROR("Attempted to get block difficulty for height above blockchain height"); } return 0; } @@ -1868,7 +1894,12 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container { try { - blocks.push_back(m_db->get_block(block_hash)); + blocks.push_back(std::make_pair(m_db->get_block_blob(block_hash), block())); + if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) + { + LOG_ERROR("Invalid block"); + return false; + } } catch (const BLOCK_DNE& e) { @@ -1885,7 +1916,7 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container //TODO: return type should be void, throw on exception // alternatively, return true only if no transactions missed template<class t_ids_container, class t_tx_container, class t_missed_container> -bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const +bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1894,11 +1925,42 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container { try { - txs.push_back(m_db->get_tx(tx_hash)); + cryptonote::blobdata tx; + if (m_db->get_tx_blob(tx_hash, tx)) + txs.push_back(std::move(tx)); + else + missed_txs.push_back(tx_hash); } - catch (const TX_DNE& e) + catch (const std::exception& e) + { + return false; + } + } + return true; +} +//------------------------------------------------------------------ +template<class t_ids_container, class t_tx_container, class t_missed_container> +bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + for (const auto& tx_hash : txs_ids) + { + try { - missed_txs.push_back(tx_hash); + cryptonote::blobdata tx; + if (m_db->get_tx_blob(tx_hash, tx)) + { + txs.push_back(transaction()); + if (!parse_and_validate_tx_from_blob(tx, txs.back())) + { + LOG_ERROR("Invalid transaction"); + return false; + } + } + else + missed_txs.push_back(tx_hash); } catch (const std::exception& e) { @@ -1916,7 +1978,7 @@ void Blockchain::print_blockchain(uint64_t start_index, uint64_t end_index) cons auto h = m_db->height(); if(start_index > h) { - LOG_PRINT_L1("Wrong starter index set: " << start_index << ", expected max index " << h); + MERROR("Wrong starter index set: " << start_index << ", expected max index " << h); return; } @@ -1924,8 +1986,7 @@ void Blockchain::print_blockchain(uint64_t start_index, uint64_t end_index) cons { ss << "height " << i << ", timestamp " << m_db->get_block_timestamp(i) << ", cumul_dif " << m_db->get_block_cumulative_difficulty(i) << ", size " << m_db->get_block_size(i) << "\nid\t\t" << m_db->get_block_hash_from_height(i) << "\ndifficulty\t\t" << m_db->get_block_difficulty(i) << ", nonce " << m_db->get_block_from_height(i).nonce << ", tx_count " << m_db->get_block_from_height(i).tx_hashes.size() << std::endl; } - LOG_PRINT_L1("Current blockchain:" << std::endl << ss.str()); - LOG_PRINT_L0("Blockchain printed with log level 1"); + MCINFO("globlal", "Current blockchain:" << std::endl << ss.str()); } //------------------------------------------------------------------ void Blockchain::print_blockchain_index() const @@ -1942,7 +2003,7 @@ void Blockchain::print_blockchain_index() const } } - LOG_PRINT_L0("Current blockchain index:" << std::endl << ss.str()); + MINFO("Current blockchain index:" << std::endl << ss.str()); } //------------------------------------------------------------------ //TODO: remove this function and references to it @@ -1966,12 +2027,14 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc return false; } + m_db->block_txn_start(true); resp.total_height = get_current_blockchain_height(); size_t count = 0; for(size_t i = resp.start_height; i < resp.total_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { resp.m_block_ids.push_back(m_db->get_block_hash_from_height(i)); } + m_db->block_txn_stop(); return true; } //------------------------------------------------------------------ @@ -1979,7 +2042,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc // find split point between ours and foreign blockchain (or start at // blockchain height <req_start_block>), and return up to max_count FULL // blocks by reference. -bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const +bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2002,16 +2065,20 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons } } + m_db->block_txn_start(true); total_height = get_current_blockchain_height(); size_t count = 0; for(size_t i = start_height; i < total_height && count < max_count; i++, count++) { blocks.resize(blocks.size()+1); - blocks.back().first = m_db->get_block_from_height(i); + blocks.back().first = m_db->get_block_blob_from_height(i); + block b; + CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first, b), false, "internal error, invalid block"); std::list<crypto::hash> mis; - get_transactions(blocks.back().first.tx_hashes, blocks.back().second, mis); + get_transactions_blobs(b.tx_hashes, blocks.back().second, mis); CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); } + m_db->block_txn_stop(); return true; } //------------------------------------------------------------------ @@ -2029,7 +2096,7 @@ bool Blockchain::add_block_as_invalid(const block_extended_info& bei, const cryp CRITICAL_REGION_LOCAL(m_blockchain_lock); auto i_res = m_invalid_blocks.insert(std::map<crypto::hash, block_extended_info>::value_type(h, bei)); CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed"); - LOG_PRINT_L1("BLOCK ADDED AS INVALID: " << h << std::endl << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size()); + MINFO("BLOCK ADDED AS INVALID: " << h << std::endl << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size()); return true; } //------------------------------------------------------------------ @@ -2146,7 +2213,7 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u uint64_t tx_index; if (!m_db->tx_exists(tx_id, tx_index)) { - LOG_PRINT_RED_L1("warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id); + MERROR_VER("get_tx_outputs_gindexs failed to find transaction with id = " << tx_id); return false; } @@ -2186,7 +2253,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh max_used_block_height = 0; TIME_MEASURE_FINISH(a); if(m_show_time_stats) - LOG_PRINT_L0("HASH: " << "-" << " VIN/VOUT: " << tx.vin.size() << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); + { + size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0; + MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); + } return true; } #endif @@ -2195,8 +2265,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh bool res = check_tx_inputs(tx, tvc, &max_used_block_height); TIME_MEASURE_FINISH(a); if(m_show_time_stats) - LOG_PRINT_L0("HASH: " << "+" << " VIN/VOUT: " << tx.vin.size() << "/" << tx.vout.size() << " H: " << max_used_block_height << " chcktx: " << a + m_fake_scan_time); - + { + size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0; + MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx)); + } if (!res) return false; @@ -2235,6 +2307,19 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v4, forbid invalid pubkeys + if (m_hardfork->get_current_version() >= 4) { + for (const auto &o: tx.vout) { + if (o.target.type() == typeid(txout_to_key)) { + const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target); + if (!crypto::check_key(out_to_key.key)) { + tvc.m_invalid_output = true; + return false; + } + } + } + } + return true; } //------------------------------------------------------------------ @@ -2251,6 +2336,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) { + PERF_TIMER(expand_transaction_2); CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); rct::rctSig &rv = tx.rct_signatures; @@ -2312,10 +2398,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); } - // outPk - CHECK_AND_ASSERT_MES(rv.outPk.size() == tx.vout.size(), false, "Bad outPk size"); - for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n) - rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); + // outPk was already done by handle_incoming_tx return true; } @@ -2327,6 +2410,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr // using threads, etc.) bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) { + PERF_TIMER(check_tx_inputs); LOG_PRINT_L3("Blockchain::" << __func__); size_t sig_index = 0; if(pmax_used_block_height) @@ -2342,7 +2426,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { size_t n_unmixable = 0, n_mixable = 0; size_t mixin = std::numeric_limits<size_t>::max(); - const size_t min_mixin = hf_version >= 5 ? 4 : 2; + const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; for (const auto& txin : tx.vin) { // non txin_to_key inputs will be rejected below @@ -2359,7 +2443,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, else { uint64_t n_outputs = m_db->get_num_outputs(in_to_key.amount); - LOG_PRINT_L2("output size " << print_money(in_to_key.amount) << ": " << n_outputs << " available"); + MDEBUG("output size " << print_money(in_to_key.amount) << ": " << n_outputs << " available"); // n_outputs includes the output we're considering if (n_outputs <= min_mixin) ++n_unmixable; @@ -2375,13 +2459,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (n_unmixable == 0) { - LOG_PRINT_L1("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and no unmixable inputs"); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and no unmixable inputs"); tvc.m_low_mixin = true; return false; } if (n_mixable > 1) { - LOG_PRINT_L1("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and more than one mixable input with unmixable inputs"); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and more than one mixable input with unmixable inputs"); tvc.m_low_mixin = true; return false; } @@ -2391,14 +2475,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, const size_t max_tx_version = (hf_version <= 3) ? 1 : 2; if (tx.version > max_tx_version) { - LOG_PRINT_L1("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); + MERROR_VER("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); tvc.m_verifivation_failed = true; return false; } - const size_t min_tx_version = (n_unmixable > 0 ? 1 : (hf_version >= 5) ? 2 : 1); + const size_t min_tx_version = (n_unmixable > 0 ? 1 : (hf_version >= HF_VERSION_ENFORCE_RCT) ? 2 : 1); if (tx.version < min_tx_version) { - LOG_PRINT_L1("transaction version " << (unsigned)tx.version << " is lower than min accepted version " << min_tx_version); + MERROR_VER("transaction version " << (unsigned)tx.version << " is lower than min accepted version " << min_tx_version); tvc.m_verifivation_failed = true; return false; } @@ -2457,7 +2541,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if(have_tx_keyimg_as_spent(in_to_key.k_image)) { - LOG_PRINT_L1("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image)); + MERROR_VER("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image)); tvc.m_double_spend = true; return false; } @@ -2473,7 +2557,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if(!itk->second) { - LOG_PRINT_L1("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); + MERROR_VER("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); return false; } @@ -2489,10 +2573,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height)) { it->second[in_to_key.k_image] = false; - LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); + MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() { - LOG_PRINT_L1(" *pmax_used_block_height: " << *pmax_used_block_height); + MERROR_VER(" *pmax_used_block_height: " << *pmax_used_block_height); } return false; @@ -2512,11 +2596,11 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (!results[sig_index]) { it->second[in_to_key.k_image] = false; - LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); + MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() { - LOG_PRINT_L1("*pmax_used_block_height: " << *pmax_used_block_height); + MERROR_VER("*pmax_used_block_height: " << *pmax_used_block_height); } return false; @@ -2546,7 +2630,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (failed) { - LOG_PRINT_L1("Failed to check ring signatures!, t_loop: " << t_t1); + MERROR_VER("Failed to check ring signatures!, t_loop: " << t_t1); return false; } } @@ -2555,7 +2639,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) { - LOG_PRINT_L1("Failed to expand rct signatures!"); + MERROR_VER("Failed to expand rct signatures!"); return false; } @@ -2567,7 +2651,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { case rct::RCTTypeNull: { // we only accept no signatures for coinbase txes - LOG_PRINT_L1("Null rct signature on non-coinbase tx"); + MERROR_VER("Null rct signature on non-coinbase tx"); return false; } case rct::RCTTypeSimple: { @@ -2575,14 +2659,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (pubkeys.size() != rv.mixRing.size()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); return false; } for (size_t i = 0; i < pubkeys.size(); ++i) { if (pubkeys[i].size() != rv.mixRing[i].size()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); return false; } } @@ -2593,12 +2677,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); return false; } if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); return false; } } @@ -2607,21 +2691,21 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (rv.p.MGs.size() != tx.vin.size()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched MGs/vin sizes"); + MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); return false; } for (size_t n = 0; n < tx.vin.size(); ++n) { if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched key image"); + MERROR_VER("Failed to check ringct signatures: mismatched key image"); return false; } } - if (!rct::verRctSimple(rv)) + if (!rct::verRctSimple(rv, false)) { - LOG_PRINT_L1("Failed to check ringct signatures!"); + MERROR_VER("Failed to check ringct signatures!"); return false; } break; @@ -2636,7 +2720,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, size_matches &= pubkeys.size() == rv.mixRing[i].size(); if (!size_matches) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); return false; } @@ -2646,12 +2730,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[m][n].dest)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); return false; } if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[m][n].mask)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); return false; } } @@ -2660,32 +2744,32 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (rv.p.MGs.size() != 1) { - LOG_PRINT_L1("Failed to check ringct signatures: Bad MGs size"); + MERROR_VER("Failed to check ringct signatures: Bad MGs size"); return false; } if (rv.p.MGs[0].II.size() != tx.vin.size()) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); + MERROR_VER("Failed to check ringct signatures: mismatched II/vin sizes"); return false; } for (size_t n = 0; n < tx.vin.size(); ++n) { if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[0].II[n], 32)) { - LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); + MERROR_VER("Failed to check ringct signatures: mismatched II/vin sizes"); return false; } } - if (!rct::verRct(rv)) + if (!rct::verRct(rv, false)) { - LOG_PRINT_L1("Failed to check ringct signatures!"); + MERROR_VER("Failed to check ringct signatures!"); return false; } break; } default: - LOG_PRINT_L1("Unsupported rct type: " << rv.type); + MERROR_VER("Unsupported rct type: " << rv.type); return false; } } @@ -2712,6 +2796,113 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const } //------------------------------------------------------------------ +static uint64_t get_fee_quantization_mask() +{ + static uint64_t mask = 0; + if (mask == 0) + { + mask = 1; + for (size_t n = PER_KB_FEE_QUANTIZATION_DECIMALS; n < CRYPTONOTE_DISPLAY_DECIMAL_POINT; ++n) + mask *= 10; + } + return mask; +} + +//------------------------------------------------------------------ +uint64_t Blockchain::get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size, uint8_t version) +{ + const uint64_t min_block_size = get_min_block_size(version); + const uint64_t fee_per_kb_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; + + if (median_block_size < min_block_size) + median_block_size = min_block_size; + + uint64_t unscaled_fee_per_kb = (fee_per_kb_base * min_block_size / median_block_size); + uint64_t hi, lo = mul128(unscaled_fee_per_kb, block_reward, &hi); + static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); + static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits<uint32_t>::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); + + // divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't + div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); + div128_32(hi, lo, 1000000, &hi, &lo); + assert(hi == 0); + + // quantize fee up to 8 decimals + uint64_t mask = get_fee_quantization_mask(); + uint64_t qlo = (lo + mask - 1) / mask * mask; + MDEBUG("lo " << print_money(lo) << ", qlo " << print_money(qlo) << ", mask " << mask); + + return qlo; +} + +//------------------------------------------------------------------ +bool Blockchain::check_fee(size_t blob_size, uint64_t fee) const +{ + const uint8_t version = get_current_hard_fork_version(); + + uint64_t fee_per_kb; + if (version < HF_VERSION_DYNAMIC_FEE) + { + fee_per_kb = FEE_PER_KB; + } + else + { + uint64_t median = m_current_block_cumul_sz_limit / 2; + uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) + return false; + fee_per_kb = get_dynamic_per_kb_fee(base_reward, median, version); + } + MDEBUG("Using " << print_money(fee) << "/kB fee"); + + uint64_t needed_fee = blob_size / 1024; + needed_fee += (blob_size % 1024) ? 1 : 0; + needed_fee *= fee_per_kb; + + if (fee < needed_fee * 0.98) // keep a little buffer on acceptance + { + MERROR_VER("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); + return false; + } + return true; +} + +//------------------------------------------------------------------ +uint64_t Blockchain::get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks) const +{ + const uint8_t version = get_current_hard_fork_version(); + + if (version < HF_VERSION_DYNAMIC_FEE) + return FEE_PER_KB; + + if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) + grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; + + const uint64_t min_block_size = get_min_block_size(version); + std::vector<size_t> sz; + get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); + for (size_t i = 0; i < grace_blocks; ++i) + sz.push_back(min_block_size); + + uint64_t median = epee::misc_utils::median(sz); + if(median <= min_block_size) + median = min_block_size; + + uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) + { + MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); + base_reward = BLOCK_REWARD_OVERESTIMATE; + } + + uint64_t fee = get_dynamic_per_kb_fee(base_reward, median, version); + MDEBUG("Estimating " << grace_blocks << "-block fee at " << print_money(fee) << "/kB"); + return fee; +} + +//------------------------------------------------------------------ // This function checks to see if a tx is unlocked. unlock_time is either // a block index or a unix time. bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const @@ -2762,7 +2953,7 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons //check tx unlock time if (!m_bch.is_tx_spendtime_unlocked(unlock_time)) { - LOG_PRINT_L1("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time); + MERROR_VER("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time); return false; } @@ -2782,13 +2973,13 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons outputs_visitor vi(output_keys, *this); if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height)) { - LOG_PRINT_L1("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); + MERROR_VER("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); return false; } if(txin.key_offsets.size() != output_keys.size()) { - LOG_PRINT_L1("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); + MERROR_VER("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); return false; } if (tx_version == 1) { @@ -2814,7 +3005,7 @@ bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const if(b.timestamp < median_ts) { - LOG_PRINT_L1("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); + MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); return false; } @@ -2833,7 +3024,7 @@ bool Blockchain::check_block_timestamp(const block& b) const LOG_PRINT_L3("Blockchain::" << __func__); if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) { - LOG_PRINT_L1("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); + MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); return false; } @@ -2867,9 +3058,9 @@ void Blockchain::return_tx_to_pool(const std::vector<transaction> &txs) // that might not be always true. Unlikely though, and always relaying // these again might cause a spike of traffic as many nodes re-relay // all the transactions in a popped block when a reorg happens. - if (!m_tx_pool.add_tx(tx, tvc, true, true, version)) + if (!m_tx_pool.add_tx(tx, tvc, true, true, false, version)) { - LOG_PRINT_L0("Failed to return taken transaction with hash: " << get_transaction_hash(tx) << " to tx_pool"); + MERROR("Failed to return taken transaction with hash: " << get_transaction_hash(tx) << " to tx_pool"); } } } @@ -2884,11 +3075,11 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids) cryptonote::transaction tx; size_t blob_size; uint64_t fee; - bool relayed; - LOG_PRINT_L1("Removing txid " << txid << " from the pool"); - if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed)) + bool relayed, do_not_relay; + MINFO("Removing txid " << txid << " from the pool"); + if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay)) { - LOG_PRINT_L0("Failed to remove txid " << txid << " from the pool"); + MERROR("Failed to remove txid " << txid << " from the pool"); res = false; } } @@ -2909,7 +3100,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& m_db->block_txn_start(true); if(bl.prev_id != get_tail_id()) { - LOG_PRINT_L1("Block with id: " << id << std::endl << "has wrong prev_id: " << bl.prev_id << std::endl << "expected: " << get_tail_id()); + MERROR_VER("Block with id: " << id << std::endl << "has wrong prev_id: " << bl.prev_id << std::endl << "expected: " << get_tail_id()); leave: m_db->block_txn_stop(); return false; @@ -2918,7 +3109,7 @@ leave: // this is a cheap test if (!m_hardfork->check(bl)) { - LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version: " << (unsigned)bl.major_version << std::endl << "current: " << (unsigned)m_hardfork->get_current_version()); + MERROR_VER("Block with id: " << id << std::endl << "has old version: " << (unsigned)bl.major_version << std::endl << "current: " << (unsigned)m_hardfork->get_current_version()); bvc.m_verifivation_failed = true; goto leave; } @@ -2930,7 +3121,7 @@ leave: // of a set number of the most recent blocks. if(!check_block_timestamp(bl)) { - LOG_PRINT_L1("Block with id: " << id << std::endl << "has invalid timestamp: " << bl.timestamp); + MERROR_VER("Block with id: " << id << std::endl << "has invalid timestamp: " << bl.timestamp); bvc.m_verifivation_failed = true; goto leave; } @@ -2970,7 +3161,7 @@ leave: auto hash = get_block_hash(bl); if (memcmp(&hash, &m_blocks_hash_check[m_db->height()], sizeof(hash)) != 0) { - LOG_PRINT_L1("Block with id is INVALID: " << id); + MERROR_VER("Block with id is INVALID: " << id); bvc.m_verifivation_failed = true; goto leave; } @@ -2991,7 +3182,7 @@ leave: // validate proof_of_work versus difficulty target if(!check_hash(proof_of_work, current_diffic)) { - LOG_PRINT_L1("Block with id: " << id << std::endl << "does not have enough proof of work: " << proof_of_work << std::endl << "unexpected difficulty: " << current_diffic); + MERROR_VER("Block with id: " << id << std::endl << "does not have enough proof of work: " << proof_of_work << std::endl << "unexpected difficulty: " << current_diffic); bvc.m_verifivation_failed = true; goto leave; } @@ -3018,7 +3209,7 @@ leave: // sanity check basic miner tx properties; if(!prevalidate_miner_transaction(bl, m_db->height())) { - LOG_PRINT_L1("Block with id: " << id << " failed to pass prevalidation"); + MERROR_VER("Block with id: " << id << " failed to pass prevalidation"); bvc.m_verifivation_failed = true; goto leave; } @@ -3047,13 +3238,13 @@ leave: transaction tx; size_t blob_size = 0; uint64_t fee = 0; - bool relayed = false; + bool relayed = false, do_not_relay = false; TIME_MEASURE_START(aa); // XXX old code does not check whether tx exists if (m_db->tx_exists(tx_id)) { - LOG_PRINT_L1("Block with id: " << id << " attempting to add transaction already in blockchain with id: " << tx_id); + MERROR("Block with id: " << id << " attempting to add transaction already in blockchain with id: " << tx_id); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3064,9 +3255,9 @@ leave: TIME_MEASURE_START(bb); // get transaction with hash <tx_id> from tx_pool - if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed)) + if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay)) { - LOG_PRINT_L1("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); + MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3105,11 +3296,11 @@ leave: tx_verification_context tvc; if(!check_tx_inputs(tx, tvc)) { - LOG_PRINT_L1("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); + MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); //TODO: why is this done? make sure that keeping invalid blocks makes sense. add_block_as_invalid(bl, id); - LOG_PRINT_L1("Block with id " << id << " added as invalid because of wrong inputs in transactions"); + MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3122,10 +3313,10 @@ leave: // the transaction inputs, but do some sanity checks anyway. if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) { - LOG_PRINT_L1("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); + MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); //TODO: why is this done? make sure that keeping invalid blocks makes sense. add_block_as_invalid(bl, id); - LOG_PRINT_L1("Block with id " << id << " added as invalid because of wrong inputs in transactions"); + MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3145,7 +3336,7 @@ leave: uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; if(!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins, bvc.m_partial_block_reward, m_hardfork->get_current_version())) { - LOG_PRINT_L1("Block with id: " << id << " has incorrect miner transaction"); + MERROR_VER("Block with id: " << id << " has incorrect miner transaction"); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3204,10 +3395,10 @@ leave: // do this after updating the hard fork state since the size limit may change due to fork update_next_cumulative_size_limit(); - LOG_PRINT_L1("+++++ BLOCK SUCCESSFULLY ADDED" << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "HEIGHT " << new_height-1 << ", difficulty:\t" << current_diffic << std::endl << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) << "), coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms"); + MINFO("+++++ BLOCK SUCCESSFULLY ADDED" << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "HEIGHT " << new_height-1 << ", difficulty:\t" << current_diffic << std::endl << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) << "), coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms"); if(m_show_time_stats) { - LOG_PRINT_L0("Height: " << new_height << " blob: " << coinbase_blob_size << " cumm: " + MINFO("Height: " << new_height << " blob: " << coinbase_blob_size << " cumm: " << cumulative_block_size << " p/t: " << block_processing_time << " (" << target_calculating_time << "/" << longhash_calculating_time << "/" << t1 << "/" << t2 << "/" << t3 << "/" << t_exists << "/" << t_pool @@ -3225,7 +3416,7 @@ leave: //------------------------------------------------------------------ bool Blockchain::update_next_cumulative_size_limit() { - uint64_t full_reward_zone = get_current_hard_fork_version() < 2 ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + uint64_t full_reward_zone = get_min_block_size(get_current_hard_fork_version()); LOG_PRINT_L3("Blockchain::" << __func__); std::vector<size_t> sz; @@ -3275,9 +3466,10 @@ bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) { const auto& pts = points.get_points(); + bool stop_batch; CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_db->batch_start(); + stop_batch = m_db->batch_start(); for (const auto& pt : pts) { // if the checkpoint is for a block we don't have yet, move on @@ -3301,7 +3493,8 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor } } } - m_db->batch_stop(); + if (stop_batch) + m_db->batch_stop(); } //------------------------------------------------------------------ // returns false if any of the checkpoints loading returns false. @@ -3333,7 +3526,7 @@ bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns } else { - LOG_PRINT_L0("One or more checkpoints fetched from DNS conflicted with existing checkpoints!"); + MERROR("One or more checkpoints fetched from DNS conflicted with existing checkpoints!"); } } @@ -3357,6 +3550,8 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< // the height of the block passed to it for (const auto & block : blocks) { + if (m_cancel) + return; crypto::hash id = get_block_hash(block); crypto::hash pow = get_block_longhash(block, height); map.emplace(id, pow); @@ -3369,10 +3564,11 @@ void Blockchain::block_longhash_worker(const uint64_t height, const std::vector< //------------------------------------------------------------------ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) { - LOG_PRINT_YELLOW("Blockchain::" << __func__, LOG_LEVEL_3); + MTRACE("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); TIME_MEASURE_START(t1); + m_db->batch_stop(); if (m_sync_counter > 0) { if (force_sync) @@ -3414,11 +3610,11 @@ void Blockchain::output_scan_worker(const uint64_t amount, const std::vector<uin { try { - m_db->get_output_key(amount, offsets, outputs); + m_db->get_output_key(amount, offsets, outputs, true); } catch (const std::exception& e) { - LOG_PRINT_L1("EXCEPTION: " << e.what()); + MERROR_VER("EXCEPTION: " << e.what()); } catch (...) { @@ -3435,13 +3631,20 @@ void Blockchain::output_scan_worker(const uint64_t amount, const std::vector<uin // keys. bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_entry> &blocks_entry) { - LOG_PRINT_YELLOW("Blockchain::" << __func__, LOG_LEVEL_3); + MTRACE("Blockchain::" << __func__); TIME_MEASURE_START(prepare); + bool stop_batch; CRITICAL_REGION_LOCAL(m_blockchain_lock); if(blocks_entry.size() == 0) return false; + while (!(stop_batch = m_db->batch_start(blocks_entry.size()))) { + m_blockchain_lock.unlock(); + epee::misc_utils::sleep_no_w(1000); + m_blockchain_lock.lock(); + } + if ((m_db->height() + blocks_entry.size()) < m_blocks_hash_check.size()) return true; @@ -3458,10 +3661,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e std::vector<boost::thread *> thread_list; int batches = blocks_entry.size() / threads; int extra = blocks_entry.size() % threads; - LOG_PRINT_L1("block_batches: " << batches); + MDEBUG("block_batches: " << batches); std::vector<std::unordered_map<crypto::hash, crypto::hash>> maps(threads); std::vector < std::vector < block >> blocks(threads); auto it = blocks_entry.begin(); + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); for (uint64_t i = 0; i < threads; i++) { @@ -3481,7 +3686,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e crypto::hash tophash = m_db->top_block_hash(); if (block.prev_id != tophash) { - LOG_PRINT_L1("Skipping prepare blocks. New blocks don't belong to chain."); + MDEBUG("Skipping prepare blocks. New blocks don't belong to chain."); return true; } } @@ -3521,7 +3726,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e m_blocks_longhash_table.clear(); for (uint64_t i = 0; i < threads; i++) { - thread_list.push_back(new boost::thread(&Blockchain::block_longhash_worker, this, height + (i * batches), std::cref(blocks[i]), std::ref(maps[i]))); + thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, height + (i * batches), std::cref(blocks[i]), std::ref(maps[i])))); } for (size_t j = 0; j < thread_list.size(); j++) @@ -3532,6 +3737,9 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e thread_list.clear(); + if (m_cancel) + return false; + for (const auto & map : maps) { m_blocks_longhash_table.insert(map.begin(), map.end()); @@ -3539,9 +3747,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e } } + if (m_cancel) + return false; + if (blocks_exist) { - LOG_PRINT_L0("Skipping prepare blocks. Blocks exist."); + MDEBUG("Skipping prepare blocks. Blocks exist."); return true; } @@ -3555,7 +3766,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e m_fake_pow_calc_time = prepare / blocks_entry.size(); if (blocks_entry.size() > 1 && threads > 1 && m_show_time_stats) - LOG_PRINT_L0("Prepare blocks took: " << prepare << " ms"); + MDEBUG("Prepare blocks took: " << prepare << " ms"); TIME_MEASURE_START(scantable); @@ -3568,7 +3779,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e #define SCAN_TABLE_QUIT(m) \ do { \ - LOG_PRINT_L0(m) ;\ + MERROR_VER(m) ;\ m_scan_table.clear(); \ return false; \ } while(0); \ @@ -3576,6 +3787,9 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e // generate sorted tables for all amounts and absolute offsets for (const auto &entry : blocks_entry) { + if (m_cancel) + return false; + for (const auto &tx_blob : entry.txs) { crypto::hash tx_hash = null_hash; @@ -3684,6 +3898,9 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e // now generate a table for each tx_prefix and k_image hashes for (const auto &entry : blocks_entry) { + if (m_cancel) + return false; + for (const auto &tx_blob : entry.txs) { crypto::hash tx_hash = null_hash; @@ -3736,7 +3953,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e { m_fake_scan_time = scantable / total_txs; if(m_show_time_stats) - LOG_PRINT_L0("Prepare scantable took: " << scantable << " ms"); + MDEBUG("Prepare scantable took: " << scantable << " ms"); } return true; @@ -3760,16 +3977,80 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } -std::map<uint64_t, uint64_t> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const +std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { - return m_db->get_output_histogram(amounts, unlocked); + return m_db->get_output_histogram(amounts, unlocked, recent_cutoff); +} + +std::list<std::pair<Blockchain::block_extended_info,uint64_t>> Blockchain::get_alternative_chains() const +{ + std::list<std::pair<Blockchain::block_extended_info,uint64_t>> chains; + + for (const auto &i: m_alternative_chains) + { + const crypto::hash &top = i.first; + bool found = false; + for (const auto &j: m_alternative_chains) + { + if (j.second.bl.prev_id == top) + { + found = true; + break; + } + } + if (!found) + { + uint64_t length = 1; + auto h = i.second.bl.prev_id; + blocks_ext_by_hash::const_iterator prev; + while ((prev = m_alternative_chains.find(h)) != m_alternative_chains.end()) + { + h = prev->second.bl.prev_id; + ++length; + } + chains.push_back(std::make_pair(i.second, length)); + } + } + return chains; +} + +void Blockchain::cancel() +{ + m_cancel = true; } #if defined(PER_BLOCK_CHECKPOINT) +static const char expected_block_hashes_hash[] = "23d8a8c73de7b2383c72a016d9a6034e69d62dd48077d1c414e064ceab6daa94"; void Blockchain::load_compiled_in_block_hashes() { - if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr) + if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr && get_blocks_dat_size(m_testnet) > 0) { + MINFO("Loading precomputed blocks (" << get_blocks_dat_size(m_testnet) << " bytes)"); + + if (!m_testnet) + { + // first check hash + crypto::hash hash; + if (!tools::sha256sum(get_blocks_dat_start(m_testnet), get_blocks_dat_size(m_testnet), hash)) + { + MERROR("Failed to hash precomputed blocks data"); + return; + } + MINFO("precomputed blocks hash: " << hash << ", expected " << expected_block_hashes_hash); + cryptonote::blobdata expected_hash_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(std::string(expected_block_hashes_hash), expected_hash_data) || expected_hash_data.size() != sizeof(crypto::hash)) + { + MERROR("Failed to parse expected block hashes hash"); + return; + } + const crypto::hash expected_hash = *reinterpret_cast<const crypto::hash*>(expected_hash_data.data()); + if (hash != expected_hash) + { + MERROR("Block hash data does not match expected hash"); + return; + } + } + if (get_blocks_dat_size(m_testnet) > 4) { const unsigned char *p = get_blocks_dat_start(m_testnet); @@ -3777,7 +4058,6 @@ void Blockchain::load_compiled_in_block_hashes() const size_t size_needed = 4 + nblocks * sizeof(crypto::hash); if(nblocks > 0 && nblocks > m_db->height() && get_blocks_dat_size(m_testnet) >= size_needed) { - LOG_PRINT_L0("Loading precomputed blocks: " << nblocks); p += sizeof(uint32_t); for (uint32_t i = 0; i < nblocks; i++) { @@ -3786,6 +4066,7 @@ void Blockchain::load_compiled_in_block_hashes() p += sizeof(hash.data); m_blocks_hash_check.push_back(hash); } + MINFO(nblocks << " block hashes loaded"); // FIXME: clear tx_pool because the process might have been // terminated and caused it to store txs kept by blocks. @@ -3797,12 +4078,12 @@ void Blockchain::load_compiled_in_block_hashes() size_t blob_size; uint64_t fee; - bool relayed; + bool relayed, do_not_relay; transaction pool_tx; for(const transaction &tx : txs) { crypto::hash tx_hash = get_transaction_hash(tx); - m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed); + m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay); } } } @@ -3829,3 +4110,7 @@ bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypt { return m_db->for_all_outputs(f);; } + +namespace cryptonote { +template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::list<transaction>&, std::list<crypto::hash>&) const; +} diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 94701608e..46f7ac682 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once +#include <boost/asio/io_service.hpp> #include <boost/serialization/serialization.hpp> #include <boost/serialization/version.hpp> #include <boost/serialization/list.hpp> @@ -36,23 +37,22 @@ #include <boost/multi_index/global_fun.hpp> #include <boost/multi_index/hashed_index.hpp> #include <boost/multi_index/member.hpp> -#include <boost/foreach.hpp> #include <atomic> #include <unordered_map> #include <unordered_set> #include "syncobj.h" #include "string_tools.h" -#include "cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "common/util.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "difficulty.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "verification_context.h" +#include "cryptonote_basic/difficulty.h" +#include "cryptonote_tx_utils.h" +#include "cryptonote_basic/verification_context.h" #include "crypto/hash.h" -#include "checkpoints.h" -#include "hardfork.h" +#include "cryptonote_basic/checkpoints.h" +#include "cryptonote_basic/hardfork.h" #include "blockchain_db/blockchain_db.h" namespace cryptonote @@ -154,7 +154,7 @@ namespace cryptonote * * @return false if start_offset > blockchain height, else true */ - bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const; + bool get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const; /** * @brief get blocks from blocks based on start height and count @@ -165,7 +165,7 @@ namespace cryptonote * * @return false if start_offset > blockchain height, else true */ - bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const; + bool get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks) const; /** * @brief compiles a list of all blocks stored as alternative chains @@ -197,10 +197,11 @@ namespace cryptonote * * @param h the hash to look for * @param blk return-by-reference variable to put result block in + * @param orphan if non-NULL, will be set to true if not in the main chain, false otherwise * * @return true if the block was found, else false */ - bool get_block_by_hash(const crypto::hash &h, block &blk) const; + bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL) const; /** * @brief get all block hashes (main chain, alt chains, and invalid blocks) @@ -322,11 +323,12 @@ namespace cryptonote * @param miner_address address new coins for the block will go to * @param di return-by-reference tells the miner what the difficulty target is * @param height return-by-reference tells the miner what height it's mining against + * @param expected_reward return-by-reference the total reward awarded to the miner finding this block, including transaction fees * @param ex_nonce extra data to be added to the miner transaction's extra * * @return true if block template filled in successfully, else false */ - bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); + bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** * @brief checks if a block is known about with a given hash @@ -407,7 +409,7 @@ namespace cryptonote * * @return true if a block found in common or req_start_block specified, else false */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; /** * @brief retrieves a set of blocks and their transactions, and possibly other transactions @@ -452,7 +454,7 @@ namespace cryptonote * * @return true */ - bool get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const; + bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; /** * @brief gets random ringct outputs to mix with @@ -513,6 +515,48 @@ namespace cryptonote bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); /** + * @brief get dynamic per kB fee for a given block size + * + * The dynamic fee is based on the block size in a past window, and + * the current block reward. It is expressed by kB. + * + * @param block_reward the current block reward + * @param median_block_size the median blob's size in the past window + * @param version hard fork version for rules and constants to use + * + * @return the per kB fee + */ + static uint64_t get_dynamic_per_kb_fee(uint64_t block_reward, size_t median_block_size, uint8_t version); + + /** + * @brief get dynamic per kB fee estimate for the next few blocks + * + * The dynamic fee is based on the block size in a past window, and + * the current block reward. It is expressed by kB. This function + * calculates an estimate for a dynamic fee which will be valid for + * the next grace_blocks + * + * @param grace_blocks number of blocks we want the fee to be valid for + * + * @return the per kB fee estimate + */ + uint64_t get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks) const; + + /** + * @brief validate a transaction's fee + * + * This function validates the fee is enough for the transaction. + * This is based on the size of the transaction blob, and, after a + * height threshold, on the average size of transaction in a past window + * + * @param blob_size the transaction blob's size + * @param fee the fee + * + * @return true if the fee is enough, false otherwise + */ + bool check_fee(size_t blob_size, uint64_t fee) const; + + /** * @brief check that a transaction's outputs conform to current standards * * This function checks, for example at the time of this writing, that @@ -580,9 +624,10 @@ namespace cryptonote * @return false if an unexpected exception occurs, else true */ template<class t_ids_container, class t_tx_container, class t_missed_container> + bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; + template<class t_ids_container, class t_tx_container, class t_missed_container> bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; - //debug functions /** @@ -729,10 +774,11 @@ namespace cryptonote * * @param amounts optional set of amounts to lookup * @param unlocked whether to restrict instances to unlocked ones + * @param recent_cutoff timestamp to consider outputs as recent * * @return a set of amount/instances */ - std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked) const; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const; /** * @brief perform a check on all key images in the blockchain @@ -801,6 +847,16 @@ namespace cryptonote */ void block_longhash_worker(const uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const; + + /** + * @brief returns a set of known alternate chains + * + * @return a list of chains + */ + std::list<std::pair<block_extended_info,uint64_t>> get_alternative_chains() const; + + void cancel(); + private: // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage @@ -870,6 +926,8 @@ namespace cryptonote bool m_testnet; + std::atomic<bool> m_cancel; + /** * @brief collects the keys for all outputs being "spent" as an input * diff --git a/src/cryptonote_core/blockchain_storage_boost_serialization.h b/src/cryptonote_core/blockchain_storage_boost_serialization.h index 0518b9aa7..e87a51f10 100644 --- a/src/cryptonote_core/blockchain_storage_boost_serialization.h +++ b/src/cryptonote_core/blockchain_storage_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 4abf6a898..9c122f511 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,27 +31,37 @@ #include "include_base_utils.h" using namespace epee; -#include <boost/foreach.hpp> #include <unordered_set> #include "cryptonote_core.h" #include "common/command_line.h" #include "common/util.h" +#include "common/updates.h" +#include "common/download.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" -#include "cryptonote_format_utils.h" +#include "cryptonote_tx_utils.h" #include "misc_language.h" #include <csignal> -#include "cryptonote_core/checkpoints.h" +#include <p2p/net_node.h> +#include "cryptonote_basic/checkpoints.h" #include "ringct/rctTypes.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) #include "blockchain_db/berkeleydb/db_bdb.h" #endif +#include "ringct/rctSigs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" DISABLE_VS_WARNINGS(4355) +#define MERROR_VER(x) MCERROR("verify", x) + +#define BAD_SEMANTICS_TXES_MAX_SIZE 100 + namespace cryptonote { @@ -65,7 +75,8 @@ namespace cryptonote m_target_blockchain_height(0), m_checkpoints_path(""), m_last_dns_checkpoints_update(0), - m_last_json_checkpoints_update(0) + m_last_json_checkpoints_update(0), + m_update_download(0) { set_cryptonote_protocol(pprotocol); } @@ -123,7 +134,16 @@ namespace cryptonote //----------------------------------------------------------------------------------- void core::stop() { - graceful_exit(); + m_blockchain_storage.cancel(); + + tools::download_async_handle handle; + { + boost::lock_guard<boost::mutex> lock(m_update_mutex); + handle = m_update_download; + m_update_download = 0; + } + if (handle) + tools::download_cancel(handle); } //----------------------------------------------------------------------------------- void core::init_options(boost::program_options::options_description& desc) @@ -141,8 +161,14 @@ namespace cryptonote command_line::add_arg(desc, command_line::arg_fast_block_sync); command_line::add_arg(desc, command_line::arg_db_sync_mode); command_line::add_arg(desc, command_line::arg_show_time_stats); - command_line::add_arg(desc, command_line::arg_db_auto_remove_logs); command_line::add_arg(desc, command_line::arg_block_sync_size); + command_line::add_arg(desc, command_line::arg_check_updates); + + // we now also need some of net_node's options (p2p bind arg, for separate data dir) + command_line::add_arg(desc, nodetool::arg_testnet_p2p_bind_port, false); + command_line::add_arg(desc, nodetool::arg_p2p_bind_port, false); + + miner::init_options(desc); } //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) @@ -190,75 +216,70 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const + bool core::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const { return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs); } //----------------------------------------------------------------------------------------------- - bool core::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const + bool core::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks) const { return m_blockchain_storage.get_blocks(start_offset, count, blocks); - } //----------------------------------------------------------------------------------------------- - bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs) const + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const { - return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); + std::list<std::pair<cryptonote::blobdata, cryptonote::block>> bs; + if (!m_blockchain_storage.get_blocks(start_offset, count, bs)) + return false; + for (const auto &b: bs) + blocks.push_back(b.second); + return true; } //----------------------------------------------------------------------------------------------- - bool core::get_alternative_blocks(std::list<block>& blocks) const + bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<cryptonote::blobdata>& txs, std::list<crypto::hash>& missed_txs) const { - return m_blockchain_storage.get_alternative_blocks(blocks); + return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - size_t core::get_alternative_blocks_count() const + bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs) const { - return m_blockchain_storage.get_alternative_blocks_count(); + return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::lock_db_directory(const boost::filesystem::path &path) + bool core::get_alternative_blocks(std::list<block>& blocks) const { - // boost doesn't like locking directories... - const boost::filesystem::path lock_path = path / ".daemon_lock"; - - try - { - // ensure the file exists - std::ofstream(lock_path.string(), std::ios::out).close(); - - db_lock = boost::interprocess::file_lock(lock_path.string().c_str()); - LOG_PRINT_L1("Locking " << lock_path.string()); - if (!db_lock.try_lock()) - { - LOG_PRINT_L0("Failed to lock " << lock_path.string()); - return false; - } - return true; - } - catch (const std::exception &e) - { - LOG_PRINT_L0("Error trying to lock " << lock_path.string() << ": " << e.what()); - return false; - } + return m_blockchain_storage.get_alternative_blocks(blocks); } //----------------------------------------------------------------------------------------------- - bool core::unlock_db_directory() + size_t core::get_alternative_blocks_count() const { - db_lock.unlock(); - db_lock = boost::interprocess::file_lock(); - return true; + return m_blockchain_storage.get_alternative_blocks_count(); } //----------------------------------------------------------------------------------------------- bool core::init(const boost::program_options::variables_map& vm, const cryptonote::test_options *test_options) { + start_time = std::time(nullptr); + m_fakechain = test_options != NULL; bool r = handle_command_line(vm); + bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + auto p2p_bind_arg = testnet ? nodetool::arg_testnet_p2p_bind_port : nodetool::arg_p2p_bind_port; + std::string m_port = command_line::get_arg(vm, p2p_bind_arg); + std::string m_config_folder_mempool = m_config_folder; + + if ((!testnet && m_port != std::to_string(::config::P2P_DEFAULT_PORT)) + || (testnet && m_port != std::to_string(::config::testnet::P2P_DEFAULT_PORT))) { + m_config_folder_mempool = m_config_folder_mempool + "/" + m_port; + } - r = m_mempool.init(m_fakechain ? std::string() : m_config_folder); + r = m_mempool.init(m_fakechain ? std::string() : m_config_folder_mempool); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); std::string db_type = command_line::get_arg(vm, command_line::arg_db_type); std::string db_sync_mode = command_line::get_arg(vm, command_line::arg_db_sync_mode); bool fast_sync = command_line::get_arg(vm, command_line::arg_fast_block_sync) != 0; uint64_t blocks_threads = command_line::get_arg(vm, command_line::arg_prep_blocks_threads); + std::string check_updates_string = command_line::get_arg(vm, command_line::arg_check_updates); boost::filesystem::path folder(m_config_folder); if (m_fakechain) @@ -267,11 +288,6 @@ namespace cryptonote // make sure the data directory exists, and try to lock it CHECK_AND_ASSERT_MES (boost::filesystem::exists(folder) || boost::filesystem::create_directories(folder), false, std::string("Failed to create directory ").append(folder.string()).c_str()); - if (!lock_db_directory (folder)) - { - LOG_ERROR ("Failed to lock " << folder); - return false; - } // check for blockchain.bin try @@ -279,10 +295,10 @@ namespace cryptonote const boost::filesystem::path old_files = folder; if (boost::filesystem::exists(old_files / "blockchain.bin")) { - LOG_PRINT_RED_L0("Found old-style blockchain.bin in " << old_files.string()); - LOG_PRINT_RED_L0("Monero now uses a new format. You can either remove blockchain.bin to start syncing"); - LOG_PRINT_RED_L0("the blockchain anew, or use monero-blockchain-export and monero-blockchain-import to"); - LOG_PRINT_RED_L0("convert your existing blockchain.bin to the new format. See README.md for instructions."); + MWARNING("Found old-style blockchain.bin in " << old_files.string()); + MWARNING("Monero now uses a new format. You can either remove blockchain.bin to start syncing"); + MWARNING("the blockchain anew, or use monero-blockchain-export and monero-blockchain-import to"); + MWARNING("convert your existing blockchain.bin to the new format. See README.md for instructions."); return false; } } @@ -300,31 +316,19 @@ namespace cryptonote DBS_FAST_MODE = MDB_NORDAHEAD | MDB_NOSYNC; DBS_FASTEST_MODE = MDB_NORDAHEAD | MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC; } - else if (db_type == "berkeley") - { -#if defined(BERKELEY_DB) - db = new BlockchainBDB(); - DBS_FAST_MODE = DB_TXN_WRITE_NOSYNC; - DBS_FASTEST_MODE = DB_TXN_NOSYNC; - DBS_SAFE_MODE = DB_TXN_SYNC; -#else - LOG_ERROR("BerkeleyDB support disabled."); - return false; -#endif - } else { - LOG_ERROR("Attempted to use non-existant database type"); + LOG_ERROR("Attempted to use non-existent database type"); return false; } folder /= db->get_db_name(); - LOG_PRINT_L0("Loading blockchain from folder " << folder.string() << " ..."); + MGINFO("Loading blockchain from folder " << folder.string() << " ..."); const std::string filename = folder.string(); - // temporarily default to fastest:async:1000 + // default to fast:async:1 blockchain_db_sync_mode sync_mode = db_async; - uint64_t blocks_per_sync = 1000; + uint64_t blocks_per_sync = 1; try { @@ -335,14 +339,14 @@ namespace cryptonote boost::split(options, db_sync_mode, boost::is_any_of(" :")); for(const auto &option : options) - LOG_PRINT_L0("option: " << option); + MDEBUG("option: " << option); - // default to fast:async:1000 + // default to fast:async:1 uint64_t DEFAULT_FLAGS = DBS_FAST_MODE; if(options.size() == 0) { - // temporarily default to fastest:async:1000 + // default to fast:async:1 db_flags = DEFAULT_FLAGS; } @@ -358,7 +362,10 @@ namespace cryptonote else if(options[0] == "fast") db_flags = DBS_FAST_MODE; else if(options[0] == "fastest") + { db_flags = DBS_FASTEST_MODE; + blocks_per_sync = 1000; // default to fastest:async:1000 + } else db_flags = DEFAULT_FLAGS; } @@ -379,15 +386,13 @@ namespace cryptonote blocks_per_sync = bps; } - bool auto_remove_logs = command_line::get_arg(vm, command_line::arg_db_auto_remove_logs) != 0; - db->set_auto_remove_logs(auto_remove_logs); db->open(filename, db_flags); if(!db->m_open) return false; } catch (const DB_ERROR& e) { - LOG_PRINT_L0("Error opening database: " << e.what()); + LOG_ERROR("Error opening database: " << e.what()); return false; } @@ -412,6 +417,20 @@ namespace cryptonote // with respect to what blocks we already have CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + // DNS versions checking + if (check_updates_string == "disabled") + check_updates_level = UPDATES_DISABLED; + else if (check_updates_string == "notify") + check_updates_level = UPDATES_NOTIFY; + else if (check_updates_string == "download") + check_updates_level = UPDATES_DOWNLOAD; + else if (check_updates_string == "update") + check_updates_level = UPDATES_UPDATE; + else { + MERROR("Invalid argument to --dns-versions-check: " << check_updates_string); + return false; + } + r = m_miner.init(vm, m_testnet); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize miner instance"); @@ -433,24 +452,10 @@ namespace cryptonote { m_miner.stop(); m_mempool.deinit(); - if (!m_fast_exit) - { - m_blockchain_storage.deinit(); - } - unlock_db_directory(); + m_blockchain_storage.deinit(); return true; } //----------------------------------------------------------------------------------------------- - void core::set_fast_exit() - { - m_fast_exit = true; - } - //----------------------------------------------------------------------------------------------- - bool core::get_fast_exit() - { - return m_fast_exit; - } - //----------------------------------------------------------------------------------------------- void core::test_drop_download() { m_test_drop_download = false; @@ -477,7 +482,7 @@ namespace cryptonote return false; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed) + bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { tvc = boost::value_initialized<tx_verification_context>(); //want to process all transactions sequentially @@ -503,6 +508,16 @@ namespace cryptonote } //std::cout << "!"<< tx.vin.size() << std::endl; + for (int idx = 0; idx < 2; ++idx) + { + if (bad_semantics_txes[idx].find(tx_hash) != bad_semantics_txes[idx].end()) + { + LOG_PRINT_L1("Transaction already seen with bad semantics, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + } + uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); const size_t max_tx_version = version == 1 ? 1 : 2; if (tx.version == 0 || tx.version > max_tx_version) @@ -512,6 +527,18 @@ namespace cryptonote return false; } + if(m_mempool.have_tx(tx_hash)) + { + LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); + return true; + } + + if(m_blockchain_storage.have_tx(tx_hash)) + { + LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); + return true; + } + if(!check_tx_syntax(tx)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); @@ -519,21 +546,44 @@ namespace cryptonote return false; } + // resolve outPk references in rct txes + // outPk aren't the only thing that need resolving for a fully resolved tx, + // but outPk (1) are needed now to check range proof semantics, and + // (2) do not need access to the blockchain to find data + if (tx.version >= 2) + { + rct::rctSig &rv = tx.rct_signatures; + if (rv.outPk.size() != tx.vout.size()) + { + LOG_PRINT_L1("WRONG TRANSACTION BLOB, Bad outPk size in tx " << tx_hash << ", rejected"); + tvc.m_verifivation_failed = true; + return false; + } + for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n) + rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); + } + if(!check_tx_semantic(tx, keeped_by_block)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); tvc.m_verifivation_failed = true; + bad_semantics_txes[0].insert(tx_hash); + if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE) + { + std::swap(bad_semantics_txes[0], bad_semantics_txes[1]); + bad_semantics_txes[0].clear(); + } return false; } - bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed); + bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed, do_not_relay); if(tvc.m_verifivation_failed) - {LOG_PRINT_RED_L1("Transaction verification failed: " << tx_hash);} + {MERROR_VER("Transaction verification failed: " << tx_hash);} else if(tvc.m_verifivation_impossible) - {LOG_PRINT_RED_L1("Transaction verification impossible: " << tx_hash);} + {MERROR_VER("Transaction verification impossible: " << tx_hash);} if(tvc.m_added_to_pool) - LOG_PRINT_L1("tx added: " << tx_hash); + MDEBUG("tx added: " << tx_hash); return r; } //----------------------------------------------------------------------------------------------- @@ -552,33 +602,33 @@ namespace cryptonote { if(!tx.vin.size()) { - LOG_PRINT_RED_L1("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); + MERROR_VER("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } if(!check_inputs_types_supported(tx)) { - LOG_PRINT_RED_L1("unsupported input types for tx id= " << get_transaction_hash(tx)); + MERROR_VER("unsupported input types for tx id= " << get_transaction_hash(tx)); return false; } if(!check_outs_valid(tx)) { - LOG_PRINT_RED_L1("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); + MERROR_VER("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } if (tx.version > 1) { if (tx.rct_signatures.outPk.size() != tx.vout.size()) { - LOG_PRINT_RED_L1("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx)); + MERROR_VER("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx)); return false; } } if(!check_money_overflow(tx)) { - LOG_PRINT_RED_L1("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); + MERROR_VER("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); return false; } @@ -590,7 +640,7 @@ namespace cryptonote if(amount_in <= amount_out) { - LOG_PRINT_RED_L1("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + MERROR_VER("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); return false; } } @@ -598,17 +648,50 @@ namespace cryptonote if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) { - LOG_PRINT_RED_L1("tx is too large " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + MERROR_VER("tx is too large " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); return false; } //check if tx use different key images if(!check_tx_inputs_keyimages_diff(tx)) { - LOG_PRINT_RED_L1("tx uses a single key image more than once"); + MERROR_VER("tx uses a single key image more than once"); return false; } + if (!check_tx_inputs_keyimages_domain(tx)) + { + MERROR_VER("tx uses key image not in the valid domain"); + return false; + } + + if (tx.version >= 2) + { + const rct::rctSig &rv = tx.rct_signatures; + switch (rv.type) { + case rct::RCTTypeNull: + // coinbase should not come here, so we reject for all other types + MERROR_VER("Unexpected Null rctSig type"); + return false; + case rct::RCTTypeSimple: + if (!rct::verRctSimple(rv, true)) + { + MERROR_VER("rct signature semantics check failed"); + return false; + } + break; + case rct::RCTTypeFull: + if (!rct::verRct(rv, true)) + { + MERROR_VER("rct signature semantics check failed"); + return false; + } + break; + default: + MERROR_VER("Unknown rct type: " << rv.type); + return false; + } + } return true; } @@ -621,17 +704,42 @@ namespace cryptonote bool core::are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const { spent.clear(); - BOOST_FOREACH(auto& ki, key_im) + for(auto& ki: key_im) { spent.push_back(m_blockchain_storage.have_tx_keyimg_as_spent(ki)); } return true; } //----------------------------------------------------------------------------------------------- + std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) + { + std::list<block> blocks; + uint64_t emission_amount = 0; + uint64_t total_fee_amount = 0; + this->get_blocks(start_offset, count, blocks); + for(auto& b: blocks) + { + std::list<transaction> txs; + std::list<crypto::hash> missed_txs; + uint64_t coinbase_amount = get_outs_money_amount(b.miner_tx); + this->get_transactions(b.tx_hashes, txs, missed_txs); + uint64_t tx_fee_amount = 0; + for(const auto& tx: txs) + { + tx_fee_amount += get_tx_fee(tx); + } + + emission_amount += coinbase_amount - tx_fee_amount; + total_fee_amount += tx_fee_amount; + } + + return std::pair<uint64_t, uint64_t>(emission_amount, total_fee_amount); + } + //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const { std::unordered_set<crypto::key_image> ki; - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); if(!ki.insert(tokey_in.k_image).second) @@ -640,13 +748,25 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed) + bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const + { + std::unordered_set<crypto::key_image> ki; + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if (!(rct::scalarmultKey(rct::ki2rct(tokey_in.k_image), rct::curveOrder()) == rct::identity())) + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { crypto::hash tx_hash = get_transaction_hash(tx); crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); blobdata bl; t_serializable_object_to_blob(tx, bl); - return add_new_tx(tx, tx_hash, tx_prefix_hash, bl.size(), tvc, keeped_by_block, relayed); + return add_new_tx(tx, tx_hash, tx_prefix_hash, bl.size(), tvc, keeped_by_block, relayed, do_not_relay); } //----------------------------------------------------------------------------------------------- size_t core::get_blockchain_total_transactions() const @@ -654,7 +774,7 @@ namespace cryptonote return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed) + bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { if(m_mempool.have_tx(tx_hash)) { @@ -669,14 +789,14 @@ namespace cryptonote } uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); - return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block, relayed, version); + return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version); } //----------------------------------------------------------------------------------------------- bool core::relay_txpool_transactions() { // we attempt to relay txes that should be relayed, but were not std::list<std::pair<crypto::hash, cryptonote::transaction>> txs; - if (m_mempool.get_relayable_transactions(txs)) + if (m_mempool.get_relayable_transactions(txs) && !txs.empty()) { cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); @@ -693,9 +813,23 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) + void core::on_transaction_relayed(const cryptonote::blobdata& tx_blob) { - return m_blockchain_storage.create_block_template(b, adr, diffic, height, ex_nonce); + std::list<std::pair<crypto::hash, cryptonote::transaction>> txs; + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash, tx_prefix_hash)) + { + LOG_ERROR("Failed to parse relayed transaction"); + return; + } + txs.push_back(std::make_pair(tx_hash, std::move(tx))); + m_mempool.set_relayed(txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) + { + return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); } //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const @@ -703,7 +837,7 @@ namespace cryptonote return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); } //----------------------------------------------------------------------------------------------- - bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const + bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const { return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, max_count); } @@ -728,7 +862,7 @@ namespace cryptonote return m_blockchain_storage.get_random_outs_for_amounts(req, res); } //----------------------------------------------------------------------------------------------- - bool core::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const + bool core::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const { return m_blockchain_storage.get_outs(req, res); } @@ -753,11 +887,28 @@ namespace cryptonote m_miner.resume(); } //----------------------------------------------------------------------------------------------- + block_complete_entry get_block_complete_entry(block& b, tx_memory_pool &pool) + { + block_complete_entry bce; + bce.block = cryptonote::block_to_blob(b); + for (const auto &tx_hash: b.tx_hashes) + { + cryptonote::transaction tx; + CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, tx), "Transaction not found in pool"); + bce.txs.push_back(cryptonote::tx_to_blob(tx)); + } + return bce; + } + //----------------------------------------------------------------------------------------------- bool core::handle_block_found(block& b) { block_verification_context bvc = boost::value_initialized<block_verification_context>(); m_miner.pause(); + std::list<block_complete_entry> blocks; + blocks.push_back(get_block_complete_entry(b, m_mempool)); + prepare_handle_incoming_blocks(blocks); m_blockchain_storage.add_new_block(b, bvc); + cleanup_handle_incoming_blocks(true); //anyway - update miner template update_miner_block_template(); m_miner.resume(); @@ -771,8 +922,8 @@ namespace cryptonote arg.hop = 0; arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); std::list<crypto::hash> missed_txs; - std::list<transaction> txs; - m_blockchain_storage.get_transactions(b.tx_hashes, txs, missed_txs); + std::list<cryptonote::blobdata> txs; + m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, missed_txs); if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) { LOG_PRINT_L1("Block found but, seems that reorganize just happened after that, do not relay this block"); @@ -783,8 +934,8 @@ namespace cryptonote block_to_blob(b, arg.b.block); //pack transactions - BOOST_FOREACH(auto& tx, txs) - arg.b.txs.push_back(t_serializable_object_to_blob(tx)); + for(auto& tx: txs) + arg.b.txs.push_back(tx); m_pprotocol->relay_block(arg, exclude_context); } @@ -886,6 +1037,17 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const + { + m_mempool.get_transaction_hashes(txs); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transaction(const crypto::hash &id, transaction& tx) const + { + return m_mempool.get_transaction(id, tx); + } + //----------------------------------------------------------------------------------------------- bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const { return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos); @@ -906,9 +1068,9 @@ namespace cryptonote return m_blockchain_storage.get_block_id_by_height(height); } //----------------------------------------------------------------------------------------------- - bool core::get_block_by_hash(const crypto::hash &h, block &blk) const + bool core::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const { - return m_blockchain_storage.get_block_by_hash(h, blk); + return m_blockchain_storage.get_block_by_hash(h, blk, orphan); } //----------------------------------------------------------------------------------------------- std::string core::print_pool(bool short_format) const @@ -926,20 +1088,20 @@ namespace cryptonote { if(!m_starter_message_showed) { - LOG_PRINT_L0(ENDL << "**********************************************************************" << ENDL + MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << "The daemon will start synchronizing with the network. It may take up to several hours." << ENDL << ENDL - << "You can set the level of process detailization* through \"set_log <level>\" command*, where <level> is between 0 (no details) and 4 (very verbose)." << ENDL - << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL + << "You can set the level of process detailization* through \"set_log <level|categories>\" command*," << ENDL + << "where <level> is between 0 (no details) and 4 (very verbose), or custom category based levels (eg, *:WARNING)" << ENDL << ENDL - << "Note: in case you need to interrupt the process, use \"exit\" command. Otherwise, the current progress won't be saved." << ENDL - << "**********************************************************************"); + << "Use the \"help\" command to see the list of available commands." << ENDL + << "**********************************************************************" << ENDL); m_starter_message_showed = true; } m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this)); m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); + m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); m_miner.on_idle(); m_mempool.on_idle(); return true; @@ -948,19 +1110,18 @@ namespace cryptonote bool core::check_fork_time() { HardFork::State state = m_blockchain_storage.get_hard_fork_state(); + const el::Level level = el::Level::Warning; switch (state) { case HardFork::LikelyForked: - LOG_PRINT_RED_L0(ENDL - << "**********************************************************************" << ENDL - << "Last scheduled hard fork is too far in the past." << ENDL - << "We are most likely forked from the network. Daemon update needed now." << ENDL - << "**********************************************************************" << ENDL); + MCLOG_RED(level, "global", "**********************************************************************"); + MCLOG_RED(level, "global", "Last scheduled hard fork is too far in the past."); + MCLOG_RED(level, "global", "We are most likely forked from the network. Daemon update needed now."); + MCLOG_RED(level, "global", "**********************************************************************"); break; case HardFork::UpdateNeeded: - LOG_PRINT_RED_L0(ENDL - << "**********************************************************************" << ENDL - << "Last scheduled hard fork time shows a daemon update is needed now." << ENDL - << "**********************************************************************" << ENDL); + MCLOG_RED(level, "global", "**********************************************************************"); + MCLOG_RED(level, "global", "Last scheduled hard fork time shows a daemon update is needed now."); + MCLOG_RED(level, "global", "**********************************************************************"); break; default: break; @@ -968,12 +1129,102 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - void core::set_target_blockchain_height(uint64_t target_blockchain_height) + bool core::check_updates() { - if (target_blockchain_height > m_target_blockchain_height) + static const char software[] = "monero"; + static const char subdir[] = "cli"; // because it can never be simple +#ifdef BUILD_TAG + static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); +#else + static const char buildtag[] = "source"; +#endif + + if (check_updates_level == UPDATES_DISABLED) + return true; + + std::string version, hash; + MCDEBUG("updates", "Checking for a new " << software << " version for " << buildtag); + if (!tools::check_updates(software, buildtag, version, hash)) + return false; + + if (tools::vercmp(version.c_str(), MONERO_VERSION) <= 0) + return true; + + std::string url = tools::get_update_url(software, subdir, buildtag, version, true); + MCLOG_CYAN(el::Level::Info, "global", "Version " << version << " of " << software << " for " << buildtag << " is available: " << url << ", SHA256 hash " << hash); + + if (check_updates_level == UPDATES_NOTIFY) + return true; + + url = tools::get_update_url(software, subdir, buildtag, version, false); + std::string filename; + const char *slash = strrchr(url.c_str(), '/'); + if (slash) + filename = slash + 1; + else + filename = std::string(software) + "-update-" + version; + boost::filesystem::path path(epee::string_tools::get_current_module_folder()); + path /= filename; + + boost::unique_lock<boost::mutex> lock(m_update_mutex); + + if (m_update_download != 0) + { + MCDEBUG("updates", "Already downloading update"); + return true; + } + + crypto::hash file_hash; + if (!tools::sha256sum(path.string(), file_hash) || (hash != epee::string_tools::pod_to_hex(file_hash))) + { + MCDEBUG("updates", "We don't have that file already, downloading"); + m_last_update_length = 0; + m_update_download = tools::download_async(path.string(), url, [this, hash](const std::string &path, const std::string &uri, bool success) { + if (success) + { + crypto::hash file_hash; + if (!tools::sha256sum(path, file_hash)) + { + MCERROR("updates", "Failed to hash " << path); + } + if (hash != epee::string_tools::pod_to_hex(file_hash)) + { + MCERROR("updates", "Download from " << uri << " does not match the expected hash"); + } + MCLOG_CYAN(el::Level::Info, "updates", "New version downloaded to " << path); + } + else + { + MCERROR("updates", "Failed to download " << uri); + } + boost::unique_lock<boost::mutex> lock(m_update_mutex); + m_update_download = 0; + }, [this](const std::string &path, const std::string &uri, size_t length, ssize_t content_length) { + if (length >= m_last_update_length + 1024 * 1024 * 10) + { + m_last_update_length = length; + MCDEBUG("updates", "Downloaded " << length << "/" << (content_length ? std::to_string(content_length) : "unknown")); + } + return true; + }); + } + else { - m_target_blockchain_height = target_blockchain_height; + MCDEBUG("updates", "We already have " << path << " with expected hash"); } + + lock.unlock(); + + if (check_updates_level == UPDATES_DOWNLOAD) + return true; + + MCERROR("updates", "Download/update not implemented yet"); + return true; + } + //----------------------------------------------------------------------------------------------- + void core::set_target_blockchain_height(uint64_t target_blockchain_height) + { + m_target_blockchain_height = target_blockchain_height; } //----------------------------------------------------------------------------------------------- uint64_t core::get_target_blockchain_height() const @@ -981,10 +1232,13 @@ namespace cryptonote return m_target_blockchain_height; } //----------------------------------------------------------------------------------------------- + std::time_t core::get_start_time() const + { + return start_time; + } + //----------------------------------------------------------------------------------------------- void core::graceful_exit() { raise(SIGTERM); } - - std::atomic<bool> core::m_fast_exit(false); } diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 97abf3271..e56c2dcf1 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -39,11 +39,12 @@ #include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" +#include "common/download.h" #include "tx_pool.h" #include "blockchain.h" -#include "miner.h" -#include "connection_context.h" -#include "cryptonote_core/cryptonote_stat_info.h" +#include "cryptonote_basic/miner.h" +#include "cryptonote_basic/connection_context.h" +#include "cryptonote_basic/cryptonote_stat_info.h" #include "warnings.h" #include "crypto/hash.h" @@ -107,10 +108,11 @@ namespace cryptonote * @param tvc metadata about the transaction's validity * @param keeped_by_block if the transaction has been in a block * @param relayed whether or not the transaction was relayed to us + * @param do_not_relay whether to prevent the transaction from being relayed * * @return true if the transaction made it to the transaction pool, otherwise false */ - bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** * @brief handles an incoming block @@ -178,7 +180,12 @@ namespace cryptonote * * @note see Blockchain::create_block_template */ - virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + + /** + * @brief called when a transaction is relayed + */ + virtual void on_transaction_relayed(const cryptonote::blobdata& tx); /** @@ -230,29 +237,11 @@ namespace cryptonote * * Uninitializes the miner instance, transaction pool, and Blockchain * - * if m_fast_exit is set, the call to Blockchain::deinit() is not made. - * * @return true */ bool deinit(); /** - * @brief sets fast exit flag - * - * @note see deinit() - */ - static void set_fast_exit(); - - /** - * @brief gets the current state of the fast exit flag - * - * @return the fast exit flag - * - * @note see deinit() - */ - static bool get_fast_exit(); - - /** * @brief sets to drop blocks downloaded (for testing) */ void test_drop_download(); @@ -299,16 +288,23 @@ namespace cryptonote bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; /** - * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<block>&, std::list<transaction>&) const + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&, std::list<transaction>&) const * - * @note see Blockchain::get_blocks(uint64_t, size_t, std::list<block>&, std::list<transaction>&) const + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&, std::list<transaction>&) const */ - bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const; + bool get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const; /** - * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<block>&) const + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&) const * - * @note see Blockchain::get_blocks(uint64_t, size_t, std::list<block>&) const + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&) const + */ + bool get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks) const; + + /** + * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&) const + * + * @note see Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&) const */ bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const; @@ -335,6 +331,13 @@ namespace cryptonote * * @note see Blockchain::get_transactions */ + bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<cryptonote::blobdata>& txs, std::list<crypto::hash>& missed_txs) const; + + /** + * @copydoc Blockchain::get_transactions + * + * @note see Blockchain::get_transactions + */ bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs) const; /** @@ -342,7 +345,7 @@ namespace cryptonote * * @note see Blockchain::get_block_by_hash */ - bool get_block_by_hash(const crypto::hash &h, block &blk) const; + bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL) const; /** * @copydoc Blockchain::get_alternative_blocks @@ -392,6 +395,20 @@ namespace cryptonote * @note see tx_memory_pool::get_transactions */ bool get_pool_transactions(std::list<transaction>& txs) const; + + /** + * @copydoc tx_memory_pool::get_transactions + * + * @note see tx_memory_pool::get_transactions + */ + bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const; + + /** + * @copydoc tx_memory_pool::get_transaction + * + * @note see tx_memory_pool::get_transaction + */ + bool get_pool_transaction(const crypto::hash& id, transaction& tx) const; /** * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info @@ -436,11 +453,11 @@ namespace cryptonote bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; /** - * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::list<std::pair<block, std::list<transaction> > >&, uint64_t&, uint64_t&, size_t) const + * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >&, uint64_t&, uint64_t&, size_t) const * - * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::list<std::pair<block, std::list<transaction> > >&, uint64_t&, uint64_t&, size_t) const + * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::list<std::pair<cryptonote::blobdata, std::list<transaction> > >&, uint64_t&, uint64_t&, size_t) const */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; /** * @brief gets some stats about the daemon @@ -477,7 +494,7 @@ namespace cryptonote * * @note see Blockchain::get_outs */ - bool get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const; + bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; /** * @@ -566,6 +583,12 @@ namespace cryptonote uint64_t get_target_blockchain_height() const; /** + * @brief gets start_time + * + */ + std::time_t get_start_time() const; + + /** * @brief tells the Blockchain to update its checkpoints * * This function will check if enough time has passed since the last @@ -618,6 +641,20 @@ namespace cryptonote */ size_t get_block_sync_size() const { return block_sync_size; } + /** + * @brief get the sum of coinbase tx amounts between blocks + * + * @return the number of blocks to sync in one go + */ + std::pair<uint64_t, uint64_t> get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); + + /** + * @brief get whether we're on testnet or not + * + * @return are we on testnet? + */ + bool get_testnet() const { return m_testnet; }; + private: /** @@ -627,9 +664,10 @@ namespace cryptonote * @param tx_prefix_hash the transaction prefix' hash * @param blob_size the size of the transaction * @param relayed whether or not the transaction was relayed to us + * @param do_not_relay whether to prevent the transaction from being relayed * */ - bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** * @brief add a new transaction to the transaction pool @@ -640,12 +678,13 @@ namespace cryptonote * @param tvc return-by-reference metadata about the transaction's verification process * @param keeped_by_block whether or not the transaction has been in a block * @param relayed whether or not the transaction was relayed to us + * @param do_not_relay whether to prevent the transaction from being relayed * * @return true if the transaction is already in the transaction pool, * is already in a block on the Blockchain, or is successfully added * to the transaction pool */ - bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed); + bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** * @copydoc Blockchain::add_new_block @@ -728,6 +767,16 @@ namespace cryptonote bool check_tx_inputs_keyimages_diff(const transaction& tx) const; /** + * @brief verify that each input key image in a transaction is in + * the valid domain + * + * @param tx the transaction to check + * + * @return false if any key image is not in the valid domain, otherwise true + */ + bool check_tx_inputs_keyimages_domain(const transaction& tx) const; + + /** * @brief checks HardFork status and prints messages about it * * Checks the status of HardFork and logs/prints if an update to @@ -747,24 +796,11 @@ namespace cryptonote bool relay_txpool_transactions(); /** - * @brief locks a file in the BlockchainDB directory + * @brief checks DNS versions * - * @param path the directory in which to place the file - * - * @return true if lock acquired successfully, otherwise false + * @return true on success, false otherwise */ - bool lock_db_directory(const boost::filesystem::path &path); - - /** - * @brief unlocks the db directory - * - * @note see lock_db_directory() - * - * @return true - */ - bool unlock_db_directory(); - - static std::atomic<bool> m_fast_exit; //!< whether or not to deinit Blockchain on exit + bool check_updates(); bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) @@ -786,10 +822,10 @@ namespace cryptonote cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled - epee::math_helper::once_a_time_seconds<60*60*2, false> m_fork_moaner; //!< interval for checking HardFork status + epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions + epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions - friend class tx_validate_inputs; std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? uint64_t m_target_blockchain_height; //!< blockchain height target @@ -804,9 +840,22 @@ namespace cryptonote std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once - boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory - size_t block_sync_size; + + time_t start_time; + + std::unordered_set<crypto::hash> bad_semantics_txes[2]; + + enum { + UPDATES_DISABLED, + UPDATES_NOTIFY, + UPDATES_DOWNLOAD, + UPDATES_UPDATE, + } check_updates_level; + + tools::download_async_handle m_update_download; + size_t m_last_update_length; + boost::mutex m_update_mutex; }; } diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp new file mode 100644 index 000000000..26d5fb767 --- /dev/null +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -0,0 +1,499 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "include_base_utils.h" +using namespace epee; + +#include "cryptonote_tx_utils.h" +#include "cryptonote_config.h" +#include "cryptonote_basic/miner.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "ringct/rctSigs.h" + +namespace cryptonote +{ + //--------------------------------------------------------------- + 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, uint8_t hard_fork_version) { + tx.vin.clear(); + tx.vout.clear(); + tx.extra.clear(); + + keypair txkey = keypair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + if(!extra_nonce.empty()) + if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + return false; + + txin_gen in; + in.height = height; + + uint64_t block_reward; + if(!get_block_reward(median_size, current_block_size, already_generated_coins, block_reward, hard_fork_version)) + { + LOG_PRINT_L0("Block is too big"); + return false; + } + +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: reward " << block_reward << + ", fee " << fee); +#endif + block_reward += fee; + + // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and + // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the + // emission schedule + // from hard fork 4, we use a single "dusty" output. This makes the tx even smaller, + // and avoids the quantization. These outputs will be added as rct outputs with identity + // masks, to they can be used as rct inputs. + if (hard_fork_version >= 2 && hard_fork_version < 4) { + block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; + } + + std::vector<uint64_t> out_amounts; + decompose_amount_into_digits(block_reward, hard_fork_version >= 2 ? 0 : ::config::DEFAULT_DUST_THRESHOLD, + [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, + [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); + + CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); + if (height == 0 || hard_fork_version >= 4) + { + // the genesis block was not decomposed, for unknown reasons + while (max_outs < out_amounts.size()) + { + //out_amounts[out_amounts.size() - 2] += out_amounts.back(); + //out_amounts.resize(out_amounts.size() - 1); + out_amounts[1] += out_amounts[0]; + for (size_t n = 1; n < out_amounts.size(); ++n) + out_amounts[n - 1] = out_amounts[n]; + out_amounts.resize(out_amounts.size() - 1); + } + } + else + { + CHECK_AND_ASSERT_MES(max_outs >= out_amounts.size(), false, "max_out exceeded"); + } + + uint64_t summary_amounts = 0; + for (size_t no = 0; no < out_amounts.size(); no++) + { + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); + + txout_to_key tk; + tk.key = out_eph_public_key; + + tx_out out; + summary_amounts += out.amount = out_amounts[no]; + out.target = tk; + tx.vout.push_back(out); + } + + CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); + + if (hard_fork_version >= 4) + tx.version = 2; + else + tx.version = 1; + + //lock + tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.vin.push_back(in); + + tx.invalidate_hashes(); + + //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; + } + //--------------------------------------------------------------- + crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys) + { + if (destinations.empty()) + return null_pkey; + for (size_t n = 1; n < destinations.size(); ++n) + { + if (!memcmp(&destinations[n].addr, &sender_keys.m_account_address, sizeof(destinations[0].addr))) + continue; + if (destinations[n].amount == 0) + continue; + if (memcmp(&destinations[n].addr, &destinations[0].addr, sizeof(destinations[0].addr))) + return null_pkey; + } + return destinations[0].addr.m_view_public_key; + } + //--------------------------------------------------------------- + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) + { + std::vector<rct::key> amount_keys; + tx.set_null(); + amount_keys.clear(); + + tx.version = rct ? 2 : 1; + tx.unlock_time = unlock_time; + + tx.extra = extra; + keypair txkey = keypair::generate(); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); + add_tx_pub_key_to_extra(tx, txkey.pub); + tx_key = txkey.sec; + + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector<tx_extra_field> tx_extra_fields; + if (parse_tx_extra(tx.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash8 payment_id = null_hash8; + if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id); + crypto::public_key view_key_pub = get_destination_view_key_pub(destinations, sender_account_keys); + if (view_key_pub == null_pkey) + { + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_nonce)); + if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id); + } + } + } + else + { + LOG_ERROR("Failed to parse tx extra"); + return false; + } + + struct input_generation_context_data + { + keypair in_ephemeral; + }; + std::vector<input_generation_context_data> in_contexts; + + uint64_t summary_inputs_money = 0; + //fill inputs + int idx = -1; + for(const tx_source_entry& src_entr: sources) + { + ++idx; + if(src_entr.real_output >= src_entr.outputs.size()) + { + LOG_ERROR("real_output index (" << src_entr.real_output << ")bigger than output_keys.size()=" << src_entr.outputs.size()); + return false; + } + summary_inputs_money += src_entr.amount; + + //key_derivation recv_derivation; + in_contexts.push_back(input_generation_context_data()); + keypair& in_ephemeral = in_contexts.back().in_ephemeral; + crypto::key_image img; + if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img)) + return false; + + //check that derivated key is equal with real output key + if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + { + LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" + << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" + << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second) ); + LOG_ERROR("amount " << src_entr.amount << ", rct " << src_entr.rct); + LOG_ERROR("tx pubkey " << src_entr.real_out_tx_key << ", real_output_in_tx_index " << src_entr.real_output_in_tx_index); + return false; + } + + //put key image into tx input + txin_to_key input_to_key; + input_to_key.amount = src_entr.amount; + input_to_key.k_image = img; + + //fill outputs array and use relative offsets + for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) + input_to_key.key_offsets.push_back(out_entry.first); + + input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + tx.vin.push_back(input_to_key); + } + + // "Shuffle" outs + std::vector<tx_destination_entry> shuffled_dsts(destinations); + std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; } ); + + uint64_t summary_outs_money = 0; + //fill outputs + size_t output_index = 0; + for(const tx_destination_entry& dst_entr: shuffled_dsts) + { + CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")"); + + if (tx.version > 1) + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, output_index, scalar1); + amount_keys.push_back(rct::sk2rct(scalar1)); + } + r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); + + tx_out out; + out.amount = dst_entr.amount; + txout_to_key tk; + tk.key = out_eph_public_key; + out.target = tk; + tx.vout.push_back(out); + output_index++; + summary_outs_money += dst_entr.amount; + } + + //check money + if(summary_outs_money > summary_inputs_money ) + { + LOG_ERROR("Transaction inputs money ("<< summary_inputs_money << ") less than outputs money (" << summary_outs_money << ")"); + return false; + } + + // check for watch only wallet + bool zero_secret_key = true; + for (size_t i = 0; i < sizeof(sender_account_keys.m_spend_secret_key); ++i) + zero_secret_key &= (sender_account_keys.m_spend_secret_key.data[i] == 0); + if (zero_secret_key) + { + MDEBUG("Null secret key, skipping signatures"); + } + + if (tx.version == 1) + { + //generate ring signatures + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + + std::stringstream ss_ring_s; + size_t i = 0; + for(const tx_source_entry& src_entr: sources) + { + ss_ring_s << "pub_keys:" << ENDL; + std::vector<const crypto::public_key*> keys_ptrs; + std::vector<crypto::public_key> keys(src_entr.outputs.size()); + size_t ii = 0; + for(const tx_source_entry::output_entry& o: src_entr.outputs) + { + keys[ii] = rct2pk(o.second.dest); + keys_ptrs.push_back(&keys[ii]); + ss_ring_s << o.second.dest << ENDL; + ++ii; + } + + tx.signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + if (!zero_secret_key) + crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + ss_ring_s << "signatures:" << ENDL; + std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output << ENDL; + i++; + } + + MCINFO("construct_tx", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str()); + } + else + { + size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct + + // the non-simple version is slightly smaller, but assumes all real inputs + // are on the same index, so can only be used if there just one ring. + bool use_simple_rct = sources.size() > 1; + + if (!use_simple_rct) + { + // non simple ringct requires all real inputs to be at the same index for all inputs + for(const tx_source_entry& src_entr: sources) + { + if(src_entr.real_output != sources.begin()->real_output) + { + LOG_ERROR("All inputs must have the same index for non-simple ringct"); + return false; + } + } + + // enforce same mixin for all outputs + for (size_t i = 1; i < sources.size(); ++i) { + if (n_total_outs != sources[i].outputs.size()) { + LOG_ERROR("Non-simple ringct transaction has varying mixin"); + return false; + } + } + } + + uint64_t amount_in = 0, amount_out = 0; + rct::ctkeyV inSk; + // mixRing indexing is done the other way round for simple + rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); + rct::keyV destinations; + std::vector<uint64_t> inamounts, outamounts; + std::vector<unsigned int> index; + for (size_t i = 0; i < sources.size(); ++i) + { + rct::ctkey ctkey; + amount_in += sources[i].amount; + inamounts.push_back(sources[i].amount); + index.push_back(sources[i].real_output); + // inSk: (secret key, mask) + ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); + ctkey.mask = sources[i].mask; + inSk.push_back(ctkey); + // inPk: (public key, commitment) + // will be done when filling in mixRing + } + for (size_t i = 0; i < tx.vout.size(); ++i) + { + destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); + outamounts.push_back(tx.vout[i].amount); + amount_out += tx.vout[i].amount; + } + + if (use_simple_rct) + { + // mixRing indexing is done the other way round for simple + for (size_t i = 0; i < sources.size(); ++i) + { + mixRing[i].resize(sources[i].outputs.size()); + for (size_t n = 0; n < sources[i].outputs.size(); ++n) + { + mixRing[i][n] = sources[i].outputs[n].second; + } + } + } + else + { + for (size_t i = 0; i < n_total_outs; ++i) // same index assumption + { + mixRing[i].resize(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + { + mixRing[i][n] = sources[n].outputs[i].second; + } + } + } + + // fee + if (!use_simple_rct && amount_in > amount_out) + outamounts.push_back(amount_in - amount_out); + + // zero out all amounts to mask rct outputs, real amounts are now encrypted + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (sources[i].rct) + boost::get<txin_to_key>(tx.vin[i]).amount = 0; + } + for (size_t i = 0; i < tx.vout.size(); ++i) + tx.vout[i].amount = 0; + + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + rct::ctkeyV outSk; + if (use_simple_rct) + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk); + else + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk); // same index assumption + + CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); + + MCINFO("construct_tx", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL); + } + + tx.invalidate_hashes(); + + return true; + } + //--------------------------------------------------------------- + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time) + { + crypto::secret_key tx_key; + return construct_tx_and_get_tx_key(sender_account_keys, sources, destinations, extra, tx, unlock_time, tx_key); + } + //--------------------------------------------------------------- + bool generate_genesis_block( + block& bl + , std::string const & genesis_tx + , uint32_t nonce + ) + { + //genesis block + bl = boost::value_initialized<block>(); + + + account_public_address ac = boost::value_initialized<account_public_address>(); + std::vector<size_t> sz; + 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); + + std::string genesis_coinbase_tx_hex = config::GENESIS_TX; + + blobdata tx_bl; + string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); + bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; + bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; + bl.timestamp = 0; + bl.nonce = nonce; + miner::find_nonce_for_given_block(bl, 1, 0); + bl.invalidate_hashes(); + return true; + } + //--------------------------------------------------------------- +} diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h new file mode 100644 index 000000000..933070e1e --- /dev/null +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -0,0 +1,101 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "cryptonote_basic/cryptonote_format_utils.h" +#include <boost/serialization/vector.hpp> +#include <boost/serialization/utility.hpp> + +namespace cryptonote +{ + //--------------------------------------------------------------- + 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 = 999, uint8_t hard_fork_version = 1); + + struct tx_source_entry + { + typedef std::pair<uint64_t, rct::ctkey> output_entry; + + std::vector<output_entry> outputs; //index + key + optional ringct commitment + size_t real_output; //index in outputs vector of real output_entry + crypto::public_key real_out_tx_key; //incoming real tx public key + size_t real_output_in_tx_index; //index in transaction outputs vector + uint64_t amount; //money + bool rct; //true if the output is rct + rct::key mask; //ringct amount mask + + void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } + }; + + struct tx_destination_entry + { + uint64_t amount; //money + account_public_address addr; //destination address + + tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { } + tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(addr) + END_SERIALIZE() + }; + + //--------------------------------------------------------------- + crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys); + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); + + bool generate_genesis_block( + block& bl + , std::string const & genesis_tx + , uint32_t nonce + ); + +} + +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) + +namespace boost +{ + namespace serialization + { + template <class Archive> + inline void serialize(Archive &a, cryptonote::tx_source_entry &x, const boost::serialization::version_type ver) + { + a & x.outputs; + a & x.real_output; + a & x.real_out_tx_key; + a & x.real_output_in_tx_index; + a & x.amount; + a & x.rct; + a & x.mask; + } + } +} diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp deleted file mode 100644 index ec717a13d..000000000 --- a/src/cryptonote_core/miner.cpp +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include <sstream> -#include <numeric> -#include <boost/utility/value_init.hpp> -#include <boost/interprocess/detail/atomic.hpp> -#include <boost/limits.hpp> -#include <boost/foreach.hpp> -#include "misc_language.h" -#include "include_base_utils.h" -#include "cryptonote_basic_impl.h" -#include "cryptonote_format_utils.h" -#include "file_io_utils.h" -#include "common/command_line.h" -#include "string_coding.h" -#include "storages/portable_storage_template_helper.h" - -using namespace epee; - -#include "miner.h" - - -extern "C" void slow_hash_allocate_state(); -extern "C" void slow_hash_free_state(); -namespace cryptonote -{ - - namespace - { - const command_line::arg_descriptor<std::string> arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; - const command_line::arg_descriptor<std::string> arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; - const command_line::arg_descriptor<uint32_t> arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; - } - - - miner::miner(i_miner_handler* phandler):m_stop(1), - m_template(boost::value_initialized<block>()), - m_template_no(0), - m_diffic(0), - m_thread_index(0), - m_phandler(phandler), - m_height(0), - m_pausers_count(0), - m_threads_total(0), - m_starter_nonce(0), - m_last_hr_merge_time(0), - m_hashes(0), - m_do_print_hashrate(false), - m_do_mining(false), - m_current_hash_rate(0) - { - - } - //----------------------------------------------------------------------------------------------------- - miner::~miner() - { - stop(); - } - //----------------------------------------------------------------------------------------------------- - bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height) - { - CRITICAL_REGION_LOCAL(m_template_lock); - m_template = bl; - m_diffic = di; - m_height = height; - ++m_template_no; - m_starter_nonce = crypto::rand<uint32_t>(); - return true; - } - //----------------------------------------------------------------------------------------------------- - bool miner::on_block_chain_update() - { - if(!is_mining()) - return true; - - return request_block_template(); - } - //----------------------------------------------------------------------------------------------------- - bool miner::request_block_template() - { - block bl = AUTO_VAL_INIT(bl); - difficulty_type di = AUTO_VAL_INIT(di); - uint64_t height = AUTO_VAL_INIT(height); - cryptonote::blobdata extra_nonce; - if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) - { - extra_nonce = m_extra_messages[m_config.current_extra_message_index]; - } - - if(!m_phandler->get_block_template(bl, m_mine_address, di, height, extra_nonce)) - { - LOG_ERROR("Failed to get_block_template(), stopping mining"); - return false; - } - set_block_template(bl, di, height); - return true; - } - //----------------------------------------------------------------------------------------------------- - bool miner::on_idle() - { - m_update_block_template_interval.do_call([&](){ - if(is_mining())request_block_template(); - return true; - }); - - m_update_merge_hr_interval.do_call([&](){ - merge_hr(); - return true; - }); - - return true; - } - //----------------------------------------------------------------------------------------------------- - void miner::do_print_hashrate(bool do_hr) - { - m_do_print_hashrate = do_hr; - } - //----------------------------------------------------------------------------------------------------- - void miner::merge_hr() - { - if(m_last_hr_merge_time && is_mining()) - { - m_current_hash_rate = m_hashes * 1000 / ((misc_utils::get_tick_count() - m_last_hr_merge_time + 1)); - CRITICAL_REGION_LOCAL(m_last_hash_rates_lock); - m_last_hash_rates.push_back(m_current_hash_rate); - if(m_last_hash_rates.size() > 19) - m_last_hash_rates.pop_front(); - if(m_do_print_hashrate) - { - uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0); - float hr = static_cast<float>(total_hr)/static_cast<float>(m_last_hash_rates.size()); - std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << ENDL; - } - } - m_last_hr_merge_time = misc_utils::get_tick_count(); - m_hashes = 0; - } - //----------------------------------------------------------------------------------------------------- - void miner::init_options(boost::program_options::options_description& desc) - { - command_line::add_arg(desc, arg_extra_messages); - command_line::add_arg(desc, arg_start_mining); - command_line::add_arg(desc, arg_mining_threads); - } - //----------------------------------------------------------------------------------------------------- - bool miner::init(const boost::program_options::variables_map& vm, bool testnet) - { - if(command_line::has_arg(vm, arg_extra_messages)) - { - std::string buff; - bool r = file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_extra_messages), buff); - CHECK_AND_ASSERT_MES(r, false, "Failed to load file with extra messages: " << command_line::get_arg(vm, arg_extra_messages)); - std::vector<std::string> extra_vec; - boost::split(extra_vec, buff, boost::is_any_of("\n"), boost::token_compress_on ); - m_extra_messages.resize(extra_vec.size()); - for(size_t i = 0; i != extra_vec.size(); i++) - { - string_tools::trim(extra_vec[i]); - if(!extra_vec[i].size()) - continue; - std::string buff = string_encoding::base64_decode(extra_vec[i]); - if(buff != "0") - m_extra_messages[i] = buff; - } - m_config_folder_path = boost::filesystem::path(command_line::get_arg(vm, arg_extra_messages)).parent_path().string(); - m_config = AUTO_VAL_INIT(m_config); - epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); - LOG_PRINT_L0("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); - } - - if(command_line::has_arg(vm, arg_start_mining)) - { - if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining))) - { - LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); - return false; - } - m_threads_total = 1; - m_do_mining = true; - if(command_line::has_arg(vm, arg_mining_threads)) - { - m_threads_total = command_line::get_arg(vm, arg_mining_threads); - } - } - - return true; - } - //----------------------------------------------------------------------------------------------------- - bool miner::is_mining() const - { - return !m_stop; - } - //----------------------------------------------------------------------------------------------------- - const account_public_address& miner::get_mining_address() const - { - return m_mine_address; - } - //----------------------------------------------------------------------------------------------------- - uint32_t miner::get_threads_count() const { - return m_threads_total; - } - //----------------------------------------------------------------------------------------------------- - bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs) - { - m_mine_address = adr; - m_threads_total = static_cast<uint32_t>(threads_count); - m_starter_nonce = crypto::rand<uint32_t>(); - CRITICAL_REGION_LOCAL(m_threads_lock); - if(is_mining()) - { - LOG_ERROR("Starting miner but it's already started"); - return false; - } - - if(!m_threads.empty()) - { - LOG_ERROR("Unable to start miner because there are active mining threads"); - return false; - } - - if(!m_template_no) - request_block_template();//lets update block template - - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); - boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); - - for(size_t i = 0; i != threads_count; i++) - { - m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this))); - } - - LOG_PRINT_L0("Mining has started with " << threads_count << " threads, good luck!" ); - return true; - } - //----------------------------------------------------------------------------------------------------- - uint64_t miner::get_speed() const - { - if(is_mining()) { - return m_current_hash_rate; - } - else { - return 0; - } - } - //----------------------------------------------------------------------------------------------------- - void miner::send_stop_signal() - { - boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); - } - //----------------------------------------------------------------------------------------------------- - bool miner::stop() - { - if (!is_mining()) - return true; - - send_stop_signal(); - CRITICAL_REGION_LOCAL(m_threads_lock); - - BOOST_FOREACH(boost::thread& th, m_threads) - th.join(); - - LOG_PRINT_L0("Mining has been stopped, " << m_threads.size() << " finished" ); - m_threads.clear(); - return true; - } - //----------------------------------------------------------------------------------------------------- - bool miner::find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height) - { - for(; bl.nonce != std::numeric_limits<uint32_t>::max(); bl.nonce++) - { - crypto::hash h; - get_block_longhash(bl, h, height); - - if(check_hash(h, diffic)) - { - return true; - } - } - return false; - } - //----------------------------------------------------------------------------------------------------- - void miner::on_synchronized() - { - if(m_do_mining) - { - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - - start(m_mine_address, m_threads_total, attrs); - } - } - //----------------------------------------------------------------------------------------------------- - void miner::pause() - { - CRITICAL_REGION_LOCAL(m_miners_count_lock); - ++m_pausers_count; - if(m_pausers_count == 1 && is_mining()) - LOG_PRINT_L2("MINING PAUSED"); - } - //----------------------------------------------------------------------------------------------------- - void miner::resume() - { - CRITICAL_REGION_LOCAL(m_miners_count_lock); - --m_pausers_count; - if(m_pausers_count < 0) - { - m_pausers_count = 0; - LOG_PRINT_RED_L0("Unexpected miner::resume() called"); - } - if(!m_pausers_count && is_mining()) - LOG_PRINT_L2("MINING RESUMED"); - } - //----------------------------------------------------------------------------------------------------- - bool miner::worker_thread() - { - uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); - LOG_PRINT_L0("Miner thread was started ["<< th_local_index << "]"); - log_space::log_singletone::set_thread_log_prefix(std::string("[miner ") + std::to_string(th_local_index) + "]"); - uint32_t nonce = m_starter_nonce + th_local_index; - uint64_t height = 0; - difficulty_type local_diff = 0; - uint32_t local_template_ver = 0; - block b; - slow_hash_allocate_state(); - while(!m_stop) - { - if(m_pausers_count)//anti split workaround - { - misc_utils::sleep_no_w(100); - continue; - } - - if(local_template_ver != m_template_no) - { - CRITICAL_REGION_BEGIN(m_template_lock); - b = m_template; - local_diff = m_diffic; - height = m_height; - CRITICAL_REGION_END(); - local_template_ver = m_template_no; - nonce = m_starter_nonce + th_local_index; - } - - if(!local_template_ver)//no any set_block_template call - { - LOG_PRINT_L2("Block template not set yet"); - epee::misc_utils::sleep_no_w(1000); - continue; - } - - b.nonce = nonce; - crypto::hash h; - get_block_longhash(b, h, height); - - if(check_hash(h, local_diff)) - { - //we lucky! - ++m_config.current_extra_message_index; - LOG_PRINT_GREEN("Found block for difficulty: " << local_diff, LOG_LEVEL_0); - if(!m_phandler->handle_block_found(b)) - { - --m_config.current_extra_message_index; - }else - { - //success update, lets update config - if (!m_config_folder_path.empty()) - epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); - } - } - nonce+=m_threads_total; - ++m_hashes; - } - slow_hash_free_state(); - LOG_PRINT_L0("Miner thread stopped ["<< th_local_index << "]"); - return true; - } - //----------------------------------------------------------------------------------------------------- -} - diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 46fab4dcf..f78f673c7 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,16 +34,20 @@ #include <vector> #include "tx_pool.h" -#include "cryptonote_format_utils.h" -#include "cryptonote_boost_serialization.h" +#include "cryptonote_tx_utils.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_config.h" #include "blockchain.h" #include "common/boost_serialization_helper.h" #include "common/int-util.h" #include "misc_language.h" #include "warnings.h" +#include "common/perf_timer.h" #include "crypto/hash.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "txpool" + DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated namespace cryptonote @@ -55,10 +59,9 @@ namespace cryptonote // codebase. As it stands, it is at best nontrivial to test // whether or not changing these parameters (or adding new) // will work correctly. - size_t const TRANSACTION_SIZE_LIMIT_V1 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); - size_t const TRANSACTION_SIZE_LIMIT_V2 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); time_t const MIN_RELAY_TIME = (60 * 5); // only start re-relaying transactions after that many seconds time_t const MAX_RELAY_TIME = (60 * 60 * 4); // at most that many seconds between resends + float const ACCEPT_THRESHOLD = 1.0f; // a kind of increasing backoff within min/max bounds time_t get_relay_delay(time_t now, time_t received) @@ -68,6 +71,16 @@ namespace cryptonote d = MAX_RELAY_TIME; return d; } + + uint64_t template_accept_threshold(uint64_t amount) + { + return amount * ACCEPT_THRESHOLD; + } + + uint64_t get_transaction_size_limit(uint8_t version) + { + return get_min_block_size(version) * 125 / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + } } //--------------------------------------------------------------------------------- //--------------------------------------------------------------------------------- @@ -76,8 +89,9 @@ namespace cryptonote } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version) + bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version) { + PERF_TIMER(add_tx); if (tx.version == 0) { // v0 never accepted @@ -131,18 +145,14 @@ namespace cryptonote fee = tx.rct_signatures.txnFee; } - uint64_t needed_fee = blob_size / 1024; - needed_fee += (blob_size % 1024) ? 1 : 0; - needed_fee *= FEE_PER_KB; - if (!kept_by_block && fee < needed_fee) + if (!kept_by_block && !m_blockchain.check_fee(blob_size, fee)) { - LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); tvc.m_verifivation_failed = true; tvc.m_fee_too_low = true; return false; } - size_t tx_size_limit = (version < 2 ? TRANSACTION_SIZE_LIMIT_V1 : TRANSACTION_SIZE_LIMIT_V2); + size_t tx_size_limit = get_transaction_size_limit(version); if (!kept_by_block && blob_size >= tx_size_limit) { LOG_PRINT_L1("transaction is too big: " << blob_size << " bytes, maximum size: " << tx_size_limit); @@ -173,6 +183,8 @@ namespace cryptonote return false; } + time_t receive_time = time(nullptr); + crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; tx_details txd; @@ -191,10 +203,13 @@ namespace cryptonote txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; + txd_p.first->second.last_failed_height = 0; + txd_p.first->second.last_failed_id = null_hash; txd_p.first->second.kept_by_block = kept_by_block; - txd_p.first->second.receive_time = time(nullptr); + txd_p.first->second.receive_time = receive_time; txd_p.first->second.last_relayed_time = time(NULL); txd_p.first->second.relayed = relayed; + txd_p.first->second.do_not_relay = do_not_relay; tvc.m_verifivation_impossible = true; tvc.m_added_to_pool = true; }else @@ -207,7 +222,7 @@ namespace cryptonote { //update transactions container auto txd_p = m_transactions.insert(transactions_container::value_type(id, txd)); - CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); + CHECK_AND_ASSERT_MES(txd_p.second, false, "internal error: transaction already exists at inserting in memorypool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.fee = fee; @@ -215,19 +230,20 @@ namespace cryptonote txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; txd_p.first->second.last_failed_id = null_hash; - txd_p.first->second.receive_time = time(nullptr); + txd_p.first->second.receive_time = receive_time; txd_p.first->second.last_relayed_time = time(NULL); txd_p.first->second.relayed = relayed; + txd_p.first->second.do_not_relay = do_not_relay; tvc.m_added_to_pool = true; - if(txd_p.first->second.fee > 0) + if(txd_p.first->second.fee > 0 && !do_not_relay) tvc.m_should_be_relayed = true; } // assume failure during verification steps until success is certain tvc.m_verifivation_failed = true; - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; @@ -240,17 +256,18 @@ namespace cryptonote tvc.m_verifivation_failed = false; - m_txs_by_fee.emplace((double)blob_size / fee, id); + m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)blob_size, receive_time), id); + MINFO("Transaction added to pool: txid " << id << " bytes: " << blob_size << " fee/byte: " << (fee / (double)blob_size)); return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, uint8_t version) + bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay, uint8_t version) { crypto::hash h = null_hash; size_t blob_size = 0; get_transaction_hash(tx, h, blob_size); - return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, version); + return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version); } //--------------------------------------------------------------------------------- //FIXME: Can return early before removal of all of the key images. @@ -262,7 +279,7 @@ namespace cryptonote // ND: Speedup // 1. Move transaction hash calcuation outside of loop. ._. crypto::hash actual_hash = get_transaction_hash(tx); - BOOST_FOREACH(const txin_v& vi, tx.vin) + for(const txin_v& vi: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(vi, const txin_to_key, txin, false); auto it = m_spent_key_images.find(txin.k_image); @@ -286,7 +303,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed) + bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay) { CRITICAL_REGION_LOCAL(m_transactions_lock); auto it = m_transactions.find(id); @@ -295,16 +312,17 @@ namespace cryptonote auto sorted_it = find_tx_in_sorted_container(id); - if (sorted_it == m_txs_by_fee.end()) + if (sorted_it == m_txs_by_fee_and_receive_time.end()) return false; tx = it->second.tx; blob_size = it->second.blob_size; fee = it->second.fee; relayed = it->second.relayed; + do_not_relay = it->second.do_not_relay; remove_transaction_keyimages(it->second.tx); m_transactions.erase(it); - m_txs_by_fee.erase(sorted_it); + m_txs_by_fee_and_receive_time.erase(sorted_it); return true; } //--------------------------------------------------------------------------------- @@ -315,7 +333,7 @@ namespace cryptonote //--------------------------------------------------------------------------------- sorted_tx_container::iterator tx_memory_pool::find_tx_in_sorted_container(const crypto::hash& id) const { - return std::find_if( m_txs_by_fee.begin(), m_txs_by_fee.end() + return std::find_if( m_txs_by_fee_and_receive_time.begin(), m_txs_by_fee_and_receive_time.end() , [&](const sorted_tx_container::value_type& a){ return a.second == id; } @@ -336,13 +354,13 @@ namespace cryptonote LOG_PRINT_L1("Tx " << it->first << " removed from tx pool due to outdated, age: " << tx_age ); remove_transaction_keyimages(it->second.tx); auto sorted_it = find_tx_in_sorted_container(it->first); - if (sorted_it == m_txs_by_fee.end()) + if (sorted_it == m_txs_by_fee_and_receive_time.end()) { LOG_PRINT_L1("Removing tx " << it->first << " from tx pool, but it was not found in the sorted txs container!"); } else { - m_txs_by_fee.erase(sorted_it); + m_txs_by_fee_and_receive_time.erase(sorted_it); } m_timed_out_transactions.insert(it->first); auto pit = it++; @@ -361,7 +379,7 @@ namespace cryptonote for(auto it = m_transactions.begin(); it!= m_transactions.end();) { // 0 fee transactions are never relayed - if(it->second.fee > 0 && now - it->second.last_relayed_time > get_relay_delay(now, it->second.receive_time)) + if(it->second.fee > 0 && !it->second.do_not_relay && now - it->second.last_relayed_time > get_relay_delay(now, it->second.receive_time)) { // if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem // mentioned by smooth where nodes would flush txes at slightly different times, causing @@ -385,7 +403,10 @@ namespace cryptonote { auto i = m_transactions.find(it->first); if (i != m_transactions.end()) + { + i->second.relayed = true; i->second.last_relayed_time = now; + } } } //--------------------------------------------------------------------------------- @@ -398,10 +419,17 @@ namespace cryptonote void tx_memory_pool::get_transactions(std::list<transaction>& txs) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(const auto& tx_vt, m_transactions) + for(const auto& tx_vt: m_transactions) txs.push_back(tx_vt.second.tx); } //------------------------------------------------------------------ + void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + for(const auto& tx_vt: m_transactions) + txs.push_back(get_transaction_hash(tx_vt.second.tx)); + } + //------------------------------------------------------------------ //TODO: investigate whether boolean return is appropriate bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const { @@ -420,6 +448,9 @@ namespace cryptonote txi.last_failed_height = txd.last_failed_height; txi.last_failed_id_hash = epee::string_tools::pod_to_hex(txd.last_failed_id); txi.receive_time = txd.receive_time; + txi.relayed = txd.relayed; + txi.last_relayed_time = txd.last_relayed_time; + txi.do_not_relay = txd.do_not_relay; tx_infos.push_back(txi); } @@ -468,7 +499,7 @@ namespace cryptonote bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - BOOST_FOREACH(const auto& in, tx.vin) + for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail if(have_tx_keyimg_as_spent(tokey_in.k_image)) @@ -582,7 +613,7 @@ namespace cryptonote } //--------------------------------------------------------------------------------- //TODO: investigate whether boolean return is appropriate - 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) + 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, uint64_t &expected_reward, uint8_t version) { // Warning: This function takes already_generated_ // coins as an argument and appears to do nothing @@ -590,47 +621,77 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); + uint64_t best_coinbase = 0, coinbase = 0; total_size = 0; fee = 0; + + //baseline empty block + get_block_reward(median_size, total_size, already_generated_coins, best_coinbase, version); - // Maximum block size is 130% of the median block size. This gives a - // little extra headroom for the max size transaction. - size_t max_total_size = (130 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + + size_t max_total_size_pre_v5 = (130 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_size_v5 = 2 * median_size - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_size = version >= 5 ? max_total_size_v5 : max_total_size_pre_v5; std::unordered_set<crypto::key_image> k_images; - auto sorted_it = m_txs_by_fee.begin(); - while (sorted_it != m_txs_by_fee.end()) + LOG_PRINT_L2("Filling block template, median size " << median_size << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); + auto sorted_it = m_txs_by_fee_and_receive_time.begin(); + while (sorted_it != m_txs_by_fee_and_receive_time.end()) { auto tx_it = m_transactions.find(sorted_it->second); + LOG_PRINT_L2("Considering " << tx_it->first << ", size " << tx_it->second.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase)); // Can not exceed maximum block size if (max_total_size < total_size + tx_it->second.blob_size) { + LOG_PRINT_L2(" would exceed maximum block size"); sorted_it++; continue; } - // If adding this tx will make the block size - // greater than CRYPTONOTE_GETBLOCKTEMPLATE_MAX - // _BLOCK_SIZE bytes, reject the tx; this will - // keep block sizes from becoming too unwieldly - // to propagate at 60s block times. - if ( (total_size + tx_it->second.blob_size) > CRYPTONOTE_GETBLOCKTEMPLATE_MAX_BLOCK_SIZE ) + // start using the optimal filling algorithm from v5 + if (version >= 5) { - sorted_it++; - continue; + // If we're getting lower coinbase tx, + // stop including more tx + uint64_t block_reward; + if(!get_block_reward(median_size, total_size + tx_it->second.blob_size, already_generated_coins, block_reward, version)) + { + LOG_PRINT_L2(" would exceed maximum block size"); + sorted_it++; + continue; + } + coinbase = block_reward + fee + tx_it->second.fee; + if (coinbase < template_accept_threshold(best_coinbase)) + { + LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase)); + sorted_it++; + continue; + } + } + else + { + // If we've exceeded the penalty free size, + // stop including more tx + if (total_size > median_size) + { + LOG_PRINT_L2(" would exceed median block size"); + break; + } } - - // If we've exceeded the penalty free size, - // stop including more tx - if (total_size > median_size) - break; // Skip transactions that are not ready to be // included into the blockchain or that are // missing key images - if (!is_transaction_ready_to_go(tx_it->second) || have_key_images(k_images, tx_it->second.tx)) + if (!is_transaction_ready_to_go(tx_it->second)) { + LOG_PRINT_L2(" not ready to go"); + sorted_it++; + continue; + } + if (have_key_images(k_images, tx_it->second.tx)) + { + LOG_PRINT_L2(" key images already seen"); sorted_it++; continue; } @@ -638,10 +699,16 @@ namespace cryptonote bl.tx_hashes.push_back(tx_it->first); total_size += tx_it->second.blob_size; fee += tx_it->second.fee; + best_coinbase = coinbase; append_key_images(k_images, tx_it->second.tx); sorted_it++; + LOG_PRINT_L2(" added, new block size " << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase)); } + expected_reward = best_coinbase; + LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, size " + << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase) + << " (including " << print_money(fee) << " in fees)"); return true; } //--------------------------------------------------------------------------------- @@ -649,19 +716,28 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); size_t n_removed = 0; - size_t tx_size_limit = (version < 2 ? TRANSACTION_SIZE_LIMIT_V1 : TRANSACTION_SIZE_LIMIT_V2); + size_t tx_size_limit = get_transaction_size_limit(version); for (auto it = m_transactions.begin(); it != m_transactions.end(); ) { + bool remove = false; + const crypto::hash &txid = get_transaction_hash(it->second.tx); if (it->second.blob_size >= tx_size_limit) { - LOG_PRINT_L1("Transaction " << get_transaction_hash(it->second.tx) << " is too big (" << it->second.blob_size << " bytes), removing it from pool"); + LOG_PRINT_L1("Transaction " << txid << " is too big (" << it->second.blob_size << " bytes), removing it from pool"); + remove = true; + } + else if (m_blockchain.have_tx(txid)) { + LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool"); + remove = true; + } + if (remove) { remove_transaction_keyimages(it->second.tx); - auto sorted_it = find_tx_in_sorted_container(it->first); - if (sorted_it == m_txs_by_fee.end()) + auto sorted_it = find_tx_in_sorted_container(txid); + if (sorted_it == m_txs_by_fee_and_receive_time.end()) { - LOG_PRINT_L1("Removing tx " << it->first << " from tx pool, but it was not found in the sorted txs container!"); + LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); } else { - m_txs_by_fee.erase(sorted_it); + m_txs_by_fee_and_receive_time.erase(sorted_it); } auto pit = it++; m_transactions.erase(pit); @@ -689,17 +765,17 @@ namespace cryptonote bool res = tools::unserialize_obj_from_file(*this, state_file_path); if(!res) { - LOG_PRINT_L1("Failed to load memory pool from file " << state_file_path); + LOG_ERROR("Failed to load memory pool from file " << state_file_path); m_transactions.clear(); - m_txs_by_fee.clear(); + m_txs_by_fee_and_receive_time.clear(); m_spent_key_images.clear(); } // no need to store queue of sorted transactions, as it's easy to generate. for (const auto& tx : m_transactions) { - m_txs_by_fee.emplace((double)tx.second.blob_size / tx.second.fee, tx.first); + m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(tx.second.fee / (double)tx.second.blob_size, tx.second.receive_time), tx.first); } // Ignore deserialization error @@ -710,12 +786,17 @@ namespace cryptonote //TODO: investigate whether only ever returning true is correct bool tx_memory_pool::deinit() { + LOG_PRINT_L1("Received signal to deactivate memory pool store"); + if (m_config_folder.empty()) + { + LOG_PRINT_L1("Memory pool store already empty"); return true; + } if (!tools::create_directories_if_necessary(m_config_folder)) { - LOG_PRINT_L1("Failed to create data directory: " << m_config_folder); + LOG_ERROR("Failed to create memory pool data directory: " << m_config_folder); return false; } @@ -723,8 +804,14 @@ namespace cryptonote bool res = tools::serialize_obj_to_file(*this, state_file_path); if(!res) { - LOG_PRINT_L1("Failed to serialize memory pool to file " << state_file_path); + LOG_ERROR("Failed to serialize memory pool to file " << state_file_path); + return false; } - return true; + else + { + LOG_PRINT_L1("Memory pool store deactivated successfully"); + return true; + } + } } diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 794b86719..f68bc0bb9 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -41,8 +41,8 @@ #include "string_tools.h" #include "syncobj.h" #include "math_helper.h" -#include "cryptonote_basic_impl.h" -#include "verification_context.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/verification_context.h" #include "crypto/hash.h" #include "rpc/core_rpc_server_commands_defs.h" @@ -54,23 +54,25 @@ namespace cryptonote /************************************************************************/ //! pair of <transaction fee, transaction hash> for organization - typedef std::pair<double, crypto::hash> tx_by_fee_entry; + typedef std::pair<std::pair<double, std::time_t>, crypto::hash> tx_by_fee_and_receive_time_entry; class txCompare { public: - bool operator()(const tx_by_fee_entry& a, const tx_by_fee_entry& b) + bool operator()(const tx_by_fee_and_receive_time_entry& a, const tx_by_fee_and_receive_time_entry& b) { // sort by greatest first, not least - if (a.first > b.first) return true; - else if (a.first < b.first) return false; + if (a.first.first > b.first.first) return true; + else if (a.first.first < b.first.first) return false; + else if (a.first.second < b.first.second) return true; + else if (a.first.second > b.first.second) return false; else if (a.second != b.second) return true; else return false; } }; //! container for sorting transactions by fee per unit size - typedef std::set<tx_by_fee_entry, txCompare> sorted_tx_container; + typedef std::set<tx_by_fee_and_receive_time_entry, txCompare> sorted_tx_container; /** * @brief Transaction pool, handles transactions which are not part of a block @@ -103,7 +105,7 @@ namespace cryptonote * @param id the transaction's hash * @param blob_size the transaction's size */ - bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version); + bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version); /** * @brief add a transaction to the transaction pool @@ -117,11 +119,12 @@ namespace cryptonote * @param tvc return-by-reference status about the transaction verification * @param kept_by_block has this transaction been in a block? * @param relayed was this transaction from the network or a local client? + * @param do_not_relay to avoid relaying the transaction to the network * @param version the version used to create the transaction * * @return true if the transaction passes validations, otherwise false */ - bool add_tx(const transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version); + bool add_tx(const transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version); /** * @brief takes a transaction with the given hash from the pool @@ -131,10 +134,11 @@ namespace cryptonote * @param blob_size return-by-reference the transaction's size * @param fee the transaction fee * @param relayed return-by-reference was transaction relayed to us by the network? + * @param do_not_relay return-by-reference is transaction not to be relayed to the network? * * @return true unless the transaction cannot be found in the pool */ - bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed); + bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay); /** * @brief checks if the pool has a transaction with the given hash @@ -216,10 +220,12 @@ namespace cryptonote * @param already_generated_coins the current total number of coins "minted" * @param total_size return-by-reference the total size of the new block * @param fee return-by-reference the total of fees from the included transactions + * @param expected_reward return-by-reference the total reward awarded to the miner finding this block, including transaction fees + * @param version hard fork version to use for consensus rules * * @return true */ - bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, 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, uint64_t &expected_reward, uint8_t version); /** * @brief get a list of all transactions in the pool @@ -229,6 +235,13 @@ namespace cryptonote void get_transactions(std::list<transaction>& txs) const; /** + * @brief get a list of all transaction hashes in the pool + * + * @param txs return-by-reference the list of transactions + */ + void get_transaction_hashes(std::vector<crypto::hash>& txs) const; + + /** * @brief get information about all transactions and key images in the pool * * see documentation on tx_info and spent_key_image_info for more details @@ -302,7 +315,7 @@ namespace cryptonote #define CURRENT_MEMPOOL_ARCHIVE_VER 11 -#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 11 +#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 12 /** * @brief serialize the transaction pool to/from disk @@ -361,6 +374,7 @@ namespace cryptonote time_t last_relayed_time; //!< the last time the transaction was relayed to the network bool relayed; //!< whether or not the transaction has been relayed to the network + bool do_not_relay; //!< to avoid relay this transaction to the network }; private: @@ -472,7 +486,8 @@ private: epee::math_helper::once_a_time_seconds<30> m_remove_stuck_tx_interval; //TODO: look into doing this better - sorted_tx_container m_txs_by_fee; //!< container for transactions organized by fee per size + //!< container for transactions organized by fee per size and receive time + sorted_tx_container m_txs_by_fee_and_receive_time; /** * @brief get an iterator to a transaction in the sorted container @@ -514,6 +529,9 @@ namespace boost if (version < 11) return; ar & td.kept_by_block; + if (version < 12) + return; + ar & td.do_not_relay; } } } diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt index 21925b60e..4ce380a48 100644 --- a/src/cryptonote_protocol/CMakeLists.txt +++ b/src/cryptonote_protocol/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -27,14 +27,16 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmake_minimum_required (VERSION 2.6) -project (bitmonero CXX) +project (monero CXX) file(GLOB CRYPTONOTE_PROTOCOL *) source_group(cryptonote_protocol FILES ${CRYPTONOTE_PROTOCOL}) -#bitmonero_private_headers(cryptonote_protocol ${CRYPTONOTE_PROTOCOL}) -bitmonero_add_library(cryptonote_protocol ${CRYPTONOTE_PROTOCOL}) +#monero_private_headers(cryptonote_protocol ${CRYPTONOTE_PROTOCOL}) +monero_add_library(cryptonote_protocol ${CRYPTONOTE_PROTOCOL}) target_link_libraries(cryptonote_protocol + PUBLIC + p2p PRIVATE ${EXTRA_LIBRARIES}) add_dependencies(cryptonote_protocol diff --git a/src/cryptonote_protocol/blobdatatype.h b/src/cryptonote_protocol/blobdatatype.h index 17285ee1c..2d12a84af 100644 --- a/src/cryptonote_protocol/blobdatatype.h +++ b/src/cryptonote_protocol/blobdatatype.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index edcf2af8e..fd5b980b8 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,7 +32,7 @@ #include <list> #include "serialization/keyvalue_serialization.h" -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_protocol/blobdatatype.h" namespace cryptonote { @@ -69,6 +69,8 @@ namespace cryptonote uint64_t avg_upload; uint64_t current_upload; + + uint32_t support_flags; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(incoming) @@ -87,6 +89,7 @@ namespace cryptonote KV_SERIALIZE(current_download) KV_SERIALIZE(avg_upload) KV_SERIALIZE(current_upload) + KV_SERIALIZE(support_flags) END_KV_SERIALIZE_MAP() }; @@ -223,5 +226,49 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + /************************************************************************/ + /* */ + /************************************************************************/ + struct NOTIFY_NEW_FLUFFY_BLOCK + { + const static int ID = BC_COMMANDS_POOL_BASE + 8; + + struct request + { + block_complete_entry b; + uint64_t current_blockchain_height; + uint32_t hop; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(b) + KV_SERIALIZE(current_blockchain_height) + KV_SERIALIZE(hop) + END_KV_SERIALIZE_MAP() + }; + }; + + /************************************************************************/ + /* */ + /************************************************************************/ + struct NOTIFY_REQUEST_FLUFFY_MISSING_TX + { + const static int ID = BC_COMMANDS_POOL_BASE + 9; + struct request + { + crypto::hash block_hash; + uint64_t current_blockchain_height; + std::vector<size_t> missing_tx_indices; + uint32_t hop; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB(block_hash) + KV_SERIALIZE(current_blockchain_height) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missing_tx_indices) + KV_SERIALIZE(hop) + END_KV_SERIALIZE_MAP() + }; + }; + } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp index 9d9ab3321..e31276031 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief This is the place to implement our handlers for protocol network actions, e.g. for ratelimit for download-requests -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -52,7 +52,6 @@ #include "../../contrib/epee/include/net/net_utils_base.h" #include "../../contrib/epee/include/misc_log_ex.h" #include <boost/lambda/bind.hpp> -#include <boost/foreach.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> #include <boost/chrono.hpp> @@ -73,11 +72,11 @@ #include "../../src/cryptonote_protocol/cryptonote_protocol_handler.h" #include "../../src/p2p/network_throttle.hpp" -#include "../../contrib/otshell_utils/utils.hpp" -using namespace nOT::nUtils; - #include "../../../src/cryptonote_core/cryptonote_core.h" // e.g. for the send_stop_signal() +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.cn" + // ################################################################################################ // ################################################################################################ // the "header part". Not separated out for .hpp because point of this modification is @@ -122,25 +121,24 @@ cryptonote_protocol_handler_base::~cryptonote_protocol_handler_base() { void cryptonote_protocol_handler_base::handler_request_blocks_history(std::list<crypto::hash>& ids) { using namespace epee::net_utils; - LOG_PRINT_L1("### ~~~RRRR~~~~ ### sending request (type 2), limit = " << ids.size()); - LOG_PRINT_RED("RATE LIMIT NOT IMPLEMENTED HERE YET (download at unlimited speed?)" , LOG_LEVEL_1); - _note_c("net/req2", "### ~~~RRRR~~~~ ### sending request (type 2), limit = " << ids.size()); + MDEBUG("### ~~~RRRR~~~~ ### sending request (type 2), limit = " << ids.size()); + MDEBUG("RATE LIMIT NOT IMPLEMENTED HERE YET (download at unlimited speed?)"); // TODO } -void cryptonote_protocol_handler_base::handler_response_blocks_now(size_t packet_size) { _scope_dbg1(""); +void cryptonote_protocol_handler_base::handler_response_blocks_now(size_t packet_size) { using namespace epee::net_utils; double delay=0; // will be calculated - _dbg1("Packet size: " << packet_size); + MDEBUG("Packet size: " << packet_size); do { // rate limiting //XXX /*if (::cryptonote::core::get_is_stopping()) { - _dbg1("We are stopping - so abort sleep"); + MDEBUG("We are stopping - so abort sleep"); return; }*/ /*if (m_was_shutdown) { - _dbg2_c("net/netuse/sleep","m_was_shutdown - so abort sleep"); + MDEBUG("m_was_shutdown - so abort sleep"); return; }*/ @@ -155,9 +153,7 @@ void cryptonote_protocol_handler_base::handler_response_blocks_now(size_t packet if (delay > 0) { //delay += rand2*0.1; long int ms = (long int)(delay * 1000); - _info_c("net/sleep", "Sleeping in " << __FUNCTION__ << " for " << ms << " ms before packet_size="<<packet_size); // XXX debug sleep - _dbg1_c("net/sleep/", "sleep in sleep_before_packet"); - _dbg2("Sleep for " << ms); + MDEBUG("Sleeping for " << ms << " ms before packet_size="<<packet_size); // XXX debug sleep boost::this_thread::sleep(boost::posix_time::milliseconds( ms ) ); // TODO randomize sleeps } } while(delay > 0); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 1c92958c9..9d8bc43c2 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the orginal cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -42,9 +42,9 @@ #include "warnings.h" #include "cryptonote_protocol_defs.h" #include "cryptonote_protocol_handler_common.h" -#include "cryptonote_core/connection_context.h" -#include "cryptonote_core/cryptonote_stat_info.h" -#include "cryptonote_core/verification_context.h" +#include "cryptonote_basic/connection_context.h" +#include "cryptonote_basic/cryptonote_stat_info.h" +#include "cryptonote_basic/verification_context.h" // #include <netinet/in.h> #include <boost/circular_buffer.hpp> @@ -69,8 +69,6 @@ namespace cryptonote virtual double get_avg_block_size() = 0; virtual double estimate_one_block_size() noexcept; // for estimating size of blocks to download - - virtual std::ofstream& get_logreq() const =0; }; template<class t_core> @@ -91,6 +89,8 @@ namespace cryptonote HANDLE_NOTIFY_T2(NOTIFY_RESPONSE_GET_OBJECTS, &cryptonote_protocol_handler::handle_response_get_objects) HANDLE_NOTIFY_T2(NOTIFY_REQUEST_CHAIN, &cryptonote_protocol_handler::handle_request_chain) HANDLE_NOTIFY_T2(NOTIFY_RESPONSE_CHAIN_ENTRY, &cryptonote_protocol_handler::handle_response_chain_entry) + HANDLE_NOTIFY_T2(NOTIFY_NEW_FLUFFY_BLOCK, &cryptonote_protocol_handler::handle_notify_new_fluffy_block) + HANDLE_NOTIFY_T2(NOTIFY_REQUEST_FLUFFY_MISSING_TX, &cryptonote_protocol_handler::handle_request_fluffy_missing_tx) END_INVOKE_MAP2() bool on_idle(); @@ -107,6 +107,7 @@ namespace cryptonote bool is_synchronized(){return m_synchronized;} void log_connections(); std::list<connection_info> get_connections(); + void stop(); private: //----------------- commands handlers ---------------------------------------------- int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context); @@ -115,8 +116,9 @@ namespace cryptonote int handle_response_get_objects(int command, NOTIFY_RESPONSE_GET_OBJECTS::request& arg, cryptonote_connection_context& context); int handle_request_chain(int command, NOTIFY_REQUEST_CHAIN::request& arg, cryptonote_connection_context& context); int handle_response_chain_entry(int command, NOTIFY_RESPONSE_CHAIN_ENTRY::request& arg, cryptonote_connection_context& context); - - + int handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context); + int handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context); + //----------------- i_bc_protocol_layout --------------------------------------- virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context); virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context); @@ -132,32 +134,31 @@ namespace cryptonote std::atomic<uint32_t> m_syncronized_connections_count; std::atomic<bool> m_synchronized; bool m_one_request = true; + std::atomic<bool> m_stopping; + epee::critical_section m_sync_lock; - // static std::ofstream m_logreq; boost::mutex m_buffer_mutex; double get_avg_block_size(); boost::circular_buffer<size_t> m_avg_buffer = boost::circular_buffer<size_t>(10); - template<class t_parametr> - bool post_notify(typename t_parametr::request& arg, cryptonote_connection_context& context) + template<class t_parameter> + bool post_notify(typename t_parameter::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(context) << "] post " << typeid(t_parametr).name() << " -->"); + LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(context) << "] post " << typeid(t_parameter).name() << " -->"); std::string blob; epee::serialization::store_t_to_binary(arg, blob); //handler_response_blocks_now(blob.size()); // XXX - return m_p2p->invoke_notify_to_peer(t_parametr::ID, blob, context); + return m_p2p->invoke_notify_to_peer(t_parameter::ID, blob, context); } - template<class t_parametr> - bool relay_post_notify(typename t_parametr::request& arg, cryptonote_connection_context& exlude_context) + template<class t_parameter> + bool relay_post_notify(typename t_parameter::request& arg, cryptonote_connection_context& exclude_context) { - LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(exlude_context) << "] post relay " << typeid(t_parametr).name() << " -->"); + LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << typeid(t_parameter).name() << " -->"); std::string arg_buff; epee::serialization::store_t_to_binary(arg, arg_buff); - return m_p2p->relay_notify_to_all(t_parametr::ID, arg_buff, exlude_context); + return m_p2p->relay_notify_to_all(t_parameter::ID, arg_buff, exclude_context); } - - virtual std::ofstream& get_logreq() const ; }; } // namespace diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 6dfc9fbc5..79578a34e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the orginal cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -37,20 +37,19 @@ #include <boost/interprocess/detail/atomic.hpp> #include <list> +#include <unordered_map> -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "profile_tools.h" -#include "../../contrib/otshell_utils/utils.hpp" #include "../../src/p2p/network_throttle-detail.hpp" -#include "../../src/p2p/data_logger.hpp" -using namespace nOT::nUtils; -namespace cryptonote -{ +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.cn" +#define MLOG_P2P_MESSAGE(x) MCINFO("net.p2p.msg", context << x) -// static -// template<class t_core> std::ofstream t_cryptonote_protocol_handler<t_core>::m_logreq("logreq.txt"); // static +namespace cryptonote +{ @@ -59,7 +58,8 @@ namespace cryptonote t_cryptonote_protocol_handler<t_core>::t_cryptonote_protocol_handler(t_core& rcore, nodetool::i_p2p_endpoint<connection_context>* p_net_layout):m_core(rcore), m_p2p(p_net_layout), m_syncronized_connections_count(0), - m_synchronized(false) + m_synchronized(false), + m_stopping(false) { if(!m_p2p) @@ -75,8 +75,6 @@ namespace cryptonote template<class t_core> bool t_cryptonote_protocol_handler<t_core>::deinit() { - - return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -126,6 +124,7 @@ namespace cryptonote ss << std::setw(30) << std::left << "Remote Host" << std::setw(20) << "Peer id" + << std::setw(20) << "Support Flags" << std::setw(30) << "Recv/Sent (inactive,sec)" << std::setw(25) << "State" << std::setw(20) << "Livetime(sec)" @@ -136,7 +135,7 @@ namespace cryptonote << ENDL; uint32_t ip; - m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) + m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) { bool local_ip = false; ip = ntohl(cntxt.m_remote_ip); @@ -147,6 +146,7 @@ namespace cryptonote ss << std::setw(30) << std::left << std::string(cntxt.m_is_income ? " [INC]":"[OUT]") + epee::string_tools::get_ip_string_from_int32(cntxt.m_remote_ip) + ":" + std::to_string(cntxt.m_remote_port) << std::setw(20) << std::hex << peer_id + << std::setw(20) << std::hex << support_flags << std::setw(30) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) << std::setw(20) << std::to_string(time(NULL) - cntxt.m_started) @@ -186,7 +186,7 @@ namespace cryptonote { std::list<connection_info> connections; - m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) + m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) { connection_info cnx; auto timestamp = time(NULL); @@ -199,6 +199,8 @@ namespace cryptonote std::stringstream peer_id_str; peer_id_str << std::hex << peer_id; peer_id_str >> cnx.peer_id; + + cnx.support_flags = support_flags; cnx.recv_count = cntxt.m_recv_cnt; cnx.recv_idle_time = timestamp - cntxt.m_last_recv; @@ -263,27 +265,33 @@ namespace cryptonote if(context.m_state == cryptonote_connection_context::state_synchronizing) return true; - if(m_core.have_block(hshd.top_id) && !(hshd.current_height < m_core.get_target_blockchain_height())) + uint64_t target = m_core.get_target_blockchain_height(); + if (target == 0) + target = m_core.get_current_blockchain_height(); + + if(m_core.have_block(hshd.top_id)) { context.m_state = cryptonote_connection_context::state_normal; - if(is_inital) + if(is_inital && target == m_core.get_current_blockchain_height()) on_connection_synchronized(); return true; } + if (hshd.current_height > target) + { /* As I don't know if accessing hshd from core could be a good practice, I prefer pushing target height to the core at the same time it is pushed to the user. Nz. */ m_core.set_target_blockchain_height(static_cast<int64_t>(hshd.current_height)); - int64_t diff = static_cast<int64_t>(hshd.current_height) - static_cast<int64_t>(m_core.get_current_blockchain_height()); int64_t max_block_height = max(static_cast<int64_t>(hshd.current_height),static_cast<int64_t>(m_core.get_current_blockchain_height())); int64_t last_block_v1 = 1009826; int64_t diff_v2 = max_block_height > last_block_v1 ? min(abs(diff), max_block_height - last_block_v1) : 0; - LOG_PRINT_CCONTEXT_YELLOW("Sync data returned unknown top block: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height - << " [" << std::abs(diff) << " blocks (" << ((abs(diff) - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " + MCLOG(is_inital ? el::Level::Info : el::Level::Debug, "global", context << "Sync data returned a new top block candidate: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height + << " [Your node is " << std::abs(diff) << " blocks (" << ((abs(diff) - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) - << "] " << ENDL << "SYNCHRONIZATION started", (is_inital ? LOG_LEVEL_0:LOG_LEVEL_1)); + << "] " << ENDL << "SYNCHRONIZATION started"); + } LOG_PRINT_L1("Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id); context.m_state = cryptonote_connection_context::state_synchronizing; context.m_remote_blockchain_height = hshd.current_height; @@ -314,7 +322,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_NEW_BLOCK (hop " << arg.hop << ")"); + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; m_core.pause_mine(); @@ -324,7 +332,7 @@ namespace cryptonote for(auto tx_blob_it = arg.b.txs.begin(); tx_blob_it!=arg.b.txs.end();tx_blob_it++) { cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true); + m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true, false); if(tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection"); @@ -363,16 +371,370 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> + int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context) + { + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); + if(context.m_state != cryptonote_connection_context::state_normal) + return 1; + + m_core.pause_mine(); + + block new_block; + transaction miner_tx; + if(parse_and_validate_block_from_blob(arg.b.block, new_block)) + { + // This is a second notification, we must have asked for some missing tx + if(!context.m_requested_objects.empty()) + { + // What we asked for != to what we received .. + if(context.m_requested_objects.size() != arg.b.txs.size()) + { + LOG_ERROR_CCONTEXT + ( + "NOTIFY_NEW_FLUFFY_BLOCK -> request/response mismatch, " + << "block = " << epee::string_tools::pod_to_hex(get_blob_hash(arg.b.block)) + << ", requested = " << context.m_requested_objects.size() + << ", received = " << new_block.tx_hashes.size() + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + } + + std::list<blobdata> have_tx; + + // Instead of requesting missing transactions by hash like BTC, + // we do it by index (thanks to a suggestion from moneromooo) because + // we're way cooler .. and also because they're smaller than hashes. + // + // Also, remember to pepper some whitespace changes around to bother + // moneromooo ... only because I <3 him. + std::vector<size_t> need_tx_indices; + + transaction tx; + crypto::hash tx_hash; + + for(auto& tx_blob: arg.b.txs) + { + if(parse_and_validate_tx_from_blob(tx_blob, tx)) + { + try + { + if(!get_transaction_hash(tx, tx_hash)) + { + LOG_PRINT_CCONTEXT_L1 + ( + "NOTIFY_NEW_FLUFFY_BLOCK: get_transaction_hash failed" + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + } + catch(...) + { + LOG_PRINT_CCONTEXT_L1 + ( + "NOTIFY_NEW_FLUFFY_BLOCK: get_transaction_hash failed" + << ", exception thrown" + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + + // hijacking m_requested objects in connection context to patch up + // a possible DOS vector pointed out by @monero-moo where peers keep + // sending (0...n-1) transactions. + // If requested objects is not empty, then we must have asked for + // some missing transacionts, make sure that they're all there. + // + // Can I safely re-use this field? I think so, but someone check me! + if(!context.m_requested_objects.empty()) + { + auto req_tx_it = context.m_requested_objects.find(tx_hash); + if(req_tx_it == context.m_requested_objects.end()) + { + LOG_ERROR_CCONTEXT + ( + "Peer sent wrong transaction (NOTIFY_NEW_FLUFFY_BLOCK): " + << "transaction with id = " << tx_hash << " wasn't requested, " + << "dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + + context.m_requested_objects.erase(req_tx_it); + } + + // we might already have the tx that the peer + // sent in our pool, so don't verify again.. + if(!m_core.get_pool_transaction(tx_hash, tx)) + { + MDEBUG("Incoming tx " << tx_hash << " not in pool, adding"); + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + if(!m_core.handle_incoming_tx(tx_blob, tvc, true, true, false) || tvc.m_verifivation_failed) + { + LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection"); + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + + // + // future todo: + // tx should only not be added to pool if verification failed, but + // maybe in the future could not be added for other reasons + // according to monero-moo so keep track of these separately .. + // + } + } + else + { + LOG_ERROR_CCONTEXT + ( + "sent wrong tx: failed to parse and validate transaction: " + << epee::string_tools::buff_to_hex_nodelimer(tx_blob) + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + } + + // The initial size equality check could have been fooled if the sender + // gave us the number of transactions we asked for, but not the right + // ones. This check make sure the transactions we asked for were the + // ones we received. + if(context.m_requested_objects.size()) + { + MERROR + ( + "NOTIFY_NEW_FLUFFY_BLOCK: peer sent the number of transaction requested" + << ", but not the actual transactions requested" + << ", context.m_requested_objects.size() = " << context.m_requested_objects.size() + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + m_core.resume_mine(); + return 1; + } + + size_t tx_idx = 0; + for(auto& tx_hash: new_block.tx_hashes) + { + if(m_core.get_pool_transaction(tx_hash, tx)) + { + have_tx.push_back(tx_to_blob(tx)); + } + else + { + std::vector<crypto::hash> tx_ids; + std::list<transaction> txes; + std::list<crypto::hash> missing; + tx_ids.push_back(tx_hash); + if (m_core.get_transactions(tx_ids, txes, missing) && missing.empty()) + { + if (txes.size() == 1) + { + have_tx.push_back(tx_to_blob(txes.front())); + } + else + { + MERROR("1 tx requested, none not found, but " << txes.size() << " returned"); + m_core.resume_mine(); + return 1; + } + } + else + { + MDEBUG("Tx " << tx_hash << " not found in pool"); + need_tx_indices.push_back(tx_idx); + } + } + + ++tx_idx; + } + + if(!need_tx_indices.empty()) // drats, we don't have everything.. + { + // request non-mempool txs + MDEBUG("We are missing " << need_tx_indices.size() << " txes for this fluffy block"); + for (auto txidx: need_tx_indices) + MDEBUG(" tx " << new_block.tx_hashes[txidx]); + NOTIFY_REQUEST_FLUFFY_MISSING_TX::request missing_tx_req; + missing_tx_req.block_hash = get_block_hash(new_block); + missing_tx_req.hop = arg.hop; + missing_tx_req.current_blockchain_height = arg.current_blockchain_height; + missing_tx_req.missing_tx_indices = std::move(need_tx_indices); + + m_core.resume_mine(); + post_notify<NOTIFY_REQUEST_FLUFFY_MISSING_TX>(missing_tx_req, context); + } + else // whoo-hoo we've got em all .. + { + MDEBUG("We have all needed txes for this fluffy block"); + + block_complete_entry b; + b.block = arg.b.block; + b.txs = have_tx; + + std::list<block_complete_entry> blocks; + blocks.push_back(b); + m_core.prepare_handle_incoming_blocks(blocks); + + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + m_core.handle_incoming_block(arg.b.block, bvc); // got block from handle_notify_new_block + m_core.cleanup_handle_incoming_blocks(true); + m_core.resume_mine(); + + if( bvc.m_verifivation_failed ) + { + LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); + m_p2p->drop_connection(context); + return 1; + } + if( bvc.m_added_to_main_chain ) + { + ++arg.hop; + //TODO: Add here announce protocol usage + NOTIFY_NEW_BLOCK::request reg_arg = AUTO_VAL_INIT(reg_arg); + reg_arg.hop = arg.hop; + reg_arg.current_blockchain_height = arg.current_blockchain_height; + reg_arg.b = b; + relay_block(reg_arg, context); + } + else if( bvc.m_marked_as_orphaned ) + { + context.m_state = cryptonote_connection_context::state_synchronizing; + NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>(); + m_core.get_short_chain_history(r.block_ids); + LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); + post_notify<NOTIFY_REQUEST_CHAIN>(r, context); + } + } + } + else + { + LOG_ERROR_CCONTEXT + ( + "sent wrong block: failed to parse and validate block: " + << epee::string_tools::buff_to_hex_nodelimer(arg.b.block) + << ", dropping connection" + ); + + m_core.resume_mine(); + m_p2p->drop_connection(context); + + return 1; + } + + return 1; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> + int t_cryptonote_protocol_handler<t_core>::handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context) + { + MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_FLUFFY_MISSING_TX (" << arg.missing_tx_indices.size() << " txes), block hash " << arg.block_hash); + + std::list<std::pair<cryptonote::blobdata, block>> local_blocks; + std::list<cryptonote::blobdata> local_txs; + + block b; + if (!m_core.get_block_by_hash(arg.block_hash, b)) + { + LOG_ERROR_CCONTEXT("failed to find block: " << arg.block_hash << ", dropping connection"); + m_p2p->drop_connection(context); + return 1; + } + + for (auto txidx: arg.missing_tx_indices) + MDEBUG(" tx " << b.tx_hashes[txidx]); + + std::vector<crypto::hash> txids; + NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response; + fluffy_response.b.block = t_serializable_object_to_blob(b); + fluffy_response.current_blockchain_height = arg.current_blockchain_height; + fluffy_response.hop = arg.hop; + for(auto& tx_idx: arg.missing_tx_indices) + { + if(tx_idx < b.tx_hashes.size()) + { + txids.push_back(b.tx_hashes[tx_idx]); + } + else + { + LOG_ERROR_CCONTEXT + ( + "Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX" + << ", request is asking for a tx whose index is out of bounds " + << ", tx index = " << tx_idx << ", block tx count " << b.tx_hashes.size() + << ", block_height = " << arg.current_blockchain_height + << ", dropping connection" + ); + + m_p2p->drop_connection(context); + return 1; + } + } + + std::list<cryptonote::transaction> txs; + std::list<crypto::hash> missed; + if (!m_core.get_transactions(txids, txs, missed)) + { + LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, " + << "failed to get requested transactions"); + m_p2p->drop_connection(context); + return 1; + } + if (!missed.empty() || txs.size() != txids.size()) + { + LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, " + << missed.size() << " requested transactions not found" << ", dropping connection"); + m_p2p->drop_connection(context); + return 1; + } + + for(auto& tx: txs) + { + fluffy_response.b.txs.push_back(t_serializable_object_to_blob(tx)); + } + + LOG_PRINT_CCONTEXT_L2 + ( + "-->>NOTIFY_RESPONSE_FLUFFY_MISSING_TX: " + << ", txs.size()=" << fluffy_response.b.txs.size() + << ", rsp.current_blockchain_height=" << fluffy_response.current_blockchain_height + ); + + post_notify<NOTIFY_NEW_FLUFFY_BLOCK>(fluffy_response, context); + return 1; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_NEW_TRANSACTIONS"); + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end();) { cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(*tx_blob_it, tvc, false, true); + m_core.handle_incoming_tx(*tx_blob_it, tvc, false, true, false); if(tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); @@ -397,7 +759,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_REQUEST_GET_OBJECTS"); + MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)"); NOTIFY_RESPONSE_GET_OBJECTS::request rsp; if(!m_core.handle_get_objects(arg, rsp, context)) { @@ -437,7 +799,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_response_get_objects(int command, NOTIFY_RESPONSE_GET_OBJECTS::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_RESPONSE_GET_OBJECTS"); + MLOG_P2P_MESSAGE("Received NOTIFY_RESPONSE_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)"); // calculate size of request - mainly for logging/debug size_t size = 0; @@ -482,21 +844,29 @@ namespace cryptonote context.m_remote_blockchain_height = arg.current_blockchain_height; size_t count = 0; - BOOST_FOREACH(const block_complete_entry& block_entry, arg.blocks) + std::vector<crypto::hash> block_hashes; + block_hashes.reserve(arg.blocks.size()); + for(const block_complete_entry& block_entry: arg.blocks) { + if (m_stopping) + { + return 1; + } + ++count; block b; if(!parse_and_validate_block_from_blob(block_entry.block, b)) { - LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: \r\n" - << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << "\r\n dropping connection"); + LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: " + << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection"); m_p2p->drop_connection(context); return 1; } //to avoid concurrency in core between connections, suspend connections which delivered block later then first one + const crypto::hash block_hash = get_block_hash(b); if(count == 2) { - if(m_core.have_block(get_block_hash(b))) + if(m_core.have_block(block_hash)) { context.m_state = cryptonote_connection_context::state_idle; context.m_needed_objects.clear(); @@ -506,7 +876,7 @@ namespace cryptonote } } - auto req_it = context.m_requested_objects.find(get_block_hash(b)); + auto req_it = context.m_requested_objects.find(block_hash); if(req_it == context.m_requested_objects.end()) { LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block)) @@ -523,40 +893,74 @@ namespace cryptonote } context.m_requested_objects.erase(req_it); + block_hashes.push_back(block_hash); } if(context.m_requested_objects.size()) { - LOG_PRINT_CCONTEXT_RED("returned not all requested objects (context.m_requested_objects.size()=" - << context.m_requested_objects.size() << "), dropping connection", LOG_LEVEL_0); + MERROR("returned not all requested objects (context.m_requested_objects.size()=" + << context.m_requested_objects.size() << "), dropping connection"); m_p2p->drop_connection(context); return 1; } { - m_core.pause_mine(); - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler( - boost::bind(&t_core::resume_mine, &m_core)); - - LOG_PRINT_CCONTEXT_YELLOW( "Got NEW BLOCKS inside of " << __FUNCTION__ << ": size: " << arg.blocks.size() , LOG_LEVEL_1); + MLOG_YELLOW(el::Level::Debug, "Got NEW BLOCKS inside of " << __FUNCTION__ << ": size: " << arg.blocks.size()); if (m_core.get_test_drop_download() && m_core.get_test_drop_download_height()) { // DISCARD BLOCKS for testing - uint64_t previous_height = m_core.get_current_blockchain_height(); + // we lock all the rest to avoid having multiple connections redo a lot + // of the same work, and one of them doing it for nothing: subsequent + // connections will wait until the current one's added its blocks, then + // will add any extra it has, if any + CRITICAL_REGION_LOCAL(m_sync_lock); + + m_core.pause_mine(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler( + boost::bind(&t_core::resume_mine, &m_core)); + + const uint64_t previous_height = m_core.get_current_blockchain_height(); + + // dismiss what another connection might already have done (likely everything) + uint64_t top_height; + crypto::hash top_hash; + if (m_core.get_blockchain_top(top_height, top_hash)) { + uint64_t dismiss = 1; + for (const auto &h: block_hashes) { + if (top_hash == h) { + LOG_DEBUG_CC(context, "Found current top block in synced blocks, dismissing " + << dismiss << "/" << arg.blocks.size() << " blocks"); + while (dismiss--) + arg.blocks.pop_front(); + break; + } + ++dismiss; + } + } + + if (arg.blocks.empty()) + goto skip; m_core.prepare_handle_incoming_blocks(arg.blocks); - BOOST_FOREACH(const block_complete_entry& block_entry, arg.blocks) + + for(const block_complete_entry& block_entry: arg.blocks) { + if (m_stopping) + { + m_core.cleanup_handle_incoming_blocks(); + return 1; + } + // process transactions TIME_MEASURE_START(transactions_process_time); - BOOST_FOREACH(auto& tx_blob, block_entry.txs) + for(auto& tx_blob: block_entry.txs) { tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(tx_blob, tvc, true, true); + m_core.handle_incoming_tx(tx_blob, tvc, true, true, false); if(tvc.m_verifivation_failed) { - LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, \r\ntx_id = " + LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, tx_id = " << epee::string_tools::pod_to_hex(get_blob_hash(tx_blob)) << ", dropping connection"); m_p2p->drop_connection(context); m_core.cleanup_handle_incoming_blocks(); @@ -592,20 +996,18 @@ namespace cryptonote TIME_MEASURE_FINISH(block_process_time); LOG_PRINT_CCONTEXT_L2("Block process time: " << block_process_time + transactions_process_time << "(" << transactions_process_time << "/" << block_process_time << ")ms"); - epee::net_utils::data_logger::get_instance().add_data("calc_time", block_process_time + transactions_process_time); - epee::net_utils::data_logger::get_instance().add_data("block_processing", 1); - } // each download block m_core.cleanup_handle_incoming_blocks(); if (m_core.get_current_blockchain_height() > previous_height) { - LOG_PRINT_CCONTEXT_YELLOW( "Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() , LOG_LEVEL_0); + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height()); } } // if not DISCARD BLOCK } +skip: request_missing_objects(context, true); return 1; } @@ -619,11 +1021,12 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_request_chain(int command, NOTIFY_REQUEST_CHAIN::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << arg.block_ids.size()); + MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_CHAIN (" << arg.block_ids.size() << " blocks"); NOTIFY_RESPONSE_CHAIN_ENTRY::request r; if(!m_core.find_blockchain_supplement(arg.block_ids, r)) { LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN."); + m_p2p->drop_connection(context); return 1; } LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size()); @@ -651,7 +1054,7 @@ namespace cryptonote auto it = context.m_needed_objects.begin(); const size_t count_limit = m_core.get_block_sync_size(); - _note_c("net/req-calc" , "Setting count_limit: " << count_limit); + MDEBUG("Setting count_limit: " << count_limit); while(it != context.m_needed_objects.end() && count < count_limit) { if( !(check_having_blocks && m_core.have_block(*it))) @@ -693,7 +1096,7 @@ namespace cryptonote << "\r\non connection [" << epee::net_utils::print_connection_context_short(context)<< "]"); context.m_state = cryptonote_connection_context::state_normal; - LOG_PRINT_CCONTEXT_GREEN(" SYNCHRONIZED OK", LOG_LEVEL_0); + MGINFO_GREEN("SYNCHRONIZED OK"); on_connection_synchronized(); } return true; @@ -705,13 +1108,10 @@ namespace cryptonote bool val_expected = false; if(m_synchronized.compare_exchange_strong(val_expected, true)) { - LOG_PRINT_L0(ENDL << "**********************************************************************" << ENDL + MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << "You are now synchronized with the network. You may now start monero-wallet-cli." << ENDL << ENDL - << "Please note, that the blockchain will be saved only after you quit the daemon with \"exit\" command or if you use \"save\" command." << ENDL - << "Otherwise, you will possibly need to synchronize the blockchain again." << ENDL - << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL + << "Use the \"help\" command to see the list of available commands." << ENDL << "**********************************************************************"); m_core.on_synchronized(); } @@ -733,7 +1133,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_response_chain_entry(int command, NOTIFY_RESPONSE_CHAIN_ENTRY::request& arg, cryptonote_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("NOTIFY_RESPONSE_CHAIN_ENTRY: m_block_ids.size()=" << arg.m_block_ids.size() + MLOG_P2P_MESSAGE("Received NOTIFY_RESPONSE_CHAIN_ENTRY: m_block_ids.size()=" << arg.m_block_ids.size() << ", m_start_height=" << arg.start_height << ", m_total_height=" << arg.total_height); if(!arg.m_block_ids.size()) @@ -757,13 +1157,13 @@ namespace cryptonote context.m_last_response_height = arg.start_height + arg.m_block_ids.size()-1; if(context.m_last_response_height > context.m_remote_blockchain_height) { - LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_CHAIN_ENTRY, with \r\nm_total_height=" << arg.total_height - << "\r\nm_start_height=" << arg.start_height - << "\r\nm_block_ids.size()=" << arg.m_block_ids.size()); + LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_CHAIN_ENTRY, with m_total_height=" << arg.total_height + << ", m_start_height=" << arg.start_height + << ", m_block_ids.size()=" << arg.m_block_ids.size()); m_p2p->drop_connection(context); } - BOOST_FOREACH(auto& bl_id, arg.m_block_ids) + for(auto& bl_id: arg.m_block_ids) { if(!m_core.have_block(bl_id)) context.m_needed_objects.push_back(bl_id); @@ -776,26 +1176,59 @@ namespace cryptonote template<class t_core> bool t_cryptonote_protocol_handler<t_core>::relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context) { - return relay_post_notify<NOTIFY_NEW_BLOCK>(arg, exclude_context); + NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_arg = AUTO_VAL_INIT(fluffy_arg); + fluffy_arg.hop = arg.hop; + fluffy_arg.current_blockchain_height = arg.current_blockchain_height; + std::list<blobdata> fluffy_txs; + fluffy_arg.b = arg.b; + fluffy_arg.b.txs = fluffy_txs; + + // pre-serialize them + std::string fullBlob, fluffyBlob; + epee::serialization::store_t_to_binary(arg, fullBlob); + epee::serialization::store_t_to_binary(fluffy_arg, fluffyBlob); + + // sort peers between fluffy ones and others + std::list<boost::uuids::uuid> fullConnections, fluffyConnections; + m_p2p->for_each_connection([this, &arg, &fluffy_arg, &exclude_context, &fullConnections, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) + { + if (peer_id && exclude_context.m_connection_id != context.m_connection_id) + { + if(m_core.get_testnet() && (support_flags & P2P_SUPPORT_FLAG_FLUFFY_BLOCKS)) + { + LOG_DEBUG_CC(context, "PEER SUPPORTS FLUFFY BLOCKS - RELAYING THIN/COMPACT WHATEVER BLOCK"); + fluffyConnections.push_back(context.m_connection_id); + } + else + { + LOG_DEBUG_CC(context, "PEER DOESN'T SUPPORT FLUFFY BLOCKS - RELAYING FULL BLOCK"); + fullConnections.push_back(context.m_connection_id); + } + } + return true; + }); + + // send fluffy ones first, we want to encourage people to run that + m_p2p->relay_notify_to_list(NOTIFY_NEW_FLUFFY_BLOCK::ID, fluffyBlob, fluffyConnections); + m_p2p->relay_notify_to_list(NOTIFY_NEW_BLOCK::ID, fullBlob, fullConnections); + + return 1; } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context) { + // no check for success, so tell core they're relayed unconditionally + for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end(); ++tx_blob_it) + m_core.on_transaction_relayed(*tx_blob_it); return relay_post_notify<NOTIFY_NEW_TRANSACTIONS>(arg, exclude_context); } - /// @deprecated - template<class t_core> std::ofstream& t_cryptonote_protocol_handler<t_core>::get_logreq() const { - static std::ofstream * logreq=NULL; - if (!logreq) { - LOG_PRINT_RED("LOG OPENED",LOG_LEVEL_0); - logreq = new std::ofstream("logreq.txt"); // leak mem (singleton) - *logreq << "Opened log" << std::endl; - } - LOG_PRINT_YELLOW("LOG USED",LOG_LEVEL_0); - (*logreq) << "log used" << std::endl; - return *logreq; + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> + void t_cryptonote_protocol_handler<t_core>::stop() + { + m_stopping = true; + m_core.stop(); } - } // namespace diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index 06061f5d0..1163a0fe8 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,7 +32,7 @@ #include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "cryptonote_core/connection_context.h" +#include "cryptonote_basic/connection_context.h" namespace cryptonote { /************************************************************************/ diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 8f9a50f13..649823a59 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -29,9 +29,9 @@ set(blocksdat "") if(PER_BLOCK_CHECKPOINT) if(APPLE) - add_custom_command(OUTPUT blocksdat.o COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) else() - add_custom_command(OUTPUT blocksdat.o COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) endif() set(blocksdat "blocksdat.o") endif() @@ -74,9 +74,9 @@ set(daemon_private_headers ../p2p/p2p_protocol_defs.h ../p2p/stdafx.h) -bitmonero_private_headers(daemon +monero_private_headers(daemon ${daemon_private_headers}) -bitmonero_add_executable(daemon +monero_add_executable(daemon ${daemon_sources} ${daemon_headers} ${daemon_private_headers} @@ -89,7 +89,6 @@ target_link_libraries(daemon cryptonote_core crypto common - otshell_utils p2p cryptonote_protocol daemonizer diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index 1fe2ddcf6..8eb3db195 100644 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -46,10 +46,10 @@ namespace daemon_args , "Specify log file" , "" }; - const command_line::arg_descriptor<int> arg_log_level = { + const command_line::arg_descriptor<std::string> arg_log_level = { "log-level" , "" - , LOG_LEVEL_0 + , "" }; const command_line::arg_descriptor<std::vector<std::string>> arg_command = { "daemon_command" diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 83892c661..a7caeeffc 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -26,19 +26,22 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "common/dns_utils.h" #include "daemon/command_parser_executor.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { t_command_parser_executor::t_command_parser_executor( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& login , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_executor(ip, port, user_agent, is_rpc, rpc_server) + : m_executor(ip, port, login, is_rpc, rpc_server) {} bool t_command_parser_executor::print_peer_list(const std::vector<std::string>& args) @@ -48,6 +51,13 @@ bool t_command_parser_executor::print_peer_list(const std::vector<std::string>& return m_executor.print_peer_list(); } +bool t_command_parser_executor::print_peer_list_stats(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_peer_list_stats(); +} + bool t_command_parser_executor::save_blockchain(const std::vector<std::string>& args) { if (!args.empty()) return false; @@ -117,24 +127,24 @@ bool t_command_parser_executor::set_log_level(const std::vector<std::string>& ar { if(args.size() != 1) { - std::cout << "use: set_log <log_level_number_0-4>" << std::endl; + std::cout << "use: set_log [<log_level_number_0-4> | <categories>]" << std::endl; return true; } uint16_t l = 0; - if(!epee::string_tools::get_xtype_from_string(l, args[0])) + if(epee::string_tools::get_xtype_from_string(l, args[0])) { - std::cout << "wrong number format, use: set_log <log_level_number_0-4>" << std::endl; - return true; + if(4 < l) + { + std::cout << "wrong number range, use: set_log <log_level_number_0-4>" << std::endl; + return true; + } + return m_executor.set_log_level(l); } - - if(LOG_LEVEL_4 < l) + else { - std::cout << "wrong number range, use: set_log <log_level_number_0-4>" << std::endl; - return true; + return m_executor.set_log_categories(args.front()); } - - return m_executor.set_log_level(l); } bool t_command_parser_executor::print_height(const std::vector<std::string>& args) @@ -222,6 +232,13 @@ bool t_command_parser_executor::print_transaction_pool_short(const std::vector<s return m_executor.print_transaction_pool_short(); } +bool t_command_parser_executor::print_transaction_pool_stats(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_transaction_pool_stats(); +} + bool t_command_parser_executor::start_mining(const std::vector<std::string>& args) { if(!args.size()) @@ -231,29 +248,60 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg } cryptonote::account_public_address adr; + bool has_payment_id; + crypto::hash8 payment_id; bool testnet = false; - if(!cryptonote::get_account_address_from_str(adr, false, args.front())) + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, args.front())) { - if(!cryptonote::get_account_address_from_str(adr, true, args.front())) + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front())) { - std::cout << "target account address has wrong format" << std::endl; - return true; + bool dnssec_valid; + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid); + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str)) + { + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str)) + { + std::cout << "target account address has wrong format" << std::endl; + return true; + } + else + { + testnet = true; + } + } + } + else + { + testnet = true; } - testnet = true; - std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; } + if(testnet) + std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; uint64_t threads_count = 1; - if(args.size() > 2) + bool do_background_mining = false; + bool ignore_battery = false; + if(args.size() > 4) { return false; } - else if(args.size() == 2) + + if(args.size() == 4) + { + ignore_battery = args[3] == "true"; + } + + if(args.size() >= 3) + { + do_background_mining = args[2] == "true"; + } + + if(args.size() >= 2) { bool ok = epee::string_tools::get_xtype_from_string(threads_count, args[1]); threads_count = (ok && 0 < threads_count) ? threads_count : 1; } - m_executor.start_mining(adr, threads_count, testnet); + m_executor.start_mining(adr, threads_count, testnet, do_background_mining, ignore_battery); return true; } @@ -336,12 +384,6 @@ bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& a return m_executor.set_limit_down(limit); } -bool t_command_parser_executor::fast_exit(const std::vector<std::string>& args) -{ - if (!args.empty()) return false; - return m_executor.fast_exit(); -} - bool t_command_parser_executor::out_peers(const std::vector<std::string>& args) { if (args.empty()) return false; @@ -458,5 +500,82 @@ bool t_command_parser_executor::output_histogram(const std::vector<std::string>& return m_executor.output_histogram(min_count, max_count); } +bool t_command_parser_executor::print_coinbase_tx_sum(const std::vector<std::string>& args) +{ + if(!args.size()) + { + std::cout << "need block height parameter" << std::endl; + return false; + } + uint64_t height = 0; + uint64_t count = 0; + if(!epee::string_tools::get_xtype_from_string(height, args[0])) + { + std::cout << "wrong starter block height parameter" << std::endl; + return false; + } + if(args.size() >1 && !epee::string_tools::get_xtype_from_string(count, args[1])) + { + std::cout << "wrong count parameter" << std::endl; + return false; + } + + return m_executor.print_coinbase_tx_sum(height, count); +} + +bool t_command_parser_executor::alt_chain_info(const std::vector<std::string>& args) +{ + if(args.size()) + { + std::cout << "No parameters allowed" << std::endl; + return false; + } + + return m_executor.alt_chain_info(); +} + +bool t_command_parser_executor::print_blockchain_dynamic_stats(const std::vector<std::string>& args) +{ + if(args.size() != 1) + { + std::cout << "Exactly one parameter is needed" << std::endl; + return false; + } + + uint64_t nblocks = 0; + if(!epee::string_tools::get_xtype_from_string(nblocks, args[0]) || nblocks == 0) + { + std::cout << "wrong number of blocks" << std::endl; + return false; + } + + return m_executor.print_blockchain_dynamic_stats(nblocks); +} + +bool t_command_parser_executor::update(const std::vector<std::string>& args) +{ + if(args.size() != 1) + { + std::cout << "Exactly one parameter is needed: check, download, or update" << std::endl; + return false; + } + + return m_executor.update(args.front()); +} + +bool t_command_parser_executor::relay_tx(const std::vector<std::string>& args) +{ + if (args.size() != 1) return false; + + std::string txid; + crypto::hash hash; + if (!parse_hash256(args[0], hash)) + { + std::cout << "failed to parse tx id" << std::endl; + return true; + } + txid = args[0]; + return m_executor.relay_tx(txid); +} } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index e59f51cdb..a453553f1 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -36,7 +36,10 @@ #pragma once +#include <boost/optional/optional_fwd.hpp> + #include "daemon/rpc_command_executor.h" +#include "common/common_fwd.h" #include "rpc/core_rpc_server.h" namespace daemonize { @@ -49,13 +52,15 @@ public: t_command_parser_executor( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& login , bool is_rpc , cryptonote::core_rpc_server* rpc_server = NULL ); bool print_peer_list(const std::vector<std::string>& args); + bool print_peer_list_stats(const std::vector<std::string>& args); + bool save_blockchain(const std::vector<std::string>& args); bool show_hash_rate(const std::vector<std::string>& args); @@ -72,6 +77,8 @@ public: bool set_log_level(const std::vector<std::string>& args); + bool set_log_categories(const std::vector<std::string>& args); + bool print_height(const std::vector<std::string>& args); bool print_block(const std::vector<std::string>& args); @@ -84,6 +91,8 @@ public: bool print_transaction_pool_short(const std::vector<std::string>& args); + bool print_transaction_pool_stats(const std::vector<std::string>& args); + bool start_mining(const std::vector<std::string>& args); bool stop_mining(const std::vector<std::string>& args); @@ -98,8 +107,6 @@ public: bool set_limit_down(const std::vector<std::string>& args); - bool fast_exit(const std::vector<std::string>& args); - bool out_peers(const std::vector<std::string>& args); bool start_save_graph(const std::vector<std::string>& args); @@ -117,6 +124,16 @@ public: bool flush_txpool(const std::vector<std::string>& args); bool output_histogram(const std::vector<std::string>& args); + + bool print_coinbase_tx_sum(const std::vector<std::string>& args); + + bool alt_chain_info(const std::vector<std::string>& args); + + bool print_blockchain_dynamic_stats(const std::vector<std::string>& args); + + bool update(const std::vector<std::string>& args); + + bool relay_tx(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 2c3c54841..21f550a85 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,6 +30,9 @@ #include "version.h" #include "daemon/command_server.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { namespace p = std::placeholders; @@ -37,11 +40,11 @@ namespace p = std::placeholders; t_command_server::t_command_server( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& login , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_parser(ip, port, user_agent, is_rpc, rpc_server) + : m_parser(ip, port, login, is_rpc, rpc_server) , m_command_lookup() , m_is_rpc(is_rpc) { @@ -66,6 +69,11 @@ t_command_server::t_command_server( , "Print peer list" ); m_command_lookup.set_handler( + "print_pl_stats" + , std::bind(&t_command_parser_executor::print_peer_list_stats, &m_parser, p::_1) + , "Print peer list stats" + ); + m_command_lookup.set_handler( "print_cn" , std::bind(&t_command_parser_executor::print_connections, &m_parser, p::_1) , "Print connections" @@ -93,7 +101,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "start_mining" , std::bind(&t_command_parser_executor::start_mining, &m_parser, p::_1) - , "Start mining for specified address, start_mining <addr> [<threads>], default 1 thread" + , "Start mining for specified address, start_mining <addr> [<threads>] [do_background_mining] [ignore_battery], default 1 thread, no background mining" ); m_command_lookup.set_handler( "stop_mining" @@ -111,6 +119,11 @@ t_command_server::t_command_server( , "Print transaction pool (short format)" ); m_command_lookup.set_handler( + "print_pool_stats" + , std::bind(&t_command_parser_executor::print_transaction_pool_stats, &m_parser, p::_1) + , "Print transaction pool statistics" + ); + m_command_lookup.set_handler( "show_hr" , std::bind(&t_command_parser_executor::show_hash_rate, &m_parser, p::_1) , "Start showing hash rate" @@ -128,7 +141,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "set_log" , std::bind(&t_command_parser_executor::set_log_level, &m_parser, p::_1) - , "set_log <level> - Change current log detalization level, <level> is a number 0-4" + , "set_log <level>|<categories> - Change current loglevel, <level> is a number 0-4" ); m_command_lookup.set_handler( "diff" @@ -171,11 +184,6 @@ t_command_server::t_command_server( , "limit <kB/s> - Set download limit" ); m_command_lookup.set_handler( - "fast_exit" - , std::bind(&t_command_parser_executor::fast_exit, &m_parser, p::_1) - , "Exit" - ); - m_command_lookup.set_handler( "out_peers" , std::bind(&t_command_parser_executor::out_peers, &m_parser, p::_1) , "Set max number of out peers" @@ -220,6 +228,31 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1) , "Print output histogram (amount, instances)" ); + m_command_lookup.set_handler( + "print_coinbase_tx_sum" + , std::bind(&t_command_parser_executor::print_coinbase_tx_sum, &m_parser, p::_1) + , "Print sum of coinbase transactions (start height, block count)" + ); + m_command_lookup.set_handler( + "alt_chain_info" + , std::bind(&t_command_parser_executor::alt_chain_info, &m_parser, p::_1) + , "Print information about alternative chains" + ); + m_command_lookup.set_handler( + "bc_dyn_stats" + , std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1) + , "Print information about current blockchain dynamic state" + ); + m_command_lookup.set_handler( + "update" + , std::bind(&t_command_parser_executor::update, &m_parser, p::_1) + , "subcommands: check (check if an update is available), download (download it is there is), update (not implemented)" + ); + m_command_lookup.set_handler( + "relay_tx" + , std::bind(&t_command_parser_executor::relay_tx, &m_parser, p::_1) + , "Relay a given transaction by its txid" + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index fb1702aae..476b75141 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -9,7 +9,7 @@ Passing RPC commands: */ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -39,6 +39,8 @@ Passing RPC commands: #pragma once +#include <boost/optional/optional_fwd.hpp> +#include "common/common_fwd.h" #include "console_handler.h" #include "daemon/command_parser_executor.h" @@ -54,7 +56,7 @@ public: t_command_server( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& login , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); diff --git a/src/daemon/core.h b/src/daemon/core.h index 2b7f0d177..9e6ff5e29 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -33,6 +33,9 @@ #include "misc_log_ex.h" #include "daemon/command_line_args.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { @@ -42,7 +45,6 @@ public: static void init_options(boost::program_options::options_description & option_spec) { cryptonote::core::init_options(option_spec); - cryptonote::miner::init_options(option_spec); } private: typedef cryptonote::t_cryptonote_protocol_handler<cryptonote::core> t_protocol_raw; @@ -68,12 +70,12 @@ public: bool run() { //initialize core here - LOG_PRINT_L0("Initializing core..."); + MGINFO("Initializing core..."); if (!m_core.init(m_vm_HACK)) { return false; } - LOG_PRINT_L0("Core initialized OK"); + MGINFO("Core initialized OK"); return true; } @@ -84,12 +86,12 @@ public: ~t_core() { - LOG_PRINT_L0("Deinitializing core..."); + MGINFO("Deinitializing core..."); try { m_core.deinit(); m_core.set_cryptonote_protocol(nullptr); } catch (...) { - LOG_PRINT_L0("Failed to deinitialize core..."); + MERROR("Failed to deinitialize core..."); } } }; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 74875bfb0..241cb3883 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -33,6 +33,7 @@ #include "misc_log_ex.h" #include "daemon/daemon.h" +#include "common/password.h" #include "common/util.h" #include "daemon/core.h" #include "daemon/p2p.h" @@ -46,6 +47,9 @@ using namespace epee; #include <functional> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { struct t_internals { @@ -120,33 +124,35 @@ bool t_daemon::run(bool interactive) return false; mp_internals->rpc.run(); - daemonize::t_command_server* rpc_commands; + std::unique_ptr<daemonize::t_command_server> rpc_commands; if (interactive) { - rpc_commands = new daemonize::t_command_server(0, 0, "", false, mp_internals->rpc.get_server()); + // The first three variables are not used when the fourth is false + rpc_commands.reset(new daemonize::t_command_server(0, 0, boost::none, false, mp_internals->rpc.get_server())); rpc_commands->start_handling(std::bind(&daemonize::t_daemon::stop_p2p, this)); } mp_internals->p2p.run(); // blocks until p2p goes down - if (interactive) + if (rpc_commands) { rpc_commands->stop_handling(); } mp_internals->rpc.stop(); - LOG_PRINT("Node stopped.", LOG_LEVEL_0); + mp_internals->core.get().get_miner().stop(); + MGINFO("Node stopped."); return true; } catch (std::exception const & ex) { - LOG_ERROR("Uncaught exception! " << ex.what()); + MFATAL("Uncaught exception! " << ex.what()); return false; } catch (...) { - LOG_ERROR("Uncaught exception!"); + MFATAL("Uncaught exception!"); return false; } } @@ -157,6 +163,7 @@ void t_daemon::stop() { throw std::runtime_error{"Can't stop stopped daemon"}; } + mp_internals->core.get().get_miner().stop(); mp_internals->p2p.stop(); mp_internals->rpc.stop(); mp_internals.reset(nullptr); // Ensure resources are cleaned up before we return diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 561cb4d9d..2b9f18669 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,9 +29,12 @@ #pragma once #include <boost/program_options.hpp> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { -class t_internals; +struct t_internals; class t_daemon final { public: diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp index 3db3cb68d..6130bfa28 100644 --- a/src/daemon/executor.cpp +++ b/src/daemon/executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -26,16 +26,19 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "daemon/executor.h" - #include "misc_log_ex.h" +#include "daemon/executor.h" + #include "common/command_line.h" #include "cryptonote_config.h" #include "version.h" #include <string> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { std::string const t_executor::NAME = "Monero Daemon"; @@ -56,15 +59,21 @@ namespace daemonize boost::program_options::variables_map const & vm ) { - LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL); + LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ") Daemonised"); return t_daemon{vm}; } + bool t_executor::run_non_interactive( + boost::program_options::variables_map const & vm + ) + { + return t_daemon{vm}.run(false); + } + bool t_executor::run_interactive( boost::program_options::variables_map const & vm ) { - epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); return t_daemon{vm}.run(true); } } diff --git a/src/daemon/executor.h b/src/daemon/executor.h index 37b4970f4..137e7209c 100644 --- a/src/daemon/executor.h +++ b/src/daemon/executor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,6 +34,9 @@ #include <string> #include <vector> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { class t_executor final @@ -53,6 +56,10 @@ namespace daemonize boost::program_options::variables_map const & vm ); + bool run_non_interactive( + boost::program_options::variables_map const & vm + ); + bool run_interactive( boost::program_options::variables_map const & vm ); diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 0895e1bf1..19dd02171 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,9 +30,10 @@ #include "common/command_line.h" #include "common/scoped_message_writer.h" +#include "common/password.h" #include "common/util.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/miner.h" +#include "cryptonote_basic/miner.h" #include "daemon/command_server.h" #include "daemon/daemon.h" #include "daemon/executor.h" @@ -40,6 +41,7 @@ #include "misc_log_ex.h" #include "p2p/net_node.h" #include "rpc/core_rpc_server.h" +#include "rpc/rpc_args.h" #include "daemon/command_line_args.h" #include "blockchain_db/db_types.h" @@ -47,6 +49,9 @@ #include "common/stack_trace.h" #endif // STACK_TRACE +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace po = boost::program_options; namespace bf = boost::filesystem; @@ -54,7 +59,6 @@ int main(int argc, char const * argv[]) { try { - _note_c("dbg/main", "Begin of main()"); // TODO parse the debug options like set log level right here at start tools::sanitize_locale(); @@ -79,7 +83,6 @@ int main(int argc, char const * argv[]) bf::path default_conf = default_data_dir / std::string(CRYPTONOTE_NAME ".conf"); command_line::add_arg(visible_options, daemon_args::arg_config_file, default_conf.string()); command_line::add_arg(visible_options, command_line::arg_test_dbg_lock_sleep); - cryptonote::core::init_options(core_settings); // Settings bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log"); @@ -165,7 +168,6 @@ int main(int argc, char const * argv[]) // Create data dir if it doesn't exist boost::filesystem::path data_dir = boost::filesystem::absolute( command_line::get_arg(vm, data_dir_arg)); - tools::create_directories_if_necessary(data_dir.string()); // FIXME: not sure on windows implementation default, needs further review //bf::path relative_path_base = daemonizer::get_relative_path_base(vm); @@ -196,19 +198,39 @@ int main(int argc, char const * argv[]) } po::notify(vm); + // log_file_path + // default: <data_dir>/<CRYPTONOTE_NAME>.log + // if log-file argument given: + // absolute path + // relative path: relative to data_dir + bf::path log_file_path {data_dir / std::string(CRYPTONOTE_NAME ".log")}; + if (! vm["log-file"].defaulted()) + log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); + log_file_path = bf::absolute(log_file_path, relative_path_base); + mlog_configure(log_file_path.string(), true); + + // Set log level + if (!vm["log-level"].defaulted()) + { + mlog_set_log(command_line::get_arg(vm, daemon_args::arg_log_level).c_str()); + } + + // after logs initialized + tools::create_directories_if_necessary(data_dir.string()); + // If there are positional options, we're running a daemon command { auto command = command_line::get_arg(vm, daemon_args::arg_command); if (command.size()) { - auto rpc_ip_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_ip); + const cryptonote::rpc_args::descriptors arg{}; + auto rpc_ip_str = command_line::get_arg(vm, arg.rpc_bind_ip); auto rpc_port_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_port); if (testnet_mode) { rpc_port_str = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_testnet_rpc_bind_port); } - auto user_agent = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_user_agent); uint32_t rpc_ip; uint16_t rpc_port; @@ -223,7 +245,20 @@ int main(int argc, char const * argv[]) return 1; } - daemonize::t_command_server rpc_commands{rpc_ip, rpc_port, user_agent}; + boost::optional<tools::login> login{}; + if (command_line::has_arg(vm, arg.rpc_login)) + { + login = tools::login::parse( + command_line::get_arg(vm, arg.rpc_login), false, "Daemon client password" + ); + if (!login) + { + std::cerr << "Failed to obtain password" << std::endl; + return 1; + } + } + + daemonize::t_command_server rpc_commands{rpc_ip, rpc_port, std::move(login)}; if (rpc_commands.process_command_vec(command)) { return 0; @@ -236,55 +271,17 @@ int main(int argc, char const * argv[]) } } - // Start with log level 0 - epee::log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); - - // Set log level - { - int new_log_level = command_line::get_arg(vm, daemon_args::arg_log_level); - if(new_log_level < LOG_LEVEL_MIN || new_log_level > LOG_LEVEL_MAX) - { - LOG_PRINT_L0("Wrong log level value: " << new_log_level); - } - else if (epee::log_space::get_set_log_detalisation_level(false) != new_log_level) - { - epee::log_space::get_set_log_detalisation_level(true, new_log_level); - int otshell_utils_log_level = 100 - (new_log_level * 20); - gCurrentLogger.setDebugLevel(otshell_utils_log_level); - LOG_PRINT_L0("LOG_LEVEL set to " << new_log_level); - } - } - - // log_file_path - // default: <data_dir>/<CRYPTONOTE_NAME>.log - // if log-file argument given: - // absolute path - // relative path: relative to data_dir - - // Set log file - { - bf::path log_file_path {data_dir / std::string(CRYPTONOTE_NAME ".log")}; - if (! vm["log-file"].defaulted()) - log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); - log_file_path = bf::absolute(log_file_path, relative_path_base); - - epee::log_space::log_singletone::add_logger( - LOGGER_FILE - , log_file_path.filename().string().c_str() - , log_file_path.parent_path().string().c_str() - ); #ifdef STACK_TRACE - tools::set_stack_trace_log(log_file_path.filename().string()); + tools::set_stack_trace_log(log_file_path.filename().string()); #endif // STACK_TRACE - } if (command_line::has_arg(vm, daemon_args::arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, daemon_args::arg_max_concurrency)); // logging is now set up - LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); + MGINFO("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); - _note_c("dbg/main", "Moving from main() into the daemonize now."); + MINFO("Moving from main() into the daemonize now."); return daemonizer::daemonize(argc, argv, daemonize::t_executor{}, vm); } diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h index 3858989ce..309eb7453 100644 --- a/src/daemon/p2p.h +++ b/src/daemon/p2p.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,6 +34,9 @@ #include "p2p/net_node.h" #include "daemon/protocol.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { @@ -57,12 +60,12 @@ public: : m_server{protocol.get()} { //initialize objects - LOG_PRINT_L0("Initializing p2p server..."); + MGINFO("Initializing p2p server..."); if (!m_server.init(vm)) { throw std::runtime_error("Failed to initialize p2p server."); } - LOG_PRINT_L0("P2p server initialized OK"); + MGINFO("P2p server initialized OK"); } t_node_server & get() @@ -72,9 +75,9 @@ public: void run() { - LOG_PRINT_L0("Starting p2p net loop..."); + MGINFO("Starting p2p net loop..."); m_server.run(); - LOG_PRINT_L0("p2p net loop stopped"); + MGINFO("p2p net loop stopped"); } void stop() @@ -84,11 +87,11 @@ public: ~t_p2p() { - LOG_PRINT_L0("Deinitializing p2p..."); + MGINFO("Deinitializing p2p..."); try { m_server.deinit(); } catch (...) { - LOG_PRINT_L0("Failed to deinitialize p2p..."); + MERROR("Failed to deinitialize p2p..."); } } }; diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 8e2add4a2..fc5edbcaa 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,6 +30,11 @@ #pragma once +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + +#include "common/scoped_message_writer.h" + namespace daemonize { @@ -47,12 +52,12 @@ public: ) : m_protocol{core.get(), nullptr} { - LOG_PRINT_L0("Initializing cryptonote protocol..."); + MGINFO("Initializing cryptonote protocol..."); if (!m_protocol.init(vm)) { throw std::runtime_error("Failed to initialize cryptonote protocol."); } - LOG_PRINT_L0("Cryptonote protocol initialized OK"); + MGINFO("Cryptonote protocol initialized OK"); } t_protocol_raw & get() @@ -69,12 +74,13 @@ public: ~t_protocol() { - LOG_PRINT_L0("Deinitializing cryptonote_protocol..."); + MGINFO("Stopping cryptonote protocol..."); try { m_protocol.deinit(); m_protocol.set_p2p_endpoint(nullptr); + MGINFO("Cryptonote protocol stopped successfully"); } catch (...) { - LOG_PRINT_L0("Failed to deinitialize protocol..."); + LOG_ERROR("Failed to stop cryptonote protocol!"); } } }; diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h index bfd2afd84..0ecfdd120 100644 --- a/src/daemon/rpc.h +++ b/src/daemon/rpc.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,6 +32,9 @@ #include "rpc/core_rpc_server.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { @@ -52,27 +55,27 @@ public: ) : m_server{core.get(), p2p.get()} { - LOG_PRINT_L0("Initializing core rpc server..."); + MGINFO("Initializing core rpc server..."); if (!m_server.init(vm)) { throw std::runtime_error("Failed to initialize core rpc server."); } - LOG_PRINT_GREEN("Core rpc server initialized OK on port: " << m_server.get_binded_port(), LOG_LEVEL_0); + MGINFO("Core rpc server initialized OK on port: " << m_server.get_binded_port()); } void run() { - LOG_PRINT_L0("Starting core rpc server..."); + MGINFO("Starting core rpc server..."); if (!m_server.run(2, false)) { throw std::runtime_error("Failed to start core rpc server."); } - LOG_PRINT_L0("Core rpc server started ok"); + MGINFO("Core rpc server started ok"); } void stop() { - LOG_PRINT_L0("Stopping core rpc server..."); + MGINFO("Stopping core rpc server..."); m_server.send_stop_signal(); m_server.timed_wait_server_stop(5000); } @@ -84,11 +87,11 @@ public: ~t_rpc() { - LOG_PRINT_L0("Deinitializing rpc server..."); + MGINFO("Deinitializing rpc server..."); try { m_server.deinit(); } catch (...) { - LOG_PRINT_L0("Failed to deinitialize rpc server..."); + MERROR("Failed to deinitialize rpc server..."); } } }; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 67e51398c..31f432918 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,15 +29,19 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "string_tools.h" +#include "common/password.h" #include "common/scoped_message_writer.h" #include "daemon/rpc_command_executor.h" #include "rpc/core_rpc_server_commands_defs.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/hardfork.h" +#include "cryptonote_basic/hardfork.h" #include <boost/format.hpp> #include <ctime> #include <string> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { namespace { @@ -57,7 +61,7 @@ namespace { tools::msg_writer() << boost::format("%-10s %-25s %-25s %s") % prefix % id_str % addr_str % elapsed; } - void print_block_header(cryptonote::block_header_responce const & header) + void print_block_header(cryptonote::block_header_response const & header) { tools::success_msg_writer() << "timestamp: " << boost::lexical_cast<std::string>(header.timestamp) << std::endl @@ -87,12 +91,19 @@ namespace { s = boost::lexical_cast<std::string>(dt/(3600*24)) + " days"; return s + " " + (t > now ? "in the future" : "ago"); } + + std::string make_error(const std::string &base, const std::string &status) + { + if (status == CORE_RPC_STATUS_OK) + return base; + return base + " -- " + status; + } } t_rpc_command_executor::t_rpc_command_executor( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& login , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) @@ -100,7 +111,10 @@ t_rpc_command_executor::t_rpc_command_executor( { if (is_rpc) { - m_rpc_client = new tools::t_rpc_client(ip, port); + boost::optional<epee::net_utils::http::login> http_login{}; + if (login) + http_login.emplace(login->username, login->password.password()); + m_rpc_client = new tools::t_rpc_client(ip, port, std::move(http_login)); } else { @@ -155,6 +169,34 @@ bool t_rpc_command_executor::print_peer_list() { return true; } +bool t_rpc_command_executor::print_peer_list_stats() { + cryptonote::COMMAND_RPC_GET_PEER_LIST::request req; + cryptonote::COMMAND_RPC_GET_PEER_LIST::response res; + + std::string failure_message = "Couldn't retrieve peer list"; + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/get_peer_list", failure_message.c_str())) + { + return false; + } + } + else + { + if (!m_rpc_server->on_get_peer_list(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << failure_message; + return false; + } + } + + tools::msg_writer() + << "White list size: " << res.white_list.size() << "/" << P2P_LOCAL_WHITE_PEERLIST_LIMIT << " (" << res.white_list.size() * 100.0 / P2P_LOCAL_WHITE_PEERLIST_LIMIT << "%)" << std::endl + << "Gray list size: " << res.gray_list.size() << "/" << P2P_LOCAL_GRAY_PEERLIST_LIMIT << " (" << res.gray_list.size() * 100.0 / P2P_LOCAL_GRAY_PEERLIST_LIMIT << "%)"; + + return true; +} + bool t_rpc_command_executor::save_blockchain() { cryptonote::COMMAND_RPC_SAVE_BC::request req; cryptonote::COMMAND_RPC_SAVE_BC::response res; @@ -172,7 +214,7 @@ bool t_rpc_command_executor::save_blockchain() { { if (!m_rpc_server->on_save_bc(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -200,7 +242,7 @@ bool t_rpc_command_executor::show_hash_rate() { { if (!m_rpc_server->on_set_log_hash_rate(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); } } @@ -227,7 +269,7 @@ bool t_rpc_command_executor::hide_hash_rate() { { if (!m_rpc_server->on_set_log_hash_rate(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -254,7 +296,7 @@ bool t_rpc_command_executor::show_difficulty() { { if (!m_rpc_server->on_get_info(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message.c_str(), res.status); return true; } } @@ -338,12 +380,12 @@ bool t_rpc_command_executor::show_status() { { if (!m_rpc_server->on_get_info(ireq, ires) || ires.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, ires.status); return true; } if (!m_rpc_server->on_hard_fork_info(hfreq, hfres, error_resp) || hfres.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, hfres.status); return true; } if (!m_rpc_server->on_mining_status(mreq, mres)) @@ -358,22 +400,29 @@ bool t_rpc_command_executor::show_status() { } else if (mres.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, mres.status); return true; } } - tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u+%u connections") + std::time_t uptime = std::time(nullptr) - ires.start_time; + + tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections, uptime %ud %uh %um %us") % (unsigned long long)ires.height % (unsigned long long)(ires.target_height >= ires.height ? ires.target_height : ires.height) % get_sync_percentage(ires) % (ires.testnet ? "testnet" : "mainnet") - % (mining_busy ? "syncing" : mres.active ? "mining at " + get_mining_speed(mres.speed) : "not mining") + % (mining_busy ? "syncing" : mres.active ? ( ( mres.is_background_mining_enabled ? "smart " : "" ) + std::string("mining at ") + get_mining_speed(mres.speed) ) : "not mining") % get_mining_speed(ires.difficulty / ires.target) % (unsigned)hfres.version % get_fork_extra_info(hfres.earliest_height, ires.height, ires.target) % (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked") - % (unsigned)ires.outgoing_connections_count % (unsigned)ires.incoming_connections_count + % (unsigned)ires.outgoing_connections_count + % (unsigned)ires.incoming_connections_count + % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0) + % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0)) + % (unsigned int)floor(fmod((uptime / 60.0), 60.0)) + % (unsigned int)fmod(uptime, 60.0) ; return true; @@ -397,13 +446,14 @@ bool t_rpc_command_executor::print_connections() { { if (!m_rpc_server->on_get_connections(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } tools::msg_writer() << std::setw(30) << std::left << "Remote Host" << std::setw(20) << "Peer id" + << std::setw(20) << "Support Flags" << std::setw(30) << "Recv/Sent (inactive,sec)" << std::setw(25) << "State" << std::setw(20) << "Livetime(sec)" @@ -422,6 +472,7 @@ bool t_rpc_command_executor::print_connections() { //<< std::setw(30) << std::left << in_out << std::setw(30) << std::left << address << std::setw(20) << info.peer_id + << std::setw(20) << info.support_flags << std::setw(30) << std::to_string(info.recv_count) + "(" + std::to_string(info.recv_idle_time) + ")/" + std::to_string(info.send_count) + "(" + std::to_string(info.send_idle_time) + ")" << std::setw(25) << info.state << std::setw(20) << info.live_time @@ -440,11 +491,6 @@ bool t_rpc_command_executor::print_connections() { } bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index) { - -// this function appears to not exist in the json rpc api, and so is commented -// until such a time as it does. - -/* cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req; cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res; epee::json_rpc::error error_resp; @@ -463,25 +509,27 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u } else { - if (!m_rpc_server->on_getblockheadersrange(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + if (!m_rpc_server->on_get_block_headers_range(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } + bool first = true; for (auto & header : res.headers) { + if (!first) + std::cout << std::endl; std::cout - << "major version: " << header.major_version << std::endl - << "minor version: " << header.minor_version << std::endl - << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty << std::endl - << "block id: " << header.hash << std::endl - << "previous block id: " << header.prev_hash << std::endl - << "difficulty: " << header.difficulty << ", nonce " << header.nonce << std::endl; + << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty + << ", size: " << header.block_size << std::endl + << "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl + << "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl + << "difficulty: " << header.difficulty << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << std::endl; + first = false; } -*/ return true; } @@ -503,7 +551,7 @@ bool t_rpc_command_executor::set_log_level(int8_t level) { { if (!m_rpc_server->on_set_log_level(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -513,6 +561,34 @@ bool t_rpc_command_executor::set_log_level(int8_t level) { return true; } +bool t_rpc_command_executor::set_log_categories(const std::string &categories) { + cryptonote::COMMAND_RPC_SET_LOG_CATEGORIES::request req; + cryptonote::COMMAND_RPC_SET_LOG_CATEGORIES::response res; + req.categories = categories; + + std::string fail_message = "Unsuccessful"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/set_log_categories", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_set_log_categories(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + tools::success_msg_writer() << "Log categories are now " << categories; + + return true; +} + bool t_rpc_command_executor::print_height() { cryptonote::COMMAND_RPC_GET_HEIGHT::request req; cryptonote::COMMAND_RPC_GET_HEIGHT::response res; @@ -530,7 +606,7 @@ bool t_rpc_command_executor::print_height() { { if (!m_rpc_server->on_get_height(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -560,7 +636,7 @@ bool t_rpc_command_executor::print_block_by_hash(crypto::hash block_hash) { { if (!m_rpc_server->on_get_block(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -591,7 +667,7 @@ bool t_rpc_command_executor::print_block_by_height(uint64_t height) { { if (!m_rpc_server->on_get_block(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -620,7 +696,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { { if (!m_rpc_server->on_get_transactions(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -683,7 +759,7 @@ bool t_rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { { if (!m_rpc_server->on_is_key_image_spent(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -718,7 +794,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() { { if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -737,7 +813,10 @@ bool t_rpc_command_executor::print_transaction_pool_long() { << tx_info.tx_json << std::endl << "blob_size: " << tx_info.blob_size << std::endl << "fee: " << cryptonote::print_money(tx_info.fee) << std::endl + << "fee/byte: " << cryptonote::print_money(tx_info.fee / (double)tx_info.blob_size) << std::endl << "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl + << "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl + << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl << "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl << "max_used_block_height: " << tx_info.max_used_block_height << std::endl << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl @@ -799,7 +878,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() { { if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -816,7 +895,10 @@ bool t_rpc_command_executor::print_transaction_pool_short() { tools::msg_writer() << "id: " << tx_info.id_hash << std::endl << "blob_size: " << tx_info.blob_size << std::endl << "fee: " << cryptonote::print_money(tx_info.fee) << std::endl + << "fee/byte: " << cryptonote::print_money(tx_info.fee / (double)tx_info.blob_size) << std::endl << "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl + << "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl + << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl << "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl << "max_used_block_height: " << tx_info.max_used_block_height << std::endl << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl @@ -828,12 +910,70 @@ bool t_rpc_command_executor::print_transaction_pool_short() { return true; } -bool t_rpc_command_executor::start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet) { +bool t_rpc_command_executor::print_transaction_pool_stats() { + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; + + std::string fail_message = "Problem fetching transaction pool"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/get_transaction_pool", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + size_t n_transactions = res.transactions.size(); + size_t bytes = 0, min_bytes = 0, max_bytes = 0; + size_t n_not_relayed = 0; + uint64_t fee = 0; + uint64_t oldest = 0; + size_t n_10m = 0; + size_t n_failing = 0; + const uint64_t now = time(NULL); + for (const auto &tx_info: res.transactions) + { + bytes += tx_info.blob_size; + if (min_bytes == 0 || tx_info.blob_size < min_bytes) + min_bytes = tx_info.blob_size; + if (tx_info.blob_size > max_bytes) + max_bytes = tx_info.blob_size; + if (!tx_info.relayed) + n_not_relayed++; + fee += tx_info.fee; + if (oldest == 0 || tx_info.receive_time < oldest) + oldest = tx_info.receive_time; + if (tx_info.receive_time < now - 600) + n_10m++; + if (tx_info.last_failed_height) + ++n_failing; + } + size_t avg_bytes = n_transactions ? bytes / n_transactions : 0; + + tools::msg_writer() << n_transactions << " tx(es), " << bytes << " bytes total (min " << min_bytes << ", max " << max_bytes << ", avg " << avg_bytes << ")" << std::endl + << "fees " << cryptonote::print_money(fee) << " (avg " << cryptonote::print_money(n_transactions ? fee / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(bytes ? fee / bytes : 0) << " per byte )" << std::endl + << n_not_relayed << " not relayed, " << n_failing << " failing, " << n_10m << " older than 10 minutes (oldest " << (oldest == 0 ? "-" : get_human_time_ago(oldest, now)) << ")" << std::endl; + + return true; +} + +bool t_rpc_command_executor::start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet, bool do_background_mining, bool ignore_battery) { cryptonote::COMMAND_RPC_START_MINING::request req; cryptonote::COMMAND_RPC_START_MINING::response res; req.miner_address = cryptonote::get_account_address_as_str(testnet, address); req.threads_count = num_threads; - + req.do_background_mining = do_background_mining; + req.ignore_battery = ignore_battery; + std::string fail_message = "Mining did not start"; if (m_is_rpc) @@ -847,7 +987,7 @@ bool t_rpc_command_executor::start_mining(cryptonote::account_public_address add { if (!m_rpc_server->on_start_mining(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -872,7 +1012,7 @@ bool t_rpc_command_executor::stop_mining() { { if (!m_rpc_server->on_stop_mining(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -913,7 +1053,7 @@ bool t_rpc_command_executor::stop_daemon() { if (!m_rpc_server->on_stop_daemon(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -989,34 +1129,6 @@ bool t_rpc_command_executor::set_limit_down(int limit) return true; } -bool t_rpc_command_executor::fast_exit() -{ - cryptonote::COMMAND_RPC_FAST_EXIT::request req; - cryptonote::COMMAND_RPC_FAST_EXIT::response res; - - std::string fail_message = "Daemon did not stop"; - - if (m_is_rpc) - { - if (!m_rpc_client->rpc_request(req, res, "/fast_exit", fail_message.c_str())) - { - return true; - } - } - - else - { - if (!m_rpc_server->on_fast_exit(req, res) || res.status != CORE_RPC_STATUS_OK) - { - tools::fail_msg_writer() << fail_message.c_str(); - return true; - } - } - - tools::success_msg_writer() << "Daemon stopped"; - return true; -} - bool t_rpc_command_executor::out_peers(uint64_t limit) { cryptonote::COMMAND_RPC_OUT_PEERS::request req; @@ -1039,7 +1151,7 @@ bool t_rpc_command_executor::out_peers(uint64_t limit) { if (!m_rpc_server->on_out_peers(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1067,7 +1179,7 @@ bool t_rpc_command_executor::start_save_graph() { if (!m_rpc_server->on_start_save_graph(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1093,7 +1205,7 @@ bool t_rpc_command_executor::stop_save_graph() { if (!m_rpc_server->on_stop_save_graph(req, res) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1120,7 +1232,7 @@ bool t_rpc_command_executor::hard_fork_info(uint8_t version) { if (!m_rpc_server->on_hard_fork_info(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1151,7 +1263,7 @@ bool t_rpc_command_executor::print_bans() { if (!m_rpc_server->on_get_bans(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1193,7 +1305,7 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) { if (!m_rpc_server->on_set_bans(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1229,7 +1341,7 @@ bool t_rpc_command_executor::unban(const std::string &ip) { if (!m_rpc_server->on_set_bans(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1256,9 +1368,9 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid) } else { - if (!m_rpc_server->on_flush_txpool(req, res, error_resp)) + if (!m_rpc_server->on_flush_txpool(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } @@ -1275,6 +1387,8 @@ bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_c req.min_count = min_count; req.max_count = max_count; + req.unlocked = false; + req.recent_cutoff = 0; if (m_is_rpc) { @@ -1285,22 +1399,281 @@ bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_c } else { - if (!m_rpc_server->on_get_output_histogram(req, res, error_resp)) + if (!m_rpc_server->on_get_output_histogram(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) { - tools::fail_msg_writer() << fail_message.c_str(); + tools::fail_msg_writer() << make_error(fail_message, res.status); return true; } } std::sort(res.histogram.begin(), res.histogram.end(), - [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.instances < e2.instances; }); + [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.total_instances < e2.total_instances; }); for (const auto &e: res.histogram) { - tools::msg_writer() << e.instances << " " << cryptonote::print_money(e.amount); + tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); + } + + return true; +} + +bool t_rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count) +{ + cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::request req; + cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::response res; + epee::json_rpc::error error_resp; + + req.height = height; + req.count = count; + + std::string fail_message = "Unsuccessful"; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "get_coinbase_tx_sum", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_coinbase_tx_sum(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + tools::msg_writer() << "Sum of coinbase transactions between block heights [" + << height << ", " << (height + count) << ") is " + << cryptonote::print_money(res.emission_amount + res.fee_amount) << " " + << "consisting of " << cryptonote::print_money(res.emission_amount) + << " in emissions, and " << cryptonote::print_money(res.fee_amount) << " in fees"; + return true; +} + +bool t_rpc_command_executor::alt_chain_info() +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::request req; + cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::response res; + epee::json_rpc::error error_resp; + + std::string fail_message = "Unsuccessful"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(ireq, ires, "/getinfo", fail_message.c_str())) + { + return true; + } + if (!m_rpc_client->json_rpc_request(req, res, "get_alternate_chains", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_info(ireq, ires) || ires.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, ires.status); + return true; + } + if (!m_rpc_server->on_get_alternate_chains(req, res, error_resp)) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; } + } + + tools::msg_writer() << boost::lexical_cast<std::string>(res.chains.size()) << " alternate chains found:"; + for (const auto chain: res.chains) + { + uint64_t start_height = (chain.height - chain.length + 1); + tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) + << " deep), diff " << chain.difficulty << ": " << chain.block_hash; + } + return true; +} + +bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request bhreq; + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response bhres; + cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request fereq; + cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response feres; + epee::json_rpc::error error_resp; + + std::string fail_message = "Problem fetching info"; + + fereq.grace_blocks = 0; + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(ireq, ires, "/getinfo", fail_message.c_str())) + { + return true; + } + if (!m_rpc_client->json_rpc_request(fereq, feres, "get_fee_estimate", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_info(ireq, ires) || ires.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, ires.status); + return true; + } + if (!m_rpc_server->on_get_per_kb_fee_estimate(fereq, feres, error_resp) || feres.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, feres.status); + return true; + } + } + tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.difficulty << ", cum. diff " << ires.cumulative_difficulty + << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee) << "/kB"; + + if (nblocks > 0) + { + if (nblocks > ires.height) + nblocks = ires.height; + + bhreq.start_height = ires.height - nblocks; + bhreq.end_height = ires.height - 1; + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(bhreq, bhres, "getblockheadersrange", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_block_headers_range(bhreq, bhres, error_resp) || bhres.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, bhres.status); + return true; + } + } + + double avgdiff = 0; + double avgnumtxes = 0; + double avgreward = 0; + std::vector<uint64_t> sizes; + sizes.reserve(nblocks); + std::vector<unsigned> major_versions(256, 0), minor_versions(256, 0); + for (const auto &bhr: bhres.headers) + { + avgdiff += bhr.difficulty; + avgnumtxes += bhr.num_txes; + avgreward += bhr.reward; + sizes.push_back(bhr.block_size); + static_assert(sizeof(bhr.major_version) == 1, "major_version expected to be uint8_t"); + static_assert(sizeof(bhr.minor_version) == 1, "major_version expected to be uint8_t"); + major_versions[(unsigned)bhr.major_version]++; + minor_versions[(unsigned)bhr.minor_version]++; + } + avgdiff /= nblocks; + avgnumtxes /= nblocks; + avgreward /= nblocks; + uint64_t median_block_size = epee::misc_utils::median(sizes); + tools::msg_writer() << "Last " << nblocks << ": avg. diff " << (uint64_t)avgdiff << ", avg num txes " << avgnumtxes + << ", avg. reward " << cryptonote::print_money(avgreward) << ", median block size " << median_block_size; + + unsigned int max_major = 256, max_minor = 256; + while (max_major > 0 && !major_versions[--max_major]); + while (max_minor > 0 && !minor_versions[--max_minor]); + std::string s = ""; + for (unsigned n = 0; n <= max_major; ++n) + if (major_versions[n]) + s += (s.empty() ? "" : ", ") + boost::lexical_cast<std::string>(major_versions[n]) + std::string(" v") + boost::lexical_cast<std::string>(n); + tools::msg_writer() << "Block versions: " << s; + s = ""; + for (unsigned n = 0; n <= max_minor; ++n) + if (minor_versions[n]) + s += (s.empty() ? "" : ", ") + boost::lexical_cast<std::string>(minor_versions[n]) + std::string(" v") + boost::lexical_cast<std::string>(n); + tools::msg_writer() << "Voting for: " << s; + } + return true; +} + +bool t_rpc_command_executor::update(const std::string &command) +{ + cryptonote::COMMAND_RPC_UPDATE::request req; + cryptonote::COMMAND_RPC_UPDATE::response res; + epee::json_rpc::error error_resp; + + std::string fail_message = "Problem fetching info"; + + req.command = command; + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/update", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_update(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + if (!res.update) + { + tools::msg_writer() << "No update available"; return true; + } + + tools::msg_writer() << "Update available: v" << res.version << ": " << res.user_uri << ", hash " << res.hash; + if (command == "check") + return true; + + if (!res.path.empty()) + tools::msg_writer() << "Update downloaded to: " << res.path; + else + tools::msg_writer() << "Update download failed: " << res.status; + if (command == "download") + return true; + + tools::msg_writer() << "'update' not implemented yet"; + + return true; } +bool t_rpc_command_executor::relay_tx(const std::string &txid) +{ + cryptonote::COMMAND_RPC_RELAY_TX::request req; + cryptonote::COMMAND_RPC_RELAY_TX::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + req.txids.push_back(txid); + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "relay_tx", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_relay_tx(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + return true; +} }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index afb2b5f36..3f551bd14 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -38,6 +38,9 @@ #pragma once +#include <boost/optional/optional_fwd.hpp> + +#include "common/common_fwd.h" #include "common/rpc_client.h" #include "misc_log_ex.h" #include "cryptonote_core/cryptonote_core.h" @@ -45,6 +48,9 @@ #include "p2p/net_node.h" #include "rpc/core_rpc_server.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon" + namespace daemonize { class t_rpc_command_executor final { @@ -57,7 +63,7 @@ public: t_rpc_command_executor( uint32_t ip , uint16_t port - , const std::string &user_agent + , const boost::optional<tools::login>& user , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); @@ -66,6 +72,8 @@ public: bool print_peer_list(); + bool print_peer_list_stats(); + bool save_blockchain(); bool show_hash_rate(); @@ -82,6 +90,8 @@ public: bool set_log_level(int8_t level); + bool set_log_categories(const std::string &categories); + bool print_height(); bool print_block_by_hash(crypto::hash block_hash); @@ -96,7 +106,9 @@ public: bool print_transaction_pool_short(); - bool start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet); + bool print_transaction_pool_stats(); + + bool start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet, bool do_background_mining = false, bool ignore_battery = false); bool stop_mining(); @@ -116,8 +128,6 @@ public: bool set_limit_down(int limit); - bool fast_exit(); - bool out_peers(uint64_t limit); bool start_save_graph(); @@ -135,6 +145,16 @@ public: bool flush_txpool(const std::string &txid); bool output_histogram(uint64_t min_count, uint64_t max_count); + + bool print_coinbase_tx_sum(uint64_t height, uint64_t count); + + bool alt_chain_info(); + + bool print_blockchain_dynamic_stats(uint64_t nblocks); + + bool update(const std::string &command); + + bool relay_tx(const std::string &txid); }; } // namespace daemonize diff --git a/src/daemonizer/CMakeLists.txt b/src/daemonizer/CMakeLists.txt index eee2c5091..c8cb1b445 100644 --- a/src/daemonizer/CMakeLists.txt +++ b/src/daemonizer/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -54,9 +54,9 @@ else() ) endif() -bitmonero_private_headers(daemonizer +monero_private_headers(daemonizer ${daemonizer_private_headers}) -bitmonero_add_library(daemonizer +monero_add_library(daemonizer ${daemonizer_sources} ${daemonizer_headers} ${daemonizer_private_headers}) diff --git a/src/daemonizer/daemonizer.h b/src/daemonizer/daemonizer.h index 3e30d85ee..5f53d062b 100644 --- a/src/daemonizer/daemonizer.h +++ b/src/daemonizer/daemonizer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl index 926f0a3ac..f8be15dda 100644 --- a/src/daemonizer/posix_daemonizer.inl +++ b/src/daemonizer/posix_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -43,6 +43,10 @@ namespace daemonizer "detach" , "Run as daemon" }; + const command_line::arg_descriptor<bool> arg_non_interactive = { + "non-interactive" + , "Run non-interactive" + }; } inline void init_options( @@ -51,6 +55,7 @@ namespace daemonizer ) { command_line::add_arg(normal_options, arg_detach); + command_line::add_arg(normal_options, arg_non_interactive); } inline boost::filesystem::path get_default_data_dir() @@ -79,6 +84,10 @@ namespace daemonizer auto daemon = executor.create_daemon(vm); return daemon.run(); } + else if (command_line::has_arg(vm, arg_non_interactive)) + { + return executor.run_non_interactive(vm); + } else { //LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL); diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h index ef627a43e..459417d25 100644 --- a/src/daemonizer/posix_fork.h +++ b/src/daemonizer/posix_fork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index d8023944e..012cd1e67 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp index f8cc0c6c7..d540f5bf8 100644 --- a/src/daemonizer/windows_service.cpp +++ b/src/daemonizer/windows_service.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service.h b/src/daemonizer/windows_service.h index 2712d13a3..070434b04 100644 --- a/src/daemonizer/windows_service.h +++ b/src/daemonizer/windows_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service_runner.h b/src/daemonizer/windows_service_runner.h index f4258a215..528d13a53 100644 --- a/src/daemonizer/windows_service_runner.h +++ b/src/daemonizer/windows_service_runner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index 34670f32d..c521a0932 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -33,23 +33,28 @@ set(mnemonics_headers) set(mnemonics_private_headers electrum-words.h + chinese_simplified.h english.h + dutch.h + french.h german.h italian.h japanese.h language_base.h - old_english.h + english_old.h portuguese.h russian.h singleton.h spanish.h) -bitmonero_private_headers(mnemonics +monero_private_headers(mnemonics ${mnemonics_private_headers}) -bitmonero_add_library(mnemonics +monero_add_library(mnemonics ${mnemonics_sources} ${mnemonics_headers} ${mnemonics_private_headers}) target_link_libraries(mnemonics + PUBLIC + ${Boost_SYSTEM_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/mnemonics/chinese_simplified.h b/src/mnemonics/chinese_simplified.h new file mode 100644 index 000000000..a85bbe963 --- /dev/null +++ b/src/mnemonics/chinese_simplified.h @@ -0,0 +1,1709 @@ +// Word list originally created by dabura667 and released under The MIT License (MIT)
+//
+// The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Code surrounding the word list is Copyright (c) 2014-2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/*!
+ * \file chinese_simplified.h
+ *
+ * \brief Simplified Chinese word list and map.
+ */
+
+#ifndef CHINESE_SIMPLIFIED_H
+#define CHINESE_SIMPLIFIED_H
+
+#include <vector>
+#include <unordered_map>
+#include "language_base.h"
+#include <string>
+
+/*!
+ * \namespace Language
+ * \brief Mnemonic language related namespace.
+ */
+namespace Language
+{
+ class Chinese_Simplified: public Base
+ {
+ public:
+ Chinese_Simplified(): Base("简体中文 (中国)", std::vector<std::string>({
+ "的",
+ "一",
+ "是",
+ "在",
+ "不",
+ "了",
+ "有",
+ "和",
+ "人",
+ "这",
+ "中",
+ "大",
+ "为",
+ "上",
+ "个",
+ "国",
+ "我",
+ "以",
+ "要",
+ "他",
+ "时",
+ "来",
+ "用",
+ "们",
+ "生",
+ "到",
+ "作",
+ "地",
+ "于",
+ "出",
+ "就",
+ "分",
+ "对",
+ "成",
+ "会",
+ "可",
+ "主",
+ "发",
+ "年",
+ "动",
+ "同",
+ "工",
+ "也",
+ "能",
+ "下",
+ "过",
+ "子",
+ "说",
+ "产",
+ "种",
+ "面",
+ "而",
+ "方",
+ "后",
+ "多",
+ "定",
+ "行",
+ "学",
+ "法",
+ "所",
+ "民",
+ "得",
+ "经",
+ "十",
+ "三",
+ "之",
+ "进",
+ "着",
+ "等",
+ "部",
+ "度",
+ "家",
+ "电",
+ "力",
+ "里",
+ "如",
+ "水",
+ "化",
+ "高",
+ "自",
+ "二",
+ "理",
+ "起",
+ "小",
+ "物",
+ "现",
+ "实",
+ "加",
+ "量",
+ "都",
+ "两",
+ "体",
+ "制",
+ "机",
+ "当",
+ "使",
+ "点",
+ "从",
+ "业",
+ "本",
+ "去",
+ "把",
+ "性",
+ "好",
+ "应",
+ "开",
+ "它",
+ "合",
+ "还",
+ "因",
+ "由",
+ "其",
+ "些",
+ "然",
+ "前",
+ "外",
+ "天",
+ "政",
+ "四",
+ "日",
+ "那",
+ "社",
+ "义",
+ "事",
+ "平",
+ "形",
+ "相",
+ "全",
+ "表",
+ "间",
+ "样",
+ "与",
+ "关",
+ "各",
+ "重",
+ "新",
+ "线",
+ "内",
+ "数",
+ "正",
+ "心",
+ "反",
+ "你",
+ "明",
+ "看",
+ "原",
+ "又",
+ "么",
+ "利",
+ "比",
+ "或",
+ "但",
+ "质",
+ "气",
+ "第",
+ "向",
+ "道",
+ "命",
+ "此",
+ "变",
+ "条",
+ "只",
+ "没",
+ "结",
+ "解",
+ "问",
+ "意",
+ "建",
+ "月",
+ "公",
+ "无",
+ "系",
+ "军",
+ "很",
+ "情",
+ "者",
+ "最",
+ "立",
+ "代",
+ "想",
+ "已",
+ "通",
+ "并",
+ "提",
+ "直",
+ "题",
+ "党",
+ "程",
+ "展",
+ "五",
+ "果",
+ "料",
+ "象",
+ "员",
+ "革",
+ "位",
+ "入",
+ "常",
+ "文",
+ "总",
+ "次",
+ "品",
+ "式",
+ "活",
+ "设",
+ "及",
+ "管",
+ "特",
+ "件",
+ "长",
+ "求",
+ "老",
+ "头",
+ "基",
+ "资",
+ "边",
+ "流",
+ "路",
+ "级",
+ "少",
+ "图",
+ "山",
+ "统",
+ "接",
+ "知",
+ "较",
+ "将",
+ "组",
+ "见",
+ "计",
+ "别",
+ "她",
+ "手",
+ "角",
+ "期",
+ "根",
+ "论",
+ "运",
+ "农",
+ "指",
+ "几",
+ "九",
+ "区",
+ "强",
+ "放",
+ "决",
+ "西",
+ "被",
+ "干",
+ "做",
+ "必",
+ "战",
+ "先",
+ "回",
+ "则",
+ "任",
+ "取",
+ "据",
+ "处",
+ "队",
+ "南",
+ "给",
+ "色",
+ "光",
+ "门",
+ "即",
+ "保",
+ "治",
+ "北",
+ "造",
+ "百",
+ "规",
+ "热",
+ "领",
+ "七",
+ "海",
+ "口",
+ "东",
+ "导",
+ "器",
+ "压",
+ "志",
+ "世",
+ "金",
+ "增",
+ "争",
+ "济",
+ "阶",
+ "油",
+ "思",
+ "术",
+ "极",
+ "交",
+ "受",
+ "联",
+ "什",
+ "认",
+ "六",
+ "共",
+ "权",
+ "收",
+ "证",
+ "改",
+ "清",
+ "美",
+ "再",
+ "采",
+ "转",
+ "更",
+ "单",
+ "风",
+ "切",
+ "打",
+ "白",
+ "教",
+ "速",
+ "花",
+ "带",
+ "安",
+ "场",
+ "身",
+ "车",
+ "例",
+ "真",
+ "务",
+ "具",
+ "万",
+ "每",
+ "目",
+ "至",
+ "达",
+ "走",
+ "积",
+ "示",
+ "议",
+ "声",
+ "报",
+ "斗",
+ "完",
+ "类",
+ "八",
+ "离",
+ "华",
+ "名",
+ "确",
+ "才",
+ "科",
+ "张",
+ "信",
+ "马",
+ "节",
+ "话",
+ "米",
+ "整",
+ "空",
+ "元",
+ "况",
+ "今",
+ "集",
+ "温",
+ "传",
+ "土",
+ "许",
+ "步",
+ "群",
+ "广",
+ "石",
+ "记",
+ "需",
+ "段",
+ "研",
+ "界",
+ "拉",
+ "林",
+ "律",
+ "叫",
+ "且",
+ "究",
+ "观",
+ "越",
+ "织",
+ "装",
+ "影",
+ "算",
+ "低",
+ "持",
+ "音",
+ "众",
+ "书",
+ "布",
+ "复",
+ "容",
+ "儿",
+ "须",
+ "际",
+ "商",
+ "非",
+ "验",
+ "连",
+ "断",
+ "深",
+ "难",
+ "近",
+ "矿",
+ "千",
+ "周",
+ "委",
+ "素",
+ "技",
+ "备",
+ "半",
+ "办",
+ "青",
+ "省",
+ "列",
+ "习",
+ "响",
+ "约",
+ "支",
+ "般",
+ "史",
+ "感",
+ "劳",
+ "便",
+ "团",
+ "往",
+ "酸",
+ "历",
+ "市",
+ "克",
+ "何",
+ "除",
+ "消",
+ "构",
+ "府",
+ "称",
+ "太",
+ "准",
+ "精",
+ "值",
+ "号",
+ "率",
+ "族",
+ "维",
+ "划",
+ "选",
+ "标",
+ "写",
+ "存",
+ "候",
+ "毛",
+ "亲",
+ "快",
+ "效",
+ "斯",
+ "院",
+ "查",
+ "江",
+ "型",
+ "眼",
+ "王",
+ "按",
+ "格",
+ "养",
+ "易",
+ "置",
+ "派",
+ "层",
+ "片",
+ "始",
+ "却",
+ "专",
+ "状",
+ "育",
+ "厂",
+ "京",
+ "识",
+ "适",
+ "属",
+ "圆",
+ "包",
+ "火",
+ "住",
+ "调",
+ "满",
+ "县",
+ "局",
+ "照",
+ "参",
+ "红",
+ "细",
+ "引",
+ "听",
+ "该",
+ "铁",
+ "价",
+ "严",
+ "首",
+ "底",
+ "液",
+ "官",
+ "德",
+ "随",
+ "病",
+ "苏",
+ "失",
+ "尔",
+ "死",
+ "讲",
+ "配",
+ "女",
+ "黄",
+ "推",
+ "显",
+ "谈",
+ "罪",
+ "神",
+ "艺",
+ "呢",
+ "席",
+ "含",
+ "企",
+ "望",
+ "密",
+ "批",
+ "营",
+ "项",
+ "防",
+ "举",
+ "球",
+ "英",
+ "氧",
+ "势",
+ "告",
+ "李",
+ "台",
+ "落",
+ "木",
+ "帮",
+ "轮",
+ "破",
+ "亚",
+ "师",
+ "围",
+ "注",
+ "远",
+ "字",
+ "材",
+ "排",
+ "供",
+ "河",
+ "态",
+ "封",
+ "另",
+ "施",
+ "减",
+ "树",
+ "溶",
+ "怎",
+ "止",
+ "案",
+ "言",
+ "士",
+ "均",
+ "武",
+ "固",
+ "叶",
+ "鱼",
+ "波",
+ "视",
+ "仅",
+ "费",
+ "紧",
+ "爱",
+ "左",
+ "章",
+ "早",
+ "朝",
+ "害",
+ "续",
+ "轻",
+ "服",
+ "试",
+ "食",
+ "充",
+ "兵",
+ "源",
+ "判",
+ "护",
+ "司",
+ "足",
+ "某",
+ "练",
+ "差",
+ "致",
+ "板",
+ "田",
+ "降",
+ "黑",
+ "犯",
+ "负",
+ "击",
+ "范",
+ "继",
+ "兴",
+ "似",
+ "余",
+ "坚",
+ "曲",
+ "输",
+ "修",
+ "故",
+ "城",
+ "夫",
+ "够",
+ "送",
+ "笔",
+ "船",
+ "占",
+ "右",
+ "财",
+ "吃",
+ "富",
+ "春",
+ "职",
+ "觉",
+ "汉",
+ "画",
+ "功",
+ "巴",
+ "跟",
+ "虽",
+ "杂",
+ "飞",
+ "检",
+ "吸",
+ "助",
+ "升",
+ "阳",
+ "互",
+ "初",
+ "创",
+ "抗",
+ "考",
+ "投",
+ "坏",
+ "策",
+ "古",
+ "径",
+ "换",
+ "未",
+ "跑",
+ "留",
+ "钢",
+ "曾",
+ "端",
+ "责",
+ "站",
+ "简",
+ "述",
+ "钱",
+ "副",
+ "尽",
+ "帝",
+ "射",
+ "草",
+ "冲",
+ "承",
+ "独",
+ "令",
+ "限",
+ "阿",
+ "宣",
+ "环",
+ "双",
+ "请",
+ "超",
+ "微",
+ "让",
+ "控",
+ "州",
+ "良",
+ "轴",
+ "找",
+ "否",
+ "纪",
+ "益",
+ "依",
+ "优",
+ "顶",
+ "础",
+ "载",
+ "倒",
+ "房",
+ "突",
+ "坐",
+ "粉",
+ "敌",
+ "略",
+ "客",
+ "袁",
+ "冷",
+ "胜",
+ "绝",
+ "析",
+ "块",
+ "剂",
+ "测",
+ "丝",
+ "协",
+ "诉",
+ "念",
+ "陈",
+ "仍",
+ "罗",
+ "盐",
+ "友",
+ "洋",
+ "错",
+ "苦",
+ "夜",
+ "刑",
+ "移",
+ "频",
+ "逐",
+ "靠",
+ "混",
+ "母",
+ "短",
+ "皮",
+ "终",
+ "聚",
+ "汽",
+ "村",
+ "云",
+ "哪",
+ "既",
+ "距",
+ "卫",
+ "停",
+ "烈",
+ "央",
+ "察",
+ "烧",
+ "迅",
+ "境",
+ "若",
+ "印",
+ "洲",
+ "刻",
+ "括",
+ "激",
+ "孔",
+ "搞",
+ "甚",
+ "室",
+ "待",
+ "核",
+ "校",
+ "散",
+ "侵",
+ "吧",
+ "甲",
+ "游",
+ "久",
+ "菜",
+ "味",
+ "旧",
+ "模",
+ "湖",
+ "货",
+ "损",
+ "预",
+ "阻",
+ "毫",
+ "普",
+ "稳",
+ "乙",
+ "妈",
+ "植",
+ "息",
+ "扩",
+ "银",
+ "语",
+ "挥",
+ "酒",
+ "守",
+ "拿",
+ "序",
+ "纸",
+ "医",
+ "缺",
+ "雨",
+ "吗",
+ "针",
+ "刘",
+ "啊",
+ "急",
+ "唱",
+ "误",
+ "训",
+ "愿",
+ "审",
+ "附",
+ "获",
+ "茶",
+ "鲜",
+ "粮",
+ "斤",
+ "孩",
+ "脱",
+ "硫",
+ "肥",
+ "善",
+ "龙",
+ "演",
+ "父",
+ "渐",
+ "血",
+ "欢",
+ "械",
+ "掌",
+ "歌",
+ "沙",
+ "刚",
+ "攻",
+ "谓",
+ "盾",
+ "讨",
+ "晚",
+ "粒",
+ "乱",
+ "燃",
+ "矛",
+ "乎",
+ "杀",
+ "药",
+ "宁",
+ "鲁",
+ "贵",
+ "钟",
+ "煤",
+ "读",
+ "班",
+ "伯",
+ "香",
+ "介",
+ "迫",
+ "句",
+ "丰",
+ "培",
+ "握",
+ "兰",
+ "担",
+ "弦",
+ "蛋",
+ "沉",
+ "假",
+ "穿",
+ "执",
+ "答",
+ "乐",
+ "谁",
+ "顺",
+ "烟",
+ "缩",
+ "征",
+ "脸",
+ "喜",
+ "松",
+ "脚",
+ "困",
+ "异",
+ "免",
+ "背",
+ "星",
+ "福",
+ "买",
+ "染",
+ "井",
+ "概",
+ "慢",
+ "怕",
+ "磁",
+ "倍",
+ "祖",
+ "皇",
+ "促",
+ "静",
+ "补",
+ "评",
+ "翻",
+ "肉",
+ "践",
+ "尼",
+ "衣",
+ "宽",
+ "扬",
+ "棉",
+ "希",
+ "伤",
+ "操",
+ "垂",
+ "秋",
+ "宜",
+ "氢",
+ "套",
+ "督",
+ "振",
+ "架",
+ "亮",
+ "末",
+ "宪",
+ "庆",
+ "编",
+ "牛",
+ "触",
+ "映",
+ "雷",
+ "销",
+ "诗",
+ "座",
+ "居",
+ "抓",
+ "裂",
+ "胞",
+ "呼",
+ "娘",
+ "景",
+ "威",
+ "绿",
+ "晶",
+ "厚",
+ "盟",
+ "衡",
+ "鸡",
+ "孙",
+ "延",
+ "危",
+ "胶",
+ "屋",
+ "乡",
+ "临",
+ "陆",
+ "顾",
+ "掉",
+ "呀",
+ "灯",
+ "岁",
+ "措",
+ "束",
+ "耐",
+ "剧",
+ "玉",
+ "赵",
+ "跳",
+ "哥",
+ "季",
+ "课",
+ "凯",
+ "胡",
+ "额",
+ "款",
+ "绍",
+ "卷",
+ "齐",
+ "伟",
+ "蒸",
+ "殖",
+ "永",
+ "宗",
+ "苗",
+ "川",
+ "炉",
+ "岩",
+ "弱",
+ "零",
+ "杨",
+ "奏",
+ "沿",
+ "露",
+ "杆",
+ "探",
+ "滑",
+ "镇",
+ "饭",
+ "浓",
+ "航",
+ "怀",
+ "赶",
+ "库",
+ "夺",
+ "伊",
+ "灵",
+ "税",
+ "途",
+ "灭",
+ "赛",
+ "归",
+ "召",
+ "鼓",
+ "播",
+ "盘",
+ "裁",
+ "险",
+ "康",
+ "唯",
+ "录",
+ "菌",
+ "纯",
+ "借",
+ "糖",
+ "盖",
+ "横",
+ "符",
+ "私",
+ "努",
+ "堂",
+ "域",
+ "枪",
+ "润",
+ "幅",
+ "哈",
+ "竟",
+ "熟",
+ "虫",
+ "泽",
+ "脑",
+ "壤",
+ "碳",
+ "欧",
+ "遍",
+ "侧",
+ "寨",
+ "敢",
+ "彻",
+ "虑",
+ "斜",
+ "薄",
+ "庭",
+ "纳",
+ "弹",
+ "饲",
+ "伸",
+ "折",
+ "麦",
+ "湿",
+ "暗",
+ "荷",
+ "瓦",
+ "塞",
+ "床",
+ "筑",
+ "恶",
+ "户",
+ "访",
+ "塔",
+ "奇",
+ "透",
+ "梁",
+ "刀",
+ "旋",
+ "迹",
+ "卡",
+ "氯",
+ "遇",
+ "份",
+ "毒",
+ "泥",
+ "退",
+ "洗",
+ "摆",
+ "灰",
+ "彩",
+ "卖",
+ "耗",
+ "夏",
+ "择",
+ "忙",
+ "铜",
+ "献",
+ "硬",
+ "予",
+ "繁",
+ "圈",
+ "雪",
+ "函",
+ "亦",
+ "抽",
+ "篇",
+ "阵",
+ "阴",
+ "丁",
+ "尺",
+ "追",
+ "堆",
+ "雄",
+ "迎",
+ "泛",
+ "爸",
+ "楼",
+ "避",
+ "谋",
+ "吨",
+ "野",
+ "猪",
+ "旗",
+ "累",
+ "偏",
+ "典",
+ "馆",
+ "索",
+ "秦",
+ "脂",
+ "潮",
+ "爷",
+ "豆",
+ "忽",
+ "托",
+ "惊",
+ "塑",
+ "遗",
+ "愈",
+ "朱",
+ "替",
+ "纤",
+ "粗",
+ "倾",
+ "尚",
+ "痛",
+ "楚",
+ "谢",
+ "奋",
+ "购",
+ "磨",
+ "君",
+ "池",
+ "旁",
+ "碎",
+ "骨",
+ "监",
+ "捕",
+ "弟",
+ "暴",
+ "割",
+ "贯",
+ "殊",
+ "释",
+ "词",
+ "亡",
+ "壁",
+ "顿",
+ "宝",
+ "午",
+ "尘",
+ "闻",
+ "揭",
+ "炮",
+ "残",
+ "冬",
+ "桥",
+ "妇",
+ "警",
+ "综",
+ "招",
+ "吴",
+ "付",
+ "浮",
+ "遭",
+ "徐",
+ "您",
+ "摇",
+ "谷",
+ "赞",
+ "箱",
+ "隔",
+ "订",
+ "男",
+ "吹",
+ "园",
+ "纷",
+ "唐",
+ "败",
+ "宋",
+ "玻",
+ "巨",
+ "耕",
+ "坦",
+ "荣",
+ "闭",
+ "湾",
+ "键",
+ "凡",
+ "驻",
+ "锅",
+ "救",
+ "恩",
+ "剥",
+ "凝",
+ "碱",
+ "齿",
+ "截",
+ "炼",
+ "麻",
+ "纺",
+ "禁",
+ "废",
+ "盛",
+ "版",
+ "缓",
+ "净",
+ "睛",
+ "昌",
+ "婚",
+ "涉",
+ "筒",
+ "嘴",
+ "插",
+ "岸",
+ "朗",
+ "庄",
+ "街",
+ "藏",
+ "姑",
+ "贸",
+ "腐",
+ "奴",
+ "啦",
+ "惯",
+ "乘",
+ "伙",
+ "恢",
+ "匀",
+ "纱",
+ "扎",
+ "辩",
+ "耳",
+ "彪",
+ "臣",
+ "亿",
+ "璃",
+ "抵",
+ "脉",
+ "秀",
+ "萨",
+ "俄",
+ "网",
+ "舞",
+ "店",
+ "喷",
+ "纵",
+ "寸",
+ "汗",
+ "挂",
+ "洪",
+ "贺",
+ "闪",
+ "柬",
+ "爆",
+ "烯",
+ "津",
+ "稻",
+ "墙",
+ "软",
+ "勇",
+ "像",
+ "滚",
+ "厘",
+ "蒙",
+ "芳",
+ "肯",
+ "坡",
+ "柱",
+ "荡",
+ "腿",
+ "仪",
+ "旅",
+ "尾",
+ "轧",
+ "冰",
+ "贡",
+ "登",
+ "黎",
+ "削",
+ "钻",
+ "勒",
+ "逃",
+ "障",
+ "氨",
+ "郭",
+ "峰",
+ "币",
+ "港",
+ "伏",
+ "轨",
+ "亩",
+ "毕",
+ "擦",
+ "莫",
+ "刺",
+ "浪",
+ "秘",
+ "援",
+ "株",
+ "健",
+ "售",
+ "股",
+ "岛",
+ "甘",
+ "泡",
+ "睡",
+ "童",
+ "铸",
+ "汤",
+ "阀",
+ "休",
+ "汇",
+ "舍",
+ "牧",
+ "绕",
+ "炸",
+ "哲",
+ "磷",
+ "绩",
+ "朋",
+ "淡",
+ "尖",
+ "启",
+ "陷",
+ "柴",
+ "呈",
+ "徒",
+ "颜",
+ "泪",
+ "稍",
+ "忘",
+ "泵",
+ "蓝",
+ "拖",
+ "洞",
+ "授",
+ "镜",
+ "辛",
+ "壮",
+ "锋",
+ "贫",
+ "虚",
+ "弯",
+ "摩",
+ "泰",
+ "幼",
+ "廷",
+ "尊",
+ "窗",
+ "纲",
+ "弄",
+ "隶",
+ "疑",
+ "氏",
+ "宫",
+ "姐",
+ "震",
+ "瑞",
+ "怪",
+ "尤",
+ "琴",
+ "循",
+ "描",
+ "膜",
+ "违",
+ "夹",
+ "腰",
+ "缘",
+ "珠",
+ "穷",
+ "森",
+ "枝",
+ "竹",
+ "沟",
+ "催",
+ "绳",
+ "忆",
+ "邦",
+ "剩",
+ "幸",
+ "浆",
+ "栏",
+ "拥",
+ "牙",
+ "贮",
+ "礼",
+ "滤",
+ "钠",
+ "纹",
+ "罢",
+ "拍",
+ "咱",
+ "喊",
+ "袖",
+ "埃",
+ "勤",
+ "罚",
+ "焦",
+ "潜",
+ "伍",
+ "墨",
+ "欲",
+ "缝",
+ "姓",
+ "刊",
+ "饱",
+ "仿",
+ "奖",
+ "铝",
+ "鬼",
+ "丽",
+ "跨",
+ "默",
+ "挖",
+ "链",
+ "扫",
+ "喝",
+ "袋",
+ "炭",
+ "污",
+ "幕",
+ "诸",
+ "弧",
+ "励",
+ "梅",
+ "奶",
+ "洁",
+ "灾",
+ "舟",
+ "鉴",
+ "苯",
+ "讼",
+ "抱",
+ "毁",
+ "懂",
+ "寒",
+ "智",
+ "埔",
+ "寄",
+ "届",
+ "跃",
+ "渡",
+ "挑",
+ "丹",
+ "艰",
+ "贝",
+ "碰",
+ "拔",
+ "爹",
+ "戴",
+ "码",
+ "梦",
+ "芽",
+ "熔",
+ "赤",
+ "渔",
+ "哭",
+ "敬",
+ "颗",
+ "奔",
+ "铅",
+ "仲",
+ "虎",
+ "稀",
+ "妹",
+ "乏",
+ "珍",
+ "申",
+ "桌",
+ "遵",
+ "允",
+ "隆",
+ "螺",
+ "仓",
+ "魏",
+ "锐",
+ "晓",
+ "氮",
+ "兼",
+ "隐",
+ "碍",
+ "赫",
+ "拨",
+ "忠",
+ "肃",
+ "缸",
+ "牵",
+ "抢",
+ "博",
+ "巧",
+ "壳",
+ "兄",
+ "杜",
+ "讯",
+ "诚",
+ "碧",
+ "祥",
+ "柯",
+ "页",
+ "巡",
+ "矩",
+ "悲",
+ "灌",
+ "龄",
+ "伦",
+ "票",
+ "寻",
+ "桂",
+ "铺",
+ "圣",
+ "恐",
+ "恰",
+ "郑",
+ "趣",
+ "抬",
+ "荒",
+ "腾",
+ "贴",
+ "柔",
+ "滴",
+ "猛",
+ "阔",
+ "辆",
+ "妻",
+ "填",
+ "撤",
+ "储",
+ "签",
+ "闹",
+ "扰",
+ "紫",
+ "砂",
+ "递",
+ "戏",
+ "吊",
+ "陶",
+ "伐",
+ "喂",
+ "疗",
+ "瓶",
+ "婆",
+ "抚",
+ "臂",
+ "摸",
+ "忍",
+ "虾",
+ "蜡",
+ "邻",
+ "胸",
+ "巩",
+ "挤",
+ "偶",
+ "弃",
+ "槽",
+ "劲",
+ "乳",
+ "邓",
+ "吉",
+ "仁",
+ "烂",
+ "砖",
+ "租",
+ "乌",
+ "舰",
+ "伴",
+ "瓜",
+ "浅",
+ "丙",
+ "暂",
+ "燥",
+ "橡",
+ "柳",
+ "迷",
+ "暖",
+ "牌",
+ "秧",
+ "胆",
+ "详",
+ "簧",
+ "踏",
+ "瓷",
+ "谱",
+ "呆",
+ "宾",
+ "糊",
+ "洛",
+ "辉",
+ "愤",
+ "竞",
+ "隙",
+ "怒",
+ "粘",
+ "乃",
+ "绪",
+ "肩",
+ "籍",
+ "敏",
+ "涂",
+ "熙",
+ "皆",
+ "侦",
+ "悬",
+ "掘",
+ "享",
+ "纠",
+ "醒",
+ "狂",
+ "锁",
+ "淀",
+ "恨",
+ "牲",
+ "霸",
+ "爬",
+ "赏",
+ "逆",
+ "玩",
+ "陵",
+ "祝",
+ "秒",
+ "浙",
+ "貌"
+ }), 1)
+ {
+ populate_maps();
+ }
+ };
+}
+
+#endif
diff --git a/src/mnemonics/dutch.h b/src/mnemonics/dutch.h new file mode 100644 index 000000000..1cf9b606e --- /dev/null +++ b/src/mnemonics/dutch.h @@ -0,0 +1,1686 @@ +// Copyright (c) 2014-2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/*!
+ * \file dutch.h
+ *
+ * \brief New Dutch word list and map.
+ */
+
+#ifndef DUTCH_H
+#define DUTCH_H
+
+#include <vector>
+#include <unordered_map>
+#include "language_base.h"
+#include <string>
+
+/*!
+ * \namespace Language
+ * \brief Mnemonic language related namespace.
+ */
+namespace Language
+{
+ class Dutch: public Base
+ {
+ public:
+ Dutch(): Base("Nederlands", std::vector<std::string>({
+ "aalglad",
+ "aalscholver",
+ "aambeeld",
+ "aangeef",
+ "aanlandig",
+ "aanvaard",
+ "aanwakker",
+ "aapmens",
+ "aarten",
+ "abdicatie",
+ "abnormaal",
+ "abrikoos",
+ "accu",
+ "acuut",
+ "adjudant",
+ "admiraal",
+ "advies",
+ "afbidding",
+ "afdracht",
+ "affaire",
+ "affiche",
+ "afgang",
+ "afkick",
+ "afknap",
+ "aflees",
+ "afmijner",
+ "afname",
+ "afpreekt",
+ "afrader",
+ "afspeel",
+ "aftocht",
+ "aftrek",
+ "afzijdig",
+ "ahornboom",
+ "aktetas",
+ "akzo",
+ "alchemist",
+ "alcohol",
+ "aldaar",
+ "alexander",
+ "alfabet",
+ "alfredo",
+ "alice",
+ "alikruik",
+ "allrisk",
+ "altsax",
+ "alufolie",
+ "alziend",
+ "amai",
+ "ambacht",
+ "ambieer",
+ "amina",
+ "amnestie",
+ "amok",
+ "ampul",
+ "amuzikaal",
+ "angela",
+ "aniek",
+ "antje",
+ "antwerpen",
+ "anya",
+ "aorta",
+ "apache",
+ "apekool",
+ "appelaar",
+ "arganolie",
+ "argeloos",
+ "armoede",
+ "arrenslee",
+ "artritis",
+ "arubaan",
+ "asbak",
+ "ascii",
+ "asgrauw",
+ "asjes",
+ "asml",
+ "aspunt",
+ "asurn",
+ "asveld",
+ "aterling",
+ "atomair",
+ "atrium",
+ "atsma",
+ "atypisch",
+ "auping",
+ "aura",
+ "avifauna",
+ "axiaal",
+ "azoriaan",
+ "azteek",
+ "azuur",
+ "bachelor",
+ "badderen",
+ "badhotel",
+ "badmantel",
+ "badsteden",
+ "balie",
+ "ballans",
+ "balvers",
+ "bamibal",
+ "banneling",
+ "barracuda",
+ "basaal",
+ "batelaan",
+ "batje",
+ "beambte",
+ "bedlamp",
+ "bedwelmd",
+ "befaamd",
+ "begierd",
+ "begraaf",
+ "behield",
+ "beijaard",
+ "bejaagd",
+ "bekaaid",
+ "beks",
+ "bektas",
+ "belaad",
+ "belboei",
+ "belderbos",
+ "beloerd",
+ "beluchten",
+ "bemiddeld",
+ "benadeeld",
+ "benijd",
+ "berechten",
+ "beroemd",
+ "besef",
+ "besseling",
+ "best",
+ "betichten",
+ "bevind",
+ "bevochten",
+ "bevraagd",
+ "bewust",
+ "bidplaats",
+ "biefstuk",
+ "biemans",
+ "biezen",
+ "bijbaan",
+ "bijeenkom",
+ "bijfiguur",
+ "bijkaart",
+ "bijlage",
+ "bijpaard",
+ "bijtgaar",
+ "bijweg",
+ "bimmel",
+ "binck",
+ "bint",
+ "biobak",
+ "biotisch",
+ "biseks",
+ "bistro",
+ "bitter",
+ "bitumen",
+ "bizar",
+ "blad",
+ "bleken",
+ "blender",
+ "bleu",
+ "blief",
+ "blijven",
+ "blozen",
+ "bock",
+ "boef",
+ "boei",
+ "boks",
+ "bolder",
+ "bolus",
+ "bolvormig",
+ "bomaanval",
+ "bombarde",
+ "bomma",
+ "bomtapijt",
+ "bookmaker",
+ "boos",
+ "borg",
+ "bosbes",
+ "boshuizen",
+ "bosloop",
+ "botanicus",
+ "bougie",
+ "bovag",
+ "boxspring",
+ "braad",
+ "brasem",
+ "brevet",
+ "brigade",
+ "brinckman",
+ "bruid",
+ "budget",
+ "buffel",
+ "buks",
+ "bulgaar",
+ "buma",
+ "butaan",
+ "butler",
+ "buuf",
+ "cactus",
+ "cafeetje",
+ "camcorder",
+ "cannabis",
+ "canyon",
+ "capoeira",
+ "capsule",
+ "carkit",
+ "casanova",
+ "catalaan",
+ "ceintuur",
+ "celdeling",
+ "celplasma",
+ "cement",
+ "censeren",
+ "ceramisch",
+ "cerberus",
+ "cerebraal",
+ "cesium",
+ "cirkel",
+ "citeer",
+ "civiel",
+ "claxon",
+ "clenbuterol",
+ "clicheren",
+ "clijsen",
+ "coalitie",
+ "coassistentschap",
+ "coaxiaal",
+ "codetaal",
+ "cofinanciering",
+ "cognac",
+ "coltrui",
+ "comfort",
+ "commandant",
+ "condensaat",
+ "confectie",
+ "conifeer",
+ "convector",
+ "copier",
+ "corfu",
+ "correct",
+ "coup",
+ "couvert",
+ "creatie",
+ "credit",
+ "crematie",
+ "cricket",
+ "croupier",
+ "cruciaal",
+ "cruijff",
+ "cuisine",
+ "culemborg",
+ "culinair",
+ "curve",
+ "cyrano",
+ "dactylus",
+ "dading",
+ "dagblind",
+ "dagje",
+ "daglicht",
+ "dagprijs",
+ "dagranden",
+ "dakdekker",
+ "dakpark",
+ "dakterras",
+ "dalgrond",
+ "dambord",
+ "damkat",
+ "damlengte",
+ "damman",
+ "danenberg",
+ "debbie",
+ "decibel",
+ "defect",
+ "deformeer",
+ "degelijk",
+ "degradant",
+ "dejonghe",
+ "dekken",
+ "deppen",
+ "derek",
+ "derf",
+ "derhalve",
+ "detineren",
+ "devalueer",
+ "diaken",
+ "dicht",
+ "dictaat",
+ "dief",
+ "digitaal",
+ "dijbreuk",
+ "dijkmans",
+ "dimbaar",
+ "dinsdag",
+ "diode",
+ "dirigeer",
+ "disbalans",
+ "dobermann",
+ "doenbaar",
+ "doerak",
+ "dogma",
+ "dokhaven",
+ "dokwerker",
+ "doling",
+ "dolphijn",
+ "dolven",
+ "dombo",
+ "dooraderd",
+ "dopeling",
+ "doping",
+ "draderig",
+ "drama",
+ "drenkbak",
+ "dreumes",
+ "drol",
+ "drug",
+ "duaal",
+ "dublin",
+ "duplicaat",
+ "durven",
+ "dusdanig",
+ "dutchbat",
+ "dutje",
+ "dutten",
+ "duur",
+ "duwwerk",
+ "dwaal",
+ "dweil",
+ "dwing",
+ "dyslexie",
+ "ecostroom",
+ "ecotaks",
+ "educatie",
+ "eeckhout",
+ "eede",
+ "eemland",
+ "eencellig",
+ "eeneiig",
+ "eenruiter",
+ "eenwinter",
+ "eerenberg",
+ "eerrover",
+ "eersel",
+ "eetmaal",
+ "efteling",
+ "egaal",
+ "egtberts",
+ "eickhoff",
+ "eidooier",
+ "eiland",
+ "eind",
+ "eisden",
+ "ekster",
+ "elburg",
+ "elevatie",
+ "elfkoppig",
+ "elfrink",
+ "elftal",
+ "elimineer",
+ "elleboog",
+ "elma",
+ "elodie",
+ "elsa",
+ "embleem",
+ "embolie",
+ "emoe",
+ "emonds",
+ "emplooi",
+ "enduro",
+ "enfin",
+ "engageer",
+ "entourage",
+ "entstof",
+ "epileer",
+ "episch",
+ "eppo",
+ "erasmus",
+ "erboven",
+ "erebaan",
+ "erelijst",
+ "ereronden",
+ "ereteken",
+ "erfhuis",
+ "erfwet",
+ "erger",
+ "erica",
+ "ermitage",
+ "erna",
+ "ernie",
+ "erts",
+ "ertussen",
+ "eruitzien",
+ "ervaar",
+ "erven",
+ "erwt",
+ "esbeek",
+ "escort",
+ "esdoorn",
+ "essing",
+ "etage",
+ "eter",
+ "ethanol",
+ "ethicus",
+ "etholoog",
+ "eufonisch",
+ "eurocent",
+ "evacuatie",
+ "exact",
+ "examen",
+ "executant",
+ "exen",
+ "exit",
+ "exogeen",
+ "exotherm",
+ "expeditie",
+ "expletief",
+ "expres",
+ "extase",
+ "extinctie",
+ "faal",
+ "faam",
+ "fabel",
+ "facultair",
+ "fakir",
+ "fakkel",
+ "faliekant",
+ "fallisch",
+ "famke",
+ "fanclub",
+ "fase",
+ "fatsoen",
+ "fauna",
+ "federaal",
+ "feedback",
+ "feest",
+ "feilbaar",
+ "feitelijk",
+ "felblauw",
+ "figurante",
+ "fiod",
+ "fitheid",
+ "fixeer",
+ "flap",
+ "fleece",
+ "fleur",
+ "flexibel",
+ "flits",
+ "flos",
+ "flow",
+ "fluweel",
+ "foezelen",
+ "fokkelman",
+ "fokpaard",
+ "fokvee",
+ "folder",
+ "follikel",
+ "folmer",
+ "folteraar",
+ "fooi",
+ "foolen",
+ "forfait",
+ "forint",
+ "formule",
+ "fornuis",
+ "fosfaat",
+ "foxtrot",
+ "foyer",
+ "fragiel",
+ "frater",
+ "freak",
+ "freddie",
+ "fregat",
+ "freon",
+ "frijnen",
+ "fructose",
+ "frunniken",
+ "fuiven",
+ "funshop",
+ "furieus",
+ "fysica",
+ "gadget",
+ "galder",
+ "galei",
+ "galg",
+ "galvlieg",
+ "galzuur",
+ "ganesh",
+ "gaswet",
+ "gaza",
+ "gazelle",
+ "geaaid",
+ "gebiecht",
+ "gebufferd",
+ "gedijd",
+ "geef",
+ "geflanst",
+ "gefreesd",
+ "gegaan",
+ "gegijzeld",
+ "gegniffel",
+ "gegraaid",
+ "gehikt",
+ "gehobbeld",
+ "gehucht",
+ "geiser",
+ "geiten",
+ "gekaakt",
+ "gekheid",
+ "gekijf",
+ "gekmakend",
+ "gekocht",
+ "gekskap",
+ "gekte",
+ "gelubberd",
+ "gemiddeld",
+ "geordend",
+ "gepoederd",
+ "gepuft",
+ "gerda",
+ "gerijpt",
+ "geseald",
+ "geshockt",
+ "gesierd",
+ "geslaagd",
+ "gesnaaid",
+ "getracht",
+ "getwijfel",
+ "geuit",
+ "gevecht",
+ "gevlagd",
+ "gewicht",
+ "gezaagd",
+ "gezocht",
+ "ghanees",
+ "giebelen",
+ "giechel",
+ "giepmans",
+ "gips",
+ "giraal",
+ "gistachtig",
+ "gitaar",
+ "glaasje",
+ "gletsjer",
+ "gleuf",
+ "glibberen",
+ "glijbaan",
+ "gloren",
+ "gluipen",
+ "gluren",
+ "gluur",
+ "gnoe",
+ "goddelijk",
+ "godgans",
+ "godschalk",
+ "godzalig",
+ "goeierd",
+ "gogme",
+ "goklustig",
+ "gokwereld",
+ "gonggrijp",
+ "gonje",
+ "goor",
+ "grabbel",
+ "graf",
+ "graveer",
+ "grif",
+ "grolleman",
+ "grom",
+ "groosman",
+ "grubben",
+ "gruijs",
+ "grut",
+ "guacamole",
+ "guido",
+ "guppy",
+ "haazen",
+ "hachelijk",
+ "haex",
+ "haiku",
+ "hakhout",
+ "hakken",
+ "hanegem",
+ "hans",
+ "hanteer",
+ "harrie",
+ "hazebroek",
+ "hedonist",
+ "heil",
+ "heineken",
+ "hekhuis",
+ "hekman",
+ "helbig",
+ "helga",
+ "helwegen",
+ "hengelaar",
+ "herkansen",
+ "hermafrodiet",
+ "hertaald",
+ "hiaat",
+ "hikspoors",
+ "hitachi",
+ "hitparade",
+ "hobo",
+ "hoeve",
+ "holocaust",
+ "hond",
+ "honnepon",
+ "hoogacht",
+ "hotelbed",
+ "hufter",
+ "hugo",
+ "huilbier",
+ "hulk",
+ "humus",
+ "huwbaar",
+ "huwelijk",
+ "hype",
+ "iconisch",
+ "idema",
+ "ideogram",
+ "idolaat",
+ "ietje",
+ "ijker",
+ "ijkheid",
+ "ijklijn",
+ "ijkmaat",
+ "ijkwezen",
+ "ijmuiden",
+ "ijsbox",
+ "ijsdag",
+ "ijselijk",
+ "ijskoud",
+ "ilse",
+ "immuun",
+ "impliceer",
+ "impuls",
+ "inbijten",
+ "inbuigen",
+ "indijken",
+ "induceer",
+ "indy",
+ "infecteer",
+ "inhaak",
+ "inkijk",
+ "inluiden",
+ "inmijnen",
+ "inoefenen",
+ "inpolder",
+ "inrijden",
+ "inslaan",
+ "invitatie",
+ "inwaaien",
+ "ionisch",
+ "isaac",
+ "isolatie",
+ "isotherm",
+ "isra",
+ "italiaan",
+ "ivoor",
+ "jacobs",
+ "jakob",
+ "jammen",
+ "jampot",
+ "jarig",
+ "jehova",
+ "jenever",
+ "jezus",
+ "joana",
+ "jobdienst",
+ "josua",
+ "joule",
+ "juich",
+ "jurk",
+ "juut",
+ "kaas",
+ "kabelaar",
+ "kabinet",
+ "kagenaar",
+ "kajuit",
+ "kalebas",
+ "kalm",
+ "kanjer",
+ "kapucijn",
+ "karregat",
+ "kart",
+ "katvanger",
+ "katwijk",
+ "kegelaar",
+ "keiachtig",
+ "keizer",
+ "kenletter",
+ "kerdijk",
+ "keus",
+ "kevlar",
+ "kezen",
+ "kickback",
+ "kieviet",
+ "kijken",
+ "kikvors",
+ "kilheid",
+ "kilobit",
+ "kilsdonk",
+ "kipschnitzel",
+ "kissebis",
+ "klad",
+ "klagelijk",
+ "klak",
+ "klapbaar",
+ "klaver",
+ "klene",
+ "klets",
+ "klijnhout",
+ "klit",
+ "klok",
+ "klonen",
+ "klotefilm",
+ "kluif",
+ "klumper",
+ "klus",
+ "knabbel",
+ "knagen",
+ "knaven",
+ "kneedbaar",
+ "knmi",
+ "knul",
+ "knus",
+ "kokhals",
+ "komiek",
+ "komkommer",
+ "kompaan",
+ "komrij",
+ "komvormig",
+ "koning",
+ "kopbal",
+ "kopklep",
+ "kopnagel",
+ "koppejan",
+ "koptekst",
+ "kopwand",
+ "koraal",
+ "kosmisch",
+ "kostbaar",
+ "kram",
+ "kraneveld",
+ "kras",
+ "kreling",
+ "krengen",
+ "kribbe",
+ "krik",
+ "kruid",
+ "krulbol",
+ "kuijper",
+ "kuipbank",
+ "kuit",
+ "kuiven",
+ "kutsmoes",
+ "kuub",
+ "kwak",
+ "kwatong",
+ "kwetsbaar",
+ "kwezelaar",
+ "kwijnen",
+ "kwik",
+ "kwinkslag",
+ "kwitantie",
+ "lading",
+ "lakbeits",
+ "lakken",
+ "laklaag",
+ "lakmoes",
+ "lakwijk",
+ "lamheid",
+ "lamp",
+ "lamsbout",
+ "lapmiddel",
+ "larve",
+ "laser",
+ "latijn",
+ "latuw",
+ "lawaai",
+ "laxeerpil",
+ "lebberen",
+ "ledeboer",
+ "leefbaar",
+ "leeman",
+ "lefdoekje",
+ "lefhebber",
+ "legboor",
+ "legsel",
+ "leguaan",
+ "leiplaat",
+ "lekdicht",
+ "lekrijden",
+ "leksteen",
+ "lenen",
+ "leraar",
+ "lesbienne",
+ "leugenaar",
+ "leut",
+ "lexicaal",
+ "lezing",
+ "lieten",
+ "liggeld",
+ "lijdzaam",
+ "lijk",
+ "lijmstang",
+ "lijnschip",
+ "likdoorn",
+ "likken",
+ "liksteen",
+ "limburg",
+ "link",
+ "linoleum",
+ "lipbloem",
+ "lipman",
+ "lispelen",
+ "lissabon",
+ "litanie",
+ "liturgie",
+ "lochem",
+ "loempia",
+ "loesje",
+ "logheid",
+ "lonen",
+ "lonneke",
+ "loom",
+ "loos",
+ "losbaar",
+ "loslaten",
+ "losplaats",
+ "loting",
+ "lotnummer",
+ "lots",
+ "louie",
+ "lourdes",
+ "louter",
+ "lowbudget",
+ "luijten",
+ "luikenaar",
+ "luilak",
+ "luipaard",
+ "luizenbos",
+ "lulkoek",
+ "lumen",
+ "lunzen",
+ "lurven",
+ "lutjeboer",
+ "luttel",
+ "lutz",
+ "luuk",
+ "luwte",
+ "luyendijk",
+ "lyceum",
+ "lynx",
+ "maakbaar",
+ "magdalena",
+ "malheid",
+ "manchet",
+ "manfred",
+ "manhaftig",
+ "mank",
+ "mantel",
+ "marion",
+ "marxist",
+ "masmeijer",
+ "massaal",
+ "matsen",
+ "matverf",
+ "matze",
+ "maude",
+ "mayonaise",
+ "mechanica",
+ "meifeest",
+ "melodie",
+ "meppelink",
+ "midvoor",
+ "midweeks",
+ "midzomer",
+ "miezel",
+ "mijnraad",
+ "minus",
+ "mirck",
+ "mirte",
+ "mispakken",
+ "misraden",
+ "miswassen",
+ "mitella",
+ "moker",
+ "molecule",
+ "mombakkes",
+ "moonen",
+ "mopperaar",
+ "moraal",
+ "morgana",
+ "mormel",
+ "mosselaar",
+ "motregen",
+ "mouw",
+ "mufheid",
+ "mutueel",
+ "muzelman",
+ "naaidoos",
+ "naald",
+ "nadeel",
+ "nadruk",
+ "nagy",
+ "nahon",
+ "naima",
+ "nairobi",
+ "napalm",
+ "napels",
+ "napijn",
+ "napoleon",
+ "narigheid",
+ "narratief",
+ "naseizoen",
+ "nasibal",
+ "navigatie",
+ "nawijn",
+ "negatief",
+ "nekletsel",
+ "nekwervel",
+ "neolatijn",
+ "neonataal",
+ "neptunus",
+ "nerd",
+ "nest",
+ "neuzelaar",
+ "nihiliste",
+ "nijenhuis",
+ "nijging",
+ "nijhoff",
+ "nijl",
+ "nijptang",
+ "nippel",
+ "nokkenas",
+ "noordam",
+ "noren",
+ "normaal",
+ "nottelman",
+ "notulant",
+ "nout",
+ "nuance",
+ "nuchter",
+ "nudorp",
+ "nulde",
+ "nullijn",
+ "nulmeting",
+ "nunspeet",
+ "nylon",
+ "obelisk",
+ "object",
+ "oblie",
+ "obsceen",
+ "occlusie",
+ "oceaan",
+ "ochtend",
+ "ockhuizen",
+ "oerdom",
+ "oergezond",
+ "oerlaag",
+ "oester",
+ "okhuijsen",
+ "olifant",
+ "olijfboer",
+ "omaans",
+ "ombudsman",
+ "omdat",
+ "omdijken",
+ "omdoen",
+ "omgebouwd",
+ "omkeer",
+ "omkomen",
+ "ommegaand",
+ "ommuren",
+ "omroep",
+ "omruil",
+ "omslaan",
+ "omsmeden",
+ "omvaar",
+ "onaardig",
+ "onedel",
+ "onenig",
+ "onheilig",
+ "onrecht",
+ "onroerend",
+ "ontcijfer",
+ "onthaal",
+ "ontvallen",
+ "ontzadeld",
+ "onzacht",
+ "onzin",
+ "onzuiver",
+ "oogappel",
+ "ooibos",
+ "ooievaar",
+ "ooit",
+ "oorarts",
+ "oorhanger",
+ "oorijzer",
+ "oorklep",
+ "oorschelp",
+ "oorworm",
+ "oorzaak",
+ "opdagen",
+ "opdien",
+ "opdweilen",
+ "opel",
+ "opgebaard",
+ "opinie",
+ "opjutten",
+ "opkijken",
+ "opklaar",
+ "opkuisen",
+ "opkwam",
+ "opnaaien",
+ "opossum",
+ "opsieren",
+ "opsmeer",
+ "optreden",
+ "opvijzel",
+ "opvlammen",
+ "opwind",
+ "oraal",
+ "orchidee",
+ "orkest",
+ "ossuarium",
+ "ostendorf",
+ "oublie",
+ "oudachtig",
+ "oudbakken",
+ "oudnoors",
+ "oudshoorn",
+ "oudtante",
+ "oven",
+ "over",
+ "oxidant",
+ "pablo",
+ "pacht",
+ "paktafel",
+ "pakzadel",
+ "paljas",
+ "panharing",
+ "papfles",
+ "paprika",
+ "parochie",
+ "paus",
+ "pauze",
+ "paviljoen",
+ "peek",
+ "pegel",
+ "peigeren",
+ "pekela",
+ "pendant",
+ "penibel",
+ "pepmiddel",
+ "peptalk",
+ "periferie",
+ "perron",
+ "pessarium",
+ "peter",
+ "petfles",
+ "petgat",
+ "peuk",
+ "pfeifer",
+ "picknick",
+ "pief",
+ "pieneman",
+ "pijlkruid",
+ "pijnacker",
+ "pijpelink",
+ "pikdonker",
+ "pikeer",
+ "pilaar",
+ "pionier",
+ "pipet",
+ "piscine",
+ "pissebed",
+ "pitchen",
+ "pixel",
+ "plamuren",
+ "plan",
+ "plausibel",
+ "plegen",
+ "plempen",
+ "pleonasme",
+ "plezant",
+ "podoloog",
+ "pofmouw",
+ "pokdalig",
+ "ponywagen",
+ "popachtig",
+ "popidool",
+ "porren",
+ "positie",
+ "potten",
+ "pralen",
+ "prezen",
+ "prijzen",
+ "privaat",
+ "proef",
+ "prooi",
+ "prozawerk",
+ "pruik",
+ "prul",
+ "publiceer",
+ "puck",
+ "puilen",
+ "pukkelig",
+ "pulveren",
+ "pupil",
+ "puppy",
+ "purmerend",
+ "pustjens",
+ "putemmer",
+ "puzzelaar",
+ "queenie",
+ "quiche",
+ "raam",
+ "raar",
+ "raat",
+ "raes",
+ "ralf",
+ "rally",
+ "ramona",
+ "ramselaar",
+ "ranonkel",
+ "rapen",
+ "rapunzel",
+ "rarekiek",
+ "rarigheid",
+ "rattenhol",
+ "ravage",
+ "reactie",
+ "recreant",
+ "redacteur",
+ "redster",
+ "reewild",
+ "regie",
+ "reijnders",
+ "rein",
+ "replica",
+ "revanche",
+ "rigide",
+ "rijbaan",
+ "rijdansen",
+ "rijgen",
+ "rijkdom",
+ "rijles",
+ "rijnwijn",
+ "rijpma",
+ "rijstafel",
+ "rijtaak",
+ "rijzwepen",
+ "rioleer",
+ "ripdeal",
+ "riphagen",
+ "riskant",
+ "rits",
+ "rivaal",
+ "robbedoes",
+ "robot",
+ "rockact",
+ "rodijk",
+ "rogier",
+ "rohypnol",
+ "rollaag",
+ "rolpaal",
+ "roltafel",
+ "roof",
+ "roon",
+ "roppen",
+ "rosbief",
+ "rosharig",
+ "rosielle",
+ "rotan",
+ "rotleven",
+ "rotten",
+ "rotvaart",
+ "royaal",
+ "royeer",
+ "rubato",
+ "ruby",
+ "ruche",
+ "rudge",
+ "ruggetje",
+ "rugnummer",
+ "rugpijn",
+ "rugtitel",
+ "rugzak",
+ "ruilbaar",
+ "ruis",
+ "ruit",
+ "rukwind",
+ "rulijs",
+ "rumoeren",
+ "rumsdorp",
+ "rumtaart",
+ "runnen",
+ "russchen",
+ "ruwkruid",
+ "saboteer",
+ "saksisch",
+ "salade",
+ "salpeter",
+ "sambabal",
+ "samsam",
+ "satelliet",
+ "satineer",
+ "saus",
+ "scampi",
+ "scarabee",
+ "scenario",
+ "schobben",
+ "schubben",
+ "scout",
+ "secessie",
+ "secondair",
+ "seculair",
+ "sediment",
+ "seeland",
+ "settelen",
+ "setwinst",
+ "sheriff",
+ "shiatsu",
+ "siciliaan",
+ "sidderaal",
+ "sigma",
+ "sijben",
+ "silvana",
+ "simkaart",
+ "sinds",
+ "situatie",
+ "sjaak",
+ "sjardijn",
+ "sjezen",
+ "sjor",
+ "skinhead",
+ "skylab",
+ "slamixen",
+ "sleijpen",
+ "slijkerig",
+ "slordig",
+ "slowaak",
+ "sluieren",
+ "smadelijk",
+ "smiecht",
+ "smoel",
+ "smos",
+ "smukken",
+ "snackcar",
+ "snavel",
+ "sneaker",
+ "sneu",
+ "snijdbaar",
+ "snit",
+ "snorder",
+ "soapbox",
+ "soetekouw",
+ "soigneren",
+ "sojaboon",
+ "solo",
+ "solvabel",
+ "somber",
+ "sommatie",
+ "soort",
+ "soppen",
+ "sopraan",
+ "soundbar",
+ "spanen",
+ "spawater",
+ "spijgat",
+ "spinaal",
+ "spionage",
+ "spiraal",
+ "spleet",
+ "splijt",
+ "spoed",
+ "sporen",
+ "spul",
+ "spuug",
+ "spuw",
+ "stalen",
+ "standaard",
+ "star",
+ "stefan",
+ "stencil",
+ "stijf",
+ "stil",
+ "stip",
+ "stopdas",
+ "stoten",
+ "stoven",
+ "straat",
+ "strobbe",
+ "strubbel",
+ "stucadoor",
+ "stuif",
+ "stukadoor",
+ "subhoofd",
+ "subregent",
+ "sudoku",
+ "sukade",
+ "sulfaat",
+ "surinaams",
+ "suus",
+ "syfilis",
+ "symboliek",
+ "sympathie",
+ "synagoge",
+ "synchroon",
+ "synergie",
+ "systeem",
+ "taanderij",
+ "tabak",
+ "tachtig",
+ "tackelen",
+ "taiwanees",
+ "talman",
+ "tamheid",
+ "tangaslip",
+ "taps",
+ "tarkan",
+ "tarwe",
+ "tasman",
+ "tatjana",
+ "taxameter",
+ "teil",
+ "teisman",
+ "telbaar",
+ "telco",
+ "telganger",
+ "telstar",
+ "tenant",
+ "tepel",
+ "terzet",
+ "testament",
+ "ticket",
+ "tiesinga",
+ "tijdelijk",
+ "tika",
+ "tiksel",
+ "tilleman",
+ "timbaal",
+ "tinsteen",
+ "tiplijn",
+ "tippelaar",
+ "tjirpen",
+ "toezeggen",
+ "tolbaas",
+ "tolgeld",
+ "tolhek",
+ "tolo",
+ "tolpoort",
+ "toltarief",
+ "tolvrij",
+ "tomaat",
+ "tondeuse",
+ "toog",
+ "tooi",
+ "toonbaar",
+ "toos",
+ "topclub",
+ "toppen",
+ "toptalent",
+ "topvrouw",
+ "toque",
+ "torment",
+ "tornado",
+ "tosti",
+ "totdat",
+ "toucheer",
+ "toulouse",
+ "tournedos",
+ "tout",
+ "trabant",
+ "tragedie",
+ "trailer",
+ "traject",
+ "traktaat",
+ "trauma",
+ "tray",
+ "trechter",
+ "tred",
+ "tref",
+ "treur",
+ "troebel",
+ "tros",
+ "trucage",
+ "truffel",
+ "tsaar",
+ "tucht",
+ "tuenter",
+ "tuitelig",
+ "tukje",
+ "tuktuk",
+ "tulp",
+ "tuma",
+ "tureluurs",
+ "twijfel",
+ "twitteren",
+ "tyfoon",
+ "typograaf",
+ "ugandees",
+ "uiachtig",
+ "uier",
+ "uisnipper",
+ "ultiem",
+ "unitair",
+ "uranium",
+ "urbaan",
+ "urendag",
+ "ursula",
+ "uurcirkel",
+ "uurglas",
+ "uzelf",
+ "vaat",
+ "vakantie",
+ "vakleraar",
+ "valbijl",
+ "valpartij",
+ "valreep",
+ "valuatie",
+ "vanmiddag",
+ "vanonder",
+ "varaan",
+ "varken",
+ "vaten",
+ "veenbes",
+ "veeteler",
+ "velgrem",
+ "vellekoop",
+ "velvet",
+ "veneberg",
+ "venlo",
+ "vent",
+ "venusberg",
+ "venw",
+ "veredeld",
+ "verf",
+ "verhaaf",
+ "vermaak",
+ "vernaaid",
+ "verraad",
+ "vers",
+ "veruit",
+ "verzaagd",
+ "vetachtig",
+ "vetlok",
+ "vetmesten",
+ "veto",
+ "vetrek",
+ "vetstaart",
+ "vetten",
+ "veurink",
+ "viaduct",
+ "vibrafoon",
+ "vicariaat",
+ "vieux",
+ "vieveen",
+ "vijfvoud",
+ "villa",
+ "vilt",
+ "vimmetje",
+ "vindbaar",
+ "vips",
+ "virtueel",
+ "visdieven",
+ "visee",
+ "visie",
+ "vlaag",
+ "vleugel",
+ "vmbo",
+ "vocht",
+ "voesenek",
+ "voicemail",
+ "voip",
+ "volg",
+ "vork",
+ "vorselaar",
+ "voyeur",
+ "vracht",
+ "vrekkig",
+ "vreten",
+ "vrije",
+ "vrozen",
+ "vrucht",
+ "vucht",
+ "vugt",
+ "vulkaan",
+ "vulmiddel",
+ "vulva",
+ "vuren",
+ "waas",
+ "wacht",
+ "wadvogel",
+ "wafel",
+ "waffel",
+ "walhalla",
+ "walnoot",
+ "walraven",
+ "wals",
+ "walvis",
+ "wandaad",
+ "wanen",
+ "wanmolen",
+ "want",
+ "warklomp",
+ "warm",
+ "wasachtig",
+ "wasteil",
+ "watt",
+ "webhandel",
+ "weblog",
+ "webpagina",
+ "webzine",
+ "wedereis",
+ "wedstrijd",
+ "weeda",
+ "weert",
+ "wegmaaien",
+ "wegscheer",
+ "wekelijks",
+ "wekken",
+ "wekroep",
+ "wektoon",
+ "weldaad",
+ "welwater",
+ "wendbaar",
+ "wenkbrauw",
+ "wens",
+ "wentelaar",
+ "wervel",
+ "wesseling",
+ "wetboek",
+ "wetmatig",
+ "whirlpool",
+ "wijbrands",
+ "wijdbeens",
+ "wijk",
+ "wijnbes",
+ "wijting",
+ "wild",
+ "wimpelen",
+ "wingebied",
+ "winplaats",
+ "winter",
+ "winzucht",
+ "wipstaart",
+ "wisgerhof",
+ "withaar",
+ "witmaker",
+ "wokkel",
+ "wolf",
+ "wonenden",
+ "woning",
+ "worden",
+ "worp",
+ "wortel",
+ "wrat",
+ "wrijf",
+ "wringen",
+ "yoghurt",
+ "ypsilon",
+ "zaaijer",
+ "zaak",
+ "zacharias",
+ "zakelijk",
+ "zakkam",
+ "zakwater",
+ "zalf",
+ "zalig",
+ "zaniken",
+ "zebracode",
+ "zeeblauw",
+ "zeef",
+ "zeegaand",
+ "zeeuw",
+ "zege",
+ "zegje",
+ "zeil",
+ "zesbaans",
+ "zesenhalf",
+ "zeskantig",
+ "zesmaal",
+ "zetbaas",
+ "zetpil",
+ "zeulen",
+ "ziezo",
+ "zigzag",
+ "zijaltaar",
+ "zijbeuk",
+ "zijlijn",
+ "zijmuur",
+ "zijn",
+ "zijwaarts",
+ "zijzelf",
+ "zilt",
+ "zimmerman",
+ "zinledig",
+ "zinnelijk",
+ "zionist",
+ "zitdag",
+ "zitruimte",
+ "zitzak",
+ "zoal",
+ "zodoende",
+ "zoekbots",
+ "zoem",
+ "zoiets",
+ "zojuist",
+ "zondaar",
+ "zotskap",
+ "zottebol",
+ "zucht",
+ "zuivel",
+ "zulk",
+ "zult",
+ "zuster",
+ "zuur",
+ "zweedijk",
+ "zwendel",
+ "zwepen",
+ "zwiep",
+ "zwijmel",
+ "zworen"
+ }), 4)
+ {
+ populate_maps();
+ }
+ };
+}
+
+#endif
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 33d36d2d0..ef1100a10 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -51,25 +51,31 @@ #include <boost/crc.hpp> #include <boost/algorithm/string/join.hpp> +#include "chinese_simplified.h" #include "english.h" +#include "dutch.h" +#include "french.h" #include "italian.h" #include "german.h" #include "spanish.h" #include "portuguese.h" #include "japanese.h" #include "russian.h" -#include "old_english.h" +#include "english_old.h" #include "language_base.h" #include "singleton.h" namespace { + uint32_t create_checksum_index(const std::vector<std::string> &word_list, + uint32_t unique_prefix_length); + bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length); /*! * \brief Finds the word list that contains the seed words and puts the indices * where matches occured in matched_indices. * \param seed List of words to match. - * \param has_checksum If word list passed checksum test, we need to only do a prefix check. + * \param has_checksum The seed has a checksum word (maybe not checked). * \param matched_indices The indices where the seed words were found are added to this. * \param language Language instance pointer to write to after it is found. * \return true if all the words were present in some language false if not. @@ -79,15 +85,19 @@ namespace { // If there's a new language added, add an instance of it here. std::vector<Language::Base*> language_instances({ + Language::Singleton<Language::Chinese_Simplified>::instance(), Language::Singleton<Language::English>::instance(), + Language::Singleton<Language::Dutch>::instance(), + Language::Singleton<Language::French>::instance(), Language::Singleton<Language::Spanish>::instance(), Language::Singleton<Language::German>::instance(), Language::Singleton<Language::Italian>::instance(), Language::Singleton<Language::Portuguese>::instance(), Language::Singleton<Language::Japanese>::instance(), Language::Singleton<Language::Russian>::instance(), - Language::Singleton<Language::OldEnglish>::instance() + Language::Singleton<Language::EnglishOld>::instance() }); + Language::Base *fallback = NULL; // Iterate through all the languages and find a match for (std::vector<Language::Base*>::iterator it1 = language_instances.begin(); @@ -126,12 +136,33 @@ namespace } if (full_match) { + // if we were using prefix only, and we have a checksum, check it now + // to avoid false positives due to prefix set being too common + if (has_checksum) + if (!checksum_test(seed, (*it1)->get_unique_prefix_length())) + { + fallback = *it1; + full_match = false; + } + } + if (full_match) + { *language = *it1; return true; } // Some didn't match. Clear the index array. matched_indices.clear(); } + + // if we get there, we've not found a good match, but we might have a fallback, + // if we detected a match which did not fit the checksum, which might be a badly + // typed/transcribed seed in the right language + if (fallback) + { + *language = fallback; + return true; + } + return false; } @@ -211,7 +242,7 @@ namespace crypto std::vector<std::string> seed; boost::algorithm::trim(words); - boost::split(seed, words, boost::is_any_of(" ")); + boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); // error on non-compliant word list if (seed.size() != seed_length/2 && seed.size() != seed_length && @@ -287,30 +318,42 @@ namespace crypto { language = Language::Singleton<Language::English>::instance(); } - else if (language_name == "Spanish") + else if (language_name == "Nederlands") + { + language = Language::Singleton<Language::Dutch>::instance(); + } + else if (language_name == "Français") + { + language = Language::Singleton<Language::French>::instance(); + } + else if (language_name == "Español") { language = Language::Singleton<Language::Spanish>::instance(); } - else if (language_name == "Portuguese") + else if (language_name == "Português") { language = Language::Singleton<Language::Portuguese>::instance(); } - else if (language_name == "Japanese") + else if (language_name == "日本語") { language = Language::Singleton<Language::Japanese>::instance(); } - else if (language_name == "Italian") + else if (language_name == "Italiano") { language = Language::Singleton<Language::Italian>::instance(); } - else if (language_name == "German") + else if (language_name == "Deutsch") { language = Language::Singleton<Language::German>::instance(); } - else if (language_name == "Russian") + else if (language_name == "русский язык") { language = Language::Singleton<Language::Russian>::instance(); } + else if (language_name == "简体中文 (中国)") + { + language = Language::Singleton<Language::Chinese_Simplified>::instance(); + } else { return false; @@ -356,13 +399,16 @@ namespace crypto void get_language_list(std::vector<std::string> &languages) { std::vector<Language::Base*> language_instances({ + Language::Singleton<Language::German>::instance(), Language::Singleton<Language::English>::instance(), Language::Singleton<Language::Spanish>::instance(), - Language::Singleton<Language::German>::instance(), + Language::Singleton<Language::French>::instance(), Language::Singleton<Language::Italian>::instance(), + Language::Singleton<Language::Dutch>::instance(), Language::Singleton<Language::Portuguese>::instance(), Language::Singleton<Language::Russian>::instance(), - Language::Singleton<Language::Japanese>::instance() + Language::Singleton<Language::Japanese>::instance(), + Language::Singleton<Language::Chinese_Simplified>::instance() }); for (std::vector<Language::Base*>::iterator it = language_instances.begin(); it != language_instances.end(); it++) diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 29a9f62f2..3655dd201 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -60,7 +60,7 @@ namespace crypto { const int seed_length = 24; - const std::string old_language_name = "OldEnglish"; + const std::string old_language_name = "EnglishOld"; /*! * \brief Converts seed words to bytes (secret key). * \param words String containing the words separated by spaces. diff --git a/src/mnemonics/english.h b/src/mnemonics/english.h index c42b752b1..e6cfa8951 100644 --- a/src/mnemonics/english.h +++ b/src/mnemonics/english.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -49,9 +49,7 @@ namespace Language class English: public Base
{
public:
- English()
- {
- word_list = new std::vector<std::string>({
+ English(): Base("English", std::vector<std::string>({
"abbey",
"abducts",
"ability",
@@ -1678,11 +1676,8 @@ namespace Language "zombie",
"zones",
"zoom"
- });
- unique_prefix_length = 3;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "English";
+ }), 3)
+ {
populate_maps();
}
};
diff --git a/src/mnemonics/old_english.h b/src/mnemonics/english_old.h index cd4aa7629..9fa5e81e1 100644 --- a/src/mnemonics/old_english.h +++ b/src/mnemonics/english_old.h @@ -1,6 +1,6 @@ // Word list originally created as part of the Electrum project, Copyright (C) 2014 Thomas Voegtlin
//
-// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -29,13 +29,13 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*!
- * \file old_english.h
+ * \file english_old.h
*
- * \brief Old English word list and map.
+ * \brief Older version of English word list and map.
*/
-#ifndef OLD_ENGLISH_H
-#define OLD_ENGLISH_H
+#ifndef ENGLISH_OLD_H
+#define ENGLISH_OLD_H
#include <vector>
#include <unordered_map>
@@ -48,12 +48,10 @@ */
namespace Language
{
- class OldEnglish: public Base
+ class EnglishOld: public Base
{
public:
- OldEnglish()
- {
- word_list = new std::vector<std::string>({
+ EnglishOld(): Base("EnglishOld", std::vector<std::string>({
"like",
"just",
"love",
@@ -1680,12 +1678,9 @@ namespace Language "unseen",
"weapon",
"weary"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "OldEnglish";
- populate_maps();
+ }), 4)
+ {
+ populate_maps(ALLOW_DUPLICATE_PREFIXES | ALLOW_SHORT_WORDS);
}
};
}
diff --git a/src/mnemonics/french.h b/src/mnemonics/french.h new file mode 100644 index 000000000..ef951b2cc --- /dev/null +++ b/src/mnemonics/french.h @@ -0,0 +1,1686 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/*! + * \file french.h + * + * \brief French word list and map. + */ + +#ifndef FRENCH_H +#define FRENCH_H + +#include <vector> +#include <unordered_map> +#include "language_base.h" +#include <string> + +/*! + * \namespace Language + * \brief Mnemonic language related namespace. + */ +namespace Language +{ + class French: public Base + { + public: + French(): Base("Français", std::vector<std::string>({ + "abandon", + "abattre", + "aboi", + "abolir", + "aborder", + "abri", + "absence", + "absolu", + "abuser", + "acacia", + "acajou", + "accent", + "accord", + "accrocher", + "accuser", + "acerbe", + "achat", + "acheter", + "acide", + "acier", + "acquis", + "acte", + "action", + "adage", + "adepte", + "adieu", + "admettre", + "admis", + "adorer", + "adresser", + "aduler", + "affaire", + "affirmer", + "afin", + "agacer", + "agent", + "agir", + "agiter", + "agonie", + "agrafe", + "agrume", + "aider", + "aigle", + "aigre", + "aile", + "ailleurs", + "aimant", + "aimer", + "ainsi", + "aise", + "ajouter", + "alarme", + "album", + "alcool", + "alerte", + "algue", + "alibi", + "aller", + "allumer", + "alors", + "amande", + "amener", + "amie", + "amorcer", + "amour", + "ample", + "amuser", + "ananas", + "ancien", + "anglais", + "angoisse", + "animal", + "anneau", + "annoncer", + "apercevoir", + "apparence", + "appel", + "apporter", + "apprendre", + "appuyer", + "arbre", + "arcade", + "arceau", + "arche", + "ardeur", + "argent", + "argile", + "aride", + "arme", + "armure", + "arracher", + "arriver", + "article", + "asile", + "aspect", + "assaut", + "assez", + "assister", + "assurer", + "astre", + "astuce", + "atlas", + "atroce", + "attacher", + "attente", + "attirer", + "aube", + "aucun", + "audace", + "auparavant", + "auquel", + "aurore", + "aussi", + "autant", + "auteur", + "autoroute", + "autre", + "aval", + "avant", + "avec", + "avenir", + "averse", + "aveu", + "avide", + "avion", + "avis", + "avoir", + "avouer", + "avril", + "azote", + "azur", + "badge", + "bagage", + "bague", + "bain", + "baisser", + "balai", + "balcon", + "balise", + "balle", + "bambou", + "banane", + "banc", + "bandage", + "banjo", + "banlieue", + "bannir", + "banque", + "baobab", + "barbe", + "barque", + "barrer", + "bassine", + "bataille", + "bateau", + "battre", + "baver", + "bavoir", + "bazar", + "beau", + "beige", + "berger", + "besoin", + "beurre", + "biais", + "biceps", + "bidule", + "bien", + "bijou", + "bilan", + "billet", + "blanc", + "blason", + "bleu", + "bloc", + "blond", + "bocal", + "boire", + "boiserie", + "boiter", + "bonbon", + "bondir", + "bonheur", + "bordure", + "borgne", + "borner", + "bosse", + "bouche", + "bouder", + "bouger", + "boule", + "bourse", + "bout", + "boxe", + "brader", + "braise", + "branche", + "braquer", + "bras", + "brave", + "brebis", + "brevet", + "brider", + "briller", + "brin", + "brique", + "briser", + "broche", + "broder", + "bronze", + "brosser", + "brouter", + "bruit", + "brute", + "budget", + "buffet", + "bulle", + "bureau", + "buriner", + "buste", + "buter", + "butiner", + "cabas", + "cabinet", + "cabri", + "cacao", + "cacher", + "cadeau", + "cadre", + "cage", + "caisse", + "caler", + "calme", + "camarade", + "camion", + "campagne", + "canal", + "canif", + "capable", + "capot", + "carat", + "caresser", + "carie", + "carpe", + "cartel", + "casier", + "casque", + "casserole", + "cause", + "cavale", + "cave", + "ceci", + "cela", + "celui", + "cendre", + "cent", + "cependant", + "cercle", + "cerise", + "cerner", + "certes", + "cerveau", + "cesser", + "chacun", + "chair", + "chaleur", + "chamois", + "chanson", + "chaque", + "charge", + "chasse", + "chat", + "chaud", + "chef", + "chemin", + "cheveu", + "chez", + "chicane", + "chien", + "chiffre", + "chiner", + "chiot", + "chlore", + "choc", + "choix", + "chose", + "chou", + "chute", + "cibler", + "cidre", + "ciel", + "cigale", + "cinq", + "cintre", + "cirage", + "cirque", + "ciseau", + "citation", + "citer", + "citron", + "civet", + "clairon", + "clan", + "classe", + "clavier", + "clef", + "climat", + "cloche", + "cloner", + "clore", + "clos", + "clou", + "club", + "cobra", + "cocon", + "coiffer", + "coin", + "colline", + "colon", + "combat", + "comme", + "compte", + "conclure", + "conduire", + "confier", + "connu", + "conseil", + "contre", + "convenir", + "copier", + "cordial", + "cornet", + "corps", + "cosmos", + "coton", + "couche", + "coude", + "couler", + "coupure", + "cour", + "couteau", + "couvrir", + "crabe", + "crainte", + "crampe", + "cran", + "creuser", + "crever", + "crier", + "crime", + "crin", + "crise", + "crochet", + "croix", + "cruel", + "cuisine", + "cuite", + "culot", + "culte", + "cumul", + "cure", + "curieux", + "cuve", + "dame", + "danger", + "dans", + "davantage", + "debout", + "dedans", + "dehors", + "delta", + "demain", + "demeurer", + "demi", + "dense", + "dent", + "depuis", + "dernier", + "descendre", + "dessus", + "destin", + "dette", + "deuil", + "deux", + "devant", + "devenir", + "devin", + "devoir", + "dicton", + "dieu", + "difficile", + "digestion", + "digue", + "diluer", + "dimanche", + "dinde", + "diode", + "dire", + "diriger", + "discours", + "disposer", + "distance", + "divan", + "divers", + "docile", + "docteur", + "dodu", + "dogme", + "doigt", + "dominer", + "donation", + "donjon", + "donner", + "dopage", + "dorer", + "dormir", + "doseur", + "douane", + "double", + "douche", + "douleur", + "doute", + "doux", + "douzaine", + "draguer", + "drame", + "drap", + "dresser", + "droit", + "duel", + "dune", + "duper", + "durant", + "durcir", + "durer", + "eaux", + "effacer", + "effet", + "effort", + "effrayant", + "elle", + "embrasser", + "emmener", + "emparer", + "empire", + "employer", + "emporter", + "enclos", + "encore", + "endive", + "endormir", + "endroit", + "enduit", + "enfant", + "enfermer", + "enfin", + "enfler", + "enfoncer", + "enfuir", + "engager", + "engin", + "enjeu", + "enlever", + "ennemi", + "ennui", + "ensemble", + "ensuite", + "entamer", + "entendre", + "entier", + "entourer", + "entre", + "envelopper", + "envie", + "envoyer", + "erreur", + "escalier", + "espace", + "espoir", + "esprit", + "essai", + "essor", + "essuyer", + "estimer", + "exact", + "examiner", + "excuse", + "exemple", + "exiger", + "exil", + "exister", + "exode", + "expliquer", + "exposer", + "exprimer", + "extase", + "fable", + "facette", + "facile", + "fade", + "faible", + "faim", + "faire", + "fait", + "falloir", + "famille", + "faner", + "farce", + "farine", + "fatigue", + "faucon", + "faune", + "faute", + "faux", + "faveur", + "favori", + "faxer", + "feinter", + "femme", + "fendre", + "fente", + "ferme", + "festin", + "feuille", + "feutre", + "fiable", + "fibre", + "ficher", + "fier", + "figer", + "figure", + "filet", + "fille", + "filmer", + "fils", + "filtre", + "final", + "finesse", + "finir", + "fiole", + "firme", + "fixe", + "flacon", + "flair", + "flamme", + "flan", + "flaque", + "fleur", + "flocon", + "flore", + "flot", + "flou", + "fluide", + "fluor", + "flux", + "focus", + "foin", + "foire", + "foison", + "folie", + "fonction", + "fondre", + "fonte", + "force", + "forer", + "forger", + "forme", + "fort", + "fosse", + "fouet", + "fouine", + "foule", + "four", + "foyer", + "frais", + "franc", + "frapper", + "freiner", + "frimer", + "friser", + "frite", + "froid", + "froncer", + "fruit", + "fugue", + "fuir", + "fuite", + "fumer", + "fureur", + "furieux", + "fuser", + "fusil", + "futile", + "futur", + "gagner", + "gain", + "gala", + "galet", + "galop", + "gamme", + "gant", + "garage", + "garde", + "garer", + "gauche", + "gaufre", + "gaule", + "gaver", + "gazon", + "geler", + "genou", + "genre", + "gens", + "gercer", + "germer", + "geste", + "gibier", + "gicler", + "gilet", + "girafe", + "givre", + "glace", + "glisser", + "globe", + "gloire", + "gluant", + "gober", + "golf", + "gommer", + "gorge", + "gosier", + "goutte", + "grain", + "gramme", + "grand", + "gras", + "grave", + "gredin", + "griffure", + "griller", + "gris", + "gronder", + "gros", + "grotte", + "groupe", + "grue", + "guerrier", + "guetter", + "guider", + "guise", + "habiter", + "hache", + "haie", + "haine", + "halte", + "hamac", + "hanche", + "hangar", + "hanter", + "haras", + "hareng", + "harpe", + "hasard", + "hausse", + "haut", + "havre", + "herbe", + "heure", + "hibou", + "hier", + "histoire", + "hiver", + "hochet", + "homme", + "honneur", + "honte", + "horde", + "horizon", + "hormone", + "houle", + "housse", + "hublot", + "huile", + "huit", + "humain", + "humble", + "humide", + "humour", + "hurler", + "idole", + "igloo", + "ignorer", + "illusion", + "image", + "immense", + "immobile", + "imposer", + "impression", + "incapable", + "inconnu", + "index", + "indiquer", + "infime", + "injure", + "inox", + "inspirer", + "instant", + "intention", + "intime", + "inutile", + "inventer", + "inviter", + "iode", + "iris", + "issue", + "ivre", + "jade", + "jadis", + "jamais", + "jambe", + "janvier", + "jardin", + "jauge", + "jaunisse", + "jeter", + "jeton", + "jeudi", + "jeune", + "joie", + "joindre", + "joli", + "joueur", + "journal", + "judo", + "juge", + "juillet", + "juin", + "jument", + "jungle", + "jupe", + "jupon", + "jurer", + "juron", + "jury", + "jusque", + "juste", + "kayak", + "ketchup", + "kilo", + "kiwi", + "koala", + "label", + "lacet", + "lacune", + "laine", + "laisse", + "lait", + "lame", + "lancer", + "lande", + "laque", + "lard", + "largeur", + "larme", + "larve", + "lasso", + "laver", + "lendemain", + "lentement", + "lequel", + "lettre", + "leur", + "lever", + "levure", + "liane", + "libre", + "lien", + "lier", + "lieutenant", + "ligne", + "ligoter", + "liguer", + "limace", + "limer", + "limite", + "lingot", + "lion", + "lire", + "lisser", + "litre", + "livre", + "lobe", + "local", + "logis", + "loin", + "loisir", + "long", + "loque", + "lors", + "lotus", + "louer", + "loup", + "lourd", + "louve", + "loyer", + "lubie", + "lucide", + "lueur", + "luge", + "luire", + "lundi", + "lune", + "lustre", + "lutin", + "lutte", + "luxe", + "machine", + "madame", + "magie", + "magnifique", + "magot", + "maigre", + "main", + "mairie", + "maison", + "malade", + "malheur", + "malin", + "manche", + "manger", + "manier", + "manoir", + "manquer", + "marche", + "mardi", + "marge", + "mariage", + "marquer", + "mars", + "masque", + "masse", + "matin", + "mauvais", + "meilleur", + "melon", + "membre", + "menacer", + "mener", + "mensonge", + "mentir", + "menu", + "merci", + "merlu", + "mesure", + "mettre", + "meuble", + "meunier", + "meute", + "miche", + "micro", + "midi", + "miel", + "miette", + "mieux", + "milieu", + "mille", + "mimer", + "mince", + "mineur", + "ministre", + "minute", + "mirage", + "miroir", + "miser", + "mite", + "mixte", + "mobile", + "mode", + "module", + "moins", + "mois", + "moment", + "momie", + "monde", + "monsieur", + "monter", + "moquer", + "moral", + "morceau", + "mordre", + "morose", + "morse", + "mortier", + "morue", + "motif", + "motte", + "moudre", + "moule", + "mourir", + "mousse", + "mouton", + "mouvement", + "moyen", + "muer", + "muette", + "mugir", + "muguet", + "mulot", + "multiple", + "munir", + "muret", + "muse", + "musique", + "muter", + "nacre", + "nager", + "nain", + "naissance", + "narine", + "narrer", + "naseau", + "nasse", + "nation", + "nature", + "naval", + "navet", + "naviguer", + "navrer", + "neige", + "nerf", + "nerveux", + "neuf", + "neutre", + "neuve", + "neveu", + "niche", + "nier", + "niveau", + "noble", + "noce", + "nocif", + "noir", + "nomade", + "nombre", + "nommer", + "nord", + "norme", + "notaire", + "notice", + "notre", + "nouer", + "nougat", + "nourrir", + "nous", + "nouveau", + "novice", + "noyade", + "noyer", + "nuage", + "nuance", + "nuire", + "nuit", + "nulle", + "nuque", + "oasis", + "objet", + "obliger", + "obscur", + "observer", + "obtenir", + "obus", + "occasion", + "occuper", + "ocre", + "octet", + "odeur", + "odorat", + "offense", + "officier", + "offrir", + "ogive", + "oiseau", + "olive", + "ombre", + "onctueux", + "onduler", + "ongle", + "onze", + "opter", + "option", + "orageux", + "oral", + "orange", + "orbite", + "ordinaire", + "ordre", + "oreille", + "organe", + "orgie", + "orgueil", + "orient", + "origan", + "orner", + "orteil", + "ortie", + "oser", + "osselet", + "otage", + "otarie", + "ouate", + "oublier", + "ouest", + "ours", + "outil", + "outre", + "ouvert", + "ouvrir", + "ovale", + "ozone", + "pacte", + "page", + "paille", + "pain", + "paire", + "paix", + "palace", + "palissade", + "palmier", + "palpiter", + "panda", + "panneau", + "papa", + "papier", + "paquet", + "parc", + "pardi", + "parfois", + "parler", + "parmi", + "parole", + "partir", + "parvenir", + "passer", + "pastel", + "patin", + "patron", + "paume", + "pause", + "pauvre", + "paver", + "pavot", + "payer", + "pays", + "peau", + "peigne", + "peinture", + "pelage", + "pelote", + "pencher", + "pendre", + "penser", + "pente", + "percer", + "perdu", + "perle", + "permettre", + "personne", + "perte", + "peser", + "pesticide", + "petit", + "peuple", + "peur", + "phase", + "photo", + "phrase", + "piano", + "pied", + "pierre", + "pieu", + "pile", + "pilier", + "pilote", + "pilule", + "piment", + "pincer", + "pinson", + "pinte", + "pion", + "piquer", + "pirate", + "pire", + "piste", + "piton", + "pitre", + "pivot", + "pizza", + "placer", + "plage", + "plaire", + "plan", + "plaque", + "plat", + "plein", + "pleurer", + "pliage", + "plier", + "plonger", + "plot", + "pluie", + "plume", + "plus", + "pneu", + "poche", + "podium", + "poids", + "poil", + "point", + "poire", + "poison", + "poitrine", + "poivre", + "police", + "pollen", + "pomme", + "pompier", + "poncer", + "pondre", + "pont", + "portion", + "poser", + "position", + "possible", + "poste", + "potage", + "potin", + "pouce", + "poudre", + "poulet", + "poumon", + "poupe", + "pour", + "pousser", + "poutre", + "pouvoir", + "prairie", + "premier", + "prendre", + "presque", + "preuve", + "prier", + "primeur", + "prince", + "prison", + "priver", + "prix", + "prochain", + "produire", + "profond", + "proie", + "projet", + "promener", + "prononcer", + "propre", + "prose", + "prouver", + "prune", + "public", + "puce", + "pudeur", + "puiser", + "pull", + "pulpe", + "puma", + "punir", + "purge", + "putois", + "quand", + "quartier", + "quasi", + "quatre", + "quel", + "question", + "queue", + "quiche", + "quille", + "quinze", + "quitter", + "quoi", + "rabais", + "raboter", + "race", + "racheter", + "racine", + "racler", + "raconter", + "radar", + "radio", + "rafale", + "rage", + "ragot", + "raideur", + "raie", + "rail", + "raison", + "ramasser", + "ramener", + "rampe", + "rance", + "rang", + "rapace", + "rapide", + "rapport", + "rarement", + "rasage", + "raser", + "rasoir", + "rassurer", + "rater", + "ratio", + "rature", + "ravage", + "ravir", + "rayer", + "rayon", + "rebond", + "recevoir", + "recherche", + "record", + "reculer", + "redevenir", + "refuser", + "regard", + "regretter", + "rein", + "rejeter", + "rejoindre", + "relation", + "relever", + "religion", + "remarquer", + "remettre", + "remise", + "remonter", + "remplir", + "remuer", + "rencontre", + "rendre", + "renier", + "renoncer", + "rentrer", + "renverser", + "repas", + "repli", + "reposer", + "reproche", + "requin", + "respect", + "ressembler", + "reste", + "retard", + "retenir", + "retirer", + "retour", + "retrouver", + "revenir", + "revoir", + "revue", + "rhume", + "ricaner", + "riche", + "rideau", + "ridicule", + "rien", + "rigide", + "rincer", + "rire", + "risquer", + "rituel", + "rivage", + "rive", + "robe", + "robot", + "robuste", + "rocade", + "roche", + "rodeur", + "rogner", + "roman", + "rompre", + "ronce", + "rondeur", + "ronger", + "roque", + "rose", + "rosir", + "rotation", + "rotule", + "roue", + "rouge", + "rouler", + "route", + "ruban", + "rubis", + "ruche", + "rude", + "ruelle", + "ruer", + "rugby", + "rugir", + "ruine", + "rumeur", + "rural", + "ruse", + "rustre", + "sable", + "sabot", + "sabre", + "sacre", + "sage", + "saint", + "saisir", + "salade", + "salive", + "salle", + "salon", + "salto", + "salut", + "salve", + "samba", + "sandale", + "sanguin", + "sapin", + "sarcasme", + "satisfaire", + "sauce", + "sauf", + "sauge", + "saule", + "sauna", + "sauter", + "sauver", + "savoir", + "science", + "scoop", + "score", + "second", + "secret", + "secte", + "seigneur", + "sein", + "seize", + "selle", + "selon", + "semaine", + "sembler", + "semer", + "semis", + "sensuel", + "sentir", + "sept", + "serpe", + "serrer", + "sertir", + "service", + "seuil", + "seulement", + "short", + "sien", + "sigle", + "signal", + "silence", + "silo", + "simple", + "singe", + "sinon", + "sinus", + "sioux", + "sirop", + "site", + "situation", + "skier", + "snob", + "sobre", + "social", + "socle", + "sodium", + "soigner", + "soir", + "soixante", + "soja", + "solaire", + "soldat", + "soleil", + "solide", + "solo", + "solvant", + "sombre", + "somme", + "somnoler", + "sondage", + "songeur", + "sonner", + "sorte", + "sosie", + "sottise", + "souci", + "soudain", + "souffrir", + "souhaiter", + "soulever", + "soumettre", + "soupe", + "sourd", + "soustraire", + "soutenir", + "souvent", + "soyeux", + "spectacle", + "sport", + "stade", + "stagiaire", + "stand", + "star", + "statue", + "stock", + "stop", + "store", + "style", + "suave", + "subir", + "sucre", + "suer", + "suffire", + "suie", + "suite", + "suivre", + "sujet", + "sulfite", + "supposer", + "surf", + "surprendre", + "surtout", + "surveiller", + "tabac", + "table", + "tabou", + "tache", + "tacler", + "tacot", + "tact", + "taie", + "taille", + "taire", + "talon", + "talus", + "tandis", + "tango", + "tanin", + "tant", + "taper", + "tapis", + "tard", + "tarif", + "tarot", + "tarte", + "tasse", + "taureau", + "taux", + "taverne", + "taxer", + "taxi", + "tellement", + "temple", + "tendre", + "tenir", + "tenter", + "tenu", + "terme", + "ternir", + "terre", + "test", + "texte", + "thym", + "tibia", + "tiers", + "tige", + "tipi", + "tique", + "tirer", + "tissu", + "titre", + "toast", + "toge", + "toile", + "toiser", + "toiture", + "tomber", + "tome", + "tonne", + "tonte", + "toque", + "torse", + "tortue", + "totem", + "toucher", + "toujours", + "tour", + "tousser", + "tout", + "toux", + "trace", + "train", + "trame", + "tranquille", + "travail", + "trembler", + "trente", + "tribu", + "trier", + "trio", + "tripe", + "triste", + "troc", + "trois", + "tromper", + "tronc", + "trop", + "trotter", + "trouer", + "truc", + "truite", + "tuba", + "tuer", + "tuile", + "turbo", + "tutu", + "tuyau", + "type", + "union", + "unique", + "unir", + "unisson", + "untel", + "urne", + "usage", + "user", + "usiner", + "usure", + "utile", + "vache", + "vague", + "vaincre", + "valeur", + "valoir", + "valser", + "valve", + "vampire", + "vaseux", + "vaste", + "veau", + "veille", + "veine", + "velours", + "velu", + "vendre", + "venir", + "vent", + "venue", + "verbe", + "verdict", + "version", + "vertige", + "verve", + "veste", + "veto", + "vexer", + "vice", + "victime", + "vide", + "vieil", + "vieux", + "vigie", + "vigne", + "ville", + "vingt", + "violent", + "virer", + "virus", + "visage", + "viser", + "visite", + "visuel", + "vitamine", + "vitrine", + "vivant", + "vivre", + "vocal", + "vodka", + "vogue", + "voici", + "voile", + "voir", + "voisin", + "voiture", + "volaille", + "volcan", + "voler", + "volt", + "votant", + "votre", + "vouer", + "vouloir", + "vous", + "voyage", + "voyou", + "vrac", + "vrai", + "yacht", + "yeti", + "yeux", + "yoga", + "zeste", + "zinc", + "zone", + "zoom" + }), 4) + { + populate_maps(); + } + }; +} + +#endif diff --git a/src/mnemonics/german.h b/src/mnemonics/german.h index 675531453..46a8cf1fe 100644 --- a/src/mnemonics/german.h +++ b/src/mnemonics/german.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -51,9 +51,7 @@ namespace Language class German: public Base
{
public:
- German()
- {
- word_list = new std::vector<std::string>({
+ German(): Base("Deutsch", std::vector<std::string>({
"Abakus",
"Abart",
"abbilden",
@@ -1680,14 +1678,11 @@ namespace Language "Zündung",
"Zweck",
"Zyklop"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "German";
+ }), 4)
+ {
populate_maps();
}
};
}
-#endif +#endif
diff --git a/src/mnemonics/italian.h b/src/mnemonics/italian.h index 08852a97b..da0e1363c 100644 --- a/src/mnemonics/italian.h +++ b/src/mnemonics/italian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -51,9 +51,7 @@ namespace Language class Italian: public Base
{
public:
- Italian()
- {
- word_list = new std::vector<std::string>({
+ Italian(): Base("Italiano", std::vector<std::string>({
"abbinare",
"abbonato",
"abisso",
@@ -1680,14 +1678,11 @@ namespace Language "zolfo",
"zombie",
"zucchero"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "Italian";
+ }), 4)
+ {
populate_maps();
}
};
}
-#endif +#endif
diff --git a/src/mnemonics/japanese.h b/src/mnemonics/japanese.h index 421188893..fd5425fb1 100644 --- a/src/mnemonics/japanese.h +++ b/src/mnemonics/japanese.h @@ -1,23 +1,43 @@ -// Word list originally created by dabura667
-//
-// Copyright (c) 2014-2016, The Monero Project
-//
+// Word list originally created by dabura667 and released under The MIT License (MIT)
+//
+// The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Code surrounding the word list is Copyright (c) 2014-2017, The Monero Project
// All rights reserved.
-//
+//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
-//
+//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
-//
+//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
-//
+//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
-//
+//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -51,9 +71,7 @@ namespace Language class Japanese: public Base
{
public:
- Japanese()
- {
- word_list = new std::vector<std::string>({
+ Japanese(): Base("日本語", std::vector<std::string>({
"あいこくしん",
"あいさつ",
"あいだ",
@@ -1680,11 +1698,8 @@ namespace Language "ひさん",
"びじゅつかん",
"ひしょ"
- });
- unique_prefix_length = 3;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "Japanese";
+ }), 3)
+ {
populate_maps();
}
};
diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index 9a60f8a53..8f0a7a9d3 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -38,6 +38,7 @@ #include <vector>
#include <unordered_map>
#include <string>
+#include "misc_log_ex.h"
/*!
* \namespace Language
@@ -73,38 +74,62 @@ namespace Language class Base
{
protected:
- std::vector<std::string> *word_list; /*!< A pointer to the array of words */
- std::unordered_map<std::string, uint32_t> *word_map; /*!< hash table to find word's index */
- std::unordered_map<std::string, uint32_t> *trimmed_word_map; /*!< hash table to find word's trimmed index */
+ enum {
+ ALLOW_SHORT_WORDS = 1<<0,
+ ALLOW_DUPLICATE_PREFIXES = 1<<1,
+ };
+ const std::vector<std::string> word_list; /*!< A pointer to the array of words */
+ std::unordered_map<std::string, uint32_t> word_map; /*!< hash table to find word's index */
+ std::unordered_map<std::string, uint32_t> trimmed_word_map; /*!< hash table to find word's trimmed index */
std::string language_name; /*!< Name of language */
uint32_t unique_prefix_length; /*!< Number of unique starting characters to trim the wordlist to when matching */
/*!
* \brief Populates the word maps after the list is ready.
*/
- void populate_maps()
+ void populate_maps(uint32_t flags = 0)
{
int ii;
- std::vector<std::string>::iterator it;
- for (it = word_list->begin(), ii = 0; it != word_list->end(); it++, ii++)
+ std::vector<std::string>::const_iterator it;
+ if (word_list.size () != 1626)
+ throw std::runtime_error("Wrong word list length for " + language_name);
+ for (it = word_list.begin(), ii = 0; it != word_list.end(); it++, ii++)
{
- (*word_map)[*it] = ii;
+ word_map[*it] = ii;
+ if ((*it).size() < unique_prefix_length)
+ {
+ if (flags & ALLOW_SHORT_WORDS)
+ MWARNING(language_name << " word '" << *it << "' is shorter than its prefix length, " << unique_prefix_length);
+ else
+ throw std::runtime_error("Too short word in " + language_name + " word list: " + *it);
+ }
+ std::string trimmed;
if (it->length() > unique_prefix_length)
{
- (*trimmed_word_map)[utf8prefix(*it, unique_prefix_length)] = ii;
+ trimmed = utf8prefix(*it, unique_prefix_length);
}
else
{
- (*trimmed_word_map)[*it] = ii;
+ trimmed = *it;
+ }
+ if (trimmed_word_map.find(trimmed) != trimmed_word_map.end())
+ {
+ if (flags & ALLOW_DUPLICATE_PREFIXES)
+ MWARNING("Duplicate prefix in " << language_name << " word list: " << trimmed);
+ else
+ throw std::runtime_error("Duplicate prefix in " + language_name + " word list: " + trimmed);
}
+ trimmed_word_map[trimmed] = ii;
}
}
public:
- Base()
+ Base(const char *language_name, const std::vector<std::string> &words, uint32_t prefix_length):
+ word_list(words),
+ unique_prefix_length(prefix_length),
+ language_name(language_name)
+ {
+ }
+ virtual ~Base()
{
- word_list = new std::vector<std::string>;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- unique_prefix_length = 4;
}
/*!
* \brief Returns a pointer to the word list.
@@ -112,7 +137,7 @@ namespace Language */
const std::vector<std::string>& get_word_list() const
{
- return *word_list;
+ return word_list;
}
/*!
* \brief Returns a pointer to the word map.
@@ -120,7 +145,7 @@ namespace Language */
const std::unordered_map<std::string, uint32_t>& get_word_map() const
{
- return *word_map;
+ return word_map;
}
/*!
* \brief Returns a pointer to the trimmed word map.
@@ -128,13 +153,13 @@ namespace Language */
const std::unordered_map<std::string, uint32_t>& get_trimmed_word_map() const
{
- return *trimmed_word_map;
+ return trimmed_word_map;
}
/*!
* \brief Returns the name of the language.
* \return Name of the language.
*/
- std::string get_language_name() const
+ const std::string &get_language_name() const
{
return language_name;
}
diff --git a/src/mnemonics/portuguese.h b/src/mnemonics/portuguese.h index 782a2ad5d..bb1fe8ee5 100644 --- a/src/mnemonics/portuguese.h +++ b/src/mnemonics/portuguese.h @@ -1,21 +1,44 @@ -// Copyright (c) 2014-2016, The Monero Project
-//
+// Word list originally created by dabura667 and released under The MIT License (MIT)
+//
+// The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Code surrounding the word list is Copyright (c) 2014-2017, The Monero Project
+//
// All rights reserved.
-//
+//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
-//
+//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
-//
+//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
-//
+//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
-//
+//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -49,9 +72,7 @@ namespace Language class Portuguese: public Base
{
public:
- Portuguese()
- {
- word_list = new std::vector<std::string>({
+ Portuguese(): Base("Português", std::vector<std::string>({
"abaular",
"abdominal",
"abeto",
@@ -1678,11 +1699,8 @@ namespace Language "zeloso",
"zenite",
"zumbi"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "Portuguese";
+ }), 4)
+ {
populate_maps();
}
};
diff --git a/src/mnemonics/russian.h b/src/mnemonics/russian.h index 6d98ca4b8..bfe970b9d 100644 --- a/src/mnemonics/russian.h +++ b/src/mnemonics/russian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor sammy007
//
-// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
@@ -51,9 +51,7 @@ namespace Language class Russian: public Base
{
public:
- Russian()
- {
- word_list = new std::vector<std::string>({
+ Russian(): Base("русский язык", std::vector<std::string>({
"абажур",
"абзац",
"абонент",
@@ -1680,14 +1678,11 @@ namespace Language "яхта",
"ячейка",
"ящик"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "Russian";
+ }), 4)
+ {
populate_maps();
}
};
}
-#endif +#endif
diff --git a/src/mnemonics/singleton.h b/src/mnemonics/singleton.h index 48b217a0c..5ba9269b1 100644 --- a/src/mnemonics/singleton.h +++ b/src/mnemonics/singleton.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project
+// Copyright (c) 2014-2017, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/spanish.h b/src/mnemonics/spanish.h index 7275f4c42..9db2a03f3 100644 --- a/src/mnemonics/spanish.h +++ b/src/mnemonics/spanish.h @@ -1,23 +1,44 @@ -// Word list originally created as part of the Electrum project, Copyright (C) 2014 Thomas Voegtlin
-//
-// Copyright (c) 2014-2016, The Monero Project
-//
+// Word list originally created by dabura667 and released under The MIT License (MIT)
+//
+// The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Code surrounding the word list is Copyright (c) 2014-2017, The Monero Project
+//
// All rights reserved.
-//
+//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
-//
+//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
-//
+//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
-//
+//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
-//
+//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
@@ -51,9 +72,7 @@ namespace Language class Spanish: public Base
{
public:
- Spanish()
- {
- word_list = new std::vector<std::string>({
+ Spanish(): Base("Español", std::vector<std::string>({
"ábaco",
"abdomen",
"abeja",
@@ -1680,14 +1699,11 @@ namespace Language "risa",
"ritmo",
"rito"
- });
- unique_prefix_length = 4;
- word_map = new std::unordered_map<std::string, uint32_t>;
- trimmed_word_map = new std::unordered_map<std::string, uint32_t>;
- language_name = "Spanish";
- populate_maps();
+ }), 4)
+ {
+ populate_maps(ALLOW_SHORT_WORDS);
}
};
}
-#endif +#endif
diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index f59bc3189..0704940b5 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -27,17 +27,18 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmake_minimum_required (VERSION 2.6) -project (bitmonero CXX) +project (monero CXX) file(GLOB P2P *) source_group(p2p FILES ${P2P}) #add_library(p2p ${P2P}) -#bitmonero_private_headers(p2p ${P2P}) -bitmonero_add_library(p2p ${P2P}) +#monero_private_headers(p2p ${P2P}) +monero_add_library(p2p ${P2P}) target_link_libraries(p2p PUBLIC + epee ${UPNP_LIBRARIES} ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/src/p2p/connection_basic.cpp b/src/p2p/connection_basic.cpp index 981a02882..df34379e2 100644 --- a/src/p2p/connection_basic.cpp +++ b/src/p2p/connection_basic.cpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief base for connection, contains e.g. the ratelimit hooks -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -56,7 +56,6 @@ #include "../../contrib/epee/include/net/net_utils_base.h" #include "../../contrib/epee/include/misc_log_ex.h" #include <boost/lambda/bind.hpp> -#include <boost/foreach.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> #include <boost/chrono.hpp> @@ -77,14 +76,13 @@ #include <boost/asio/ip/unicast.hpp> #include "../../contrib/epee/include/net/abstract_tcp_server2.h" -#include "../../contrib/otshell_utils/utils.hpp" -#include "data_logger.hpp" -using namespace nOT::nUtils; - // TODO: #include "../../src/p2p/network_throttle-detail.hpp" #include "../../src/cryptonote_core/cryptonote_core.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" + // ################################################################################################ // local (TU local) headers // ################################################################################################ @@ -219,19 +217,6 @@ uint64_t connection_basic::get_rate_down_limit() { } void connection_basic::save_limit_to_file(int limit) { - // saving limit to file - if (!epee::net_utils::data_logger::m_save_graph) - return; - - { - CRITICAL_REGION_LOCAL( network_throttle_manager::m_lock_get_global_throttle_out ); - epee::net_utils::data_logger::get_instance().add_data("upload_limit", network_throttle_manager::get_global_throttle_out().get_target_speed() / 1024); - } - - { - CRITICAL_REGION_LOCAL( network_throttle_manager::m_lock_get_global_throttle_in ); - epee::net_utils::data_logger::get_instance().add_data("download_limit", network_throttle_manager::get_global_throttle_in().get_target_speed() / 1024); - } } void connection_basic::set_tos_flag(int tos) { @@ -259,9 +244,8 @@ void connection_basic::sleep_before_packet(size_t packet_size, int phase, int q delay *= 0.50; if (delay > 0) { long int ms = (long int)(delay * 1000); - _info_c("net/sleep", "Sleeping in " << __FUNCTION__ << " for " << ms << " ms before packet_size="<<packet_size); // debug sleep + MDEBUG("Sleeping in " << __FUNCTION__ << " for " << ms << " ms before packet_size="<<packet_size); // debug sleep _dbg1("sleep in sleep_before_packet"); - epee::net_utils::data_logger::get_instance().add_data("sleep_up", ms); boost::this_thread::sleep(boost::posix_time::milliseconds( ms ) ); } } while(delay > 0); @@ -280,25 +264,21 @@ void connection_basic::set_start_time() { void connection_basic::do_send_handler_write(const void* ptr , size_t cb ) { sleep_before_packet(cb,1,-1); - _info_c("net/out/size", "handler_write (direct) - before ASIO write, for packet="<<cb<<" B (after sleep)"); + MDEBUG("handler_write (direct) - before ASIO write, for packet="<<cb<<" B (after sleep)"); set_start_time(); } void connection_basic::do_send_handler_write_from_queue( const boost::system::error_code& e, size_t cb, int q_len ) { sleep_before_packet(cb,2,q_len); - _info_c("net/out/size", "handler_write (after write, from queue="<<q_len<<") - before ASIO write, for packet="<<cb<<" B (after sleep)"); + MDEBUG("handler_write (after write, from queue="<<q_len<<") - before ASIO write, for packet="<<cb<<" B (after sleep)"); set_start_time(); } void connection_basic::logger_handle_net_read(size_t size) { // network data read - size /= 1024; - epee::net_utils::data_logger::get_instance().add_data("download", size); } void connection_basic::logger_handle_net_write(size_t size) { - size /= 1024; - epee::net_utils::data_logger::get_instance().add_data("upload", size); } double connection_basic::get_sleep_time(size_t cb) { @@ -308,7 +288,6 @@ double connection_basic::get_sleep_time(size_t cb) { } void connection_basic::set_save_graph(bool save_graph) { - epee::net_utils::data_logger::m_save_graph = save_graph; } diff --git a/src/p2p/connection_basic.hpp b/src/p2p/connection_basic.hpp index 5452fa6fb..bea2df1cd 100644 --- a/src/p2p/connection_basic.hpp +++ b/src/p2p/connection_basic.hpp @@ -8,7 +8,7 @@ // ! (how ever if in some wonderful juristdictions that is not the case, then why not make another sub-class withat that members and licence it as epee part) // ! Working on above premise, IF this is valid in your juristdictions, then consider this code as released as: -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/p2p/data_logger.cpp b/src/p2p/data_logger.cpp deleted file mode 100644 index fe54aef63..000000000 --- a/src/p2p/data_logger.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "data_logger.hpp" -#include <stdexcept> - -#include <boost/chrono.hpp> -#include <boost/filesystem.hpp> -#include <boost/thread.hpp> -#include <chrono> -#include "../../contrib/otshell_utils/utils.hpp" - -namespace epee -{ -namespace net_utils -{ - data_logger &data_logger::get_instance() { - boost::call_once(m_singleton, - [] { - _info_c("dbg/data","Creating singleton of data_logger"); - if (m_state != data_logger_state::state_before_init) { _erro_c("dbg/data","Internal error in singleton"); throw std::runtime_error("data_logger singleton"); } - m_state = data_logger_state::state_during_init; - m_obj.reset(new data_logger()); - m_state = data_logger_state::state_ready_to_use; - } - ); - - if (m_state != data_logger_state::state_ready_to_use) { - _erro ("trying to use not working data_logger"); - throw std::runtime_error("data_logger ctor state"); - } - - return * m_obj; - } - - data_logger::data_logger() { - _note_c("dbg/data","Starting data logger (for graphs data)"); - if (m_state != data_logger_state::state_during_init) { _erro_c("dbg/data","Singleton ctor state"); throw std::runtime_error("data_logger ctor state"); } - boost::lock_guard<boost::mutex> lock(mMutex); // lock - - // prepare all the files for given data channels: - mFilesMap["peers"] = data_logger::fileData("log/dr-monero/peers.data"); - mFilesMap["download"] = data_logger::fileData("log/dr-monero/net/in-all.data"); - mFilesMap["upload"] = data_logger::fileData("log/dr-monero/net/out-all.data"); - mFilesMap["request"] = data_logger::fileData("log/dr-monero/net/req-all.data"); - mFilesMap["sleep_down"] = data_logger::fileData("log/dr-monero/down_sleep_log.data"); - mFilesMap["sleep_up"] = data_logger::fileData("log/dr-monero/up_sleep_log.data"); - mFilesMap["calc_time"] = data_logger::fileData("log/dr-monero/get_objects_calc_time.data"); - mFilesMap["blockchain_processing_time"] = data_logger::fileData("log/dr-monero/blockchain_log.data"); - mFilesMap["block_processing"] = data_logger::fileData("log/dr-monero/block_proc.data"); - - mFilesMap["peers_limit"] = data_logger::fileData("log/dr-monero/peers_limit.info"); - mFilesMap["download_limit"] = data_logger::fileData("log/dr-monero/limit_down.info"); - mFilesMap["upload_limit"] = data_logger::fileData("log/dr-monero/limit_up.info"); - - mFilesMap["peers_limit"].mLimitFile = true; - mFilesMap["download_limit"].mLimitFile = true; - mFilesMap["upload_limit"].mLimitFile = true; - - // do NOT modify mFilesMap below this point, since there is no locking for this used (yet) - - _info_c("dbg/data","Creating thread for data logger"); // create timer thread - m_thread_maybe_running=true; - std::shared_ptr<boost::thread> logger_thread(new boost::thread([&]() { - _info_c("dbg/data","Inside thread for data logger"); - while (m_state == data_logger_state::state_during_init) { // wait for creation to be done (in other thread, in singleton) before actually running - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - } - _info_c("dbg/data","Inside thread for data logger - going into main loop"); - while (m_state == data_logger_state::state_ready_to_use) { // run as long as we are not closing the single object - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - saveToFile(); // save all the pending data - } - _info_c("dbg/data","Inside thread for data logger - done the main loop"); - m_thread_maybe_running=false; - })); - logger_thread->detach(); - _info_c("dbg/data","Data logger constructed"); - } - - data_logger::~data_logger() noexcept(false) { - _note_c("dbg/data","Destructor of the data logger"); - { - boost::lock_guard<boost::mutex> lock(mMutex); - m_state = data_logger_state::state_dying; - } - _info_c("dbg/data","State was set to dying"); - while(m_thread_maybe_running) { // wait for the thread to exit - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - _info_c("dbg/data","Waiting for background thread to exit"); - } - _info_c("dbg/data","Thread exited"); - } - - void data_logger::kill_instance() { - m_state = data_logger_state::state_dying; - m_obj.reset(); - } - - void data_logger::add_data(std::string filename, unsigned int data) { - boost::lock_guard<boost::mutex> lock(mMutex); - if (m_state != data_logger_state::state_ready_to_use) { _info_c("dbg/data","Data logger is not ready, returning."); return; } - - if (mFilesMap.find(filename) == mFilesMap.end()) { // no such file/counter - _erro_c("dbg/data","Trying to use not opened data file filename="<<filename); - _erro_c("dbg/data","Disabling saving of graphs due to error"); - m_save_graph=false; // <--- disabling saving graphs - return; - } - - if (mFilesMap[filename].mLimitFile) { // this holds a number (that is not additive) - e.g. the limit setting - mFilesMap[filename].mDataToSave = data; - } else { - mFilesMap[filename].mDataToSave += data; // this holds a number that should be sum of all accumulated samples - } - } - - bool data_logger::is_dying() { - if (m_state == data_logger_state::state_dying) { - return true; - } - else { - return false; - } - } - - void data_logger::saveToFile() { - _dbg2_c("dbg/data","saving to files"); - boost::lock_guard<boost::mutex> lock(mMutex); - if (m_state != data_logger_state::state_ready_to_use) { _info_c("dbg/data","Data logger is not ready, returning."); return; } - nOT::nUtils::cFilesystemUtils::CreateDirTree("log/dr-monero/net/"); - for (auto &element : mFilesMap) - { - element.second.save(); - if (!element.second.mLimitFile) element.second.mDataToSave = 0; - } - } - - // the inner class: - - double data_logger::fileData::get_current_time() { - #if defined(__APPLE__) - auto point = std::chrono::system_clock::now(); - #else - auto point = std::chrono::steady_clock::now(); - #endif - auto time_from_epoh = point.time_since_epoch(); - auto ms = std::chrono::duration_cast< std::chrono::milliseconds >( time_from_epoh ).count(); - double ms_f = ms; - return ms_f / 1000.; - } - - data_logger::fileData::fileData(std::string pFile) { - _dbg3_c("dbg/data","opening data file named pFile="<<pFile<<" for this="<<this); - mFile = std::make_shared<std::ofstream> (pFile); - _dbg1_c("dbg/data","opened data file named pFile="<<pFile<<" in mFile="<<mFile<<" for this="<<this); - mPath = pFile; - } - - void data_logger::fileData::save() { - if (!data_logger::m_save_graph) return; // <--- disabled - _dbg2_c("dbg/data","saving to the file now, mFile="<<mFile); - mFile->open(mPath, std::ios::app); - *mFile << static_cast<int>(get_current_time()) << " " << mDataToSave << std::endl; - mFile->close(); - } - - -data_logger_state data_logger::m_state(data_logger_state::state_before_init); ///< (static) state of the singleton object -std::atomic<bool> data_logger::m_save_graph(false); // (static) -std::atomic<bool> data_logger::m_thread_maybe_running(false); // (static) -boost::once_flag data_logger::m_singleton; // (static) -std::unique_ptr<data_logger> data_logger::m_obj; // (static) - -} // namespace -} // namespace - diff --git a/src/p2p/data_logger.hpp b/src/p2p/data_logger.hpp deleted file mode 100644 index 278d08bfc..000000000 --- a/src/p2p/data_logger.hpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef INCLUDED_p2p_data_logger_hpp -#define INCLUDED_p2p_data_logger_hpp - -#include <string> -#include <map> -#include <fstream> -#include <memory> -#include <boost/thread/thread.hpp> -#include <boost/thread/mutex.hpp> -#include <boost/thread/once.hpp> -#include <atomic> - -namespace epee -{ -namespace net_utils -{ - -enum class data_logger_state { state_before_init, state_during_init, state_ready_to_use, state_dying }; - -/*** -@note: use it ONLY via singleton! It will be spawned then, and will auto destruct on program exit. -@note: do call ::kill_instance() before exiting main, at end of main. But before make sure no one else (e.g. no other threads) will try to use this/singleton -@note: it is not allowed to use this class from code "runnig before or after main", e.g. from ctors of static objects, because of static-creation-order races -@note: on creation (e.g. from singleton), it spawns a thread that saves all data in background -*/ - class data_logger { - public: - static data_logger &get_instance(); ///< singleton - static void kill_instance(); ///< call this before ending main to allow more gracefull shutdown of the main singleton and it's background thread - ~data_logger() noexcept(false); ///< destr, will be called when singleton is killed when global m_obj dies. will kill theads etc - - private: - data_logger(); ///< constructor is private, use only via singleton get_instance - - public: - data_logger(const data_logger &ob) = delete; // use only one per program - data_logger(data_logger &&ob) = delete; - data_logger & operator=(const data_logger&) = delete; - data_logger & operator=(data_logger&&) = delete; - - void add_data(std::string filename, unsigned int data); ///< use this to append data here. Use it only the singleton. It locks itself. - - static std::atomic<bool> m_save_graph; ///< global setting flag, should we save all the data or not (can disable logging graphs data) - static bool is_dying(); - - private: - static boost::once_flag m_singleton; ///< to guarantee singleton creates the object exactly once - static data_logger_state m_state; ///< state of the singleton object - static std::atomic<bool> m_thread_maybe_running; ///< is the background thread (more or less) running, or is it fully finished - static std::unique_ptr<data_logger> m_obj; ///< the singleton object. Only use it via get_instance(). Can be killed by kill_instance() - - /*** - * one graph/file with data - */ - class fileData { - public: - fileData() = default; - fileData(const fileData &ob) = delete; - fileData(std::string pFile); - - std::shared_ptr<std::ofstream> mFile; - long int mDataToSave = 0; ///< sum of the data (in current interval, will be counted from 0 on next interval) - static double get_current_time(); - void save(); - std::string mPath; - bool mLimitFile = false; ///< this holds a number (that is not additive) - e.g. the limit setting - }; - - std::map<std::string, fileData> mFilesMap; - boost::mutex mMutex; - void saveToFile(); ///< write data to the target files. do not use this directly - }; - -} // namespace -} // namespace - -#endif diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 5943c248f..13cd3f5b0 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,7 +31,6 @@ #pragma once #include <boost/thread.hpp> #include <boost/bind.hpp> -#include <boost/foreach.hpp> #include <boost/bimap.hpp> #include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> @@ -63,6 +62,7 @@ namespace nodetool struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { peerid_type peer_id; + uint32_t support_flags; }; template<class t_payload_net_handler> @@ -109,7 +109,12 @@ namespace nodetool void serialize(Archive &a, const t_version_type ver) { a & m_peerlist; - a & m_config.m_peer_id; + if (ver == 0) + { + // from v1, we do not store the peer id anymore + peerid_type peer_id = AUTO_VAL_INIT (peer_id); + a & peer_id; + } } // debug functions bool log_peerlist(); @@ -146,9 +151,12 @@ namespace nodetool HANDLE_INVOKE_T2(COMMAND_REQUEST_NETWORK_STATE, &node_server::handle_get_network_state) HANDLE_INVOKE_T2(COMMAND_REQUEST_PEER_ID, &node_server::handle_get_peer_id) #endif + HANDLE_INVOKE_T2(COMMAND_REQUEST_SUPPORT_FLAGS, &node_server::handle_get_support_flags) CHAIN_INVOKE_MAP_TO_OBJ_FORCE_CONTEXT(m_payload_handler, typename t_payload_net_handler::connection_context&) END_INVOKE_MAP2() + enum PeerType { anchor = 0, white, gray }; + //----------------- commands handlers ---------------------------------------------- int handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context); int handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context); @@ -158,7 +166,9 @@ namespace nodetool int handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context); int handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context); #endif + int handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context); bool init_config(); + bool make_default_peer_id(); bool make_default_config(); bool store_config(); bool check_trust(const proof_of_trust& tr); @@ -169,12 +179,13 @@ namespace nodetool virtual void on_connection_close(p2p_connection_context& context); virtual void callback(p2p_connection_context& context); //----------------- i_p2p_endpoint ------------------------------------------------------------- + virtual bool relay_notify_to_list(int command, const std::string& data_buff, const std::list<boost::uuids::uuid> &connections); virtual bool relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context); virtual bool invoke_command_to_peer(int command, const std::string& req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context); virtual bool invoke_notify_to_peer(int command, const std::string& req_buff, const epee::net_utils::connection_context_base& context); virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); - virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type)> f); + virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); virtual bool add_ip_fail(uint32_t address); //----------------- i_connection_filter -------------------------------------------------------- virtual bool is_remote_ip_allowed(uint32_t adress); @@ -182,7 +193,6 @@ namespace nodetool bool parse_peer_from_string(nodetool::net_address& pe, const std::string& node_addr); bool handle_command_line( const boost::program_options::variables_map& vm - , bool testnet ); bool idle_worker(); bool handle_remote_peerlist(const std::list<peerlist_entry>& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context); @@ -197,17 +207,21 @@ namespace nodetool bool do_handshake_with_peer(peerid_type& pi, p2p_connection_context& context, bool just_take_peerlist = false); bool do_peer_timed_sync(const epee::net_utils::connection_context_base& context, peerid_type peer_id); + bool make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist); bool make_new_connection_from_peerlist(bool use_white_list); - bool try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, bool white = true); + bool try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0); size_t get_random_index_with_fixed_probability(size_t max_index); bool is_peer_used(const peerlist_entry& peer); + bool is_peer_used(const anchor_peerlist_entry& peer); bool is_addr_connected(const net_address& peer); template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb); - bool make_expected_connections_count(bool white_list, size_t expected_connections); + bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); + bool make_expected_connections_count(PeerType peer_type, size_t expected_connections); void cache_connect_fail_info(const net_address& addr); bool is_addr_recently_failed(const net_address& addr); bool is_priority_node(const net_address& na); + std::set<std::string> get_seed_nodes(bool testnet) const; template <class Container> bool connect_to_peerlist(const Container& peers); @@ -222,6 +236,11 @@ namespace nodetool bool set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit); bool set_rate_limit(const boost::program_options::variables_map& vm, int64_t limit); + bool has_too_many_connections(const uint32_t ip); + + bool check_connection_and_handshake_with_peer(const net_address& na, uint64_t last_seen_stamp); + bool gray_peerlist_housekeeping(); + void kill() { ///< will be called e.g. from deinit() _info("Killing the net_node"); is_closing = true; @@ -240,10 +259,12 @@ namespace nodetool { network_config m_net_config; uint64_t m_peer_id; + uint32_t m_support_flags; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_net_config) KV_SERIALIZE(m_peer_id) + KV_SERIALIZE(m_support_flags) END_KV_SERIALIZE_MAP() }; @@ -280,6 +301,7 @@ namespace nodetool epee::math_helper::once_a_time_seconds<P2P_DEFAULT_HANDSHAKE_INTERVAL> m_peer_handshake_idle_maker_interval; epee::math_helper::once_a_time_seconds<1> m_connections_maker_interval; epee::math_helper::once_a_time_seconds<60*30, false> m_peerlist_store_interval; + epee::math_helper::once_a_time_seconds<60> m_gray_peerlist_housekeeping_interval; std::string m_bind_ip; std::string m_port; @@ -303,6 +325,8 @@ namespace nodetool epee::critical_section m_ip_fails_score_lock; std::map<uint32_t, uint64_t> m_ip_fails_score; + + bool m_testnet; }; } diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 9c1d8629d..5c903b1f5 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -48,7 +48,6 @@ #include "net/local_ip.h" #include "crypto/crypto.h" #include "storages/levin_abstract_invoke2.h" -#include "data_logger.hpp" // We have to look for miniupnpc headers in different places, dependent on if its compiled or external #ifdef UPNP_STATIC @@ -61,8 +60,12 @@ #include "upnperrors.h" #endif +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" + #define NET_MAKE_IP(b1,b2,b3,b4) ((LPARAM)(((DWORD)(b1)<<24)+((DWORD)(b2)<<16)+((DWORD)(b3)<<8)+((DWORD)(b4)))) +#define MIN_WANTED_SEED_NODES 12 namespace nodetool { @@ -106,8 +109,8 @@ namespace nodetool void node_server<t_payload_net_handler>::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_p2p_bind_ip); - command_line::add_arg(desc, arg_p2p_bind_port); - command_line::add_arg(desc, arg_testnet_p2p_bind_port); + command_line::add_arg(desc, arg_p2p_bind_port, false); + command_line::add_arg(desc, arg_testnet_p2p_bind_port, false); command_line::add_arg(desc, arg_p2p_external_port); command_line::add_arg(desc, arg_p2p_allow_local_ip); command_line::add_arg(desc, arg_p2p_add_peer); @@ -137,20 +140,43 @@ namespace nodetool { try { - boost::archive::binary_iarchive a(p2p_data); + // first try reading in portable mode + boost::archive::portable_binary_iarchive a(p2p_data); a >> *this; } - catch (const std::exception &e) + catch (...) { - LOG_ERROR("Failed to load p2p config file, falling back to default config"); - m_peerlist = peerlist_manager(); // it was probably half clobbered by the failed load - make_default_config(); + // if failed, try reading in unportable mode + boost::filesystem::copy_file(state_file_path, state_file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + p2p_data.close(); + p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); + if(!p2p_data.fail()) + { + try + { + boost::archive::binary_iarchive a(p2p_data); + a >> *this; + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to load p2p config file, falling back to default config"); + m_peerlist = peerlist_manager(); // it was probably half clobbered by the failed load + make_default_config(); + } + } + else + { + make_default_config(); + } } }else { make_default_config(); } + // always recreate a new peer id + make_default_peer_id(); + //at this moment we have hardcoded config m_config.m_net_config.handshake_interval = P2P_DEFAULT_HANDSHAKE_INTERVAL; m_config.m_net_config.packet_max_size = P2P_DEFAULT_PACKET_MAX_SIZE; //20 MB limit @@ -158,6 +184,7 @@ namespace nodetool m_config.m_net_config.connection_timeout = P2P_DEFAULT_CONNECTION_TIMEOUT; m_config.m_net_config.ping_connection_timeout = P2P_DEFAULT_PING_CONNECTION_TIMEOUT; m_config.m_net_config.send_peerlist_sz = P2P_DEFAULT_PEERS_IN_HANDSHAKE; + m_config.m_support_flags = P2P_SUPPORT_FLAGS; m_first_connection_maker_call = true; CATCH_ENTRY_L0("node_server::init_config", false); @@ -165,10 +192,10 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - void node_server<t_payload_net_handler>::for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type)> f) + void node_server<t_payload_net_handler>::for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f) { m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntx){ - return f(cntx, cntx.peer_id); + return f(cntx, cntx.peer_id, cntx.support_flags); }); } //----------------------------------------------------------------------------------- @@ -182,25 +209,45 @@ namespace nodetool if(time(nullptr) >= it->second) { m_blocked_ips.erase(it); - LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked.", LOG_LEVEL_0); + MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked."); return true; } return false; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::make_default_config() + bool node_server<t_payload_net_handler>::make_default_peer_id() { m_config.m_peer_id = crypto::rand<uint64_t>(); return true; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::make_default_config() + { + return make_default_peer_id(); + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::block_ip(uint32_t addr, time_t seconds) { CRITICAL_REGION_LOCAL(m_blocked_ips_lock); m_blocked_ips[addr] = time(nullptr) + seconds; - LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << " blocked.", LOG_LEVEL_0); + + // drop any connection to that IP + std::list<boost::uuids::uuid> conns; + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if (cntxt.m_remote_ip == addr) + { + conns.push_back(cntxt.m_connection_id); + } + return true; + }); + for (const auto &c: conns) + m_net_server.get_config_object().close(c); + + MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " blocked."); return true; } //----------------------------------------------------------------------------------- @@ -212,7 +259,7 @@ namespace nodetool if (i == m_blocked_ips.end()) return false; m_blocked_ips.erase(i); - LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked.", LOG_LEVEL_0); + MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked."); return true; } //----------------------------------------------------------------------------------- @@ -221,7 +268,7 @@ namespace nodetool { CRITICAL_REGION_LOCAL(m_ip_fails_score_lock); uint64_t fails = ++m_ip_fails_score[address]; - LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(address) << " fail score=" << fails, LOG_LEVEL_1); + MDEBUG("IP " << epee::string_tools::get_ip_string_from_int32(address) << " fail score=" << fails); if(fails > P2P_IP_FAILS_BEFORE_BLOCK) { auto it = m_ip_fails_score.find(address); @@ -241,10 +288,9 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::handle_command_line( const boost::program_options::variables_map& vm - , bool testnet ) { - auto p2p_bind_arg = testnet ? arg_testnet_p2p_bind_port : arg_p2p_bind_port; + auto p2p_bind_arg = m_testnet ? arg_testnet_p2p_bind_port : arg_p2p_bind_port; m_bind_ip = command_line::get_arg(vm, arg_p2p_bind_ip); m_port = command_line::get_arg(vm, p2p_bind_arg); @@ -263,7 +309,7 @@ namespace nodetool bool r = parse_peer_from_string(pe.adr, pr_str); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); if (pe.adr.port == 0) - pe.adr.port = testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; + pe.adr.port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; m_command_line_peers.push_back(pe); } } @@ -326,7 +372,7 @@ namespace nodetool io_service io_srv; ip::tcp::resolver resolver(io_srv); - ip::tcp::resolver::query query(host, port); + ip::tcp::resolver::query query(host, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code ec; ip::tcp::resolver::iterator i = resolver.resolve(query, ec); CHECK_AND_ASSERT_MES_NO_RET(!ec, "Failed to resolve host name '" << host << "': " << ec.message() << ':' << ec.value()); @@ -341,28 +387,53 @@ namespace nodetool na.ip = boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()); na.port = endpoint.port(); seed_nodes.push_back(na); - LOG_PRINT_L4("Added seed node: " << endpoint.address().to_v4().to_string(ec) << ':' << na.port); + MINFO("Added seed node: " << endpoint.address().to_v4().to_string(ec) << ':' << na.port); } else { - LOG_PRINT_L2("IPv6 doesn't supported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); + MDEBUG("IPv6 doesn't supported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); } } } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + std::set<std::string> node_server<t_payload_net_handler>::get_seed_nodes(bool testnet) const + { + std::set<std::string> full_addrs; + if (testnet) + { + full_addrs.insert("212.83.175.67:28080"); + full_addrs.insert("5.9.100.248:28080"); + full_addrs.insert("163.172.182.165:28080"); + full_addrs.insert("195.154.123.123:28080"); + full_addrs.insert("212.83.172.165:28080"); + } + else + { + full_addrs.insert("107.152.130.98:18080"); + full_addrs.insert("212.83.175.67:18080"); + full_addrs.insert("5.9.100.248:18080"); + full_addrs.insert("163.172.182.165:18080"); + full_addrs.insert("161.67.132.39:18080"); + full_addrs.insert("198.74.231.92:18080"); + full_addrs.insert("195.154.123.123:28080"); + full_addrs.insert("212.83.172.165:28080"); + } + return full_addrs; + } + + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::init(const boost::program_options::variables_map& vm) { std::set<std::string> full_addrs; - bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); - if (testnet) + if (m_testnet) { memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16); - full_addrs.insert("163.172.182.165:28080"); - full_addrs.insert("204.12.248.66:28080"); - full_addrs.insert("5.9.100.248:28080"); + full_addrs = get_seed_nodes(true); } else { @@ -381,7 +452,7 @@ namespace nodetool { boost::thread* th = new boost::thread([=, &dns_results, &addr_str] { - LOG_PRINT_L4("dns_threads[" << result_index << "] created for: " << addr_str); + MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); // TODO: care about dnssec avail/valid bool avail, valid; std::vector<std::string> addr_list; @@ -389,7 +460,7 @@ namespace nodetool try { addr_list = tools::DNSResolver::instance().get_ipv4(addr_str, avail, valid); - LOG_PRINT_L4("dns_threads[" << result_index << "] DNS resolve done"); + MDEBUG("dns_threads[" << result_index << "] DNS resolve done"); boost::this_thread::interruption_point(); } catch(const boost::thread_interrupted&) @@ -397,11 +468,11 @@ namespace nodetool // thread interruption request // even if we now have results, finish thread without setting // result variables, which are now out of scope in main thread - LOG_PRINT_L4("dns_threads[" << result_index << "] interrupted"); + MWARNING("dns_threads[" << result_index << "] interrupted"); return; } - LOG_PRINT_L4("dns_threads[" << result_index << "] addr_str: " << addr_str << " number of results: " << addr_list.size()); + MINFO("dns_threads[" << result_index << "] addr_str: " << addr_str << " number of results: " << addr_list.size()); dns_results[result_index] = addr_list; }); @@ -409,14 +480,14 @@ namespace nodetool ++result_index; } - LOG_PRINT_L4("dns_threads created, now waiting for completion or timeout of " << CRYPTONOTE_DNS_TIMEOUT_MS << "ms"); + MDEBUG("dns_threads created, now waiting for completion or timeout of " << CRYPTONOTE_DNS_TIMEOUT_MS << "ms"); boost::chrono::system_clock::time_point deadline = boost::chrono::system_clock::now() + boost::chrono::milliseconds(CRYPTONOTE_DNS_TIMEOUT_MS); uint64_t i = 0; for (boost::thread* th : dns_threads) { if (! th->try_join_until(deadline)) { - LOG_PRINT_L4("dns_threads[" << i << "] timed out, sending interrupt"); + MWARNING("dns_threads[" << i << "] timed out, sending interrupt"); th->interrupt(); } ++i; @@ -425,7 +496,7 @@ namespace nodetool i = 0; for (const auto& result : dns_results) { - LOG_PRINT_L4("DNS lookup for " << m_seed_nodes_list[i] << ": " << result.size() << " results"); + MDEBUG("DNS lookup for " << m_seed_nodes_list[i] << ": " << result.size() << " results"); // if no results for node, thread's lookup likely timed out if (result.size()) { @@ -435,30 +506,37 @@ namespace nodetool ++i; } - if (!full_addrs.size()) + // append the fallback nodes if we have too few seed nodes to start with + if (full_addrs.size() < MIN_WANTED_SEED_NODES) { - LOG_PRINT_L0("DNS seed node lookup either timed out or failed, falling back to defaults"); - full_addrs.insert("198.74.231.92:18080"); - full_addrs.insert("161.67.132.39:18080"); - full_addrs.insert("163.172.182.165:18080"); - full_addrs.insert("204.12.248.66:18080"); - full_addrs.insert("5.9.100.248:18080"); + if (full_addrs.empty()) + MINFO("DNS seed node lookup either timed out or failed, falling back to defaults"); + else + MINFO("Not enough DNS seed nodes found, using fallback defaults too"); + + for (const auto &peer: get_seed_nodes(false)) + full_addrs.insert(peer); } } for (const auto& full_addr : full_addrs) { - LOG_PRINT_L2("Seed node: " << full_addr); + MDEBUG("Seed node: " << full_addr); append_net_address(m_seed_nodes, full_addr); } - LOG_PRINT_L1("Number of seed nodes: " << m_seed_nodes.size()); + MDEBUG("Number of seed nodes: " << m_seed_nodes.size()); - bool res = handle_command_line(vm, testnet); + bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); - auto config_arg = testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + auto config_arg = m_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; m_config_folder = command_line::get_arg(vm, config_arg); + if ((!m_testnet && m_port != std::to_string(::config::P2P_DEFAULT_PORT)) + || (m_testnet && m_port != std::to_string(::config::testnet::P2P_DEFAULT_PORT))) { + m_config_folder = m_config_folder + "/" + m_port; + } + res = init_config(); CHECK_AND_ASSERT_MES(res, false, "Failed to init config."); @@ -485,18 +563,18 @@ namespace nodetool return res; //try to bind - LOG_PRINT_L0("Binding on " << m_bind_ip << ":" << m_port); + MINFO("Binding on " << m_bind_ip << ":" << m_port); res = m_net_server.init_server(m_port, m_bind_ip); CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); m_listenning_port = m_net_server.get_binded_port(); - LOG_PRINT_GREEN("Net service bound to " << m_bind_ip << ":" << m_listenning_port, LOG_LEVEL_0); + MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listenning_port); if(m_external_port) - LOG_PRINT_L0("External port defined as " << m_external_port); + MDEBUG("External port defined as " << m_external_port); // Add UPnP port mapping if(m_no_igd == false) { - LOG_PRINT_L0("Attempting to add IGD port mapping."); + MDEBUG("Attempting to add IGD port mapping."); int result; #if MINIUPNPC_API_VERSION > 13 // default according to miniupnpc.h @@ -523,19 +601,19 @@ namespace nodetool if (portMappingResult != 0) { LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); } else { - LOG_PRINT_GREEN("Added IGD port mapping.", LOG_LEVEL_0); + MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); } } else if (result == 2) { - LOG_PRINT_L0("IGD was found but reported as not connected."); + MWARNING("IGD was found but reported as not connected."); } else if (result == 3) { - LOG_PRINT_L0("UPnP device was found but not recoginzed as IGD."); + MWARNING("UPnP device was found but not recognized as IGD."); } else { - LOG_ERROR("UPNP_GetValidIGD returned an unknown result code."); + MWARNING("UPNP_GetValidIGD returned an unknown result code."); } FreeUPNPUrls(&urls); } else { - LOG_PRINT_L0("No IGD was found."); + MINFO("No IGD was found."); } } return res; @@ -565,9 +643,6 @@ namespace nodetool }); // lambda m_current_number_of_out_peers = number_of_peers; - if (epee::net_utils::data_logger::is_dying()) - break; - epee::net_utils::data_logger::get_instance().add_data("peers", number_of_peers); boost::this_thread::sleep_for(boost::chrono::seconds(1)); } // main loop of thread @@ -584,13 +659,13 @@ namespace nodetool attrs.set_stack_size(THREAD_STACK_SIZE); //go to loop - LOG_PRINT("Run net_service loop( " << thrds_count << " threads)...", LOG_LEVEL_0); + MINFO("Run net_service loop( " << thrds_count << " threads)..."); if(!m_net_server.run_server(thrds_count, true, attrs)) { LOG_ERROR("Failed to run net tcp server!"); } - LOG_PRINT("net_service loop stopped.", LOG_LEVEL_0); + MINFO("net_service loop stopped."); return true; } @@ -617,7 +692,7 @@ namespace nodetool TRY_ENTRY(); if (!tools::create_directories_if_necessary(m_config_folder)) { - LOG_PRINT_L0("Failed to create data directory: " << m_config_folder); + MWARNING("Failed to create data directory: " << m_config_folder); return false; } @@ -626,11 +701,11 @@ namespace nodetool p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::out| std::ios::trunc); if(p2p_data.fail()) { - LOG_PRINT_L0("Failed to save config to file " << state_file_path); + MWARNING("Failed to save config to file " << state_file_path); return false; }; - boost::archive::binary_oarchive a(p2p_data); + boost::archive::portable_binary_oarchive a(p2p_data); a << *this; return true; CATCH_ENTRY_L0("blockchain_storage::save", false); @@ -641,8 +716,9 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::send_stop_signal() { + m_payload_handler.stop(); m_net_server.send_stop_signal(); - LOG_PRINT_L0("[node] Stop signal sent"); + MDEBUG("[node] Stop signal sent"); return true; } //----------------------------------------------------------------------------------- @@ -666,19 +742,19 @@ namespace nodetool if(code < 0) { - LOG_PRINT_CC_RED(context, "COMMAND_HANDSHAKE invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")", LOG_LEVEL_1); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(rsp.node_data.network_id != m_network_id) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE Failed, wrong network! (" << epee::string_tools::get_str_from_guid_a(rsp.node_data.network_id) << "), closing connection."); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE Failed, wrong network! (" << epee::string_tools::get_str_from_guid_a(rsp.node_data.network_id) << "), closing connection."); return; } if(!handle_remote_peerlist(rsp.local_peerlist, rsp.node_data.local_time, context)) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); add_ip_fail(context.m_remote_ip); return; } @@ -687,7 +763,7 @@ namespace nodetool { if(!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, true)) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE invoked, but process_payload_sync_data returned false, dropping connection."); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE invoked, but process_payload_sync_data returned false, dropping connection."); hsh_result = false; return; } @@ -697,14 +773,14 @@ namespace nodetool if(rsp.node_data.peer_id == m_config.m_peer_id) { - LOG_PRINT_CCONTEXT_L2("Connection to self detected, dropping connection"); + LOG_DEBUG_CC(context, "Connection to self detected, dropping connection"); hsh_result = false; return; } - LOG_PRINT_CCONTEXT_L1(" COMMAND_HANDSHAKE INVOKED OK"); + LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK"); }else { - LOG_PRINT_CCONTEXT_L1(" COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); + LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); } }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); @@ -715,9 +791,16 @@ namespace nodetool if(!hsh_result) { - LOG_PRINT_CC_L1(context_, "COMMAND_HANDSHAKE Failed"); + LOG_ERROR_CC(context_, "COMMAND_HANDSHAKE Failed"); m_net_server.get_config_object().close(context_.m_connection_id); } + else + { + try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) + { + flags_context.support_flags = support_flags; + }); + } return hsh_result; } @@ -733,13 +816,13 @@ namespace nodetool { if(code < 0) { - LOG_PRINT_CC_RED(context, "COMMAND_TIMED_SYNC invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")", LOG_LEVEL_1); + LOG_ERROR_CC(context, "COMMAND_TIMED_SYNC invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(!handle_remote_peerlist(rsp.local_peerlist, rsp.local_time, context)) { - LOG_ERROR_CCONTEXT("COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); + LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); m_net_server.get_config_object().close(context.m_connection_id ); add_ip_fail(context.m_remote_ip); } @@ -750,7 +833,7 @@ namespace nodetool if(!r) { - LOG_PRINT_CC_L2(context_, "COMMAND_TIMED_SYNC Failed"); + LOG_ERROR_CC(context_, "COMMAND_TIMED_SYNC Failed"); return false; } return true; @@ -765,7 +848,7 @@ namespace nodetool size_t x = crypto::rand<size_t>()%(max_index+1); size_t res = (x*x*x)/(max_index*max_index); //parabola \/ - LOG_PRINT_L3("Random connection index=" << res << "(x="<< x << ", max_index=" << max_index << ")"); + MDEBUG("Random connection index=" << res << "(x="<< x << ", max_index=" << max_index << ")"); return res; } //----------------------------------------------------------------------------------- @@ -791,6 +874,30 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::is_peer_used(const anchor_peerlist_entry& peer) + { + if(m_config.m_peer_id == peer.id) { + return true;//dont make connections to ourself + } + + bool used = false; + + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr.ip == cntxt.m_remote_ip && peer.adr.port == cntxt.m_remote_port)) + { + used = true; + + return false;//stop enumerating + } + + return true; + }); + + return used; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::is_addr_connected(const net_address& peer) { bool connected = false; @@ -810,14 +917,14 @@ namespace nodetool #define LOG_PRINT_CC_PRIORITY_NODE(priority, con, msg) \ do { \ if (priority) {\ - LOG_PRINT_CC_L1(con, msg); \ + LOG_INFO_CC(con, "[priority]" << msg); \ } else {\ - LOG_PRINT_CC_L1(con, msg); \ + LOG_INFO_CC(con, msg); \ } \ } while(0) template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, bool white) + bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp) { if (m_current_number_of_out_peers == m_config.m_net_config.connections_count) // out peers limit { @@ -829,8 +936,8 @@ namespace nodetool m_current_number_of_out_peers --; // atomic variable, update time = 1s return false; } - LOG_PRINT_L1("Connecting to " << epee::string_tools::get_ip_string_from_int32(na.ip) << ":" - << epee::string_tools::num_to_string_fast(na.port) << "(white=" << white << ", last_seen: " + MDEBUG("Connecting to " << epee::string_tools::get_ip_string_from_int32(na.ip) << ":" + << epee::string_tools::num_to_string_fast(na.port) << "(peer_type=" << peer_type << ", last_seen: " << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") << ")..."); @@ -867,7 +974,7 @@ namespace nodetool if(just_take_peerlist) { m_net_server.get_config_object().close(con.m_connection_id); - LOG_PRINT_CC_GREEN(con, "CONNECTION HANDSHAKED OK AND CLOSED.", LOG_LEVEL_2); + LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); return true; } @@ -880,7 +987,58 @@ namespace nodetool m_peerlist.append_with_peer_white(pe_local); //update last seen and push it to peerlist manager - LOG_PRINT_CC_GREEN(con, "CONNECTION HANDSHAKED OK.", LOG_LEVEL_2); + anchor_peerlist_entry ape = AUTO_VAL_INIT(ape); + ape.adr = na; + ape.id = pi; + ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr); + + m_peerlist.append_with_peer_anchor(ape); + + LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK."); + return true; + } + + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::check_connection_and_handshake_with_peer(const net_address& na, uint64_t last_seen_stamp) + { + LOG_PRINT_L1("Connecting to " << epee::string_tools::get_ip_string_from_int32(na.ip) << ":" + << epee::string_tools::num_to_string_fast(na.port) << "(last_seen: " + << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") + << ")..."); + + typename net_server::t_connection_context con = AUTO_VAL_INIT(con); + bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(na.ip), + epee::string_tools::num_to_string_fast(na.port), + m_config.m_net_config.connection_timeout, + con); + + if (!res) { + bool is_priority = is_priority_node(na); + + LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " + << epee::string_tools::get_ip_string_from_int32(na.ip) + << ":" << epee::string_tools::num_to_string_fast(na.port)); + + return false; + } + + peerid_type pi = AUTO_VAL_INIT(pi); + res = do_handshake_with_peer(pi, con, true); + + if (!res) { + bool is_priority = is_priority_node(na); + + LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " + << epee::string_tools::get_ip_string_from_int32(na.ip) + << ":" << epee::string_tools::num_to_string_fast(na.port)); + + return false; + } + + m_net_server.get_config_object().close(con.m_connection_id); + + LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); + return true; } @@ -902,6 +1060,41 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist) + { + for (const auto& pe: anchor_peerlist) { + _note("Considering connecting (out) to peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port)); + + if(is_peer_used(pe)) { + _note("Peer is used"); + continue; + } + + if(!is_remote_ip_allowed(pe.adr.ip)) { + continue; + } + + if(is_addr_recently_failed(pe.adr)) { + continue; + } + + MDEBUG("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) + << ":" << boost::lexical_cast<std::string>(pe.adr.port) + << "[peer_type=" << anchor + << "] first_seen: " << epee::misc_utils::get_time_interval_string(time(NULL) - pe.first_seen)); + + if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, 0, anchor, pe.first_seen)) { + _note("Handshake failed"); + continue; + } + + return true; + } + + return false; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::make_new_connection_from_peerlist(bool use_white_list) { size_t local_peers_count = use_white_list ? m_peerlist.get_white_peers_count():m_peerlist.get_gray_peers_count(); @@ -917,7 +1110,14 @@ namespace nodetool while(rand_count < (max_random_index+1)*3 && try_count < 10 && !m_net_server.is_stop_signal_sent()) { ++rand_count; - size_t random_index = get_random_index_with_fixed_probability(max_random_index); + size_t random_index; + + if (use_white_list) { + random_index = get_random_index_with_fixed_probability(max_random_index); + } else { + random_index = crypto::rand<size_t>() % m_peerlist.get_gray_peers_count(); + } + CHECK_AND_ASSERT_MES(random_index < local_peers_count, false, "random_starter_index < peers_local.size() failed!!"); if(tried_peers.count(random_index)) @@ -943,12 +1143,12 @@ namespace nodetool if(is_addr_recently_failed(pe.adr)) continue; - LOG_PRINT_L2("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) + MDEBUG("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port) - << "[white=" << use_white_list + << "[peer_list=" << (use_white_list ? white : gray) << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); - if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, pe.last_seen, use_white_list)) { + if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, pe.last_seen, use_white_list ? white : gray)) { _note("Handshake failed"); continue; } @@ -969,6 +1169,7 @@ namespace nodetool { size_t try_count = 0; size_t current_index = crypto::rand<size_t>()%m_seed_nodes.size(); + bool fallback_nodes_added = false; while(true) { if(m_net_server.is_stop_signal_sent()) @@ -978,8 +1179,22 @@ namespace nodetool break; if(++try_count > m_seed_nodes.size()) { - LOG_PRINT_RED_L0("Failed to connect to any of seed peers, continuing without seeds"); - break; + if (!fallback_nodes_added) + { + MWARNING("Failed to connect to any of seed peers, trying fallback seeds"); + for (const auto &peer: get_seed_nodes(m_testnet)) + { + MDEBUG("Fallback seed node: " << peer); + append_net_address(m_seed_nodes, peer); + } + fallback_nodes_added = true; + // continue for another few cycles + } + else + { + MWARNING("Failed to connect to any of seed peers, continuing without seeds"); + break; + } } if(++current_index >= m_seed_nodes.size()) current_index = 0; @@ -995,19 +1210,22 @@ namespace nodetool { if(conn_count < expected_white_connections) { - //start from white list - if(!make_expected_connections_count(true, expected_white_connections)) + //start from anchor list + if(!make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT)) return false; - //and then do grey list - if(!make_expected_connections_count(false, m_config.m_net_config.connections_count)) + //then do white list + if(!make_expected_connections_count(white, expected_white_connections)) + return false; + //then do grey list + if(!make_expected_connections_count(gray, m_config.m_net_config.connections_count)) return false; }else { //start from grey list - if(!make_expected_connections_count(false, m_config.m_net_config.connections_count)) + if(!make_expected_connections_count(gray, m_config.m_net_config.connections_count)) return false; //and then do white list - if(!make_expected_connections_count(true, m_config.m_net_config.connections_count)) + if(!make_expected_connections_count(white, m_config.m_net_config.connections_count)) return false; } } @@ -1016,11 +1234,17 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::make_expected_connections_count(bool white_list, size_t expected_connections) + bool node_server<t_payload_net_handler>::make_expected_connections_count(PeerType peer_type, size_t expected_connections) { if (m_offline) return true; + std::vector<anchor_peerlist_entry> apl; + + if (peer_type == anchor) { + m_peerlist.get_and_empty_anchor_peerlist(apl); + } + size_t conn_count = get_outgoing_connections_count(); //add new connections from white peers while(conn_count < expected_connections) @@ -1028,8 +1252,18 @@ namespace nodetool if(m_net_server.is_stop_signal_sent()) return false; - if(!make_new_connection_from_peerlist(white_list)) + if (peer_type == anchor && !make_new_connection_from_anchor_peerlist(apl)) { + break; + } + + if (peer_type == white && !make_new_connection_from_peerlist(true)) { break; + } + + if (peer_type == gray && !make_new_connection_from_peerlist(false)) { + break; + } + conn_count = get_outgoing_connections_count(); } return true; @@ -1055,6 +1289,7 @@ namespace nodetool { m_peer_handshake_idle_maker_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::peer_sync_idle_maker, this)); m_connections_maker_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::connections_maker, this)); + m_gray_peerlist_housekeeping_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::gray_peerlist_housekeeping, this)); m_peerlist_store_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::store_config, this)); return true; } @@ -1062,7 +1297,7 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::peer_sync_idle_maker() { - LOG_PRINT_L2("STARTED PEERLIST IDLE HANDSHAKE"); + MDEBUG("STARTED PEERLIST IDLE HANDSHAKE"); typedef std::list<std::pair<epee::net_utils::connection_context_base, peerid_type> > local_connects_type; local_connects_type cncts; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) @@ -1074,7 +1309,7 @@ namespace nodetool std::for_each(cncts.begin(), cncts.end(), [&](const typename local_connects_type::value_type& vl){do_peer_timed_sync(vl.first, vl.second);}); - LOG_PRINT_L2("FINISHED PEERLIST IDLE HANDSHAKE"); + MDEBUG("FINISHED PEERLIST IDLE HANDSHAKE"); return true; } //----------------------------------------------------------------------------------- @@ -1086,11 +1321,11 @@ namespace nodetool time(&now); delta = now - local_time; - BOOST_FOREACH(peerlist_entry& be, local_peerlist) + for(peerlist_entry& be: local_peerlist) { if(be.last_seen > local_time) { - LOG_PRINT_RED_L1("FOUND FUTURE peerlist for entry " << epee::string_tools::get_ip_string_from_int32(be.adr.ip) << ":" << be.adr.port << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); + MWARNING("FOUND FUTURE peerlist for entry " << epee::string_tools::get_ip_string_from_int32(be.adr.ip) << ":" << be.adr.port << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); return false; } be.last_seen += delta; @@ -1105,8 +1340,8 @@ namespace nodetool std::list<peerlist_entry> peerlist_ = peerlist; if(!fix_time_delta(peerlist_, local_time, delta)) return false; - LOG_PRINT_CCONTEXT_L2("REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size()); - LOG_PRINT_CCONTEXT_L3("REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_)); + LOG_DEBUG_CC(context, "REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size()); + LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_)); return m_peerlist.merge_peerlist(peerlist_); } //----------------------------------------------------------------------------------- @@ -1209,12 +1444,29 @@ namespace nodetool #endif //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + int node_server<t_payload_net_handler>::handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context) + { + rsp.support_flags = m_config.m_support_flags; + return 1; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> void node_server<t_payload_net_handler>::request_callback(const epee::net_utils::connection_context_base& context) { m_net_server.get_config_object().request_callback(context.m_connection_id); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::relay_notify_to_list(int command, const std::string& data_buff, const std::list<boost::uuids::uuid> &connections) + { + for(const auto& c_id: connections) + { + m_net_server.get_config_object().notify(command, data_buff, c_id); + } + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context) { std::list<boost::uuids::uuid> connections; @@ -1224,12 +1476,7 @@ namespace nodetool connections.push_back(cntxt.m_connection_id); return true; }); - - BOOST_FOREACH(const auto& c_id, connections) - { - m_net_server.get_config_object().notify(command, data_buff, c_id); - } - return true; + return relay_notify_to_list(command, data_buff, connections); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> @@ -1277,7 +1524,7 @@ namespace nodetool { if(ec) { - LOG_PRINT_CC_L2(ping_context, "back ping connect failed to " << ip << ":" << port); + LOG_WARNING_CC(ping_context, "back ping connect failed to " << ip << ":" << port); return false; } COMMAND_PING::request req; @@ -1296,13 +1543,13 @@ namespace nodetool { if(code <= 0) { - LOG_PRINT_CC_L2(ping_context, "Failed to invoke COMMAND_PING to " << ip << ":" << port << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); + LOG_ERROR_CC(ping_context, "Failed to invoke COMMAND_PING to " << ip << ":" << port << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) { - LOG_PRINT_CC_L2(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << ip << ":" << port << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); + LOG_ERROR_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << ip << ":" << port << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); m_net_server.get_config_object().close(ping_context.m_connection_id); return; } @@ -1312,7 +1559,7 @@ namespace nodetool if(!inv_call_res) { - LOG_PRINT_CC_L2(ping_context, "back ping invoke failed to " << ip << ":" << port); + LOG_ERROR_CC(ping_context, "back ping invoke failed to " << ip << ":" << port); m_net_server.get_config_object().close(ping_context.m_connection_id); return false; } @@ -1320,17 +1567,43 @@ namespace nodetool }); if(!r) { - LOG_ERROR("Failed to call connect_async, network error."); + LOG_ERROR_CC(context, "Failed to call connect_async, network error."); } return r; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f) + { + COMMAND_REQUEST_SUPPORT_FLAGS::request support_flags_request; + bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_REQUEST_SUPPORT_FLAGS::response> + ( + context.m_connection_id, + COMMAND_REQUEST_SUPPORT_FLAGS::ID, + support_flags_request, + m_net_server.get_config_object(), + [=](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_) + { + if(code < 0) + { + LOG_ERROR_CC(context_, "COMMAND_REQUEST_SUPPORT_FLAGS invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); + return; + } + + f(context_, rsp.support_flags); + }, + P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT + ); + + return r; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> int node_server<t_payload_net_handler>::handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) { if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, false)) { - LOG_ERROR_CCONTEXT("Failed to process_payload_sync_data(), dropping connection"); + LOG_ERROR_CC(context, "Failed to process_payload_sync_data(), dropping connection"); drop_connection(context); return 1; } @@ -1339,7 +1612,7 @@ namespace nodetool rsp.local_time = time(NULL); m_peerlist.get_peerlist_head(rsp.local_peerlist); m_payload_handler.get_payload_sync_data(rsp.payload_data); - LOG_PRINT_CCONTEXT_L2("COMMAND_TIMED_SYNC"); + LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); return 1; } //----------------------------------------------------------------------------------- @@ -1349,7 +1622,7 @@ namespace nodetool if(arg.node_data.network_id != m_network_id) { - LOG_PRINT_CCONTEXT_L1("WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); + LOG_INFO_CC(context, "WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); drop_connection(context); add_ip_fail(context.m_remote_ip); return 1; @@ -1357,7 +1630,7 @@ namespace nodetool if(!context.m_is_income) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE came not from incoming connection"); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE came not from incoming connection"); drop_connection(context); add_ip_fail(context.m_remote_ip); return 1; @@ -1365,24 +1638,32 @@ namespace nodetool if(context.peer_id) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE came, but seems that connection already have associated peer_id (double COMMAND_HANDSHAKE?)"); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE came, but seems that connection already have associated peer_id (double COMMAND_HANDSHAKE?)"); drop_connection(context); return 1; } if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, true)) { - LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection."); + LOG_ERROR_CC(context, "COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection."); + drop_connection(context); + return 1; + } + + if(has_too_many_connections(context.m_remote_ip)) + { + LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << epee::string_tools::get_ip_string_from_int32(context.m_remote_ip) << " REFUSED, too many connections from the same address"); drop_connection(context); return 1; } + //associate peer_id with this connection context.peer_id = arg.node_data.peer_id; if(arg.node_data.peer_id != m_config.m_peer_id && arg.node_data.my_port) { peerid_type peer_id_l = arg.node_data.peer_id; - uint32_t port_l = arg.node_data.my_port; + uint32_t port_l = arg.node_data.my_port; //try ping to be sure that we can add this peer to peer_list try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]() { @@ -1395,22 +1676,27 @@ namespace nodetool pe.last_seen = static_cast<int64_t>(last_seen); pe.id = peer_id_l; this->m_peerlist.append_with_peer_white(pe); - LOG_PRINT_CCONTEXT_L2("PING SUCCESS " << epee::string_tools::get_ip_string_from_int32(context.m_remote_ip) << ":" << port_l); + LOG_DEBUG_CC(context, "PING SUCCESS " << epee::string_tools::get_ip_string_from_int32(context.m_remote_ip) << ":" << port_l); }); } + + try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) + { + flags_context.support_flags = support_flags; + }); //fill response m_peerlist.get_peerlist_head(rsp.local_peerlist); get_local_node_data(rsp.node_data); m_payload_handler.get_payload_sync_data(rsp.payload_data); - LOG_PRINT_CCONTEXT_GREEN("COMMAND_HANDSHAKE", LOG_LEVEL_1); + LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); return 1; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> int node_server<t_payload_net_handler>::handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context) { - LOG_PRINT_CCONTEXT_L2("COMMAND_PING"); + LOG_DEBUG_CC(context, "COMMAND_PING"); rsp.status = PING_OK_RESPONSE_STATUS_TEXT; rsp.peer_id = m_config.m_peer_id; return 1; @@ -1419,17 +1705,17 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::log_peerlist() { - std::list<peerlist_entry> pl_wite; + std::list<peerlist_entry> pl_white; std::list<peerlist_entry> pl_gray; - m_peerlist.get_peerlist_full(pl_gray, pl_wite); - LOG_PRINT_L0(ENDL << "Peerlist white:" << ENDL << print_peerlist_to_string(pl_wite) << ENDL << "Peerlist gray:" << ENDL << print_peerlist_to_string(pl_gray) ); + m_peerlist.get_peerlist_full(pl_gray, pl_white); + MINFO(ENDL << "Peerlist white:" << ENDL << print_peerlist_to_string(pl_white) << ENDL << "Peerlist gray:" << ENDL << print_peerlist_to_string(pl_gray) ); return true; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::log_connections() { - LOG_PRINT_L0("Connections: \r\n" << print_connections_container() ); + MINFO("Connections: \r\n" << print_connections_container() ); return true; } //----------------------------------------------------------------------------------- @@ -1453,13 +1739,21 @@ namespace nodetool template<class t_payload_net_handler> void node_server<t_payload_net_handler>::on_connection_new(p2p_connection_context& context) { - LOG_PRINT_L2("["<< epee::net_utils::print_connection_context(context) << "] NEW CONNECTION"); + MINFO("["<< epee::net_utils::print_connection_context(context) << "] NEW CONNECTION"); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> void node_server<t_payload_net_handler>::on_connection_close(p2p_connection_context& context) { - LOG_PRINT_L2("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); + if (!m_net_server.is_stop_signal_sent() && !context.m_is_income) { + nodetool::net_address na = AUTO_VAL_INIT(na); + na.ip = context.m_remote_ip; + na.port = context.m_remote_port; + + m_peerlist.remove_from_peer_anchor(na); + } + + MINFO("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); } template<class t_payload_net_handler> @@ -1489,7 +1783,6 @@ namespace nodetool bool node_server<t_payload_net_handler>::parse_peers_and_add_to_container(const boost::program_options::variables_map& vm, const command_line::arg_descriptor<std::vector<std::string> > & arg, Container& container) { std::vector<std::string> perrs = command_line::get_arg(vm, arg); - bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); for(const std::string& pr_str: perrs) { @@ -1497,7 +1790,7 @@ namespace nodetool bool r = parse_peer_from_string(na, pr_str); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); if (na.port == 0) - na.port = testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; + na.port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; container.push_back(na); } @@ -1509,10 +1802,8 @@ namespace nodetool { if(max == -1) { m_config.m_net_config.connections_count = P2P_DEFAULT_CONNECTIONS_COUNT; - epee::net_utils::data_logger::get_instance().add_data("peers_limit", m_config.m_net_config.connections_count); return true; } - epee::net_utils::data_logger::get_instance().add_data("peers_limit", max); m_config.m_net_config.connections_count = max; return true; } @@ -1546,7 +1837,7 @@ namespace nodetool limit *= 1024; epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_up_limit( limit ); - LOG_PRINT_L0("Set limit-up to " << limit/1024 << " kB/s"); + MINFO("Set limit-up to " << limit/1024 << " kB/s"); return true; } @@ -1560,7 +1851,7 @@ namespace nodetool } limit *= 1024; epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_down_limit( limit ); - LOG_PRINT_L0("Set limit-down to " << limit/1024 << " kB/s"); + MINFO("Set limit-down to " << limit/1024 << " kB/s"); return true; } @@ -1582,13 +1873,61 @@ namespace nodetool } if(!this->islimitup) { epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_up_limit(limit_up); - LOG_PRINT_L0("Set limit-up to " << limit_up/1024 << " kB/s"); + MINFO("Set limit-up to " << limit_up/1024 << " kB/s"); } if(!this->islimitdown) { epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_down_limit(limit_down); - LOG_PRINT_L0("Set limit-down to " << limit_down/1024 << " kB/s"); + MINFO("Set limit-down to " << limit_down/1024 << " kB/s"); } return true; } + + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::has_too_many_connections(const uint32_t ip) + { + const uint8_t max_connections = 1; + uint8_t count = 0; + + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if (cntxt.m_is_income && cntxt.m_remote_ip == ip) { + count++; + + if (count > max_connections) { + return false; + } + } + + return true; + }); + + return count > max_connections; + } + + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::gray_peerlist_housekeeping() + { + peerlist_entry pe = AUTO_VAL_INIT(pe); + + if (!m_peerlist.get_random_gray_peer(pe)) { + return false; + } + + bool success = check_connection_and_handshake_with_peer(pe.adr, pe.last_seen); + + if (!success) { + m_peerlist.remove_from_peer_gray(pe); + + LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << " Peer ID: " << std::hex << pe.id); + + return true; + } + + m_peerlist.set_peer_just_seen(pe.id, pe.adr); + + LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << " Peer ID: " << std::hex << pe.id); + + return true; + } } diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 5e7645365..6aba7aa24 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -43,13 +43,14 @@ namespace nodetool template<class t_connection_context> struct i_p2p_endpoint { + virtual bool relay_notify_to_list(int command, const std::string& data_buff, const std::list<boost::uuids::uuid>& connections)=0; virtual bool relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool invoke_command_to_peer(int command, const std::string& req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool invoke_notify_to_peer(int command, const std::string& req_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; virtual void request_callback(const epee::net_utils::connection_context_base& context)=0; virtual uint64_t get_connections_count()=0; - virtual void for_each_connection(std::function<bool(t_connection_context&, peerid_type)> f)=0; + virtual void for_each_connection(std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0; virtual bool block_ip(uint32_t adress, time_t seconds = 0)=0; virtual bool unblock_ip(uint32_t adress)=0; virtual std::map<uint32_t, time_t> get_blocked_ips()=0; @@ -59,6 +60,10 @@ namespace nodetool template<class t_connection_context> struct p2p_endpoint_stub: public i_p2p_endpoint<t_connection_context> { + virtual bool relay_notify_to_list(int command, const std::string& data_buff, const std::list<boost::uuids::uuid>& connections) + { + return false; + } virtual bool relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context) { return false; @@ -79,7 +84,7 @@ namespace nodetool { } - virtual void for_each_connection(std::function<bool(t_connection_context&,peerid_type)> f) + virtual void for_each_connection(std::function<bool(t_connection_context&,peerid_type,uint32_t)> f) { } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index c19ecf65f..de0f51cc3 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -33,17 +33,18 @@ #include <list> #include <set> #include <map> -#include <boost/foreach.hpp> //#include <boost/bimap.hpp> //#include <boost/bimap/multiset_of.hpp> -#include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> #include <boost/serialization/version.hpp> #include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/identity.hpp> #include <boost/multi_index/member.hpp> +#include <boost/range/adaptor/reversed.hpp> #include "syncobj.h" @@ -53,7 +54,7 @@ #include "net_peerlist_boost_serialization.h" -#define CURRENT_PEERLIST_STORAGE_ARCHIVE_VER 4 +#define CURRENT_PEERLIST_STORAGE_ARCHIVE_VER 5 namespace nodetool { @@ -76,11 +77,15 @@ namespace nodetool bool get_gray_peer_by_index(peerlist_entry& p, size_t i); bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr); + bool append_with_peer_anchor(const anchor_peerlist_entry& ple); bool set_peer_just_seen(peerid_type peer, uint32_t ip, uint32_t port); bool set_peer_just_seen(peerid_type peer, const net_address& addr); bool set_peer_unreachable(const peerlist_entry& pr); bool is_ip_allowed(uint32_t ip); - + bool get_random_gray_peer(peerlist_entry& pe); + bool remove_from_peer_gray(const peerlist_entry& pe); + bool get_and_empty_anchor_peerlist(std::vector<anchor_peerlist_entry>& apl); + bool remove_from_peer_anchor(const net_address& addr); private: struct by_time{}; @@ -142,6 +147,16 @@ namespace nodetool boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_time>, boost::multi_index::member<peerlist_entry,int64_t,&peerlist_entry::last_seen> > > > peers_indexed_old; + + typedef boost::multi_index_container< + anchor_peerlist_entry, + boost::multi_index::indexed_by< + // access by anchor_peerlist_entry::net_adress + boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<anchor_peerlist_entry,net_address,&anchor_peerlist_entry::adr> >, + // sort by anchor_peerlist_entry::first_seen + boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_time>, boost::multi_index::member<anchor_peerlist_entry,int64_t,&anchor_peerlist_entry::first_seen> > + > + > anchor_peers_indexed; public: template <class Archive, class t_version_type> @@ -158,8 +173,15 @@ namespace nodetool peers_indexed_from_old(pio, m_peers_white); return; } + a & m_peers_white; a & m_peers_gray; + + if(ver < 5) { + return; + } + + a & m_peers_anchor; } private: @@ -175,6 +197,7 @@ namespace nodetool peers_indexed m_peers_gray; peers_indexed m_peers_white; + anchor_peers_indexed m_peers_anchor; }; //-------------------------------------------------------------------------------------------------- inline @@ -227,7 +250,7 @@ namespace nodetool bool peerlist_manager::merge_peerlist(const std::list<peerlist_entry>& outer_bs) { CRITICAL_REGION_LOCAL(m_peerlist_lock); - BOOST_FOREACH(const peerlist_entry& be, outer_bs) + for(const peerlist_entry& be: outer_bs) { append_with_peer_gray(be); } @@ -280,13 +303,15 @@ namespace nodetool CRITICAL_REGION_LOCAL(m_peerlist_lock); peers_indexed::index<by_time>::type& by_time_index=m_peers_white.get<by_time>(); uint32_t cnt = 0; - BOOST_REVERSE_FOREACH(const peers_indexed::value_type& vl, by_time_index) + for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index)) { if(!vl.last_seen) continue; - bs_head.push_back(vl); - if(cnt++ > depth) + + if(cnt++ >= depth) break; + + bs_head.push_back(vl); } return true; } @@ -296,13 +321,13 @@ namespace nodetool { CRITICAL_REGION_LOCAL(m_peerlist_lock); peers_indexed::index<by_time>::type& by_time_index_gr=m_peers_gray.get<by_time>(); - BOOST_REVERSE_FOREACH(const peers_indexed::value_type& vl, by_time_index_gr) + for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index_gr)) { pl_gray.push_back(vl); } peers_indexed::index<by_time>::type& by_time_index_wt=m_peers_white.get<by_time>(); - BOOST_REVERSE_FOREACH(const peers_indexed::value_type& vl, by_time_index_wt) + for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index_wt)) { pl_white.push_back(vl); } @@ -390,7 +415,102 @@ namespace nodetool } return true; CATCH_ENTRY_L0("peerlist_manager::append_with_peer_gray()", false); + } + //-------------------------------------------------------------------------------------------------- + inline + bool peerlist_manager::append_with_peer_anchor(const anchor_peerlist_entry& ple) + { + TRY_ENTRY(); + + CRITICAL_REGION_LOCAL(m_peerlist_lock); + + auto by_addr_it_anchor = m_peers_anchor.get<by_addr>().find(ple.adr); + + if(by_addr_it_anchor == m_peers_anchor.get<by_addr>().end()) { + m_peers_anchor.insert(ple); + } + + return true; + + CATCH_ENTRY_L0("peerlist_manager::append_with_peer_anchor()", false); + } + //-------------------------------------------------------------------------------------------------- + inline + bool peerlist_manager::get_random_gray_peer(peerlist_entry& pe) + { + TRY_ENTRY(); + + CRITICAL_REGION_LOCAL(m_peerlist_lock); + + if (m_peers_gray.empty()) { + return false; + } + + size_t random_index = crypto::rand<size_t>() % m_peers_gray.size(); + + peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); + pe = *epee::misc_utils::move_it_backward(--by_time_index.end(), random_index); + + return true; + + CATCH_ENTRY_L0("peerlist_manager::get_random_gray_peer()", false); + } + //-------------------------------------------------------------------------------------------------- + inline + bool peerlist_manager::remove_from_peer_gray(const peerlist_entry& pe) + { + TRY_ENTRY(); + + CRITICAL_REGION_LOCAL(m_peerlist_lock); + + peers_indexed::index_iterator<by_addr>::type iterator = m_peers_gray.get<by_addr>().find(pe.adr); + + if (iterator != m_peers_gray.get<by_addr>().end()) { + m_peers_gray.erase(iterator); + } + return true; + + CATCH_ENTRY_L0("peerlist_manager::remove_from_peer_gray()", false); + } + //-------------------------------------------------------------------------------------------------- + inline + bool peerlist_manager::get_and_empty_anchor_peerlist(std::vector<anchor_peerlist_entry>& apl) + { + TRY_ENTRY(); + + CRITICAL_REGION_LOCAL(m_peerlist_lock); + + auto begin = m_peers_anchor.get<by_time>().begin(); + auto end = m_peers_anchor.get<by_time>().end(); + + std::for_each(begin, end, [&apl](const anchor_peerlist_entry &a) { + apl.push_back(a); + }); + + m_peers_anchor.get<by_time>().clear(); + + return true; + + CATCH_ENTRY_L0("peerlist_manager::get_and_empty_anchor_peerlist()", false); + } + //-------------------------------------------------------------------------------------------------- + inline + bool peerlist_manager::remove_from_peer_anchor(const net_address& addr) + { + TRY_ENTRY(); + + CRITICAL_REGION_LOCAL(m_peerlist_lock); + + anchor_peers_indexed::index_iterator<by_addr>::type iterator = m_peers_anchor.get<by_addr>().find(addr); + + if (iterator != m_peers_anchor.get<by_addr>().end()) { + m_peers_anchor.erase(iterator); + } + + return true; + + CATCH_ENTRY_L0("peerlist_manager::remove_from_peer_anchor()", false); } //-------------------------------------------------------------------------------------------------- } diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index d9a11728b..6891ac80c 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -49,6 +49,14 @@ namespace boost a & pl.adr; a & pl.id; a & pl.last_seen; - } + } + + template <class Archive, class ver_type> + inline void serialize(Archive &a, nodetool::anchor_peerlist_entry& pl, const ver_type ver) + { + a & pl.adr; + a & pl.id; + a & pl.first_seen; + } } } diff --git a/src/p2p/network_throttle-detail.cpp b/src/p2p/network_throttle-detail.cpp index ed3c8e7b4..0747b6f36 100644 --- a/src/p2p/network_throttle-detail.cpp +++ b/src/p2p/network_throttle-detail.cpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief implementaion for throttling of connection (count and rate-limit speed etc) -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -54,7 +54,6 @@ #include "../../contrib/epee/include/net/net_utils_base.h" #include "../../contrib/epee/include/misc_log_ex.h" #include <boost/lambda/bind.hpp> -#include <boost/foreach.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> #include <boost/chrono.hpp> @@ -77,9 +76,8 @@ // TODO: #include "../../src/p2p/network_throttle-detail.hpp" -#include "../../contrib/otshell_utils/utils.hpp" -#include "data_logger.hpp" -using namespace nOT::nUtils; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.throttle" // ################################################################################################ // ################################################################################################ @@ -89,8 +87,6 @@ using namespace nOT::nUtils; // ################################################################################################ // ################################################################################################ -using namespace nOT::nUtils; - namespace epee { namespace net_utils @@ -163,7 +159,7 @@ void network_throttle::set_name(const std::string &name) void network_throttle::set_target_speed( network_speed_kbps target ) { m_target_speed = target * 1024; - _note_c("net/"+m_nameshort, "Setting LIMIT: " << target << " kbps"); + MINFO("Setting LIMIT: " << target << " kbps"); set_real_target_speed(target); } @@ -220,7 +216,7 @@ void network_throttle::_handle_trafic_exact(size_t packet_size, size_t orginal_s std::ostringstream oss; oss << "["; for (auto sample: m_history) oss << sample.m_size << " "; oss << "]" << std::ends; std::string history_str = oss.str(); - _dbg2_c( "net/" + m_nameshort , "Throttle " << m_name << ": packet of ~"<<packet_size<<"b " << " (from "<<orginal_size<<" b)" + MTRACE("Throttle " << m_name << ": packet of ~"<<packet_size<<"b " << " (from "<<orginal_size<<" b)" << " Speed AVG=" << std::setw(4) << ((long int)(cts .average/1024)) <<"[w="<<cts .window<<"]" << " " << std::setw(4) << ((long int)(cts2.average/1024)) <<"[w="<<cts2.window<<"]" <<" / " << " Limit="<< ((long int)(m_target_speed/1024)) <<" KiB/sec " @@ -241,8 +237,6 @@ network_time_seconds network_throttle::get_sleep_time_after_tick(size_t packet_s } void network_throttle::logger_handle_net(const std::string &filename, double time, size_t size) { - if (! epee::net_utils::data_logger::m_save_graph) - return; boost::mutex mutex; mutex.lock(); { std::fstream file; @@ -312,8 +306,7 @@ void network_throttle::calculate_times(size_t packet_size, calculate_times_struc if (dbg) { std::ostringstream oss; oss << "["; for (auto sample: m_history) oss << sample.m_size << " "; oss << "]" << std::ends; std::string history_str = oss.str(); - _dbg1_c( "net/"+m_nameshort+"_c" , - (cts.delay > 0 ? "SLEEP" : "") + MTRACE((cts.delay > 0 ? "SLEEP" : "") << "dbg " << m_name << ": " << "speed is A=" << std::setw(8) <<cts.average<<" vs " << "Max=" << std::setw(8) <<M<<" " diff --git a/src/p2p/network_throttle-detail.hpp b/src/p2p/network_throttle-detail.hpp index 5960fa91d..c514db450 100644 --- a/src/p2p/network_throttle-detail.hpp +++ b/src/p2p/network_throttle-detail.hpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief implementaion for throttling of connection (count and rate-limit speed etc) -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -67,9 +67,6 @@ class network_throttle : public i_network_throttle { network_time_seconds m_start_time; // when we were created bool m_any_packet_yet; // did we yet got any packet to count - double m_overheat; // last overheat - double m_overheat_time; // time in seconds after epoch - std::string m_name; // my name for debug and logs std::string m_nameshort; // my name for debug and logs (used in log file name) diff --git a/src/p2p/network_throttle.cpp b/src/p2p/network_throttle.cpp index 30538bb3c..6d68f3286 100644 --- a/src/p2p/network_throttle.cpp +++ b/src/p2p/network_throttle.cpp @@ -26,7 +26,7 @@ Throttling work by: */ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/p2p/network_throttle.hpp b/src/p2p/network_throttle.hpp index b954c5b3a..a747cdd71 100644 --- a/src/p2p/network_throttle.hpp +++ b/src/p2p/network_throttle.hpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief interface for throttling of connection (count and rate-limit speed etc) -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -57,7 +57,6 @@ #include "../../contrib/epee/include/net/net_utils_base.h" #include "../../contrib/epee/include/misc_log_ex.h" #include <boost/lambda/bind.hpp> -#include <boost/foreach.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> #include <boost/chrono.hpp> diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index bad4c89e3..5ec012714 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -56,6 +56,13 @@ namespace nodetool int64_t last_seen; }; + struct anchor_peerlist_entry + { + net_address adr; + peerid_type id; + int64_t first_seen; + }; + struct connection_entry { net_address adr; @@ -83,7 +90,7 @@ namespace nodetool time(&now_time); std::stringstream ss; ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; - BOOST_FOREACH(const peerlist_entry& pe, pl) + for(const peerlist_entry& pe: pl) { ss << pe.id << "\t" << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port) << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; } @@ -332,6 +339,29 @@ namespace nodetool }; }; + /************************************************************************/ + /* */ + /************************************************************************/ + struct COMMAND_REQUEST_SUPPORT_FLAGS + { + const static int ID = P2P_COMMANDS_POOL_BASE + 7; + + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t support_flags; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(support_flags) + END_KV_SERIALIZE_MAP() + }; + }; + #endif diff --git a/src/p2p/stdafx.h b/src/p2p/stdafx.h index 77daa484d..5e9baa895 100644 --- a/src/p2p/stdafx.h +++ b/src/p2p/stdafx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/platform/mingw/alloca.h b/src/platform/mingw/alloca.h index 131ad3487..f850d1cdf 100644 --- a/src/platform/mingw/alloca.h +++ b/src/platform/mingw/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/alloca.h b/src/platform/msc/alloca.h index 0007d32db..2f3f79a3f 100644 --- a/src/platform/msc/alloca.h +++ b/src/platform/msc/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/inline_c.h b/src/platform/msc/inline_c.h index f28d70733..030604ef8 100644 --- a/src/platform/msc/inline_c.h +++ b/src/platform/msc/inline_c.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/stdbool.h b/src/platform/msc/stdbool.h index 22e34c24a..05d2e7a38 100644 --- a/src/platform/msc/stdbool.h +++ b/src/platform/msc/stdbool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/sys/param.h b/src/platform/msc/sys/param.h index 4201c8784..db6eca28b 100644 --- a/src/platform/msc/sys/param.h +++ b/src/platform/msc/sys/param.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index e513c5a09..e15af205a 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -39,9 +39,9 @@ set(ringct_private_headers rctSigs.h rctTypes.h) -bitmonero_private_headers(ringct +monero_private_headers(ringct ${crypto_private_headers}) -bitmonero_add_library(ringct +monero_add_library(ringct ${ringct_sources} ${ringct_headers} ${ringct_private_headers}) @@ -49,5 +49,6 @@ target_link_libraries(ringct PUBLIC common crypto + cryptonote_basic PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/ringct/rctCryptoOps.c b/src/ringct/rctCryptoOps.c index 9bb9a6891..f69d692f6 100644 --- a/src/ringct/rctCryptoOps.c +++ b/src/ringct/rctCryptoOps.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctCryptoOps.h b/src/ringct/rctCryptoOps.h index 58c6964d8..2674c2243 100644 --- a/src/ringct/rctCryptoOps.h +++ b/src/ringct/rctCryptoOps.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index 488e47ca0..d0e0964b6 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -33,54 +33,21 @@ using namespace crypto; using namespace std; -namespace rct { - - //Various key initialization functions - - //Creates a zero scalar - void zero(key &zero) { - memset(&zero, 0, 32); - } - - //Creates a zero scalar - key zero() { - static const key z = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; - return z; - } +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "ringct" - //Creates a zero elliptic curve point - void identity(key &Id) { - Id[0] = (unsigned char)(0x01); - memset(Id.bytes+1, 0, 31); - } - - //Creates a zero elliptic curve point - key identity() { - key Id; - Id[0] = (unsigned char)(0x01); - memset(Id.bytes+1, 0, 31); - return Id; - } +#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}} - //copies a scalar or point - void copy(key &AA, const key &A) { - memcpy(&AA, &A, 32); - } - - //copies a scalar or point - key copy(const key &A) { - key AA; - memcpy(&AA, &A, 32); - return AA; - } +namespace rct { + //Various key initialization functions //initializes a key matrix; //first parameter is rows, //second is columns - keyM keyMInit(int rows, int cols) { + keyM keyMInit(size_t rows, size_t cols) { keyM rv(cols); - int i = 0; + size_t i = 0; for (i = 0 ; i < cols ; i++) { rv[i] = keyV(rows); } @@ -107,11 +74,12 @@ namespace rct { //Generates a vector of secret key //Mainly used in testing - keyV skvGen(int rows ) { + keyV skvGen(size_t rows ) { keyV rv(rows); - int i = 0; + size_t i = 0; + crypto::rand(rows * sizeof(key), (uint8_t*)&rv[0]); for (i = 0 ; i < rows ; i++) { - skGen(rv[i]); + sc_reduce32(rv[i].bytes); } return rv; } @@ -155,7 +123,7 @@ namespace rct { //generates a <secret , public> / Pedersen commitment but takes bH as input - tuple<ctkey, ctkey> ctskpkGen(key bH) { + tuple<ctkey, ctkey> ctskpkGen(const key &bH) { ctkey sk, pk; skpkGen(sk.dest, pk.dest); skpkGen(sk.mask, pk.mask); @@ -172,12 +140,12 @@ namespace rct { return mask; } - key commit(xmr_amount amount, key mask) { - mask = scalarmultBase(mask); + key commit(xmr_amount amount, const key &mask) { + key c = scalarmultBase(mask); key am = d2h(amount); key bH = scalarmultH(am); - addKeys(mask, mask, bH); - return mask; + addKeys(c, c, bH); + return c; } //generates a random uint long long (for testing) @@ -209,7 +177,7 @@ namespace rct { void scalarmultKey(key & aP, const key &P, const key &a) { ge_p3 A; ge_p2 R; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_scalarmult(&R, a.bytes, &A); ge_tobytes(aP.bytes, &R); } @@ -218,7 +186,7 @@ namespace rct { key scalarmultKey(const key & P, const key & a) { ge_p3 A; ge_p2 R; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_scalarmult(&R, a.bytes, &A); key aP; ge_tobytes(aP.bytes, &R); @@ -230,7 +198,7 @@ namespace rct { key scalarmultH(const key & a) { ge_p3 A; ge_p2 R; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, H.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, H.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_scalarmult(&R, a.bytes, &A); key aP; ge_tobytes(aP.bytes, &R); @@ -242,8 +210,8 @@ namespace rct { //for curve points: AB = A + B void addKeys(key &AB, const key &A, const key &B) { ge_p3 B2, A2; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_cached tmp2; ge_p3_to_cached(&tmp2, &B2); ge_p1p1 tmp3; @@ -265,7 +233,7 @@ namespace rct { void addKeys2(key &aGbB, const key &a, const key &b, const key & B) { ge_p2 rv; ge_p3 B2; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_double_scalarmult_base_vartime(&rv, b.bytes, &B2, a.bytes); ge_tobytes(aGbB.bytes, &rv); } @@ -274,7 +242,7 @@ namespace rct { // input B a curve point and output a ge_dsmp which has precomputation applied void precomp(ge_dsmp rv, const key & B) { ge_p3 B2; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_dsm_precomp(rv, &B2); } @@ -284,7 +252,7 @@ namespace rct { void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B) { ge_p2 rv; ge_p3 A2; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_double_scalarmult_precomp_vartime(&rv, a.bytes, &A2, b.bytes, B); ge_tobytes(aAbB.bytes, &rv); } @@ -294,8 +262,8 @@ namespace rct { //AB = A - B where A, B are curve points void subKeys(key & AB, const key &A, const key &B) { ge_p3 B2, A2; - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_cached tmp2; ge_p3_to_cached(&tmp2, &B2); ge_p1p1 tmp3; @@ -304,7 +272,7 @@ namespace rct { ge_p3_tobytes(AB.bytes, &A2); } - //checks if A, B are equal as curve points + //checks if A, B are equal in terms of bytes (may say no if one is a non-reduced scalar) //without doing curve operations bool equalKeys(const key & a, const key & b) { bool rv = true; @@ -320,7 +288,7 @@ namespace rct { //be careful these are also in crypto namespace //cn_fast_hash for arbitrary multiples of 32 bytes void cn_fast_hash(key &hash, const void * data, const std::size_t l) { - keccak((uint8_t *)data, l, hash.bytes, 32); + keccak((const uint8_t *)data, l, hash.bytes, 32); } void hash_to_scalar(key &hash, const void * data, const std::size_t l) { @@ -330,7 +298,7 @@ namespace rct { //cn_fast_hash for a 32 byte key void cn_fast_hash(key & hash, const key & in) { - keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32); } void hash_to_scalar(key & hash, const key & in) { @@ -341,7 +309,7 @@ namespace rct { //cn_fast_hash for a 32 byte key key cn_fast_hash(const key & in) { key hash; - keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32); return hash; } @@ -354,7 +322,7 @@ namespace rct { //cn_fast_hash for a 128 byte unsigned char key cn_fast_hash128(const void * in) { key hash; - keccak((uint8_t *)in, 128, hash.bytes, 32); + keccak((const uint8_t *)in, 128, hash.bytes, 32); return hash; } @@ -367,20 +335,13 @@ namespace rct { //cn_fast_hash for multisig purpose //This takes the outputs and commitments //and hashes them into a 32 byte sized key - key cn_fast_hash(ctkeyV PC) { - key rv = identity(); - std::size_t l = (std::size_t)PC.size(); - size_t i = 0, j = 0; - vector<char> m(l * 64); - for (i = 0 ; i < l ; i++) { - memcpy(&m[i * 64], &PC[i].dest, 32); - memcpy(&m[i * 64 + 32], &PC[i].mask, 32); - } - cn_fast_hash(rv, &m[0], 64*l); + key cn_fast_hash(const ctkeyV &PC) { + key rv; + cn_fast_hash(rv, &PC[0], 64*PC.size()); return rv; } - key hash_to_scalar(ctkeyV PC) { + key hash_to_scalar(const ctkeyV &PC) { key rv = cn_fast_hash(PC); sc_reduce32(rv.bytes); return rv; @@ -391,14 +352,8 @@ namespace rct { //put them in the key vector and it concatenates them //and then hashes them key cn_fast_hash(const keyV &keys) { - size_t l = keys.size(); - vector<unsigned char> m(l * 32); - size_t i; - for (i = 0 ; i < l ; i++) { - memcpy(&m[i * 32], keys[i].bytes, 32); - } key rv; - cn_fast_hash(rv, &m[0], 32 * l); + cn_fast_hash(rv, &keys[0], keys.size() * sizeof(keys[0])); //dp(rv); return rv; } @@ -409,13 +364,26 @@ namespace rct { return rv; } + key cn_fast_hash(const key64 keys) { + key rv; + cn_fast_hash(rv, &keys[0], 64 * sizeof(keys[0])); + //dp(rv); + return rv; + } + + key hash_to_scalar(const key64 keys) { + key rv = cn_fast_hash(keys); + sc_reduce32(rv.bytes); + return rv; + } + key hashToPointSimple(const key & hh) { key pointk; ge_p1p1 point2; ge_p2 point; ge_p3 res; key h = cn_fast_hash(hh); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&res, h.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&res, h.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); ge_p3_to_p2(&point, &res); ge_mul8(&point2, &point); ge_p1p1_to_p3(&res, &point2); diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 1e71c645d..cb19bbbd6 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -64,19 +64,27 @@ namespace rct { //Various key initialization functions + static const key Z = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; + static const key I = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; + static const key L = { {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; + //Creates a zero scalar - key zero(); - void zero(key &z); + inline key zero() { return Z; } + inline void zero(key &z) { memset(&z, 0, 32); } //Creates a zero elliptic curve point - key identity(); - void identity(key &Id); + inline key identity() { return I; } + inline void identity(key &Id) { memcpy(&Id, &I, 32); } + //Creates a key equal to the curve order + inline key curveOrder() { return L; } + inline void curveOrder(key &l) { l = L; } //copies a scalar or point - void copy(key &AA, const key &A); - key copy(const key & AA); + inline void copy(key &AA, const key &A) { memcpy(&AA, &A, 32); } + inline key copy(const key & A) { key AA; memcpy(&AA, &A, 32); return AA; } + //initializes a key matrix; //first parameter is rows, //second is columns - keyM keyMInit(int, int); + keyM keyMInit(size_t rows, size_t cols); //Various key generation functions @@ -85,7 +93,7 @@ namespace rct { void skGen(key &); //generates a vector of secret keys of size "int" - keyV skvGen(int ); + keyV skvGen(size_t rows ); //generates a random curve point (for testing) key pkGen(); @@ -97,9 +105,9 @@ namespace rct { //generates C =aG + bH from b, a is random void genC(key & C, const key & a, xmr_amount amount); //this one is mainly for testing, can take arbitrary amounts.. - tuple<ctkey, ctkey> ctskpkGen(key bH); + tuple<ctkey, ctkey> ctskpkGen(const key &bH); // make a pedersen commitment with given key - key commit(xmr_amount amount, key mask); + key commit(xmr_amount amount, const key &mask); // make a pedersen commitment with zero key key zeroCommit(xmr_amount amount); //generates a random uint long long @@ -149,11 +157,14 @@ namespace rct { //for mg sigs key cn_fast_hash128(const void * in); key hash_to_scalar128(const void * in); - key cn_fast_hash(ctkeyV PC); - key hash_to_scalar(ctkeyV PC); + key cn_fast_hash(const ctkeyV &PC); + key hash_to_scalar(const ctkeyV &PC); //for mg sigs key cn_fast_hash(const keyV &keys); key hash_to_scalar(const keyV &keys); + //for ANSL + key cn_fast_hash(const key64 keys); + key hash_to_scalar(const key64 keys); //returns hashToPoint as described in https://github.com/ShenNoether/ge_fromfe_writeup key hashToPointSimple(const key &in); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index ed1f8cc0e..8efd6a07c 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -29,101 +29,64 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "misc_log_ex.h" +#include "common/perf_timer.h" +#include "common/task_region.h" +#include "common/thread_group.h" +#include "common/util.h" #include "rctSigs.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" using namespace crypto; using namespace std; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "ringct" + namespace rct { - - //Schnorr Non-linkable - //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 - //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 - //These are called in the below ASNL sig generation - - void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index) { - key c1, c2, L2; - key a = skGen(); - if (index == 0) { - scalarmultBase(L1, a); - hash_to_scalar(c2, L1); - skGen(s2); - addKeys2(L2, s2, c2, P2); - hash_to_scalar(c1, L2); - //s1 = a - x * c1 - sc_mulsub(s1.bytes, x.bytes, c1.bytes, a.bytes); - } - else if (index == 1) { - scalarmultBase(L2, a); - hash_to_scalar(c1, L2); - skGen(s1); - addKeys2(L1, s1, c1, P1); - hash_to_scalar(c2, L1); - sc_mulsub(s2.bytes, x.bytes, c2.bytes, a.bytes); + //Borromean (c.f. gmax/andytoshi's paper) + boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { + key64 L[2], alpha; + key c; + int naught = 0, prime = 0, ii = 0, jj=0; + boroSig bb; + for (ii = 0 ; ii < 64 ; ii++) { + naught = indices[ii]; prime = (indices[ii] + 1) % 2; + skGen(alpha[ii]); + scalarmultBase(L[naught][ii], alpha[ii]); + if (naught == 0) { + skGen(bb.s1[ii]); + c = hash_to_scalar(L[naught][ii]); + addKeys2(L[prime][ii], bb.s1[ii], c, P2[ii]); + } } - else { - throw std::runtime_error("GenSchnorrNonLinkable: invalid index (should be 0 or 1)"); + bb.ee = hash_to_scalar(L[1]); //or L[1].. + key LL, cc; + for (jj = 0 ; jj < 64 ; jj++) { + if (!indices[jj]) { + sc_mulsub(bb.s0[jj].bytes, x[jj].bytes, bb.ee.bytes, alpha[jj].bytes); + } else { + skGen(bb.s0[jj]); + addKeys2(LL, bb.s0[jj], bb.ee, P1[jj]); //different L0 + cc = hash_to_scalar(LL); + sc_mulsub(bb.s1[jj].bytes, x[jj].bytes, cc.bytes, alpha[jj].bytes); + } } - } - - //Schnorr Non-linkable - //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 - //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 - //These are called in the below ASNL sig generation - bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2) { - key c2, L2, c1, L1p; - hash_to_scalar(c2, L1); - addKeys2(L2, s2, c2, P2); - hash_to_scalar(c1, L2); - addKeys2(L1p, s1, c1, P1); - - return equalKeys(L1, L1p); + return bb; } - //Aggregate Schnorr Non-linkable Ring Signature (ASNL) - // c.f. http://eprint.iacr.org/2015/1098 section 5. - // These are used in range proofs (alternatively Borromean could be used) - // Gen gives a signature which proves the signer knows, for each i, - // an x[i] such that x[i]G = one of P1[i] or P2[i] - // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i - asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices) { - DP("Generating Aggregate Schnorr Non-linkable Ring Signature\n"); - key64 s1; - int j = 0; - asnlSig rv; - rv.s = zero(); - for (j = 0; j < ATOMS; j++) { - GenSchnorrNonLinkable(rv.L1[j], s1[j], rv.s2[j], x[j], P1[j], P2[j], (int)indices[j]); - sc_add(rv.s.bytes, rv.s.bytes, s1[j].bytes); - } - return rv; + //see above. + bool verifyBorromean(const boroSig &bb, const key64 P1, const key64 P2) { + key64 Lv1; key chash, LL; + int ii = 0; + for (ii = 0 ; ii < 64 ; ii++) { + addKeys2(LL, bb.s0[ii], bb.ee, P1[ii]); + chash = hash_to_scalar(LL); + addKeys2(Lv1[ii], bb.s1[ii], chash, P2[ii]); + } + key eeComputed = hash_to_scalar(Lv1); //hash function fine + return equalKeys(eeComputed, bb.ee); } - //Aggregate Schnorr Non-linkable Ring Signature (ASNL) - // c.f. http://eprint.iacr.org/2015/1098 section 5. - // These are used in range proofs (alternatively Borromean could be used) - // Gen gives a signature which proves the signer knows, for each i, - // an x[i] such that x[i]G = one of P1[i] or P2[i] - // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i - bool VerASNL(const key64 P1, const key64 P2, const asnlSig &as) { - DP("Verifying Aggregate Schnorr Non-linkable Ring Signature\n"); - key LHS = identity(); - key RHS = scalarmultBase(as.s); - key c2, L2, c1; - int j = 0; - for (j = 0; j < ATOMS; j++) { - hash_to_scalar(c2, as.L1[j]); - addKeys2(L2, as.s2[j], c2, P2[j]); - addKeys(LHS, LHS, as.L1[j]); - hash_to_scalar(c1, L2); - addKeys(RHS, RHS, scalarmultKey(P1[j], c1)); - } - key cc; - sc_sub(cc.bytes, LHS.bytes, RHS.bytes); - return sc_isnonzero(cc.bytes) == 0; - } - //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) //These are aka MG signatutes in earlier drafts of the ring ct paper // c.f. http://eprint.iacr.org/2015/1098 section 2. @@ -150,7 +113,7 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -239,7 +202,7 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - bool MLSAG_Ver(key message, const keyM & pk, const mgSig & rv, size_t dsRows) { + bool MLSAG_Ver(const key &message, const keyM & pk, const mgSig & rv, size_t dsRows) { size_t cols = pk.size(); CHECK_AND_ASSERT_MES(cols >= 2, false, "Error! What is c if cols = 1!"); @@ -255,6 +218,11 @@ namespace rct { } CHECK_AND_ASSERT_MES(dsRows <= rows, false, "Bad dsRows value"); + for (size_t i = 0; i < rv.ss.size(); ++i) + for (size_t j = 0; j < rv.ss[i].size(); ++j) + CHECK_AND_ASSERT_MES(sc_check(rv.ss[i][j].bytes) == 0, false, "Bad ss slot"); + CHECK_AND_ASSERT_MES(sc_check(rv.cc.bytes) == 0, false, "Bad cc"); + size_t i = 0, j = 0, ii = 0; key c, L, R, Hi; key c_old = copy(rv.cc); @@ -319,7 +287,7 @@ namespace rct { sc_add(mask.bytes, mask.bytes, ai[i].bytes); addKeys(C, C, sig.Ci[i]); } - sig.asig = GenASNL(ai, sig.Ci, CiH, b); + sig.asig = genBorromean(ai, sig.Ci, CiH, b); return sig; } @@ -331,6 +299,9 @@ namespace rct { // mask is a such that C = aG + bH, and b = amount //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i bool verRange(const key & C, const rangeSig & as) { + try + { + PERF_TIMER(verRange); key64 CiH; int i = 0; key Ctmp = identity(); @@ -340,14 +311,18 @@ namespace rct { } if (!equalKeys(C, Ctmp)) return false; - if (!VerASNL(as.Ci, CiH, as.asig)) + if (!verifyBorromean(as.asig, as.Ci, CiH)) return false; return true; + } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } } key get_pre_mlsag_hash(const rctSig &rv) { keyV hashes; + hashes.reserve(3); hashes.push_back(rv.message); crypto::hash h; @@ -361,13 +336,14 @@ namespace rct { hashes.push_back(hash2rct(h)); keyV kv; + kv.reserve((64*3+1) * rv.p.rangeSigs.size()); for (auto r: rv.p.rangeSigs) { for (size_t n = 0; n < 64; ++n) - kv.push_back(r.asig.L1[n]); + kv.push_back(r.asig.s0[n]); for (size_t n = 0; n < 64; ++n) - kv.push_back(r.asig.s2[n]); - kv.push_back(r.asig.s); + kv.push_back(r.asig.s1[n]); + kv.push_back(r.asig.ee); for (size_t n = 0; n < 64; ++n) kv.push_back(r.Ci[n]); } @@ -467,6 +443,7 @@ namespace rct { //Ver: // verifies the above sig is created corretly bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFeeKey, const key &message) { + PERF_TIMER(verRctMG); //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); @@ -505,6 +482,9 @@ namespace rct { //This does a simplified version, assuming only post Rct //inputs bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C) { + try + { + PERF_TIMER(verRctMGSimple); //setup vars size_t rows = 1; size_t cols = pubs.size(); @@ -519,8 +499,11 @@ namespace rct { } //DP(C); return MLSAG_Ver(message, M, mg, rows); + } + catch (...) { return false; } } + //These functions get keys from blockchain //replace these when connecting blockchain //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with @@ -583,6 +566,7 @@ namespace rct { // Thus the amounts vector will be "one" longer than the destinations vectort rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); @@ -644,6 +628,7 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk"); CHECK_AND_ASSERT_THROW_MES(mixRing.size() == inSk.size(), "Different number of mixRing/inSk"); for (size_t n = 0; n < mixRing.size(); ++n) { @@ -728,34 +713,54 @@ namespace rct { //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number - bool verRct(const rctSig & rv) { + bool verRct(const rctSig & rv, bool semantics) { + PERF_TIMER(verRct); CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "verRct called on non-full rctSig"); - CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); - CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG"); + if (semantics) + { + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); + CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG"); + } + else + { + // semantics check is early, we don't have the MGs resolved yet + } // some rct ops can throw try { - size_t i = 0; - bool tmp; - DP("range proofs verified?"); - for (i = 0; i < rv.outPk.size(); i++) { - tmp = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - DP(tmp); - if (!tmp) { - LOG_ERROR("Range proof verification failed for input " << i); + if (semantics) { + std::deque<bool> results(rv.outPk.size(), false); + tools::thread_group threadpool(tools::thread_group::optimal_with_max(rv.outPk.size())); + + tools::task_region(threadpool, [&] (tools::task_region_handle& region) { + DP("range proofs verified?"); + for (size_t i = 0; i < rv.outPk.size(); i++) { + region.run([&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + }); + + for (size_t i = 0; i < rv.outPk.size(); ++i) { + if (!results[i]) { + LOG_PRINT_L1("Range proof verified failed for output " << i); return false; } + } } - //compute txn fee - key txnFeeKey = scalarmultH(d2h(rv.txnFee)); - bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv)); - DP("mg sig verified?"); - DP(mgVerd); - if (!mgVerd) { - LOG_ERROR("MG signature verification failed"); - return false; + + if (!semantics) { + //compute txn fee + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv)); + DP("mg sig verified?"); + DP(mgVerd); + if (!mgVerd) { + LOG_PRINT_L1("MG signature verification failed"); + return false; + } } return true; @@ -768,48 +773,92 @@ namespace rct { //ver RingCT simple //assumes only post-rct style inputs (at least for max anonymity) - bool verRctSimple(const rctSig & rv) { - size_t i = 0; + bool verRctSimple(const rctSig & rv, bool semantics) { + try + { + PERF_TIMER(verRctSimple); CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "verRctSimple called on non simple rctSig"); - CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); - CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs"); - CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); - - key sumOutpks = identity(); - for (i = 0; i < rv.outPk.size(); i++) { - if (!verRange(rv.outPk[i].mask, rv.p.rangeSigs[i])) { - LOG_ERROR("Range proof verified failed for input " << i); - return false; - } - addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask); + if (semantics) + { + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); + CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs"); } - DP(sumOutpks); - key txnFeeKey = scalarmultH(d2h(rv.txnFee)); - addKeys(sumOutpks, txnFeeKey, sumOutpks); - - bool tmpb = false; - key message = get_pre_mlsag_hash(rv); - key sumPseudoOuts = identity(); - for (i = 0 ; i < rv.mixRing.size() ; i++) { - tmpb = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); - addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); - DP(tmpb); - if (!tmpb) { - LOG_ERROR("verRctMGSimple failed for input " << i); - return false; + else + { + // semantics check is early, and mixRing/MGs aren't resolved yet + CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); + } + + const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); + + std::deque<bool> results(threads); + tools::thread_group threadpool(tools::thread_group::optimal_with_max(threads)); + + if (semantics) { + key sumOutpks = identity(); + for (size_t i = 0; i < rv.outPk.size(); i++) { + addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask); + } + DP(sumOutpks); + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + addKeys(sumOutpks, txnFeeKey, sumOutpks); + + key sumPseudoOuts = identity(); + for (size_t i = 0 ; i < rv.pseudoOuts.size() ; i++) { + addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); + } + DP(sumPseudoOuts); + + //check pseudoOuts vs Outs.. + if (!equalKeys(sumPseudoOuts, sumOutpks)) { + LOG_PRINT_L1("Sum check failed"); + return false; + } + + results.clear(); + results.resize(rv.outPk.size()); + tools::task_region(threadpool, [&] (tools::task_region_handle& region) { + for (size_t i = 0; i < rv.outPk.size(); i++) { + region.run([&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + }); + + for (size_t i = 0; i < results.size(); ++i) { + if (!results[i]) { + LOG_PRINT_L1("Range proof verified failed for output " << i); + return false; } + } } - DP(sumPseudoOuts); - - //check pseudoOuts vs Outs.. - if (!equalKeys(sumPseudoOuts, sumOutpks)) { - LOG_ERROR("Sum check failed"); - return false; + else { + const key message = get_pre_mlsag_hash(rv); + + results.clear(); + results.resize(rv.mixRing.size()); + tools::task_region(threadpool, [&] (tools::task_region_handle& region) { + for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { + region.run([&, i] { + results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); + }); + } + }); + + for (size_t i = 0; i < results.size(); ++i) { + if (!results[i]) { + LOG_PRINT_L1("verRctMGSimple failed for input " << i); + return false; + } + } } return true; + } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } } //RingCT protocol @@ -824,8 +873,7 @@ namespace rct { // must know the destination private key to find the correct amount, else will return a random number xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask) { CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "decodeRct called on non-full rctSig"); - CHECK_AND_ASSERT_THROW_MES(rv.p.rangeSigs.size() > 0, "Empty rv.p.rangeSigs"); - CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.p.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); //mask amount and mask @@ -853,8 +901,7 @@ namespace rct { xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask) { CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "decodeRct called on non simple rctSig"); - CHECK_AND_ASSERT_THROW_MES(rv.p.rangeSigs.size() > 0, "Empty rv.p.rangeSigs"); - CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.p.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); //mask amount and mask diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index f1c906d5e..ca40ddd85 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -66,21 +66,8 @@ using namespace crypto; namespace rct { - //Schnorr Non-linkable - //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 - //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 - //These are called in the below ASNL sig generation - void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index); - bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2); - - //Aggregate Schnorr Non-linkable Ring Signature (ASNL) - // c.f. http://eprint.iacr.org/2015/1098 section 5. - // These are used in range proofs (alternatively Borromean could be used) - // Gen gives a signature which proves the signer knows, for each i, - // an x[i] such that x[i]G = one of P1[i] or P2[i] - // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i - asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices); - bool VerASNL(const key64 P1, const key64 P2, const asnlSig &as); + boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices); + bool verifyBorromean(const boroSig &bb, const key64 P1, const key64 P2); //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) //These are aka MG signatutes in earlier drafts of the ring ct paper @@ -90,8 +77,8 @@ namespace rct { // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly keyV keyImageV(const keyV &xx); - mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); - bool MLSAG_Ver(key message, const keyM &pk, const mgSig &sig, size_t dsRows); + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); + bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); //proveRange and verRange @@ -139,8 +126,10 @@ namespace rct { rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin); rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk); - bool verRct(const rctSig & rv); - bool verRctSimple(const rctSig & rv); + bool verRct(const rctSig & rv, bool semantics); + static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } + bool verRctSimple(const rctSig & rv, bool semantics); + static inline bool verRctSimple(const rctSig & rv) { return verRctSimple(rv, true) && verRctSimple(rv, false); } xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index e773c6043..1526dcf7c 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -32,6 +32,9 @@ using namespace crypto; using namespace std; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "ringct" + namespace rct { //dp diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index bfafebb83..c820fb297 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -40,13 +40,15 @@ #include <cinttypes> extern "C" { -#include "crypto/generic-ops.h" #include "crypto/crypto-ops.h" #include "crypto/random.h" #include "crypto/keccak.h" } +#include "crypto/generic-ops.h" #include "crypto/crypto.h" +#include "hex.h" +#include "span.h" #include "serialization/serialization.h" #include "serialization/debug_archive.h" #include "serialization/binary_archive.h" @@ -125,12 +127,10 @@ namespace rct { typedef unsigned int bits[ATOMS]; typedef key key64[64]; - //just contains the necessary keys to represent asnlSigs - //c.f. http://eprint.iacr.org/2015/1098 - struct asnlSig { - key64 L1; - key64 s2; - key s; + struct boroSig { + key64 s0; + key64 s1; + key ee; }; //Container for precomp @@ -151,14 +151,14 @@ namespace rct { // FIELD(II) - not serialized, it can be reconstructed END_SERIALIZE() }; - //contains the data for an asnl sig + //contains the data for an Borromean sig // also contains the "Ci" values such that // \sum Ci = C // and the signature proves that each Ci is either // a Pedersen commitment to 0 or to 2^i //thus proving that C is in the range of [0, 2^64] struct rangeSig { - asnlSig asig; + boroSig asig; key64 Ci; BEGIN_SERIALIZE_OBJECT() @@ -281,6 +281,7 @@ namespace rct { // we save the MGs contents directly, because we want it to save its // arrays and matrices without the size prefixes, and the load can't // know what size to expect if it's not in the data + ar.begin_object(); ar.tag("ss"); ar.begin_array(); PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss); @@ -296,7 +297,7 @@ namespace rct { for (size_t k = 0; k < mg_ss2_elements; ++k) { FIELDS(MGs[i].ss[j][k]) - if (mg_ss2_elements - j > 1) + if (mg_ss2_elements - k > 1) ar.delimit_array(); } ar.end_array(); @@ -306,10 +307,13 @@ namespace rct { } ar.end_array(); + ar.tag("cc"); FIELDS(MGs[i].cc) // MGs[i].II not saved, it can be reconstructed + ar.end_object(); + if (mg_elements - i > 1) - ar.delimit_array(); + ar.delimit_array(); } ar.end_array(); return true; @@ -326,70 +330,70 @@ namespace rct { //H2 contains 2^i H in each index, i.e. H, 2H, 4H, 8H, ... //This is used for the range proofG //You can regenerate this by running python2 Test.py HPow2 in the MiniNero repo - static const key64 H2 = {{0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea, 0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94}, - {0x8f, 0xaa, 0x44, 0x8a, 0xe4, 0xb3, 0xe2, 0xbb, 0x3d, 0x4d, 0x13, 0x09, 0x09, 0xf5, 0x5f, 0xcd, 0x79, 0x71, 0x1c, 0x1c, 0x83, 0xcd, 0xbc, 0xca, 0xdd, 0x42, 0xcb, 0xe1, 0x51, 0x5e, 0x87, 0x12}, - {0x12, 0xa7, 0xd6, 0x2c, 0x77, 0x91, 0x65, 0x4a, 0x57, 0xf3, 0xe6, 0x76, 0x94, 0xed, 0x50, 0xb4, 0x9a, 0x7d, 0x9e, 0x3f, 0xc1, 0xe4, 0xc7, 0xa0, 0xbd, 0xe2, 0x9d, 0x18, 0x7e, 0x9c, 0xc7, 0x1d}, - {0x78, 0x9a, 0xb9, 0x93, 0x4b, 0x49, 0xc4, 0xf9, 0xe6, 0x78, 0x5c, 0x6d, 0x57, 0xa4, 0x98, 0xb3, 0xea, 0xd4, 0x43, 0xf0, 0x4f, 0x13, 0xdf, 0x11, 0x0c, 0x54, 0x27, 0xb4, 0xf2, 0x14, 0xc7, 0x39}, - {0x77, 0x1e, 0x92, 0x99, 0xd9, 0x4f, 0x02, 0xac, 0x72, 0xe3, 0x8e, 0x44, 0xde, 0x56, 0x8a, 0xc1, 0xdc, 0xb2, 0xed, 0xc6, 0xed, 0xb6, 0x1f, 0x83, 0xca, 0x41, 0x8e, 0x10, 0x77, 0xce, 0x3d, 0xe8}, - {0x73, 0xb9, 0x6d, 0xb4, 0x30, 0x39, 0x81, 0x9b, 0xda, 0xf5, 0x68, 0x0e, 0x5c, 0x32, 0xd7, 0x41, 0x48, 0x88, 0x84, 0xd1, 0x8d, 0x93, 0x86, 0x6d, 0x40, 0x74, 0xa8, 0x49, 0x18, 0x2a, 0x8a, 0x64}, - {0x8d, 0x45, 0x8e, 0x1c, 0x2f, 0x68, 0xeb, 0xeb, 0xcc, 0xd2, 0xfd, 0x5d, 0x37, 0x9f, 0x5e, 0x58, 0xf8, 0x13, 0x4d, 0xf3, 0xe0, 0xe8, 0x8c, 0xad, 0x3d, 0x46, 0x70, 0x10, 0x63, 0xa8, 0xd4, 0x12}, - {0x09, 0x55, 0x1e, 0xdb, 0xe4, 0x94, 0x41, 0x8e, 0x81, 0x28, 0x44, 0x55, 0xd6, 0x4b, 0x35, 0xee, 0x8a, 0xc0, 0x93, 0x06, 0x8a, 0x5f, 0x16, 0x1f, 0xa6, 0x63, 0x75, 0x59, 0x17, 0x7e, 0xf4, 0x04}, - {0xd0, 0x5a, 0x88, 0x66, 0xf4, 0xdf, 0x8c, 0xee, 0x1e, 0x26, 0x8b, 0x1d, 0x23, 0xa4, 0xc5, 0x8c, 0x92, 0xe7, 0x60, 0x30, 0x97, 0x86, 0xcd, 0xac, 0x0f, 0xed, 0xa1, 0xd2, 0x47, 0xa9, 0xc9, 0xa7}, - {0x55, 0xcd, 0xaa, 0xd5, 0x18, 0xbd, 0x87, 0x1d, 0xd1, 0xeb, 0x7b, 0xc7, 0x02, 0x3e, 0x1d, 0xc0, 0xfd, 0xf3, 0x33, 0x98, 0x64, 0xf8, 0x8f, 0xdd, 0x2d, 0xe2, 0x69, 0xfe, 0x9e, 0xe1, 0x83, 0x2d}, - {0xe7, 0x69, 0x7e, 0x95, 0x1a, 0x98, 0xcf, 0xd5, 0x71, 0x2b, 0x84, 0xbb, 0xe5, 0xf3, 0x4e, 0xd7, 0x33, 0xe9, 0x47, 0x3f, 0xcb, 0x68, 0xed, 0xa6, 0x6e, 0x37, 0x88, 0xdf, 0x19, 0x58, 0xc3, 0x06}, - {0xf9, 0x2a, 0x97, 0x0b, 0xae, 0x72, 0x78, 0x29, 0x89, 0xbf, 0xc8, 0x3a, 0xdf, 0xaa, 0x92, 0xa4, 0xf4, 0x9c, 0x7e, 0x95, 0x91, 0x8b, 0x3b, 0xba, 0x3c, 0xdc, 0x7f, 0xe8, 0x8a, 0xcc, 0x8d, 0x47}, - {0x1f, 0x66, 0xc2, 0xd4, 0x91, 0xd7, 0x5a, 0xf9, 0x15, 0xc8, 0xdb, 0x6a, 0x6d, 0x1c, 0xb0, 0xcd, 0x4f, 0x7d, 0xdc, 0xd5, 0xe6, 0x3d, 0x3b, 0xa9, 0xb8, 0x3c, 0x86, 0x6c, 0x39, 0xef, 0x3a, 0x2b}, - {0x3e, 0xec, 0x98, 0x84, 0xb4, 0x3f, 0x58, 0xe9, 0x3e, 0xf8, 0xde, 0xea, 0x26, 0x00, 0x04, 0xef, 0xea, 0x2a, 0x46, 0x34, 0x4f, 0xc5, 0x96, 0x5b, 0x1a, 0x7d, 0xd5, 0xd1, 0x89, 0x97, 0xef, 0xa7}, - {0xb2, 0x9f, 0x8f, 0x0c, 0xcb, 0x96, 0x97, 0x7f, 0xe7, 0x77, 0xd4, 0x89, 0xd6, 0xbe, 0x9e, 0x7e, 0xbc, 0x19, 0xc4, 0x09, 0xb5, 0x10, 0x35, 0x68, 0xf2, 0x77, 0x61, 0x1d, 0x7e, 0xa8, 0x48, 0x94}, - {0x56, 0xb1, 0xf5, 0x12, 0x65, 0xb9, 0x55, 0x98, 0x76, 0xd5, 0x8d, 0x24, 0x9d, 0x0c, 0x14, 0x6d, 0x69, 0xa1, 0x03, 0x63, 0x66, 0x99, 0x87, 0x4d, 0x3f, 0x90, 0x47, 0x35, 0x50, 0xfe, 0x3f, 0x2c}, - {0x1d, 0x7a, 0x36, 0x57, 0x5e, 0x22, 0xf5, 0xd1, 0x39, 0xff, 0x9c, 0xc5, 0x10, 0xfa, 0x13, 0x85, 0x05, 0x57, 0x6b, 0x63, 0x81, 0x5a, 0x94, 0xe4, 0xb0, 0x12, 0xbf, 0xd4, 0x57, 0xca, 0xaa, 0xda}, - {0xd0, 0xac, 0x50, 0x7a, 0x86, 0x4e, 0xcd, 0x05, 0x93, 0xfa, 0x67, 0xbe, 0x7d, 0x23, 0x13, 0x43, 0x92, 0xd0, 0x0e, 0x40, 0x07, 0xe2, 0x53, 0x48, 0x78, 0xd9, 0xb2, 0x42, 0xe1, 0x0d, 0x76, 0x20}, - {0xf6, 0xc6, 0x84, 0x0b, 0x9c, 0xf1, 0x45, 0xbb, 0x2d, 0xcc, 0xf8, 0x6e, 0x94, 0x0b, 0xe0, 0xfc, 0x09, 0x8e, 0x32, 0xe3, 0x10, 0x99, 0xd5, 0x6f, 0x7f, 0xe0, 0x87, 0xbd, 0x5d, 0xeb, 0x50, 0x94}, - {0x28, 0x83, 0x1a, 0x33, 0x40, 0x07, 0x0e, 0xb1, 0xdb, 0x87, 0xc1, 0x2e, 0x05, 0x98, 0x0d, 0x5f, 0x33, 0xe9, 0xef, 0x90, 0xf8, 0x3a, 0x48, 0x17, 0xc9, 0xf4, 0xa0, 0xa3, 0x32, 0x27, 0xe1, 0x97}, - {0x87, 0x63, 0x22, 0x73, 0xd6, 0x29, 0xcc, 0xb7, 0xe1, 0xed, 0x1a, 0x76, 0x8f, 0xa2, 0xeb, 0xd5, 0x17, 0x60, 0xf3, 0x2e, 0x1c, 0x0b, 0x86, 0x7a, 0x5d, 0x36, 0x8d, 0x52, 0x71, 0x05, 0x5c, 0x6e}, - {0x5c, 0x7b, 0x29, 0x42, 0x43, 0x47, 0x96, 0x4d, 0x04, 0x27, 0x55, 0x17, 0xc5, 0xae, 0x14, 0xb6, 0xb5, 0xea, 0x27, 0x98, 0xb5, 0x73, 0xfc, 0x94, 0xe6, 0xe4, 0x4a, 0x53, 0x21, 0x60, 0x0c, 0xfb}, - {0xe6, 0x94, 0x50, 0x42, 0xd7, 0x8b, 0xc2, 0xc3, 0xbd, 0x6e, 0xc5, 0x8c, 0x51, 0x1a, 0x9f, 0xe8, 0x59, 0xc0, 0xad, 0x63, 0xfd, 0xe4, 0x94, 0xf5, 0x03, 0x9e, 0x0e, 0x82, 0x32, 0x61, 0x2b, 0xd5}, - {0x36, 0xd5, 0x69, 0x07, 0xe2, 0xec, 0x74, 0x5d, 0xb6, 0xe5, 0x4f, 0x0b, 0x2e, 0x1b, 0x23, 0x00, 0xab, 0xcb, 0x42, 0x2e, 0x71, 0x2d, 0xa5, 0x88, 0xa4, 0x0d, 0x3f, 0x1e, 0xbb, 0xbe, 0x02, 0xf6}, - {0x34, 0xdb, 0x6e, 0xe4, 0xd0, 0x60, 0x8e, 0x5f, 0x78, 0x36, 0x50, 0x49, 0x5a, 0x3b, 0x2f, 0x52, 0x73, 0xc5, 0x13, 0x4e, 0x52, 0x84, 0xe4, 0xfd, 0xf9, 0x66, 0x27, 0xbb, 0x16, 0xe3, 0x1e, 0x6b}, - {0x8e, 0x76, 0x59, 0xfb, 0x45, 0xa3, 0x78, 0x7d, 0x67, 0x4a, 0xe8, 0x67, 0x31, 0xfa, 0xa2, 0x53, 0x8e, 0xc0, 0xfd, 0xf4, 0x42, 0xab, 0x26, 0xe9, 0xc7, 0x91, 0xfa, 0xda, 0x08, 0x94, 0x67, 0xe9}, - {0x30, 0x06, 0xcf, 0x19, 0x8b, 0x24, 0xf3, 0x1b, 0xb4, 0xc7, 0xe6, 0x34, 0x60, 0x00, 0xab, 0xc7, 0x01, 0xe8, 0x27, 0xcf, 0xbb, 0x5d, 0xf5, 0x2d, 0xcf, 0xa4, 0x2e, 0x9c, 0xa9, 0xff, 0x08, 0x02}, - {0xf5, 0xfd, 0x40, 0x3c, 0xb6, 0xe8, 0xbe, 0x21, 0x47, 0x2e, 0x37, 0x7f, 0xfd, 0x80, 0x5a, 0x8c, 0x60, 0x83, 0xea, 0x48, 0x03, 0xb8, 0x48, 0x53, 0x89, 0xcc, 0x3e, 0xbc, 0x21, 0x5f, 0x00, 0x2a}, - {0x37, 0x31, 0xb2, 0x60, 0xeb, 0x3f, 0x94, 0x82, 0xe4, 0x5f, 0x1c, 0x3f, 0x3b, 0x9d, 0xcf, 0x83, 0x4b, 0x75, 0xe6, 0xee, 0xf8, 0xc4, 0x0f, 0x46, 0x1e, 0xa2, 0x7e, 0x8b, 0x6e, 0xd9, 0x47, 0x3d}, - {0x9f, 0x9d, 0xab, 0x09, 0xc3, 0xf5, 0xe4, 0x28, 0x55, 0xc2, 0xde, 0x97, 0x1b, 0x65, 0x93, 0x28, 0xa2, 0xdb, 0xc4, 0x54, 0x84, 0x5f, 0x39, 0x6f, 0xfc, 0x05, 0x3f, 0x0b, 0xb1, 0x92, 0xf8, 0xc3}, - {0x5e, 0x05, 0x5d, 0x25, 0xf8, 0x5f, 0xdb, 0x98, 0xf2, 0x73, 0xe4, 0xaf, 0xe0, 0x84, 0x64, 0xc0, 0x03, 0xb7, 0x0f, 0x1e, 0xf0, 0x67, 0x7b, 0xb5, 0xe2, 0x57, 0x06, 0x40, 0x0b, 0xe6, 0x20, 0xa5}, - {0x86, 0x8b, 0xcf, 0x36, 0x79, 0xcb, 0x6b, 0x50, 0x0b, 0x94, 0x41, 0x8c, 0x0b, 0x89, 0x25, 0xf9, 0x86, 0x55, 0x30, 0x30, 0x3a, 0xe4, 0xe4, 0xb2, 0x62, 0x59, 0x18, 0x65, 0x66, 0x6a, 0x45, 0x90}, - {0xb3, 0xdb, 0x6b, 0xd3, 0x89, 0x7a, 0xfb, 0xd1, 0xdf, 0x3f, 0x96, 0x44, 0xab, 0x21, 0xc8, 0x05, 0x0e, 0x1f, 0x00, 0x38, 0xa5, 0x2f, 0x7c, 0xa9, 0x5a, 0xc0, 0xc3, 0xde, 0x75, 0x58, 0xcb, 0x7a}, - {0x81, 0x19, 0xb3, 0xa0, 0x59, 0xff, 0x2c, 0xac, 0x48, 0x3e, 0x69, 0xbc, 0xd4, 0x1d, 0x6d, 0x27, 0x14, 0x94, 0x47, 0x91, 0x42, 0x88, 0xbb, 0xea, 0xee, 0x34, 0x13, 0xe6, 0xdc, 0xc6, 0xd1, 0xeb}, - {0x10, 0xfc, 0x58, 0xf3, 0x5f, 0xc7, 0xfe, 0x7a, 0xe8, 0x75, 0x52, 0x4b, 0xb5, 0x85, 0x00, 0x03, 0x00, 0x5b, 0x7f, 0x97, 0x8c, 0x0c, 0x65, 0xe2, 0xa9, 0x65, 0x46, 0x4b, 0x6d, 0x00, 0x81, 0x9c}, - {0x5a, 0xcd, 0x94, 0xeb, 0x3c, 0x57, 0x83, 0x79, 0xc1, 0xea, 0x58, 0xa3, 0x43, 0xec, 0x4f, 0xcf, 0xf9, 0x62, 0x77, 0x6f, 0xe3, 0x55, 0x21, 0xe4, 0x75, 0xa0, 0xe0, 0x6d, 0x88, 0x7b, 0x2d, 0xb9}, - {0x33, 0xda, 0xf3, 0xa2, 0x14, 0xd6, 0xe0, 0xd4, 0x2d, 0x23, 0x00, 0xa7, 0xb4, 0x4b, 0x39, 0x29, 0x0d, 0xb8, 0x98, 0x9b, 0x42, 0x79, 0x74, 0xcd, 0x86, 0x5d, 0xb0, 0x11, 0x05, 0x5a, 0x29, 0x01}, - {0xcf, 0xc6, 0x57, 0x2f, 0x29, 0xaf, 0xd1, 0x64, 0xa4, 0x94, 0xe6, 0x4e, 0x6f, 0x1a, 0xeb, 0x82, 0x0c, 0x3e, 0x7d, 0xa3, 0x55, 0x14, 0x4e, 0x51, 0x24, 0xa3, 0x91, 0xd0, 0x6e, 0x9f, 0x95, 0xea}, - {0xd5, 0x31, 0x2a, 0x4b, 0x0e, 0xf6, 0x15, 0xa3, 0x31, 0xf6, 0x35, 0x2c, 0x2e, 0xd2, 0x1d, 0xac, 0x9e, 0x7c, 0x36, 0x39, 0x8b, 0x93, 0x9a, 0xec, 0x90, 0x1c, 0x25, 0x7f, 0x6c, 0xbc, 0x9e, 0x8e}, - {0x55, 0x1d, 0x67, 0xfe, 0xfc, 0x7b, 0x5b, 0x9f, 0x9f, 0xdb, 0xf6, 0xaf, 0x57, 0xc9, 0x6c, 0x8a, 0x74, 0xd7, 0xe4, 0x5a, 0x00, 0x20, 0x78, 0xa7, 0xb5, 0xba, 0x45, 0xc6, 0xfd, 0xe9, 0x3e, 0x33}, - {0xd5, 0x0a, 0xc7, 0xbd, 0x5c, 0xa5, 0x93, 0xc6, 0x56, 0x92, 0x8f, 0x38, 0x42, 0x80, 0x17, 0xfc, 0x7b, 0xa5, 0x02, 0x85, 0x4c, 0x43, 0xd8, 0x41, 0x49, 0x50, 0xe9, 0x6e, 0xcb, 0x40, 0x5d, 0xc3}, - {0x07, 0x73, 0xe1, 0x8e, 0xa1, 0xbe, 0x44, 0xfe, 0x1a, 0x97, 0xe2, 0x39, 0x57, 0x3c, 0xfa, 0xe3, 0xe4, 0xe9, 0x5e, 0xf9, 0xaa, 0x9f, 0xaa, 0xbe, 0xac, 0x12, 0x74, 0xd3, 0xad, 0x26, 0x16, 0x04}, - {0xe9, 0xaf, 0x0e, 0x7c, 0xa8, 0x93, 0x30, 0xd2, 0xb8, 0x61, 0x5d, 0x1b, 0x41, 0x37, 0xca, 0x61, 0x7e, 0x21, 0x29, 0x7f, 0x2f, 0x0d, 0xed, 0x8e, 0x31, 0xb7, 0xd2, 0xea, 0xd8, 0x71, 0x46, 0x60}, - {0x7b, 0x12, 0x45, 0x83, 0x09, 0x7f, 0x10, 0x29, 0xa0, 0xc7, 0x41, 0x91, 0xfe, 0x73, 0x78, 0xc9, 0x10, 0x5a, 0xcc, 0x70, 0x66, 0x95, 0xed, 0x14, 0x93, 0xbb, 0x76, 0x03, 0x42, 0x26, 0xa5, 0x7b}, - {0xec, 0x40, 0x05, 0x7b, 0x99, 0x54, 0x76, 0x65, 0x0b, 0x3d, 0xb9, 0x8e, 0x9d, 0xb7, 0x57, 0x38, 0xa8, 0xcd, 0x2f, 0x94, 0xd8, 0x63, 0xb9, 0x06, 0x15, 0x0c, 0x56, 0xaa, 0xc1, 0x9c, 0xaa, 0x6b}, - {0x01, 0xd9, 0xff, 0x72, 0x9e, 0xfd, 0x39, 0xd8, 0x37, 0x84, 0xc0, 0xfe, 0x59, 0xc4, 0xae, 0x81, 0xa6, 0x70, 0x34, 0xcb, 0x53, 0xc9, 0x43, 0xfb, 0x81, 0x8b, 0x9d, 0x8a, 0xe7, 0xfc, 0x33, 0xe5}, - {0x00, 0xdf, 0xb3, 0xc6, 0x96, 0x32, 0x8c, 0x76, 0x42, 0x45, 0x19, 0xa7, 0xbe, 0xfe, 0x8e, 0x0f, 0x6c, 0x76, 0xf9, 0x47, 0xb5, 0x27, 0x67, 0x91, 0x6d, 0x24, 0x82, 0x3f, 0x73, 0x5b, 0xaf, 0x2e}, - {0x46, 0x1b, 0x79, 0x9b, 0x4d, 0x9c, 0xee, 0xa8, 0xd5, 0x80, 0xdc, 0xb7, 0x6d, 0x11, 0x15, 0x0d, 0x53, 0x5e, 0x16, 0x39, 0xd1, 0x60, 0x03, 0xc3, 0xfb, 0x7e, 0x9d, 0x1f, 0xd1, 0x30, 0x83, 0xa8}, - {0xee, 0x03, 0x03, 0x94, 0x79, 0xe5, 0x22, 0x8f, 0xdc, 0x55, 0x1c, 0xbd, 0xe7, 0x07, 0x9d, 0x34, 0x12, 0xea, 0x18, 0x6a, 0x51, 0x7c, 0xcc, 0x63, 0xe4, 0x6e, 0x9f, 0xcc, 0xe4, 0xfe, 0x3a, 0x6c}, - {0xa8, 0xcf, 0xb5, 0x43, 0x52, 0x4e, 0x7f, 0x02, 0xb9, 0xf0, 0x45, 0xac, 0xd5, 0x43, 0xc2, 0x1c, 0x37, 0x3b, 0x4c, 0x9b, 0x98, 0xac, 0x20, 0xce, 0xc4, 0x17, 0xa6, 0xdd, 0xb5, 0x74, 0x4e, 0x94}, - {0x93, 0x2b, 0x79, 0x4b, 0xf8, 0x9c, 0x6e, 0xda, 0xf5, 0xd0, 0x65, 0x0c, 0x7c, 0x4b, 0xad, 0x92, 0x42, 0xb2, 0x56, 0x26, 0xe3, 0x7e, 0xad, 0x5a, 0xa7, 0x5e, 0xc8, 0xc6, 0x4e, 0x09, 0xdd, 0x4f}, - {0x16, 0xb1, 0x0c, 0x77, 0x9c, 0xe5, 0xcf, 0xef, 0x59, 0xc7, 0x71, 0x0d, 0x2e, 0x68, 0x44, 0x1e, 0xa6, 0xfa, 0xcb, 0x68, 0xe9, 0xb5, 0xf7, 0xd5, 0x33, 0xae, 0x0b, 0xb7, 0x8e, 0x28, 0xbf, 0x57}, - {0x0f, 0x77, 0xc7, 0x67, 0x43, 0xe7, 0x39, 0x6f, 0x99, 0x10, 0x13, 0x9f, 0x49, 0x37, 0xd8, 0x37, 0xae, 0x54, 0xe2, 0x10, 0x38, 0xac, 0x5c, 0x0b, 0x3f, 0xd6, 0xef, 0x17, 0x1a, 0x28, 0xa7, 0xe4}, - {0xd7, 0xe5, 0x74, 0xb7, 0xb9, 0x52, 0xf2, 0x93, 0xe8, 0x0d, 0xde, 0x90, 0x5e, 0xb5, 0x09, 0x37, 0x3f, 0x3f, 0x6c, 0xd1, 0x09, 0xa0, 0x22, 0x08, 0xb3, 0xc1, 0xe9, 0x24, 0x08, 0x0a, 0x20, 0xca}, - {0x45, 0x66, 0x6f, 0x8c, 0x38, 0x1e, 0x3d, 0xa6, 0x75, 0x56, 0x3f, 0xf8, 0xba, 0x23, 0xf8, 0x3b, 0xfa, 0xc3, 0x0c, 0x34, 0xab, 0xdd, 0xe6, 0xe5, 0xc0, 0x97, 0x5e, 0xf9, 0xfd, 0x70, 0x0c, 0xb9}, - {0xb2, 0x46, 0x12, 0xe4, 0x54, 0x60, 0x7e, 0xb1, 0xab, 0xa4, 0x47, 0xf8, 0x16, 0xd1, 0xa4, 0x55, 0x1e, 0xf9, 0x5f, 0xa7, 0x24, 0x7f, 0xb7, 0xc1, 0xf5, 0x03, 0x02, 0x0a, 0x71, 0x77, 0xf0, 0xdd}, - {0x7e, 0x20, 0x88, 0x61, 0x85, 0x6d, 0xa4, 0x2c, 0x8b, 0xb4, 0x6a, 0x75, 0x67, 0xf8, 0x12, 0x13, 0x62, 0xd9, 0xfb, 0x24, 0x96, 0xf1, 0x31, 0xa4, 0xaa, 0x90, 0x17, 0xcf, 0x36, 0x6c, 0xdf, 0xce}, - {0x5b, 0x64, 0x6b, 0xff, 0x6a, 0xd1, 0x10, 0x01, 0x65, 0x03, 0x7a, 0x05, 0x56, 0x01, 0xea, 0x02, 0x35, 0x8c, 0x0f, 0x41, 0x05, 0x0f, 0x9d, 0xfe, 0x3c, 0x95, 0xdc, 0xcb, 0xd3, 0x08, 0x7b, 0xe0}, - {0x74, 0x6d, 0x1d, 0xcc, 0xfe, 0xd2, 0xf0, 0xff, 0x1e, 0x13, 0xc5, 0x1e, 0x2d, 0x50, 0xd5, 0x32, 0x43, 0x75, 0xfb, 0xd5, 0xbf, 0x7c, 0xa8, 0x2a, 0x89, 0x31, 0x82, 0x8d, 0x80, 0x1d, 0x43, 0xab}, - {0xcb, 0x98, 0x11, 0x0d, 0x4a, 0x6b, 0xb9, 0x7d, 0x22, 0xfe, 0xad, 0xbc, 0x6c, 0x0d, 0x89, 0x30, 0xc5, 0xf8, 0xfc, 0x50, 0x8b, 0x2f, 0xc5, 0xb3, 0x53, 0x28, 0xd2, 0x6b, 0x88, 0xdb, 0x19, 0xae}, - {0x60, 0xb6, 0x26, 0xa0, 0x33, 0xb5, 0x5f, 0x27, 0xd7, 0x67, 0x6c, 0x40, 0x95, 0xea, 0xba, 0xbc, 0x7a, 0x2c, 0x7e, 0xde, 0x26, 0x24, 0xb4, 0x72, 0xe9, 0x7f, 0x64, 0xf9, 0x6b, 0x8c, 0xfc, 0x0e}, - {0xe5, 0xb5, 0x2b, 0xc9, 0x27, 0x46, 0x8d, 0xf7, 0x18, 0x93, 0xeb, 0x81, 0x97, 0xef, 0x82, 0x0c, 0xf7, 0x6c, 0xb0, 0xaa, 0xf6, 0xe8, 0xe4, 0xfe, 0x93, 0xad, 0x62, 0xd8, 0x03, 0x98, 0x31, 0x04}, - {0x05, 0x65, 0x41, 0xae, 0x5d, 0xa9, 0x96, 0x1b, 0xe2, 0xb0, 0xa5, 0xe8, 0x95, 0xe5, 0xc5, 0xba, 0x15, 0x3c, 0xbb, 0x62, 0xdd, 0x56, 0x1a, 0x42, 0x7b, 0xad, 0x0f, 0xfd, 0x41, 0x92, 0x31, 0x99}, - {0xf8, 0xfe, 0xf0, 0x5a, 0x3f, 0xa5, 0xc9, 0xf3, 0xeb, 0xa4, 0x16, 0x38, 0xb2, 0x47, 0xb7, 0x11, 0xa9, 0x9f, 0x96, 0x0f, 0xe7, 0x3a, 0xa2, 0xf9, 0x01, 0x36, 0xae, 0xb2, 0x03, 0x29, 0xb8, 0x88}}; + static const key64 H2 = {{{0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea, 0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94}}, + {{0x8f, 0xaa, 0x44, 0x8a, 0xe4, 0xb3, 0xe2, 0xbb, 0x3d, 0x4d, 0x13, 0x09, 0x09, 0xf5, 0x5f, 0xcd, 0x79, 0x71, 0x1c, 0x1c, 0x83, 0xcd, 0xbc, 0xca, 0xdd, 0x42, 0xcb, 0xe1, 0x51, 0x5e, 0x87, 0x12}}, + {{0x12, 0xa7, 0xd6, 0x2c, 0x77, 0x91, 0x65, 0x4a, 0x57, 0xf3, 0xe6, 0x76, 0x94, 0xed, 0x50, 0xb4, 0x9a, 0x7d, 0x9e, 0x3f, 0xc1, 0xe4, 0xc7, 0xa0, 0xbd, 0xe2, 0x9d, 0x18, 0x7e, 0x9c, 0xc7, 0x1d}}, + {{0x78, 0x9a, 0xb9, 0x93, 0x4b, 0x49, 0xc4, 0xf9, 0xe6, 0x78, 0x5c, 0x6d, 0x57, 0xa4, 0x98, 0xb3, 0xea, 0xd4, 0x43, 0xf0, 0x4f, 0x13, 0xdf, 0x11, 0x0c, 0x54, 0x27, 0xb4, 0xf2, 0x14, 0xc7, 0x39}}, + {{0x77, 0x1e, 0x92, 0x99, 0xd9, 0x4f, 0x02, 0xac, 0x72, 0xe3, 0x8e, 0x44, 0xde, 0x56, 0x8a, 0xc1, 0xdc, 0xb2, 0xed, 0xc6, 0xed, 0xb6, 0x1f, 0x83, 0xca, 0x41, 0x8e, 0x10, 0x77, 0xce, 0x3d, 0xe8}}, + {{0x73, 0xb9, 0x6d, 0xb4, 0x30, 0x39, 0x81, 0x9b, 0xda, 0xf5, 0x68, 0x0e, 0x5c, 0x32, 0xd7, 0x41, 0x48, 0x88, 0x84, 0xd1, 0x8d, 0x93, 0x86, 0x6d, 0x40, 0x74, 0xa8, 0x49, 0x18, 0x2a, 0x8a, 0x64}}, + {{0x8d, 0x45, 0x8e, 0x1c, 0x2f, 0x68, 0xeb, 0xeb, 0xcc, 0xd2, 0xfd, 0x5d, 0x37, 0x9f, 0x5e, 0x58, 0xf8, 0x13, 0x4d, 0xf3, 0xe0, 0xe8, 0x8c, 0xad, 0x3d, 0x46, 0x70, 0x10, 0x63, 0xa8, 0xd4, 0x12}}, + {{0x09, 0x55, 0x1e, 0xdb, 0xe4, 0x94, 0x41, 0x8e, 0x81, 0x28, 0x44, 0x55, 0xd6, 0x4b, 0x35, 0xee, 0x8a, 0xc0, 0x93, 0x06, 0x8a, 0x5f, 0x16, 0x1f, 0xa6, 0x63, 0x75, 0x59, 0x17, 0x7e, 0xf4, 0x04}}, + {{0xd0, 0x5a, 0x88, 0x66, 0xf4, 0xdf, 0x8c, 0xee, 0x1e, 0x26, 0x8b, 0x1d, 0x23, 0xa4, 0xc5, 0x8c, 0x92, 0xe7, 0x60, 0x30, 0x97, 0x86, 0xcd, 0xac, 0x0f, 0xed, 0xa1, 0xd2, 0x47, 0xa9, 0xc9, 0xa7}}, + {{0x55, 0xcd, 0xaa, 0xd5, 0x18, 0xbd, 0x87, 0x1d, 0xd1, 0xeb, 0x7b, 0xc7, 0x02, 0x3e, 0x1d, 0xc0, 0xfd, 0xf3, 0x33, 0x98, 0x64, 0xf8, 0x8f, 0xdd, 0x2d, 0xe2, 0x69, 0xfe, 0x9e, 0xe1, 0x83, 0x2d}}, + {{0xe7, 0x69, 0x7e, 0x95, 0x1a, 0x98, 0xcf, 0xd5, 0x71, 0x2b, 0x84, 0xbb, 0xe5, 0xf3, 0x4e, 0xd7, 0x33, 0xe9, 0x47, 0x3f, 0xcb, 0x68, 0xed, 0xa6, 0x6e, 0x37, 0x88, 0xdf, 0x19, 0x58, 0xc3, 0x06}}, + {{0xf9, 0x2a, 0x97, 0x0b, 0xae, 0x72, 0x78, 0x29, 0x89, 0xbf, 0xc8, 0x3a, 0xdf, 0xaa, 0x92, 0xa4, 0xf4, 0x9c, 0x7e, 0x95, 0x91, 0x8b, 0x3b, 0xba, 0x3c, 0xdc, 0x7f, 0xe8, 0x8a, 0xcc, 0x8d, 0x47}}, + {{0x1f, 0x66, 0xc2, 0xd4, 0x91, 0xd7, 0x5a, 0xf9, 0x15, 0xc8, 0xdb, 0x6a, 0x6d, 0x1c, 0xb0, 0xcd, 0x4f, 0x7d, 0xdc, 0xd5, 0xe6, 0x3d, 0x3b, 0xa9, 0xb8, 0x3c, 0x86, 0x6c, 0x39, 0xef, 0x3a, 0x2b}}, + {{0x3e, 0xec, 0x98, 0x84, 0xb4, 0x3f, 0x58, 0xe9, 0x3e, 0xf8, 0xde, 0xea, 0x26, 0x00, 0x04, 0xef, 0xea, 0x2a, 0x46, 0x34, 0x4f, 0xc5, 0x96, 0x5b, 0x1a, 0x7d, 0xd5, 0xd1, 0x89, 0x97, 0xef, 0xa7}}, + {{0xb2, 0x9f, 0x8f, 0x0c, 0xcb, 0x96, 0x97, 0x7f, 0xe7, 0x77, 0xd4, 0x89, 0xd6, 0xbe, 0x9e, 0x7e, 0xbc, 0x19, 0xc4, 0x09, 0xb5, 0x10, 0x35, 0x68, 0xf2, 0x77, 0x61, 0x1d, 0x7e, 0xa8, 0x48, 0x94}}, + {{0x56, 0xb1, 0xf5, 0x12, 0x65, 0xb9, 0x55, 0x98, 0x76, 0xd5, 0x8d, 0x24, 0x9d, 0x0c, 0x14, 0x6d, 0x69, 0xa1, 0x03, 0x63, 0x66, 0x99, 0x87, 0x4d, 0x3f, 0x90, 0x47, 0x35, 0x50, 0xfe, 0x3f, 0x2c}}, + {{0x1d, 0x7a, 0x36, 0x57, 0x5e, 0x22, 0xf5, 0xd1, 0x39, 0xff, 0x9c, 0xc5, 0x10, 0xfa, 0x13, 0x85, 0x05, 0x57, 0x6b, 0x63, 0x81, 0x5a, 0x94, 0xe4, 0xb0, 0x12, 0xbf, 0xd4, 0x57, 0xca, 0xaa, 0xda}}, + {{0xd0, 0xac, 0x50, 0x7a, 0x86, 0x4e, 0xcd, 0x05, 0x93, 0xfa, 0x67, 0xbe, 0x7d, 0x23, 0x13, 0x43, 0x92, 0xd0, 0x0e, 0x40, 0x07, 0xe2, 0x53, 0x48, 0x78, 0xd9, 0xb2, 0x42, 0xe1, 0x0d, 0x76, 0x20}}, + {{0xf6, 0xc6, 0x84, 0x0b, 0x9c, 0xf1, 0x45, 0xbb, 0x2d, 0xcc, 0xf8, 0x6e, 0x94, 0x0b, 0xe0, 0xfc, 0x09, 0x8e, 0x32, 0xe3, 0x10, 0x99, 0xd5, 0x6f, 0x7f, 0xe0, 0x87, 0xbd, 0x5d, 0xeb, 0x50, 0x94}}, + {{0x28, 0x83, 0x1a, 0x33, 0x40, 0x07, 0x0e, 0xb1, 0xdb, 0x87, 0xc1, 0x2e, 0x05, 0x98, 0x0d, 0x5f, 0x33, 0xe9, 0xef, 0x90, 0xf8, 0x3a, 0x48, 0x17, 0xc9, 0xf4, 0xa0, 0xa3, 0x32, 0x27, 0xe1, 0x97}}, + {{0x87, 0x63, 0x22, 0x73, 0xd6, 0x29, 0xcc, 0xb7, 0xe1, 0xed, 0x1a, 0x76, 0x8f, 0xa2, 0xeb, 0xd5, 0x17, 0x60, 0xf3, 0x2e, 0x1c, 0x0b, 0x86, 0x7a, 0x5d, 0x36, 0x8d, 0x52, 0x71, 0x05, 0x5c, 0x6e}}, + {{0x5c, 0x7b, 0x29, 0x42, 0x43, 0x47, 0x96, 0x4d, 0x04, 0x27, 0x55, 0x17, 0xc5, 0xae, 0x14, 0xb6, 0xb5, 0xea, 0x27, 0x98, 0xb5, 0x73, 0xfc, 0x94, 0xe6, 0xe4, 0x4a, 0x53, 0x21, 0x60, 0x0c, 0xfb}}, + {{0xe6, 0x94, 0x50, 0x42, 0xd7, 0x8b, 0xc2, 0xc3, 0xbd, 0x6e, 0xc5, 0x8c, 0x51, 0x1a, 0x9f, 0xe8, 0x59, 0xc0, 0xad, 0x63, 0xfd, 0xe4, 0x94, 0xf5, 0x03, 0x9e, 0x0e, 0x82, 0x32, 0x61, 0x2b, 0xd5}}, + {{0x36, 0xd5, 0x69, 0x07, 0xe2, 0xec, 0x74, 0x5d, 0xb6, 0xe5, 0x4f, 0x0b, 0x2e, 0x1b, 0x23, 0x00, 0xab, 0xcb, 0x42, 0x2e, 0x71, 0x2d, 0xa5, 0x88, 0xa4, 0x0d, 0x3f, 0x1e, 0xbb, 0xbe, 0x02, 0xf6}}, + {{0x34, 0xdb, 0x6e, 0xe4, 0xd0, 0x60, 0x8e, 0x5f, 0x78, 0x36, 0x50, 0x49, 0x5a, 0x3b, 0x2f, 0x52, 0x73, 0xc5, 0x13, 0x4e, 0x52, 0x84, 0xe4, 0xfd, 0xf9, 0x66, 0x27, 0xbb, 0x16, 0xe3, 0x1e, 0x6b}}, + {{0x8e, 0x76, 0x59, 0xfb, 0x45, 0xa3, 0x78, 0x7d, 0x67, 0x4a, 0xe8, 0x67, 0x31, 0xfa, 0xa2, 0x53, 0x8e, 0xc0, 0xfd, 0xf4, 0x42, 0xab, 0x26, 0xe9, 0xc7, 0x91, 0xfa, 0xda, 0x08, 0x94, 0x67, 0xe9}}, + {{0x30, 0x06, 0xcf, 0x19, 0x8b, 0x24, 0xf3, 0x1b, 0xb4, 0xc7, 0xe6, 0x34, 0x60, 0x00, 0xab, 0xc7, 0x01, 0xe8, 0x27, 0xcf, 0xbb, 0x5d, 0xf5, 0x2d, 0xcf, 0xa4, 0x2e, 0x9c, 0xa9, 0xff, 0x08, 0x02}}, + {{0xf5, 0xfd, 0x40, 0x3c, 0xb6, 0xe8, 0xbe, 0x21, 0x47, 0x2e, 0x37, 0x7f, 0xfd, 0x80, 0x5a, 0x8c, 0x60, 0x83, 0xea, 0x48, 0x03, 0xb8, 0x48, 0x53, 0x89, 0xcc, 0x3e, 0xbc, 0x21, 0x5f, 0x00, 0x2a}}, + {{0x37, 0x31, 0xb2, 0x60, 0xeb, 0x3f, 0x94, 0x82, 0xe4, 0x5f, 0x1c, 0x3f, 0x3b, 0x9d, 0xcf, 0x83, 0x4b, 0x75, 0xe6, 0xee, 0xf8, 0xc4, 0x0f, 0x46, 0x1e, 0xa2, 0x7e, 0x8b, 0x6e, 0xd9, 0x47, 0x3d}}, + {{0x9f, 0x9d, 0xab, 0x09, 0xc3, 0xf5, 0xe4, 0x28, 0x55, 0xc2, 0xde, 0x97, 0x1b, 0x65, 0x93, 0x28, 0xa2, 0xdb, 0xc4, 0x54, 0x84, 0x5f, 0x39, 0x6f, 0xfc, 0x05, 0x3f, 0x0b, 0xb1, 0x92, 0xf8, 0xc3}}, + {{0x5e, 0x05, 0x5d, 0x25, 0xf8, 0x5f, 0xdb, 0x98, 0xf2, 0x73, 0xe4, 0xaf, 0xe0, 0x84, 0x64, 0xc0, 0x03, 0xb7, 0x0f, 0x1e, 0xf0, 0x67, 0x7b, 0xb5, 0xe2, 0x57, 0x06, 0x40, 0x0b, 0xe6, 0x20, 0xa5}}, + {{0x86, 0x8b, 0xcf, 0x36, 0x79, 0xcb, 0x6b, 0x50, 0x0b, 0x94, 0x41, 0x8c, 0x0b, 0x89, 0x25, 0xf9, 0x86, 0x55, 0x30, 0x30, 0x3a, 0xe4, 0xe4, 0xb2, 0x62, 0x59, 0x18, 0x65, 0x66, 0x6a, 0x45, 0x90}}, + {{0xb3, 0xdb, 0x6b, 0xd3, 0x89, 0x7a, 0xfb, 0xd1, 0xdf, 0x3f, 0x96, 0x44, 0xab, 0x21, 0xc8, 0x05, 0x0e, 0x1f, 0x00, 0x38, 0xa5, 0x2f, 0x7c, 0xa9, 0x5a, 0xc0, 0xc3, 0xde, 0x75, 0x58, 0xcb, 0x7a}}, + {{0x81, 0x19, 0xb3, 0xa0, 0x59, 0xff, 0x2c, 0xac, 0x48, 0x3e, 0x69, 0xbc, 0xd4, 0x1d, 0x6d, 0x27, 0x14, 0x94, 0x47, 0x91, 0x42, 0x88, 0xbb, 0xea, 0xee, 0x34, 0x13, 0xe6, 0xdc, 0xc6, 0xd1, 0xeb}}, + {{0x10, 0xfc, 0x58, 0xf3, 0x5f, 0xc7, 0xfe, 0x7a, 0xe8, 0x75, 0x52, 0x4b, 0xb5, 0x85, 0x00, 0x03, 0x00, 0x5b, 0x7f, 0x97, 0x8c, 0x0c, 0x65, 0xe2, 0xa9, 0x65, 0x46, 0x4b, 0x6d, 0x00, 0x81, 0x9c}}, + {{0x5a, 0xcd, 0x94, 0xeb, 0x3c, 0x57, 0x83, 0x79, 0xc1, 0xea, 0x58, 0xa3, 0x43, 0xec, 0x4f, 0xcf, 0xf9, 0x62, 0x77, 0x6f, 0xe3, 0x55, 0x21, 0xe4, 0x75, 0xa0, 0xe0, 0x6d, 0x88, 0x7b, 0x2d, 0xb9}}, + {{0x33, 0xda, 0xf3, 0xa2, 0x14, 0xd6, 0xe0, 0xd4, 0x2d, 0x23, 0x00, 0xa7, 0xb4, 0x4b, 0x39, 0x29, 0x0d, 0xb8, 0x98, 0x9b, 0x42, 0x79, 0x74, 0xcd, 0x86, 0x5d, 0xb0, 0x11, 0x05, 0x5a, 0x29, 0x01}}, + {{0xcf, 0xc6, 0x57, 0x2f, 0x29, 0xaf, 0xd1, 0x64, 0xa4, 0x94, 0xe6, 0x4e, 0x6f, 0x1a, 0xeb, 0x82, 0x0c, 0x3e, 0x7d, 0xa3, 0x55, 0x14, 0x4e, 0x51, 0x24, 0xa3, 0x91, 0xd0, 0x6e, 0x9f, 0x95, 0xea}}, + {{0xd5, 0x31, 0x2a, 0x4b, 0x0e, 0xf6, 0x15, 0xa3, 0x31, 0xf6, 0x35, 0x2c, 0x2e, 0xd2, 0x1d, 0xac, 0x9e, 0x7c, 0x36, 0x39, 0x8b, 0x93, 0x9a, 0xec, 0x90, 0x1c, 0x25, 0x7f, 0x6c, 0xbc, 0x9e, 0x8e}}, + {{0x55, 0x1d, 0x67, 0xfe, 0xfc, 0x7b, 0x5b, 0x9f, 0x9f, 0xdb, 0xf6, 0xaf, 0x57, 0xc9, 0x6c, 0x8a, 0x74, 0xd7, 0xe4, 0x5a, 0x00, 0x20, 0x78, 0xa7, 0xb5, 0xba, 0x45, 0xc6, 0xfd, 0xe9, 0x3e, 0x33}}, + {{0xd5, 0x0a, 0xc7, 0xbd, 0x5c, 0xa5, 0x93, 0xc6, 0x56, 0x92, 0x8f, 0x38, 0x42, 0x80, 0x17, 0xfc, 0x7b, 0xa5, 0x02, 0x85, 0x4c, 0x43, 0xd8, 0x41, 0x49, 0x50, 0xe9, 0x6e, 0xcb, 0x40, 0x5d, 0xc3}}, + {{0x07, 0x73, 0xe1, 0x8e, 0xa1, 0xbe, 0x44, 0xfe, 0x1a, 0x97, 0xe2, 0x39, 0x57, 0x3c, 0xfa, 0xe3, 0xe4, 0xe9, 0x5e, 0xf9, 0xaa, 0x9f, 0xaa, 0xbe, 0xac, 0x12, 0x74, 0xd3, 0xad, 0x26, 0x16, 0x04}}, + {{0xe9, 0xaf, 0x0e, 0x7c, 0xa8, 0x93, 0x30, 0xd2, 0xb8, 0x61, 0x5d, 0x1b, 0x41, 0x37, 0xca, 0x61, 0x7e, 0x21, 0x29, 0x7f, 0x2f, 0x0d, 0xed, 0x8e, 0x31, 0xb7, 0xd2, 0xea, 0xd8, 0x71, 0x46, 0x60}}, + {{0x7b, 0x12, 0x45, 0x83, 0x09, 0x7f, 0x10, 0x29, 0xa0, 0xc7, 0x41, 0x91, 0xfe, 0x73, 0x78, 0xc9, 0x10, 0x5a, 0xcc, 0x70, 0x66, 0x95, 0xed, 0x14, 0x93, 0xbb, 0x76, 0x03, 0x42, 0x26, 0xa5, 0x7b}}, + {{0xec, 0x40, 0x05, 0x7b, 0x99, 0x54, 0x76, 0x65, 0x0b, 0x3d, 0xb9, 0x8e, 0x9d, 0xb7, 0x57, 0x38, 0xa8, 0xcd, 0x2f, 0x94, 0xd8, 0x63, 0xb9, 0x06, 0x15, 0x0c, 0x56, 0xaa, 0xc1, 0x9c, 0xaa, 0x6b}}, + {{0x01, 0xd9, 0xff, 0x72, 0x9e, 0xfd, 0x39, 0xd8, 0x37, 0x84, 0xc0, 0xfe, 0x59, 0xc4, 0xae, 0x81, 0xa6, 0x70, 0x34, 0xcb, 0x53, 0xc9, 0x43, 0xfb, 0x81, 0x8b, 0x9d, 0x8a, 0xe7, 0xfc, 0x33, 0xe5}}, + {{0x00, 0xdf, 0xb3, 0xc6, 0x96, 0x32, 0x8c, 0x76, 0x42, 0x45, 0x19, 0xa7, 0xbe, 0xfe, 0x8e, 0x0f, 0x6c, 0x76, 0xf9, 0x47, 0xb5, 0x27, 0x67, 0x91, 0x6d, 0x24, 0x82, 0x3f, 0x73, 0x5b, 0xaf, 0x2e}}, + {{0x46, 0x1b, 0x79, 0x9b, 0x4d, 0x9c, 0xee, 0xa8, 0xd5, 0x80, 0xdc, 0xb7, 0x6d, 0x11, 0x15, 0x0d, 0x53, 0x5e, 0x16, 0x39, 0xd1, 0x60, 0x03, 0xc3, 0xfb, 0x7e, 0x9d, 0x1f, 0xd1, 0x30, 0x83, 0xa8}}, + {{0xee, 0x03, 0x03, 0x94, 0x79, 0xe5, 0x22, 0x8f, 0xdc, 0x55, 0x1c, 0xbd, 0xe7, 0x07, 0x9d, 0x34, 0x12, 0xea, 0x18, 0x6a, 0x51, 0x7c, 0xcc, 0x63, 0xe4, 0x6e, 0x9f, 0xcc, 0xe4, 0xfe, 0x3a, 0x6c}}, + {{0xa8, 0xcf, 0xb5, 0x43, 0x52, 0x4e, 0x7f, 0x02, 0xb9, 0xf0, 0x45, 0xac, 0xd5, 0x43, 0xc2, 0x1c, 0x37, 0x3b, 0x4c, 0x9b, 0x98, 0xac, 0x20, 0xce, 0xc4, 0x17, 0xa6, 0xdd, 0xb5, 0x74, 0x4e, 0x94}}, + {{0x93, 0x2b, 0x79, 0x4b, 0xf8, 0x9c, 0x6e, 0xda, 0xf5, 0xd0, 0x65, 0x0c, 0x7c, 0x4b, 0xad, 0x92, 0x42, 0xb2, 0x56, 0x26, 0xe3, 0x7e, 0xad, 0x5a, 0xa7, 0x5e, 0xc8, 0xc6, 0x4e, 0x09, 0xdd, 0x4f}}, + {{0x16, 0xb1, 0x0c, 0x77, 0x9c, 0xe5, 0xcf, 0xef, 0x59, 0xc7, 0x71, 0x0d, 0x2e, 0x68, 0x44, 0x1e, 0xa6, 0xfa, 0xcb, 0x68, 0xe9, 0xb5, 0xf7, 0xd5, 0x33, 0xae, 0x0b, 0xb7, 0x8e, 0x28, 0xbf, 0x57}}, + {{0x0f, 0x77, 0xc7, 0x67, 0x43, 0xe7, 0x39, 0x6f, 0x99, 0x10, 0x13, 0x9f, 0x49, 0x37, 0xd8, 0x37, 0xae, 0x54, 0xe2, 0x10, 0x38, 0xac, 0x5c, 0x0b, 0x3f, 0xd6, 0xef, 0x17, 0x1a, 0x28, 0xa7, 0xe4}}, + {{0xd7, 0xe5, 0x74, 0xb7, 0xb9, 0x52, 0xf2, 0x93, 0xe8, 0x0d, 0xde, 0x90, 0x5e, 0xb5, 0x09, 0x37, 0x3f, 0x3f, 0x6c, 0xd1, 0x09, 0xa0, 0x22, 0x08, 0xb3, 0xc1, 0xe9, 0x24, 0x08, 0x0a, 0x20, 0xca}}, + {{0x45, 0x66, 0x6f, 0x8c, 0x38, 0x1e, 0x3d, 0xa6, 0x75, 0x56, 0x3f, 0xf8, 0xba, 0x23, 0xf8, 0x3b, 0xfa, 0xc3, 0x0c, 0x34, 0xab, 0xdd, 0xe6, 0xe5, 0xc0, 0x97, 0x5e, 0xf9, 0xfd, 0x70, 0x0c, 0xb9}}, + {{0xb2, 0x46, 0x12, 0xe4, 0x54, 0x60, 0x7e, 0xb1, 0xab, 0xa4, 0x47, 0xf8, 0x16, 0xd1, 0xa4, 0x55, 0x1e, 0xf9, 0x5f, 0xa7, 0x24, 0x7f, 0xb7, 0xc1, 0xf5, 0x03, 0x02, 0x0a, 0x71, 0x77, 0xf0, 0xdd}}, + {{0x7e, 0x20, 0x88, 0x61, 0x85, 0x6d, 0xa4, 0x2c, 0x8b, 0xb4, 0x6a, 0x75, 0x67, 0xf8, 0x12, 0x13, 0x62, 0xd9, 0xfb, 0x24, 0x96, 0xf1, 0x31, 0xa4, 0xaa, 0x90, 0x17, 0xcf, 0x36, 0x6c, 0xdf, 0xce}}, + {{0x5b, 0x64, 0x6b, 0xff, 0x6a, 0xd1, 0x10, 0x01, 0x65, 0x03, 0x7a, 0x05, 0x56, 0x01, 0xea, 0x02, 0x35, 0x8c, 0x0f, 0x41, 0x05, 0x0f, 0x9d, 0xfe, 0x3c, 0x95, 0xdc, 0xcb, 0xd3, 0x08, 0x7b, 0xe0}}, + {{0x74, 0x6d, 0x1d, 0xcc, 0xfe, 0xd2, 0xf0, 0xff, 0x1e, 0x13, 0xc5, 0x1e, 0x2d, 0x50, 0xd5, 0x32, 0x43, 0x75, 0xfb, 0xd5, 0xbf, 0x7c, 0xa8, 0x2a, 0x89, 0x31, 0x82, 0x8d, 0x80, 0x1d, 0x43, 0xab}}, + {{0xcb, 0x98, 0x11, 0x0d, 0x4a, 0x6b, 0xb9, 0x7d, 0x22, 0xfe, 0xad, 0xbc, 0x6c, 0x0d, 0x89, 0x30, 0xc5, 0xf8, 0xfc, 0x50, 0x8b, 0x2f, 0xc5, 0xb3, 0x53, 0x28, 0xd2, 0x6b, 0x88, 0xdb, 0x19, 0xae}}, + {{0x60, 0xb6, 0x26, 0xa0, 0x33, 0xb5, 0x5f, 0x27, 0xd7, 0x67, 0x6c, 0x40, 0x95, 0xea, 0xba, 0xbc, 0x7a, 0x2c, 0x7e, 0xde, 0x26, 0x24, 0xb4, 0x72, 0xe9, 0x7f, 0x64, 0xf9, 0x6b, 0x8c, 0xfc, 0x0e}}, + {{0xe5, 0xb5, 0x2b, 0xc9, 0x27, 0x46, 0x8d, 0xf7, 0x18, 0x93, 0xeb, 0x81, 0x97, 0xef, 0x82, 0x0c, 0xf7, 0x6c, 0xb0, 0xaa, 0xf6, 0xe8, 0xe4, 0xfe, 0x93, 0xad, 0x62, 0xd8, 0x03, 0x98, 0x31, 0x04}}, + {{0x05, 0x65, 0x41, 0xae, 0x5d, 0xa9, 0x96, 0x1b, 0xe2, 0xb0, 0xa5, 0xe8, 0x95, 0xe5, 0xc5, 0xba, 0x15, 0x3c, 0xbb, 0x62, 0xdd, 0x56, 0x1a, 0x42, 0x7b, 0xad, 0x0f, 0xfd, 0x41, 0x92, 0x31, 0x99}}, + {{0xf8, 0xfe, 0xf0, 0x5a, 0x3f, 0xa5, 0xc9, 0xf3, 0xeb, 0xa4, 0x16, 0x38, 0xb2, 0x47, 0xb7, 0x11, 0xa9, 0x9f, 0x96, 0x0f, 0xe7, 0x3a, 0xa2, 0xf9, 0x01, 0x36, 0xae, 0xb2, 0x03, 0x29, 0xb8, 0x88}}}; //Debug printing for the above types //Actually use DP(value) and #define DBG @@ -415,7 +419,7 @@ namespace rct { // then the value in the first 8 bytes is returned xmr_amount h2d(const key &test); //32 byte key to int[64] - void h2b(bits amountb2, key & test); + void h2b(bits amountb2, const key & test); //int[64] to 32 byte key void b2h(key & amountdh, bits amountb2); //int[64] to uint long long @@ -441,14 +445,15 @@ namespace cryptonote { static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } } -template<typename T> std::ostream &print256(std::ostream &o, const T &v); -inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { return print256(o, v); } +inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; +} BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::ctkey); -BLOB_SERIALIZER(rct::asnlSig); +BLOB_SERIALIZER(rct::boroSig); VARIANT_TAG(debug_archive, rct::key, "rct::key"); VARIANT_TAG(debug_archive, rct::key64, "rct::key64"); @@ -460,7 +465,7 @@ VARIANT_TAG(debug_archive, rct::ctkeyM, "rct::ctkeyM"); VARIANT_TAG(debug_archive, rct::ecdhTuple, "rct::ecdhTuple"); VARIANT_TAG(debug_archive, rct::mgSig, "rct::mgSig"); VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig"); -VARIANT_TAG(debug_archive, rct::asnlSig, "rct::asnlSig"); +VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig"); VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig"); VARIANT_TAG(binary_archive, rct::key, 0x90); @@ -473,7 +478,7 @@ VARIANT_TAG(binary_archive, rct::ctkeyM, 0x96); VARIANT_TAG(binary_archive, rct::ecdhTuple, 0x97); VARIANT_TAG(binary_archive, rct::mgSig, 0x98); VARIANT_TAG(binary_archive, rct::rangeSig, 0x99); -VARIANT_TAG(binary_archive, rct::asnlSig, 0x9a); +VARIANT_TAG(binary_archive, rct::boroSig, 0x9a); VARIANT_TAG(binary_archive, rct::rctSig, 0x9b); VARIANT_TAG(json_archive, rct::key, "rct_key"); @@ -486,7 +491,7 @@ VARIANT_TAG(json_archive, rct::ctkeyM, "rct_ctkeyM"); VARIANT_TAG(json_archive, rct::ecdhTuple, "rct_ecdhTuple"); VARIANT_TAG(json_archive, rct::mgSig, "rct_mgSig"); VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig"); -VARIANT_TAG(json_archive, rct::asnlSig, "rct_asnlSig"); +VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig"); VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 1716da75b..f6037d7e3 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -27,25 +27,30 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(rpc_sources - core_rpc_server.cpp) + core_rpc_server.cpp + rpc_args.cpp) -set(rpc_headers) +set(rpc_headers + rpc_args.h) set(rpc_private_headers core_rpc_server.h core_rpc_server_commands_defs.h core_rpc_server_error_codes.h) -bitmonero_private_headers(rpc +monero_private_headers(rpc ${rpc_private_headers}) -bitmonero_add_library(rpc +monero_add_library(rpc ${rpc_sources} ${rpc_headers} ${rpc_private_headers}) target_link_libraries(rpc PUBLIC + common cryptonote_core cryptonote_protocol + epee + ${Boost_REGEX_LIBRARY} ${Boost_THREAD_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f6431a018..76a69fbf6 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -28,19 +28,25 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include <boost/foreach.hpp> #include "include_base_utils.h" using namespace epee; #include "core_rpc_server.h" #include "common/command_line.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "cryptonote_core/account.h" -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "common/updates.h" +#include "common/download.h" +#include "common/util.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "misc_language.h" #include "crypto/hash.h" +#include "rpc/rpc_args.h" #include "core_rpc_server_error_codes.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc" + #define MAX_RESTRICTED_FAKE_OUTS_COUNT 40 #define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 500 @@ -50,11 +56,10 @@ namespace cryptonote //----------------------------------------------------------------------------------- void core_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); command_line::add_arg(desc, arg_testnet_rpc_bind_port); command_line::add_arg(desc, arg_restricted_rpc); - command_line::add_arg(desc, arg_user_agent); + cryptonote::rpc_args::init_options(desc); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -65,29 +70,29 @@ namespace cryptonote , m_p2p(p2p) {} //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::handle_command_line( + bool core_rpc_server::init( const boost::program_options::variables_map& vm ) { + m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + m_net_server.set_threads_prefix("RPC"); + auto p2p_bind_arg = m_testnet ? arg_testnet_rpc_bind_port : arg_rpc_bind_port; - m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); - m_port = command_line::get_arg(vm, p2p_bind_arg); + auto rpc_config = cryptonote::rpc_args::process(vm); + if (!rpc_config) + return false; + m_restricted = command_line::get_arg(vm, arg_restricted_rpc); - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::init( - const boost::program_options::variables_map& vm - ) - { - m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); - std::string m_user_agent = command_line::get_arg(vm, command_line::arg_user_agent); - 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<core_rpc_server, connection_context>::init(m_port, m_bind_ip, m_user_agent); + boost::optional<epee::net_utils::http::login> http_login{}; + std::string port = command_line::get_arg(vm, p2p_bind_arg); + if (rpc_config->login) + http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); + + return epee::http_server_impl_base<core_rpc_server, connection_context>::init( + std::move(port), std::move(rpc_config->bind_ip), std::move(http_login) + ); } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_busy() @@ -142,14 +147,34 @@ namespace cryptonote res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; + res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; + res.start_time = (uint64_t)m_core.get_start_time(); return true; } //------------------------------------------------------------------------------------------------------------------------------ + static cryptonote::blobdata get_pruned_tx_blob(const cryptonote::blobdata &blobdata) + { + cryptonote::transaction tx; + + if (!cryptonote::parse_and_validate_tx_from_blob(blobdata, tx)) + { + MERROR("Failed to parse and validate tx from blob"); + return blobdata; + } + + std::stringstream ss; + binary_archive<true> ba(ss); + bool r = tx.serialize_base(ba); + CHECK_AND_ASSERT_MES(r, blobdata, "Failed to serialize rct signatures base"); + return ss.str(); + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res) { CHECK_CORE_BUSY(); - std::list<std::pair<block, std::list<transaction> > > bs; + std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > > bs; if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) { @@ -157,24 +182,39 @@ namespace cryptonote return false; } - BOOST_FOREACH(auto& b, bs) + size_t pruned_size = 0, unpruned_size = 0, ntxes = 0; + for(auto& bd: bs) { res.blocks.resize(res.blocks.size()+1); - res.blocks.back().block = block_to_blob(b.first); + res.blocks.back().block = bd.first; + pruned_size += bd.first.size(); + unpruned_size += bd.first.size(); res.output_indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices()); res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); - bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(b.first.miner_tx), res.output_indices.back().indices.back().indices); + block b; + if (!parse_and_validate_block_from_blob(bd.first, b)) + { + res.status = "Invalid block"; + return false; + } + bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(b.miner_tx), res.output_indices.back().indices.back().indices); if (!r) { res.status = "Failed"; return false; } size_t txidx = 0; - BOOST_FOREACH(auto& t, b.second) + ntxes += bd.second.size(); + for(const auto& t: bd.second) { - res.blocks.back().txs.push_back(tx_to_blob(t)); + if (req.prune) + res.blocks.back().txs.push_back(get_pruned_tx_blob(t)); + else + res.blocks.back().txs.push_back(t); + pruned_size += res.blocks.back().txs.back().size(); + unpruned_size += t.size(); res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); - bool r = m_core.get_tx_outputs_gindexs(b.first.tx_hashes[txidx++], res.output_indices.back().indices.back().indices); + bool r = m_core.get_tx_outputs_gindexs(b.tx_hashes[txidx++], res.output_indices.back().indices.back().indices); if (!r) { res.status = "Failed"; @@ -183,6 +223,37 @@ namespace cryptonote } } + MDEBUG("on_get_blocks: " << bs.size() << " blocks, " << ntxes << " txes, pruned size " << pruned_size << ", unpruned size " << unpruned_size); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res) + { + CHECK_CORE_BUSY(); + res.status = "Failed"; + res.blocks.clear(); + res.blocks.reserve(req.heights.size()); + for (uint64_t height : req.heights) + { + block blk; + try + { + blk = m_core.get_blockchain_storage().get_db().get_block_from_height(height); + } + catch (...) + { + res.status = "Error retrieving block at height " + std::to_string(height); + return true; + } + std::list<transaction> txs; + std::list<crypto::hash> missed_txs; + m_core.get_transactions(blk.tx_hashes, txs, missed_txs); + res.blocks.resize(res.blocks.size() + 1); + res.blocks.back().block = block_to_blob(blk); + for (auto& tx : txs) + res.blocks.back().txs.push_back(tx_to_blob(tx)); + } res.status = CORE_RPC_STATUS_OK; return true; } @@ -245,7 +316,7 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) + bool core_rpc_server::on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) { CHECK_CORE_BUSY(); res.status = "Failed"; @@ -268,6 +339,44 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) + { + CHECK_CORE_BUSY(); + res.status = "Failed"; + + if (m_restricted) + { + if (req.outputs.size() > MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT) + { + res.status = "Too many outs requested"; + return true; + } + } + + cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req_bin; + req_bin.outputs = req.outputs; + cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res_bin; + if(!m_core.get_outs(req_bin, res_bin)) + { + return true; + } + + // convert to text + for (const auto &i: res_bin.outs) + { + res.outs.push_back(cryptonote::COMMAND_RPC_GET_OUTPUTS::outkey()); + cryptonote::COMMAND_RPC_GET_OUTPUTS::outkey &outkey = res.outs.back(); + outkey.key = epee::string_tools::pod_to_hex(i.key); + outkey.mask = epee::string_tools::pod_to_hex(i.mask); + outkey.unlocked = i.unlocked; + outkey.height = i.height; + outkey.txid = epee::string_tools::pod_to_hex(i.txid); + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) { CHECK_CORE_BUSY(); @@ -310,7 +419,7 @@ namespace cryptonote { CHECK_CORE_BUSY(); std::vector<crypto::hash> vh; - BOOST_FOREACH(const auto& tx_hex_str, req.txs_hashes) + for(const auto& tx_hex_str: req.txs_hashes) { blobdata b; if(!string_tools::parse_hexstr_to_binbuff(tx_hex_str, b)) @@ -362,7 +471,7 @@ namespace cryptonote std::list<std::string>::const_iterator txhi = req.txs_hashes.begin(); std::vector<crypto::hash>::const_iterator vhi = vh.begin(); - BOOST_FOREACH(auto& tx, txs) + for(auto& tx: txs) { res.txs.push_back(COMMAND_RPC_GET_TRANSACTIONS::entry()); COMMAND_RPC_GET_TRANSACTIONS::entry &e = res.txs.back(); @@ -387,9 +496,20 @@ namespace cryptonote res.txs_as_hex.push_back(e.as_hex); if (req.decode_as_json) res.txs_as_json.push_back(e.as_json); + + // output indices too if not in pool + if (pool_tx_hashes.find(tx_hash) == pool_tx_hashes.end()) + { + bool r = m_core.get_tx_outputs_gindexs(tx_hash, e.output_indices); + if (!r) + { + res.status = "Failed"; + return false; + } + } } - BOOST_FOREACH(const auto& miss_tx, missed_txs) + for(const auto& miss_tx: missed_txs) { res.missed_tx.push_back(string_tools::pod_to_hex(miss_tx)); } @@ -403,7 +523,7 @@ namespace cryptonote { CHECK_CORE_BUSY(); std::vector<crypto::key_image> key_images; - BOOST_FOREACH(const auto& ki_hex_str, req.key_images) + for(const auto& ki_hex_str: req.key_images) { blobdata b; if(!string_tools::parse_hexstr_to_binbuff(ki_hex_str, b)) @@ -476,7 +596,7 @@ namespace cryptonote cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) { if (tvc.m_verifivation_failed) { @@ -506,7 +626,7 @@ namespace cryptonote return true; } - if(!tvc.m_should_be_relayed || req.do_not_relay) + if(!tvc.m_should_be_relayed) { LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); res.reason = "Not relayed"; @@ -534,10 +654,27 @@ namespace cryptonote return true; } + unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4; + + // if we couldn't detect threads, set it to a ridiculously high number + if(concurrency_count == 0) + { + concurrency_count = 257; + } + + // if there are more threads requested than the hardware supports + // then we fail and log that. + if(req.threads_count > concurrency_count) + { + res.status = "Failed, too many threads relative to CPU cores."; + LOG_PRINT_L0(res.status); + return true; + } + boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs)) + if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) { res.status = "Failed, mining not started"; LOG_PRINT_L0(res.status); @@ -561,10 +698,11 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res) { - CHECK_CORE_READY(); + CHECK_CORE_BUSY(); const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); + res.is_background_mining_enabled = lMiner.get_is_background_mining_enabled(); if ( lMiner.is_mining() ) { res.speed = lMiner.get_speed(); @@ -626,17 +764,20 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res) { - if (req.level < LOG_LEVEL_MIN || req.level > LOG_LEVEL_MAX) + if (req.level < 0 || req.level > 4) { res.status = "Error: log level not valid"; + return true; } - else - { - epee::log_space::log_singletone::get_set_log_detalisation_level(true, req.level); - int otshell_utils_log_level = 100 - (req.level * 20); - gCurrentLogger.setDebugLevel(otshell_utils_log_level); - res.status = CORE_RPC_STATUS_OK; - } + mlog_set_log_level(req.level); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res) + { + mlog_set_log(req.categories.c_str()); + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -648,6 +789,14 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res) + { + CHECK_CORE_BUSY(); + m_core.get_pool_transaction_hashes(res.tx_hashes); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res) { // FIXME: replace back to original m_p2p.send_stop_signal() after @@ -734,7 +883,7 @@ namespace cryptonote block b = AUTO_VAL_INIT(b); cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); - if(!m_core.get_block_template(b, acc, res.difficulty, res.height, blob_reserve)) + if(!m_core.get_block_template(b, acc, res.difficulty, res.height, res.expected_reward, blob_reserve)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; @@ -823,26 +972,28 @@ namespace cryptonote uint64_t core_rpc_server::get_block_reward(const block& blk) { uint64_t reward = 0; - BOOST_FOREACH(const tx_out& out, blk.miner_tx.vout) + for(const tx_out& out: blk.miner_tx.vout) { reward += out.amount; } return reward; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce) + bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response) { - responce.major_version = blk.major_version; - responce.minor_version = blk.minor_version; - responce.timestamp = blk.timestamp; - responce.prev_hash = string_tools::pod_to_hex(blk.prev_id); - responce.nonce = blk.nonce; - responce.orphan_status = orphan_status; - responce.height = height; - responce.depth = m_core.get_current_blockchain_height() - height - 1; - responce.hash = string_tools::pod_to_hex(hash); - responce.difficulty = m_core.get_blockchain_storage().block_difficulty(height); - responce.reward = get_block_reward(blk); + response.major_version = blk.major_version; + response.minor_version = blk.minor_version; + response.timestamp = blk.timestamp; + response.prev_hash = string_tools::pod_to_hex(blk.prev_id); + response.nonce = blk.nonce; + response.orphan_status = orphan_status; + response.height = height; + response.depth = m_core.get_current_blockchain_height() - height - 1; + response.hash = string_tools::pod_to_hex(hash); + response.difficulty = m_core.get_blockchain_storage().block_difficulty(height); + response.reward = get_block_reward(blk); + response.block_size = m_core.get_blockchain_storage().get_db().get_block_size(height); + response.num_txes = blk.tx_hashes.size(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -871,8 +1022,8 @@ namespace cryptonote error_resp.message = "Internal error: can't get last block."; return false; } - bool responce_filled = fill_block_header_responce(last_block, false, last_block_height, last_block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -898,7 +1049,8 @@ namespace cryptonote return false; } block blk; - bool have_block = m_core.get_block_by_hash(block_hash, blk); + bool orphan = false; + bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan); if (!have_block) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -912,8 +1064,8 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -923,6 +1075,57 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_block_headers_range(const COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request& req, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response& res, epee::json_rpc::error& error_resp){ + if(!check_core_busy()) + { + error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; + error_resp.message = "Core is busy."; + return false; + } + const uint64_t bc_height = m_core.get_current_blockchain_height(); + if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) + { + error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; + error_resp.message = "Invalid start/end heights."; + return false; + } + for (uint64_t h = req.start_height; h <= req.end_height; ++h) + { + crypto::hash block_hash = m_core.get_block_id_by_height(h); + block blk; + bool have_block = m_core.get_block_by_hash(block_hash, blk); + if (!have_block) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: can't get block by height. Height = " + boost::lexical_cast<std::string>(h) + ". Hash = " + epee::string_tools::pod_to_hex(block_hash) + '.'; + return false; + } + if (blk.miner_tx.vin.front().type() != typeid(txin_gen)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; + return false; + } + uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; + if (block_height != h) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: coinbase transaction in the block has the wrong height"; + return false; + } + res.headers.push_back(block_header_response()); + bool responce_filled = fill_block_header_response(blk, false, block_height, block_hash, res.headers.back()); + if (!responce_filled) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: can't produce valid response."; + return false; + } + } + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp){ if(!check_core_busy()) { @@ -945,8 +1148,8 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.'; return false; } - bool responce_filled = fill_block_header_responce(blk, false, req.height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -985,7 +1188,8 @@ namespace cryptonote block_hash = m_core.get_block_id_by_height(req.height); } block blk; - bool have_block = m_core.get_block_by_hash(block_hash, blk); + bool orphan = false; + bool have_block = m_core.get_block_by_hash(block_hash, blk, &orphan); if (!have_block) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -999,8 +1203,8 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header); - if (!responce_filled) + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: can't produce valid response."; @@ -1041,7 +1245,14 @@ namespace cryptonote return false; } - res.height = m_core.get_current_blockchain_height(); + crypto::hash top_hash; + if (!m_core.get_blockchain_top(res.height, top_hash)) + { + res.status = "Failed"; + return false; + } + ++res.height; // turn top block height into blockchain height + res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; @@ -1054,7 +1265,10 @@ namespace cryptonote res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; + res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; + res.start_time = (uint64_t)m_core.get_start_time(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1185,10 +1399,10 @@ namespace cryptonote return false; } - std::map<uint64_t, uint64_t> histogram; + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; try { - histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked); + histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked, req.recent_cutoff); } catch (const std::exception &e) { @@ -1200,8 +1414,8 @@ namespace cryptonote res.histogram.reserve(histogram.size()); for (const auto &i: histogram) { - if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0)) - res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second)); + if (std::get<0>(i.second) >= req.min_count && (std::get<0>(i.second) <= req.max_count || req.max_count == 0)) + res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, std::get<0>(i.second), std::get<1>(i.second), std::get<2>(i.second))); } res.status = CORE_RPC_STATUS_OK; @@ -1215,12 +1429,38 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res) + bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp) { - cryptonote::core::set_fast_exit(); - m_p2p.deinit(); - m_core.deinit(); - return true; + std::pair<uint64_t, uint64_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); + res.emission_amount = amounts.first; + res.fee_amount = amounts.second; + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp) + { + res.fee = m_core.get_blockchain_storage().get_dynamic_per_kb_fee_estimate(req.grace_blocks); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp) + { + try + { + std::list<std::pair<Blockchain::block_extended_info, uint64_t>> chains = m_core.get_blockchain_storage().get_alternative_chains(); + for (const auto &i: chains) + { + res.chains.push_back(COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info{epee::string_tools::pod_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second, i.first.cumulative_difficulty}); + } + res.status = CORE_RPC_STATUS_OK; + } + catch (...) + { + res.status = "Error retrieving alternate chains"; + } + return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res) @@ -1245,21 +1485,155 @@ namespace cryptonote bool core_rpc_server::on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res) { m_p2p.set_save_graph(true); + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res) { m_p2p.set_save_graph(false); + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res) + { + static const char software[] = "monero"; +#ifdef BUILD_TAG + static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); +#else + static const char buildtag[] = "source"; +#endif - const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_bind_ip = { - "rpc-bind-ip" - , "IP for RPC server" - , "127.0.0.1" - }; + if (req.command != "check" && req.command != "download" && req.command != "update") + { + res.status = std::string("unknown command: '") + req.command + "'"; + return true; + } + + std::string version, hash; + if (!tools::check_updates(software, buildtag, version, hash)) + { + res.status = "Error checking for updates"; + return true; + } + if (tools::vercmp(version.c_str(), MONERO_VERSION) <= 0) + { + res.update = false; + res.status = CORE_RPC_STATUS_OK; + return true; + } + res.update = true; + res.version = version; + res.user_uri = tools::get_update_url(software, "cli", buildtag, version, true); + res.auto_uri = tools::get_update_url(software, "cli", buildtag, version, false); + res.hash = hash; + if (req.command == "check") + { + res.status = CORE_RPC_STATUS_OK; + return true; + } + + boost::filesystem::path path; + if (req.path.empty()) + { + std::string filename; + const char *slash = strrchr(res.auto_uri.c_str(), '/'); + if (slash) + filename = slash + 1; + else + filename = std::string(software) + "-update-" + version; + path = epee::string_tools::get_current_module_folder(); + path /= filename; + } + else + { + path = req.path; + } + + crypto::hash file_hash; + if (!tools::sha256sum(path.string(), file_hash) || (hash != epee::string_tools::pod_to_hex(file_hash))) + { + MDEBUG("We don't have that file already, downloading"); + if (!tools::download(path.string(), res.auto_uri)) + { + MERROR("Failed to download " << res.auto_uri); + return false; + } + if (!tools::sha256sum(path.string(), file_hash)) + { + MERROR("Failed to hash " << path); + return false; + } + if (hash != epee::string_tools::pod_to_hex(file_hash)) + { + MERROR("Download from " << res.auto_uri << " does not match the expected hash"); + return false; + } + MINFO("New version downloaded to " << path); + } + else + { + MDEBUG("We already have " << path << " with expected hash"); + } + res.path = path.string(); + + if (req.command == "download") + { + res.status = CORE_RPC_STATUS_OK; + return true; + } + + res.status = "'update' not implemented yet"; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp) + { + if(!check_core_busy()) + { + error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; + error_resp.message = "Core is busy."; + return false; + } + + bool failed = false; + for (const auto &str: req.txids) + { + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(str, txid_data)) + { + res.status = std::string("Invalid transaction id: ") + str; + failed = true; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + cryptonote::transaction tx; + bool r = m_core.get_pool_transaction(txid, tx); + if (r) + { + cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); + NOTIFY_NEW_TRANSACTIONS::request r; + r.txs.push_back(cryptonote::tx_to_blob(tx)); + m_core.get_protocol()->relay_transactions(r, fake_context); + //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes + } + else + { + res.status = std::string("Transaction not found in pool: ") + str; + failed = true; + } + } + + if (failed) + { + return false; + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_bind_port = { "rpc-bind-port" @@ -1278,11 +1652,4 @@ namespace cryptonote , "Restrict RPC to view only commands" , false }; - - const command_line::arg_descriptor<std::string> core_rpc_server::arg_user_agent = { - "user-agent" - , "Restrict RPC to clients using this user agent" - , "" - }; - } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 9885aa0ff..0aa222c87 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -52,11 +52,9 @@ namespace cryptonote { public: - static const command_line::arg_descriptor<std::string> arg_rpc_bind_ip; static const command_line::arg_descriptor<std::string> arg_rpc_bind_port; static const command_line::arg_descriptor<std::string> arg_testnet_rpc_bind_port; static const command_line::arg_descriptor<bool> arg_restricted_rpc; - static const command_line::arg_descriptor<std::string> arg_user_agent; typedef epee::net_utils::connection_context_base connection_context; @@ -76,10 +74,11 @@ namespace cryptonote BEGIN_URI_MAP2() MAP_URI_AUTO_JON2("/getheight", on_get_height, COMMAND_RPC_GET_HEIGHT) MAP_URI_AUTO_BIN2("/getblocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) + MAP_URI_AUTO_BIN2("/getblocks_by_height.bin", on_get_blocks_by_height, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT) MAP_URI_AUTO_BIN2("/gethashes.bin", on_get_hashes, COMMAND_RPC_GET_HASHES_FAST) MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) - MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs, COMMAND_RPC_GET_OUTPUTS) + MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs_bin, COMMAND_RPC_GET_OUTPUTS_BIN) MAP_URI_AUTO_BIN2("/getrandom_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/is_key_image_spent", on_is_key_image_spent, COMMAND_RPC_IS_KEY_IMAGE_SPENT) @@ -91,13 +90,16 @@ namespace cryptonote MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted) + MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted) MAP_URI_AUTO_JON2("/get_transaction_pool", on_get_transaction_pool, COMMAND_RPC_GET_TRANSACTION_POOL) + MAP_URI_AUTO_JON2("/get_transaction_pool_hashes.bin", on_get_transaction_pool_hashes, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES) MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) - MAP_URI_AUTO_JON2_IF("/fast_exit", on_fast_exit, COMMAND_RPC_FAST_EXIT, !m_restricted) MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted) MAP_URI_AUTO_JON2_IF("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH, !m_restricted) MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted) + MAP_URI_AUTO_JON2("/get_outs", on_get_outs, COMMAND_RPC_GET_OUTPUTS) + MAP_URI_AUTO_JON2_IF("/update", on_update, COMMAND_RPC_UPDATE, !m_restricted) BEGIN_JSON_RPC_MAP("/json_rpc") MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) @@ -106,6 +108,7 @@ namespace cryptonote MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) MAP_JON_RPC_WE("getblockheaderbyhash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) MAP_JON_RPC_WE("getblockheaderbyheight", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) + MAP_JON_RPC_WE("getblockheadersrange", on_get_block_headers_range, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE) MAP_JON_RPC_WE("getblock", on_get_block, COMMAND_RPC_GET_BLOCK) MAP_JON_RPC_WE_IF("get_connections", on_get_connections, COMMAND_RPC_GET_CONNECTIONS, !m_restricted) MAP_JON_RPC_WE("get_info", on_get_info_json, COMMAND_RPC_GET_INFO) @@ -115,11 +118,16 @@ namespace cryptonote MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted) MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM) MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION) + MAP_JON_RPC_WE("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM) + MAP_JON_RPC_WE("get_fee_estimate", on_get_per_kb_fee_estimate, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE) + MAP_JON_RPC_WE_IF("get_alternate_chains",on_get_alternate_chains, COMMAND_RPC_GET_ALTERNATE_CHAINS, !m_restricted) + MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted) END_JSON_RPC_MAP() END_URI_MAP2() bool on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res); bool on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res); + bool on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res); bool on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res); bool on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res); bool on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res); @@ -129,6 +137,7 @@ namespace cryptonote bool on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res); bool on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res); bool on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + bool on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res); bool on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res); bool on_get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res); bool on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res); @@ -136,12 +145,14 @@ namespace cryptonote bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res); bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res); bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res); + bool on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res); bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res); + bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res); bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res); - bool on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res); bool on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res); bool on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res); bool on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res); + bool on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res); //json_rpc bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res); @@ -151,6 +162,7 @@ namespace cryptonote bool on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp); bool on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp); bool on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp); + bool on_get_block_headers_range(const COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request& req, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response& res, epee::json_rpc::error& error_resp); bool on_get_block(const COMMAND_RPC_GET_BLOCK::request& req, COMMAND_RPC_GET_BLOCK::response& res, epee::json_rpc::error& error_resp); bool on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp); bool on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp); @@ -160,25 +172,25 @@ namespace cryptonote bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp); bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp); bool on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp); + bool on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp); + bool on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp); + bool on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp); + bool on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp); //----------------------- private: - - bool handle_command_line( - const boost::program_options::variables_map& vm - ); bool check_core_busy(); bool check_core_ready(); //utils uint64_t get_block_reward(const block& blk); - bool fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce); + bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response); core& m_core; nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p; - std::string m_port; - std::string m_bind_ip; bool m_testnet; bool m_restricted; }; } + +BOOST_CLASS_VERSION(nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >, 1); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 8cba53943..9bdadf0d1 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,8 +30,8 @@ #pragma once #include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/difficulty.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/difficulty.h" #include "crypto/hash.h" namespace cryptonote @@ -41,7 +41,17 @@ namespace cryptonote #define CORE_RPC_STATUS_BUSY "BUSY" #define CORE_RPC_STATUS_NOT_MINING "NOT MINING" -#define CORE_RPC_VERSION 3 +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures CORE_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given daemon without having to know in +// advance which version they will stop working with +// Don't go over 32767 for any of these +#define CORE_RPC_VERSION_MAJOR 1 +#define CORE_RPC_VERSION_MINOR 10 +#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) +#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) struct COMMAND_RPC_GET_HEIGHT { @@ -70,9 +80,11 @@ namespace cryptonote { std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; + bool prune; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) + KV_SERIALIZE(prune) END_KV_SERIALIZE_MAP() }; @@ -112,6 +124,28 @@ namespace cryptonote }; }; + struct COMMAND_RPC_GET_BLOCKS_BY_HEIGHT + { + struct request + { + std::vector<uint64_t> heights; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(heights) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<block_complete_entry> blocks; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(blocks) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HASHES_FAST { @@ -162,6 +196,7 @@ namespace cryptonote std::string as_json; bool in_pool; uint64_t block_height; + std::vector<uint64_t> output_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -169,6 +204,7 @@ namespace cryptonote KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) KV_SERIALIZE(block_height) + KV_SERIALIZE(output_indices) END_KV_SERIALIZE_MAP() }; @@ -291,22 +327,22 @@ namespace cryptonote }; }; //----------------------------------------------- - struct COMMAND_RPC_GET_OUTPUTS + struct get_outputs_out { - struct out - { - uint64_t amount; - uint64_t index; + uint64_t amount; + uint64_t index; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(index) - END_KV_SERIALIZE_MAP() - }; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(index) + END_KV_SERIALIZE_MAP() + }; + struct COMMAND_RPC_GET_OUTPUTS_BIN + { struct request { - std::vector<out> outputs; + std::vector<get_outputs_out> outputs; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(outputs) @@ -318,11 +354,55 @@ namespace cryptonote crypto::public_key key; rct::key mask; bool unlocked; + uint64_t height; + crypto::hash txid; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(key) KV_SERIALIZE_VAL_POD_AS_BLOB(mask) KV_SERIALIZE(unlocked) + KV_SERIALIZE(height) + KV_SERIALIZE_VAL_POD_AS_BLOB(txid) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<outkey> outs; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outs) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; + //----------------------------------------------- + struct COMMAND_RPC_GET_OUTPUTS + { + struct request + { + std::vector<get_outputs_out> outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs) + END_KV_SERIALIZE_MAP() + }; + + struct outkey + { + std::string key; + std::string mask; + bool unlocked; + uint64_t height; + std::string txid; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(key) + KV_SERIALIZE(mask) + KV_SERIALIZE(unlocked) + KV_SERIALIZE(height) + KV_SERIALIZE(txid) END_KV_SERIALIZE_MAP() }; @@ -422,10 +502,14 @@ namespace cryptonote { std::string miner_address; uint64_t threads_count; + bool do_background_mining; + bool ignore_battery; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(miner_address) KV_SERIALIZE(threads_count) + KV_SERIALIZE(do_background_mining) + KV_SERIALIZE(ignore_battery) END_KV_SERIALIZE_MAP() }; @@ -464,6 +548,9 @@ namespace cryptonote uint64_t grey_peerlist_size; bool testnet; std::string top_block_hash; + uint64_t cumulative_difficulty; + uint64_t block_size_limit; + uint64_t start_time; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -480,6 +567,9 @@ namespace cryptonote KV_SERIALIZE(grey_peerlist_size) KV_SERIALIZE(testnet) KV_SERIALIZE(top_block_hash) + KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(block_size_limit) + KV_SERIALIZE(start_time) END_KV_SERIALIZE_MAP() }; }; @@ -524,6 +614,7 @@ namespace cryptonote uint64_t speed; uint32_t threads_count; std::string address; + bool is_background_mining_enabled; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -531,6 +622,7 @@ namespace cryptonote KV_SERIALIZE(speed) KV_SERIALIZE(threads_count) KV_SERIALIZE(address) + KV_SERIALIZE(is_background_mining_enabled) END_KV_SERIALIZE_MAP() }; }; @@ -600,6 +692,7 @@ namespace cryptonote uint64_t difficulty; uint64_t height; uint64_t reserved_offset; + uint64_t expected_reward; std::string prev_hash; blobdata blocktemplate_blob; blobdata blockhashing_blob; @@ -609,6 +702,7 @@ namespace cryptonote KV_SERIALIZE(difficulty) KV_SERIALIZE(height) KV_SERIALIZE(reserved_offset) + KV_SERIALIZE(expected_reward) KV_SERIALIZE(prev_hash) KV_SERIALIZE(blocktemplate_blob) KV_SERIALIZE(blockhashing_blob) @@ -631,7 +725,7 @@ namespace cryptonote }; }; - struct block_header_responce + struct block_header_response { uint8_t major_version; uint8_t minor_version; @@ -644,6 +738,8 @@ namespace cryptonote std::string hash; difficulty_type difficulty; uint64_t reward; + uint64_t block_size; + uint64_t num_txes; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(major_version) @@ -657,6 +753,8 @@ namespace cryptonote KV_SERIALIZE(hash) KV_SERIALIZE(difficulty) KV_SERIALIZE(reward) + KV_SERIALIZE(block_size) + KV_SERIALIZE(num_txes) END_KV_SERIALIZE_MAP() }; @@ -671,7 +769,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -695,7 +793,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -719,7 +817,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) @@ -745,7 +843,7 @@ namespace cryptonote struct response { std::string status; - block_header_responce block_header; + block_header_response block_header; std::vector<std::string> tx_hashes; std::string blob; std::string json; @@ -843,6 +941,26 @@ namespace cryptonote }; }; + struct COMMAND_RPC_SET_LOG_CATEGORIES + { + struct request + { + std::string categories; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(categories) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; + struct tx_info { std::string id_hash; @@ -855,6 +973,9 @@ namespace cryptonote uint64_t last_failed_height; std::string last_failed_id_hash; uint64_t receive_time; + bool relayed; + uint64_t last_relayed_time; + bool do_not_relay; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(id_hash) @@ -867,6 +988,9 @@ namespace cryptonote KV_SERIALIZE(last_failed_height) KV_SERIALIZE(last_failed_id_hash) KV_SERIALIZE(receive_time) + KV_SERIALIZE(relayed) + KV_SERIALIZE(last_relayed_time) + KV_SERIALIZE(do_not_relay) END_KV_SERIALIZE_MAP() }; @@ -903,6 +1027,26 @@ namespace cryptonote }; }; + struct COMMAND_RPC_GET_TRANSACTION_POOL_HASHES + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::vector<crypto::hash> tx_hashes; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(tx_hashes) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_CONNECTIONS { struct request @@ -940,7 +1084,7 @@ namespace cryptonote struct response { std::string status; - std::vector<block_header_responce> headers; + std::vector<block_header_response> headers; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -1172,26 +1316,33 @@ namespace cryptonote uint64_t min_count; uint64_t max_count; bool unlocked; + uint64_t recent_cutoff; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amounts); KV_SERIALIZE(min_count); KV_SERIALIZE(max_count); KV_SERIALIZE(unlocked); + KV_SERIALIZE(recent_cutoff); END_KV_SERIALIZE_MAP() }; struct entry { uint64_t amount; - uint64_t instances; + uint64_t total_instances; + uint64_t unlocked_instances; + uint64_t recent_instances; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount); - KV_SERIALIZE(instances); + KV_SERIALIZE(total_instances); + KV_SERIALIZE(unlocked_instances); + KV_SERIALIZE(recent_instances); END_KV_SERIALIZE_MAP() - entry(uint64_t amount, uint64_t instances): amount(amount), instances(instances) {} + entry(uint64_t amount, uint64_t total_instances, uint64_t unlocked_instances, uint64_t recent_instances): + amount(amount), total_instances(total_instances), unlocked_instances(unlocked_instances), recent_instances(recent_instances) {} entry() {} }; @@ -1226,5 +1377,145 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; -} + struct COMMAND_RPC_GET_COINBASE_TX_SUM + { + struct request + { + uint64_t height; + uint64_t count; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height); + KV_SERIALIZE(count); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t emission_amount; + uint64_t fee_amount; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(emission_amount) + KV_SERIALIZE(fee_amount) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE + { + struct request + { + uint64_t grace_blocks; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(grace_blocks) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t fee; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(fee) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_ALTERNATE_CHAINS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct chain_info + { + std::string block_hash; + uint64_t height; + uint64_t length; + uint64_t difficulty; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(block_hash) + KV_SERIALIZE(height) + KV_SERIALIZE(length) + KV_SERIALIZE(difficulty) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::list<chain_info> chains; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(chains) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_UPDATE + { + struct request + { + std::string command; + std::string path; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(command); + KV_SERIALIZE(path); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + bool update; + std::string version; + std::string user_uri; + std::string auto_uri; + std::string hash; + std::string path; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(update) + KV_SERIALIZE(version) + KV_SERIALIZE(user_uri) + KV_SERIALIZE(auto_uri) + KV_SERIALIZE(hash) + KV_SERIALIZE(path) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_RELAY_TX + { + struct request + { + std::list<std::string> txids; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txids) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; +} diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index 4ac48a1c1..269cce2b1 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp new file mode 100644 index 000000000..4435f74d1 --- /dev/null +++ b/src/rpc/rpc_args.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#include "rpc_args.h" + +#include <boost/asio/ip/address.hpp> +#include "common/command_line.h" +#include "common/i18n.h" + +namespace cryptonote +{ + rpc_args::descriptors::descriptors() + : rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify ip to bind rpc server"), "127.0.0.1"}) + , rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true}) + , confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")}) + {} + + const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); } + + void rpc_args::init_options(boost::program_options::options_description& desc) + { + const descriptors arg{}; + command_line::add_arg(desc, arg.rpc_bind_ip); + command_line::add_arg(desc, arg.rpc_login); + command_line::add_arg(desc, arg.confirm_external_bind); + } + + boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm) + { + const descriptors arg{}; + rpc_args config{}; + + config.bind_ip = command_line::get_arg(vm, arg.rpc_bind_ip); + if (!config.bind_ip.empty()) + { + // always parse IP here for error consistency + boost::system::error_code ec{}; + const auto parsed_ip = boost::asio::ip::address::from_string(config.bind_ip, ec); + if (ec) + { + LOG_ERROR(tr("Invalid IP address given for --") << arg.rpc_bind_ip.name); + return boost::none; + } + + if (!parsed_ip.is_loopback() && !command_line::get_arg(vm, arg.confirm_external_bind)) + { + LOG_ERROR( + "--" << arg.rpc_bind_ip.name << + tr(" permits inbound unencrypted external connections. Consider SSH tunnel or SSL proxy instead. Override with --") << + arg.confirm_external_bind.name + ); + return boost::none; + } + } + + if (command_line::has_arg(vm, arg.rpc_login)) + { + config.login = tools::login::parse(command_line::get_arg(vm, arg.rpc_login), true, "RPC server password"); + if (!config.login) + return boost::none; + + if (config.login->username.empty()) + { + LOG_ERROR(tr("Username specified with --") << arg.rpc_login.name << tr(" cannot be empty")); + return boost::none; + } + } + + return {std::move(config)}; + } +} diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h new file mode 100644 index 000000000..d6e7bab07 --- /dev/null +++ b/src/rpc/rpc_args.h @@ -0,0 +1,67 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#pragma once + +#include <boost/optional/optional.hpp> +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> +#include <string> + +#include "common/command_line.h" +#include "common/password.h" + +namespace cryptonote +{ + //! Processes command line arguments related to server-side RPC + struct rpc_args + { + // non-static construction prevents initialization order issues + struct descriptors + { + descriptors(); + descriptors(const descriptors&) = delete; + descriptors(descriptors&&) = delete; + descriptors& operator=(const descriptors&) = delete; + descriptors& operator=(descriptors&&) = delete; + + const command_line::arg_descriptor<std::string> rpc_bind_ip; + const command_line::arg_descriptor<std::string> rpc_login; + const command_line::arg_descriptor<bool> confirm_external_bind; + }; + + static const char* tr(const char* str); + static void init_options(boost::program_options::options_description& desc); + + //! \return Arguments specified by user, or `boost::none` if error + static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm); + + std::string bind_ip; + boost::optional<tools::login> login; // currently `boost::none` if unspecified by user + }; +} diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 3a5343c9c..0a267b081 100644 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/binary_utils.h b/src/serialization/binary_utils.h index ab4a86c68..08eba41da 100644 --- a/src/serialization/binary_utils.h +++ b/src/serialization/binary_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 9a7e89c49..4213f2e58 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/debug_archive.h b/src/serialization/debug_archive.h index f46a7ee2b..c5365aab7 100644 --- a/src/serialization/debug_archive.h +++ b/src/serialization/debug_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index 629a37311..8f74e26b1 100644 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_utils.h b/src/serialization/json_utils.h index 5b7871072..32e7b69cf 100644 --- a/src/serialization/json_utils.h +++ b/src/serialization/json_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/list.h b/src/serialization/list.h new file mode 100644 index 000000000..d0fb72163 --- /dev/null +++ b/src/serialization/list.h @@ -0,0 +1,100 @@ +// Copyright (c) 2014-2015, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_list_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_list_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::list<T> &l) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + l.clear(); + + // very basic sanity check + if (ar.remaining_bytes() < cnt) { + ar.stream().setstate(std::ios::failbit); + return false; + } + + for (size_t i = 0; i < cnt; i++) { + if (i > 0) + ar.delimit_array(); + l.push_back(T()); + T &t = l.back(); + if (!::serialization::detail::serialize_list_element(ar, t)) + return false; + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::list<T> &l) +{ + size_t cnt = l.size(); + ar.begin_array(cnt); + for (typename std::list<T>::iterator i = l.begin(); i != l.end(); ++i) { + if (!ar.stream().good()) + return false; + if (i != l.begin()) + ar.delimit_array(); + if(!::serialization::detail::serialize_list_element(ar, *i)) + return false; + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} diff --git a/src/serialization/pair.h b/src/serialization/pair.h new file mode 100644 index 000000000..4913a74d6 --- /dev/null +++ b/src/serialization/pair.h @@ -0,0 +1,96 @@ +// Copyright (c) 2014-2015, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <memory> +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_pair_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_pair_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class F, class S> +inline bool do_serialize(Archive<false>& ar, std::pair<F,S>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + if (cnt != 2) + return false; + + if (!::serialization::detail::serialize_pair_element(ar, p.first)) + return false; + if (!ar.stream().good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_pair_element(ar, p.second)) + return false; + if (!ar.stream().good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class F, class S> +inline bool do_serialize(Archive<true>& ar, std::pair<F,S>& p) +{ + ar.begin_array(2); + if (!ar.stream().good()) + return false; + if(!::serialization::detail::serialize_pair_element(ar, p.first)) + return false; + if (!ar.stream().good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_pair_element(ar, p.second)) + return false; + if (!ar.stream().good()) + return false; + ar.end_array(); + return true; +} + diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 177cdf33a..639240820 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -41,6 +41,7 @@ #pragma once #include <vector> +#include <list> #include <string> #include <boost/type_traits/is_integral.hpp> #include <boost/type_traits/integral_constant.hpp> @@ -59,6 +60,16 @@ struct is_blob_type { typedef boost::false_type type; }; template <class T> struct has_free_serializer { typedef boost::true_type type; }; +/*! \struct is_pair_type + * + * \brief a descriptor for dispatching serialize + */ +template <class T> +struct is_pair_type { typedef boost::false_type type; }; + +template<typename F, typename S> +struct is_pair_type<std::pair<F,S>> { typedef boost::true_type type; }; + /*! \struct serializer * * \brief ... wouldn't a class be better? @@ -75,20 +86,26 @@ struct has_free_serializer { typedef boost::true_type type; }; template <class Archive, class T> struct serializer{ static bool serialize(Archive &ar, T &v) { - return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type()); + return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type(), typename is_pair_type<T>::type()); } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type) { + template<typename A> + static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type, A a) { ar.serialize_blob(&v, sizeof(v)); return true; } - static bool serialize(Archive &ar, T &v, boost::true_type, boost::false_type) { + template<typename A> + static bool serialize(Archive &ar, T &v, boost::true_type, boost::false_type, A a) { ar.serialize_int(v); return true; } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type) { + static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::false_type) { //serialize_custom(ar, v, typename has_free_serializer<T>::type()); return v.do_serialize(ar); } + static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::true_type) { + //serialize_custom(ar, v, typename has_free_serializer<T>::type()); + return do_serialize(ar, v); + } static void serialize_custom(Archive &ar, T &v, boost::true_type) { } }; @@ -328,3 +345,5 @@ namespace serialization { #include "string.h" #include "vector.h" +#include "list.h" +#include "pair.h" diff --git a/src/serialization/string.h b/src/serialization/string.h index 2a5228a3f..b94f43dd8 100644 --- a/src/serialization/string.h +++ b/src/serialization/string.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/variant.h b/src/serialization/variant.h index a2cce9fa1..9048e2963 100644 --- a/src/serialization/variant.h +++ b/src/serialization/variant.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/serialization/vector.h b/src/serialization/vector.h index 7f2bc78ba..598cfb92e 100644 --- a/src/serialization/vector.h +++ b/src/serialization/vector.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index 5745781ef..18a8bac68 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -27,18 +27,16 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(simplewallet_sources - simplewallet.cpp - password_container.cpp) + simplewallet.cpp) set(simplewallet_headers) set(simplewallet_private_headers - simplewallet.h - password_container.h) + simplewallet.h) -bitmonero_private_headers(simplewallet +monero_private_headers(simplewallet ${simplewallet_private_headers}) -bitmonero_add_executable(simplewallet +monero_add_executable(simplewallet ${simplewallet_sources} ${simplewallet_headers} ${simplewallet_private_headers}) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 279a5fa41..6c2df4b22 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -46,45 +46,36 @@ #include "common/i18n.h" #include "common/command_line.h" #include "common/util.h" +#include "common/dns_utils.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "simplewallet.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/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" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" #include "common/json_util.h" #include "ringct/rctSigs.h" +#include "wallet/wallet_args.h" #include <stdexcept> -#if defined(WIN32) -#include <crtdbg.h> -#endif - using namespace std; using namespace epee; using namespace cryptonote; using boost::lexical_cast; namespace po = boost::program_options; -namespace bf = boost::filesystem; typedef cryptonote::simple_wallet sw; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.simplewallet" + #define EXTENDED_LOGS_FILE "wallet_details.log" #define DEFAULT_MIX 4 -#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\001" - -// workaround for a suspected bug in pthread/kernel on MacOS X -#ifdef __APPLE__ -#define DEFAULT_MAX_CONCURRENCY 1 -#else -#define DEFAULT_MAX_CONCURRENCY 0 -#endif +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ @@ -105,28 +96,20 @@ typedef cryptonote::simple_wallet sw; enum TransferType { TransferOriginal, TransferNew, + TransferLocked, }; namespace { - const command_line::arg_descriptor<std::string> arg_wallet_file = {"wallet-file", sw::tr("Use wallet <arg>"), ""}; + const auto allowed_priority_strings = {"default", "unimportant", "normal", "elevated", "priority"}; + const auto arg_wallet_file = wallet_args::arg_wallet_file(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; - const command_line::arg_descriptor<std::string> arg_generate_from_json = {"generate-from-json", sw::tr("Generate wallet from JSON format file"), ""}; - const command_line::arg_descriptor<std::string> arg_daemon_address = {"daemon-address", sw::tr("Use daemon instance at <host>:<port>"), ""}; - const command_line::arg_descriptor<std::string> arg_daemon_host = {"daemon-host", sw::tr("Use daemon instance at host <arg> instead of localhost"), ""}; - const command_line::arg_descriptor<std::string> arg_password = {"password", sw::tr("Wallet password"), "", true}; - const command_line::arg_descriptor<std::string> arg_password_file = {"password-file", sw::tr("Wallet password file"), "", true}; + const auto arg_generate_from_json = wallet_args::arg_generate_from_json(); const command_line::arg_descriptor<std::string> arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false}; - const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", sw::tr("Use daemon instance at port <arg> instead of 18081"), 0}; - const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", LOG_LEVEL_0}; - const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", "Max number of threads to use for a parallel job", DEFAULT_MAX_CONCURRENCY}; - const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", sw::tr("Specify log file"), ""}; - const command_line::arg_descriptor<bool> arg_testnet = {"testnet", sw::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; - const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", sw::tr("Restricts RPC to view-only commands"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; @@ -157,8 +140,8 @@ namespace 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) + message_writer(console_colors color = console_color_default, bool bright = false, + std::string&& prefix = std::string(), el::Level log_level = el::Level::Info) : m_flush(true) , m_color(color) , m_bright(bright) @@ -194,17 +177,17 @@ namespace { m_flush = false; - LOG_PRINT(m_oss.str(), m_log_level); + MCLOG(m_log_level, "global", m_oss.str()); - if (epee::log_space::console_color_default == m_color) + if (console_color_default == m_color) { std::cout << m_oss.str(); } else { - epee::log_space::set_console_color(m_color, m_bright); + set_console_color(m_color, m_bright); std::cout << m_oss.str(); - epee::log_space::reset_console_color(); + reset_console_color(); } std::cout << std::endl; } @@ -218,32 +201,32 @@ namespace private: bool m_flush; std::stringstream m_oss; - epee::log_space::console_colors m_color; + console_colors m_color; bool m_bright; - int m_log_level; + el::Level 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); + return message_writer(color ? console_color_green : console_color_default, false, std::string(), el::Level::Info); } message_writer fail_msg_writer() { - return message_writer(epee::log_space::console_color_red, true, sw::tr("Error: "), LOG_LEVEL_0); + return message_writer(console_color_red, true, sw::tr("Error: "), el::Level::Error); } - bool is_it_true(std::string s) + bool is_it_true(const std::string& s) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - if (s == "true") - return true; - if (s == "1") + if (s == "1" || command_line::is_yes(s)) return true; - if (s == "y" || s == "yes") + + boost::algorithm::is_iequal ignore_case{}; + if (boost::algorithm::equals("true", s, ignore_case)) return true; - if (s == sw::tr("yes")) + if (boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case)) return true; + return false; } @@ -270,7 +253,7 @@ namespace return true; } } - fail_msg_writer() << tr("failed to parse refresh type"); + fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse refresh type"); return false; } @@ -284,6 +267,10 @@ namespace return "invalid"; } + std::string get_version_string(uint32_t version) + { + return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff); + } } @@ -300,16 +287,25 @@ std::string simple_wallet::get_commands_str() bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } // don't log - std::cout << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << std::endl; + std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << std::endl; + std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key) << std::endl; return true; } bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and has no spend key"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } // don't log - std::cout << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key) << std::endl; + std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key) << std::endl; + std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) << std::endl; return true; } @@ -324,6 +320,7 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (m_wallet->is_deterministic()) { if (m_wallet->get_seed_language().empty()) @@ -350,7 +347,6 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -362,92 +358,87 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s return true; } - tools::password_container pwd_container(m_wallet_file.empty()); - success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + std::string mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return true; + + m_wallet->set_seed_language(std::move(mnemonic_language)); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } + return true; +} - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) +bool simple_wallet::change_password(const std::vector<std::string> &args) +{ + const auto orig_pwd_container = get_and_verify_password(); + + if(orig_pwd_container == boost::none) { - fail_msg_writer() << tr("invalid password"); + fail_msg_writer() << tr("Your original password was incorrect."); return true; } - std::string mnemonic_language = get_mnemonic_language(); - if (mnemonic_language.empty()) + // prompts for a new password, pass true to verify the password + const auto pwd_container = tools::wallet2::password_prompt(true); + + try + { + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + m_wallet->store(); + } + catch (const tools::error::wallet_logic_error& e) + { + fail_msg_writer() << tr("Error with wallet rewrite: ") << e.what(); return true; - m_wallet->set_seed_language(mnemonic_language); - m_wallet->rewrite(m_wallet_file, pwd_container.password()); + } + return true; } bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; - if (m_wallet->watch_only()) - { - fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); - return true; - } - tools::password_container pwd_container(m_wallet_file.empty()); - success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + m_wallet->always_confirm_transfers(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } + return true; +} - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) +bool simple_wallet::set_print_ring_members(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("invalid password"); - return true; + m_wallet->print_ring_members(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } - - m_wallet->always_confirm_transfers(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container.password()); return true; } bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); return true; } - tools::password_container pwd_container(m_wallet_file.empty()); - success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + m_wallet->store_tx_info(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } - - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) - { - fail_msg_writer() << tr("invalid password"); - return true; - } - - m_wallet->store_tx_info(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container.password()); return true; } bool simple_wallet::set_default_mixin(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); @@ -469,25 +460,12 @@ bool simple_wallet::set_default_mixin(const std::vector<std::string> &args/* = s if (mixin == 0) mixin = DEFAULT_MIX; - tools::password_container pwd_container(m_wallet_file.empty()); - - success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + m_wallet->default_mixin(mixin); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } - - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) - { - fail_msg_writer() << tr("invalid password"); - return true; - } - - m_wallet->default_mixin(mixin); - m_wallet->rewrite(m_wallet_file, pwd_container.password()); return true; } catch(const boost::bad_lexical_cast &) @@ -504,18 +482,12 @@ bool simple_wallet::set_default_mixin(const std::vector<std::string> &args/* = s bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; int priority = 0; - if (m_wallet->watch_only()) - { - fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); - return true; - } try { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("priority must be 0, 1, 2, or 3 "); + fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4 "); return true; } if (args[1] == "0") @@ -525,36 +497,24 @@ bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* else { priority = boost::lexical_cast<int>(args[1]); - if (priority != 1 && priority != 2 && priority != 3) + if (priority < 1 || priority > 4) { - fail_msg_writer() << tr("priority must be 0, 1, 2, or 3"); + fail_msg_writer() << tr("priority must be 0, 1, 2, 3,or 4"); return true; } } - tools::password_container pwd_container(m_wallet_file.empty()); - success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + m_wallet->set_default_priority(priority); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } - - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) - { - fail_msg_writer() << tr("invalid password"); - return true; - } - - m_wallet->set_default_priority(priority); - m_wallet->rewrite(m_wallet_file, pwd_container.password()); return true; } catch(const boost::bad_lexical_cast &) { - fail_msg_writer() << tr("priority must be 0, 1, 2 or 3"); + fail_msg_writer() << tr("priority must be 0, 1, 2 3,or 4"); return true; } catch(...) @@ -566,64 +526,134 @@ bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* bool simple_wallet::set_auto_refresh(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - - tools::password_container pwd_container(m_wallet_file.empty()); - - bool success = pwd_container.read_password(); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); - return true; + const bool auto_refresh = is_it_true(args[1]); + m_wallet->auto_refresh(auto_refresh); + m_idle_mutex.lock(); + m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed); + m_idle_cond.notify_one(); + m_idle_mutex.unlock(); + + m_wallet->rewrite(m_wallet_file, pwd_container->password()); } + return true; +} - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) +bool simple_wallet::set_refresh_type(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + tools::wallet2::RefreshType refresh_type; + if (!parse_refresh_type(args[1], refresh_type)) { - fail_msg_writer() << tr("invalid password"); return true; } + + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->set_refresh_type(refresh_type); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} - bool auto_refresh = is_it_true(args[1]); - m_wallet->auto_refresh(auto_refresh); - m_idle_mutex.lock(); - m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed); - m_idle_cond.notify_one(); - m_idle_mutex.unlock(); - - m_wallet->rewrite(m_wallet_file, pwd_container.password()); +bool simple_wallet::set_confirm_missing_payment_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->confirm_missing_payment_id(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } return true; } -bool simple_wallet::set_refresh_type(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->ask_password(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} - tools::wallet2::RefreshType refresh_type; - if (!parse_refresh_type(args[1], refresh_type)) +bool simple_wallet::set_unit(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const std::string &unit = args[1]; + unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; + + if (unit == "monero") + decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; + else if (unit == "millinero") + decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 3; + else if (unit == "micronero") + decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 6; + else if (unit == "nanonero") + decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 9; + else if (unit == "piconero") + decimal_point = 0; + else { + fail_msg_writer() << tr("invalid unit"); return true; } - - tools::password_container pwd_container(m_wallet_file.empty()); - success = pwd_container.read_password(); - if (!success) + + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("failed to read wallet password"); + m_wallet->set_default_decimal_point(decimal_point); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_min_output_count(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + uint32_t count; + if (!string_tools::get_xtype_from_string(count, args[1])) + { + fail_msg_writer() << tr("invalid count: must be an unsigned integer"); return true; } - /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ - success = m_wallet->verify_password(pwd_container.password()); - if (!success) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) { - fail_msg_writer() << tr("invalid password"); + m_wallet->set_min_output_count(count); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_min_output_value(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + uint64_t value; + if (!cryptonote::parse_amount(value, args[1])) + { + fail_msg_writer() << tr("invalid value"); return true; } - m_wallet->set_refresh_type(refresh_type); + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->set_min_output_value(value); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} - m_wallet->rewrite(m_wallet_file, pwd_container.password()); +bool simple_wallet::set_merge_destinations(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->merge_destinations(is_it_true(args[1])); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } return true; } @@ -635,7 +665,6 @@ bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<st simple_wallet::simple_wallet() : m_allow_mismatched_daemon_version(false) - , m_daemon_port(0) , m_refresh_progress_reporter(*this) , m_idle_run(true) , m_auto_refresh_enabled(false) @@ -650,23 +679,30 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] - Show incoming transfers, all or filtered by availability")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); - m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer_original, but using a new transaction building algorithm")); + m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("Same as transfer, but using an older transaction building algorithm")); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [<priority>] [<mixin_count>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <mixin_count> is the number of extra inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [<mixin_count>] <addr> <amount> <lockblocks>(Number of blocks to lock the transaction for, max 1000000) [<payment_id>]")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); - m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); + m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [mixin] address [payment_id] - Send all unlocked balance to an address")); + m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [mixin] address [payment_id] - Send all unlocked outputs below the threshold to an address")); + m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [<mixin_count>] <amount> [payment_id] - Donate <amount> to the development team (donate.getmonero.org)")); + m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); + m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); + m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|<categories> - Change current log detail (level must be <0-4>)")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); + m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it")); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data")); m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), tr("Save a watch-only keys file")); m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key")); m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee")); + m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-output-count [n] - try to keep at least that many outputs of value at least min-output-value; min-output-value [n] - try to keep at least min-output-count outputs of at least that value - merge-destinations <1|0> - whether to merge multiple payments to the same destination address")); m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); - m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); + m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); + m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range)")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); @@ -675,6 +711,10 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status")); + m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet")); + m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); + m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), tr("Show information about a transfer to/from this address")); + m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change wallet password")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -684,15 +724,37 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) { success_msg_writer() << "seed = " << m_wallet->get_seed_language(); success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); + success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members(); success_msg_writer() << "store-tx-info = " << m_wallet->store_tx_info(); success_msg_writer() << "default-mixin = " << m_wallet->default_mixin(); success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh(); success_msg_writer() << "refresh-type = " << get_refresh_type_name(m_wallet->get_refresh_type()); success_msg_writer() << "priority = " << m_wallet->get_default_priority(); + success_msg_writer() << "confirm-missing-payment-id = " << m_wallet->confirm_missing_payment_id(); + success_msg_writer() << "ask-password = " << m_wallet->ask_password(); + success_msg_writer() << "unit = " << cryptonote::get_unit(m_wallet->get_default_decimal_point()); + success_msg_writer() << "min-outputs-count = " << m_wallet->get_min_output_count(); + success_msg_writer() << "min-outputs-value = " << cryptonote::print_money(m_wallet->get_min_output_value()); + success_msg_writer() << "merge-destinations = " << m_wallet->merge_destinations(); return true; } else { + +#define CHECK_SIMPLE_VARIABLE(name, f, help) do \ + if (args[0] == name) { \ + if (args.size() <= 1) \ + { \ + fail_msg_writer() << "set " << #name << ": " << tr("needs an argument") << " (" << help << ")"; \ + return true; \ + } \ + else \ + { \ + f(args); \ + return true; \ + } \ + } while(0) + if (args[0] == "seed") { if (args.size() == 1) @@ -702,103 +764,23 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else if (args[1] == "language") { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - seed_set_language(local_args); - return true; - } - } - else if (args[0] == "always-confirm-transfers") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set always-confirm-transfers: needs an argument (0 or 1)"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_always_confirm_transfers(local_args); - return true; - } - } - else if (args[0] == "store-tx-info") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set store-tx-info: needs an argument (0 or 1)"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_store_tx_info(local_args); - return true; - } - } - else if (args[0] == "default-mixin") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set default-mixin: needs an argument (integer >= 2)"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_default_mixin(local_args); - return true; - } - } - else if (args[0] == "auto-refresh") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set auto-refresh: needs an argument (0 or 1)"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_auto_refresh(local_args); - return true; - } - } - else if (args[0] == "refresh-type") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set refresh-type: needs an argument:") << - tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_refresh_type(local_args); - return true; - } - } - else if (args[0] == "priority") - { - if (args.size() <= 1) - { - fail_msg_writer() << tr("set priority: needs an argument: 0, 1, 2, or 3"); - return true; - } - else - { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_default_priority(local_args); + seed_set_language(args); return true; } } + CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("default-mixin", set_default_mixin, tr("integer >= 2")); + CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); + CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4")); + CHECK_SIMPLE_VARIABLE("confirm-missing-payment-id", set_confirm_missing_payment_id, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("ask-password", set_ask_password, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("unit", set_unit, tr("monero, millinero, micronero, nanop, piconero")); + CHECK_SIMPLE_VARIABLE("min-outputs-count", set_min_output_count, tr("unsigned integer")); + CHECK_SIMPLE_VARIABLE("min-outputs-value", set_min_output_value, tr("amount")); + CHECK_SIMPLE_VARIABLE("merge-destinations", set_merge_destinations, tr("0 or 1")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -809,86 +791,90 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(args.size() != 1) { - fail_msg_writer() << tr("usage: set_log <log_level_number_0-4>"); + fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>"); return true; } - uint16_t l = 0; - if(!epee::string_tools::get_xtype_from_string(l, args[0])) - { - fail_msg_writer() << tr("wrong number format, use: set_log <log_level_number_0-4>"); - return true; - } - if(LOG_LEVEL_4 < l) - { - fail_msg_writer() << tr("wrong number range, use: set_log <log_level_number_0-4>"); - return true; - } - - log_space::log_singletone::get_set_log_detalisation_level(true, l); + mlog_set_log(args[0].c_str()); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::ask_wallet_create_if_needed() { + LOG_PRINT_L3("simple_wallet::ask_wallet_create_if_needed() started"); std::string wallet_path; - - bool valid_path = false; - do { - wallet_path = command_line::input_line( - tr("Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" - "Wallet file name: ") - ); - if (std::cin.eof()) - { - return false; - } - valid_path = tools::wallet2::wallet_valid_path_format(wallet_path); - if (!valid_path) - { - fail_msg_writer() << tr("wallet file path not valid: ") << wallet_path; - } - } - while (!valid_path); - + std::string confirm_creation; + bool wallet_name_valid = false; bool keys_file_exists; bool wallet_file_exists; - tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists); - LOG_PRINT_L3("wallet_path: " << wallet_path << ""); - LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha - << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); - LOG_PRINT_L1("Loading wallet..."); - - // add logic to error out if new wallet requested but named wallet file exists - if (keys_file_exists || wallet_file_exists) - { - if (!m_generate_new.empty() || m_restoring) - { - fail_msg_writer() << tr("attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); - return false; - } - } + do{ + LOG_PRINT_L3("User asked to specify wallet file name."); + wallet_path = command_line::input_line( + tr(m_restoring ? "Specify a new wallet file name for your restored wallet (e.g., MyWallet).\n" + "Wallet file name (or Ctrl-C to quit): " : + "Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" + "Wallet file name (or Ctrl-C to quit): ") + ); + if(std::cin.eof()) + { + LOG_ERROR("Unexpected std::cin.eof() - Exited simple_wallet::ask_wallet_create_if_needed()"); + return false; + } + if(!tools::wallet2::wallet_valid_path_format(wallet_path)) + { + fail_msg_writer() << tr("Wallet name not valid. Please try again or use Ctrl-C to quit."); + wallet_name_valid = false; + } + else + { + tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists); + LOG_PRINT_L3("wallet_path: " << wallet_path << ""); + LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha + << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); - bool r; - if(keys_file_exists) - { - m_wallet_file=wallet_path; - r = true; - }else - { - if(!wallet_file_exists) - { - std::cout << tr("The wallet doesn't exist, generating new one") << std::endl; - m_generate_new = wallet_path; - r = true; - }else - { - fail_msg_writer() << tr("keys file not found: failed to open wallet: ") << "\"" << wallet_path << "\"."; - r = false; - } - } + if((keys_file_exists || wallet_file_exists) && (!m_generate_new.empty() || m_restoring)) + { + fail_msg_writer() << tr("Attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); + return false; + } + if(wallet_file_exists && keys_file_exists) //Yes wallet, yes keys + { + success_msg_writer() << tr("Wallet and key files found, loading..."); + m_wallet_file = wallet_path; + return true; + } + else if(!wallet_file_exists && keys_file_exists) //No wallet, yes keys + { + success_msg_writer() << tr("Key file found but not wallet file. Regenerating..."); + m_wallet_file = wallet_path; + return true; + } + else if(wallet_file_exists && !keys_file_exists) //Yes wallet, no keys + { + fail_msg_writer() << tr("Key file not found. Failed to open wallet: ") << "\"" << wallet_path << "\". Exiting."; + return false; + } + else if(!wallet_file_exists && !keys_file_exists) //No wallet, no keys + { + message_writer() << tr(m_restoring ? "Confirm wallet name: " : "No wallet found with that name. Confirm creation of new wallet named: ") << wallet_path; + confirm_creation = command_line::input_line(tr("(Y/Yes/N/No): ")); + if(std::cin.eof()) + { + LOG_ERROR("Unexpected std::cin.eof() - Exited simple_wallet::ask_wallet_create_if_needed()"); + return false; + } + if(command_line::is_yes(confirm_creation)) + { + success_msg_writer() << tr("Generating new wallet..."); + m_generate_new = wallet_path; + return true; + } + } + } + } while(!wallet_name_valid); - return r; + LOG_ERROR("Failed out of do-while loop in ask_wallet_create_if_needed()"); + return false; } /*! @@ -905,303 +891,21 @@ void simple_wallet::print_seed(std::string seed) // don't log std::cout << seed << std::endl; } - //---------------------------------------------------------------------------------------------------- -bool simple_wallet::get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container) -{ - // has_arg returns true even when the parameter is not passed ?? - const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); - if (!gfj.empty()) { - // will be in the json file, if any - return true; - } - - if (has_arg(vm, arg_password) && has_arg(vm, arg_password_file)) - { - fail_msg_writer() << tr("can't specify more than one of --password and --password-file"); - return false; - } - - if (command_line::has_arg(vm, arg_password)) - { - pwd_container.password(command_line::get_arg(vm, arg_password)); - return true; - } - - if (command_line::has_arg(vm, arg_password_file)) - { - std::string password; - bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_password_file), - password); - if (!r) - { - fail_msg_writer() << tr("the password file specified could not be read"); - return false; - } - - // Remove line breaks the user might have inserted - password.erase(std::remove(password.begin() - 1, password.end(), '\n'), password.end()); - password.erase(std::remove(password.end() - 1, password.end(), '\r'), password.end()); - pwd_container.password(password.c_str()); - return true; - } - - if (allow_entry) - { - //vm is already part of the password container class. just need to check vm for an already existing wallet - //here need to pass in variable map. This will indicate if the wallet already exists to the read password function - bool r = pwd_container.read_password(); - if (!r) - { - fail_msg_writer() << tr("failed to read wallet password"); - return false; - } - return true; - } - - fail_msg_writer() << tr("Wallet password not set."); - return false; -} - -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password) -{ - bool testnet = command_line::get_arg(vm, arg_testnet); - - std::string buf; - bool r = epee::file_io_utils::load_file_to_string(m_generate_from_json, buf); - if (!r) { - fail_msg_writer() << tr("Failed to load file ") << m_generate_from_json; - return false; - } - - rapidjson::Document json; - if (json.Parse(buf.c_str()).HasParseError()) { - fail_msg_writer() << tr("Failed to parse JSON"); - return false; - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true, 0); - const int current_version = 1; - if (field_version > current_version) { - fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; - return false; - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false, 0); - bool recover = field_scan_from_height_found; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false, std::string()); - password = field_password; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false, std::string()); - crypto::secret_key viewkey; - if (field_viewkey_found) - { - cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data)) - { - fail_msg_writer() << tr("failed to parse view key secret key"); - return false; - } - viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); - crypto::public_key pkey; - if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - fail_msg_writer() << tr("failed to verify view key secret key"); - return false; - } - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false, std::string()); - crypto::secret_key spendkey; - if (field_spendkey_found) - { - cryptonote::blobdata spendkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data)) - { - fail_msg_writer() << tr("failed to parse spend key secret key"); - return false; - } - spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); - crypto::public_key pkey; - if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - fail_msg_writer() << tr("failed to verify spend key secret key"); - return false; - } - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false, std::string()); - std::string old_language; - if (field_seed_found) - { - if (!crypto::ElectrumWords::words_to_bytes(field_seed, m_recovery_key, old_language)) - { - fail_msg_writer() << tr("Electrum-style word list failed verification"); - return false; - } - m_electrum_seed = field_seed; - m_restore_deterministic_wallet = true; - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); - - // compatibility checks - if (!field_seed_found && !field_viewkey_found) - { - fail_msg_writer() << tr("At least one of Electrum-style word list and private view key must be specified"); - return false; - } - if (field_seed_found && (field_viewkey_found || field_spendkey_found)) - { - fail_msg_writer() << tr("Both Electrum-style word list and private key(s) specified"); - return false; - } - - // if an address was given, we check keys against it, and deduce the spend - // public key if it was not given - if (field_address_found) - { - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address)) - { - fail_msg_writer() << tr("invalid address"); - return false; - } - if (field_viewkey_found) - { - crypto::public_key pkey; - if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - fail_msg_writer() << tr("failed to verify view key secret key"); - return false; - } - if (address.m_view_public_key != pkey) { - fail_msg_writer() << tr("view key does not match standard address"); - return false; - } - } - if (field_spendkey_found) - { - crypto::public_key pkey; - if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - fail_msg_writer() << tr("failed to verify spend key secret key"); - return false; - } - if (address.m_spend_public_key != pkey) { - fail_msg_writer() << tr("spend key does not match standard address"); - return false; - } - } - } - - m_wallet_file=field_filename; - - bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || - crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed)); - if (was_deprecated_wallet) { - fail_msg_writer() << tr("Cannot create deprecated wallets from JSON"); - return false; - } - - m_wallet.reset(new tools::wallet2(testnet)); - m_wallet->callback(this); - m_wallet->set_refresh_from_block_height(field_scan_from_height); - - try - { - if (!field_seed.empty()) - { - m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false); - } - else - { - cryptonote::account_public_address address; - if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { - fail_msg_writer() << tr("failed to verify view key secret key"); - return false; - } - - if (field_spendkey.empty()) - { - // if we have an addres but no spend key, we can deduce the spend public key - // from the address - if (field_address_found) - { - cryptonote::account_public_address address2; - bool has_payment_id; - crypto::hash8 new_payment_id; - get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address); - address.m_spend_public_key = address2.m_spend_public_key; - } - m_wallet->generate(m_wallet_file, password, address, viewkey); - } - else - { - if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { - fail_msg_writer() << tr("failed to verify spend key secret key"); - return false; - } - m_wallet->generate(m_wallet_file, password, address, spendkey, viewkey); - } - } - } - catch (const std::exception& e) - { - fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); - return false; - } - - wallet_file = m_wallet_file; - - return r; -} - -static bool is_local_daemon(const std::string &address) +static bool might_be_partial_seed(std::string words) { - // extract host - epee::net_utils::http::url_content u_c; - if (!epee::net_utils::parse_url(address, u_c)) - { - LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); - return false; - } - if (u_c.host.empty()) - { - LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); - return false; - } - - // resolve to IP - boost::asio::io_service io_service; - boost::asio::ip::tcp::resolver resolver(io_service); - boost::asio::ip::tcp::resolver::query query(u_c.host, ""); - boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); - while (i != boost::asio::ip::tcp::resolver::iterator()) - { - const boost::asio::ip::tcp::endpoint &ep = *i; - if (ep.address().is_loopback()) - return true; - ++i; - } + std::vector<std::string> seed; - return false; + boost::algorithm::trim(words); + boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); + return seed.size() < 24; } - //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { if (!handle_command_line(vm)) return false; - if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port) - { - fail_msg_writer() << tr("can't specify daemon host or port more than once"); - return false; - } - if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_json.empty()) > 1) { fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-json=\"jsonfilename\" and --generate-from-keys=\"wallet_name\""); @@ -1212,37 +916,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if(!ask_wallet_create_if_needed()) return false; } - bool testnet = command_line::get_arg(vm, arg_testnet); - - if (m_daemon_host.empty()) - m_daemon_host = "localhost"; - - if (!m_daemon_port) - { - m_daemon_port = testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT; - } - - if (m_daemon_address.empty()) - m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port); - - // set --trusted-daemon if local - try - { - if (is_local_daemon(m_daemon_address)) - { - LOG_PRINT_L1(tr("Daemon is local, assuming trusted")); - m_trusted_daemon = true; - } - } - catch (const std::exception &e) { } - tools::password_container pwd_container(m_wallet_file.empty()); //m_wallet_file will be empty at this point for new wallets - if (!cryptonote::simple_wallet::get_password(vm, true, pwd_container)) - return false; - if (!m_generate_new.empty() || m_restoring) { - if (m_wallet_file.empty()) m_wallet_file = m_generate_new; // alias for simplicity later - std::string old_language; // check for recover flag. if present, require electrum word list (only recovery option for now). if (m_restore_deterministic_wallet) @@ -1255,14 +930,20 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (m_electrum_seed.empty()) { - m_electrum_seed = command_line::input_line("Specify Electrum seed: "); - if (std::cin.eof()) - return false; - if (m_electrum_seed.empty()) + m_electrum_seed = ""; + do { - fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); - return false; - } + const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; + std::string electrum_seed = command_line::input_line(prompt); + if (std::cin.eof()) + return false; + if (electrum_seed.empty()) + { + fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); + return false; + } + m_electrum_seed += electrum_seed + " "; + } while (might_be_partial_seed(m_electrum_seed)); } if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language)) @@ -1271,24 +952,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } } - if (!m_restore_height && m_restoring) - { - std::string heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0): "); - if (std::cin.eof()) - return false; - if (heightstr.size()) - { - try { - m_restore_height = boost::lexical_cast<uint64_t>(heightstr); - } - catch (boost::bad_lexical_cast &) { - fail_msg_writer() << tr("bad m_restore_height parameter:") << " " << heightstr; - return false; - } - } - } if (!m_generate_from_view_key.empty()) { + m_wallet_file = m_generate_from_view_key; // parse address std::string address_string = command_line::input_line("Standard address: "); if (std::cin.eof()) @@ -1300,7 +966,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, address_string)) + if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string)) { fail_msg_writer() << tr("failed to parse address"); return false; @@ -1315,7 +981,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { fail_msg_writer() << tr("failed to parse view key secret key"); return false; @@ -1335,11 +1001,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - bool r = new_wallet(m_wallet_file, pwd_container.password(), address, viewkey, testnet); + bool r = new_wallet(vm, address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } else if (!m_generate_from_keys.empty()) { + m_wallet_file = m_generate_from_keys; // parse address std::string address_string = command_line::input_line("Standard address: "); if (std::cin.eof()) @@ -1351,7 +1018,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, address_string)) + if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string)) { fail_msg_writer() << tr("failed to parse address"); return false; @@ -1366,7 +1033,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } cryptonote::blobdata spendkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { fail_msg_writer() << tr("failed to parse spend key secret key"); return false; @@ -1382,7 +1049,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { fail_msg_writer() << tr("failed to parse view key secret key"); return false; @@ -1409,29 +1076,108 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("view key does not match standard address"); return false; } - - bool r = new_wallet(m_wallet_file, pwd_container.password(), address, spendkey, viewkey, testnet); + bool r = new_wallet(vm, address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } else if (!m_generate_from_json.empty()) { - std::string wallet_file, password; // we don't need to remember them - if (!generate_from_json(vm, wallet_file, password)) + m_wallet_file = m_generate_from_json; + m_wallet = tools::wallet2::make_from_json(vm, m_wallet_file); + if (!m_wallet) return false; } else { - bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet, - m_non_deterministic, testnet, old_language); + m_wallet_file = m_generate_new; + bool r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } + if (!m_restore_height && m_restoring) + { + uint32_t version; + bool connected = try_connect_to_daemon(false, &version); + while (true) + { + std::string heightstr; + if (!connected || version < MAKE_CORE_RPC_VERSION(1, 6)) + heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0): "); + else + heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0),\nor alternatively from specific date (YYYY-MM-DD): "); + if (std::cin.eof()) + return false; + if (heightstr.empty()) + { + m_restore_height = 0; + break; + } + try + { + m_restore_height = boost::lexical_cast<uint64_t>(heightstr); + break; + } + catch (const boost::bad_lexical_cast &) + { + if (!connected || version < MAKE_CORE_RPC_VERSION(1, 6)) + { + fail_msg_writer() << tr("bad m_restore_height parameter: ") << heightstr; + continue; + } + if (heightstr.size() != 10 || heightstr[4] != '-' || heightstr[7] != '-') + { + fail_msg_writer() << tr("date format must be YYYY-MM-DD"); + continue; + } + uint16_t year; + uint8_t month; // 1, 2, ..., 12 + uint8_t day; // 1, 2, ..., 31 + try + { + year = boost::lexical_cast<uint16_t>(heightstr.substr(0,4)); + // lexical_cast<uint8_t> won't work becasue uint8_t is treated as character type + month = boost::lexical_cast<uint16_t>(heightstr.substr(5,2)); + day = boost::lexical_cast<uint16_t>(heightstr.substr(8,2)); + m_restore_height = m_wallet->get_blockchain_height_by_date(year, month, day); + success_msg_writer() << tr("Restore height is: ") << m_restore_height; + std::string confirm = command_line::input_line(tr("Is this okay? (Y/Yes/N/No): ")); + if (std::cin.eof()) + return false; + if(command_line::is_yes(confirm)) + break; + } + catch (const boost::bad_lexical_cast &) + { + fail_msg_writer() << tr("bad m_restore_height parameter: ") << heightstr; + } + catch (const std::runtime_error& e) + { + fail_msg_writer() << e.what(); + } + } + } + m_wallet->set_refresh_from_block_height(m_restore_height); + } } else { - bool r = open_wallet(m_wallet_file, pwd_container.password(), testnet); + assert(!m_wallet_file.empty()); + bool r = open_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("failed to open account")); } + assert(m_wallet); + + // set --trusted-daemon if local + try + { + if (tools::is_local_address(m_wallet->get_daemon_address())) + { + MINFO(tr("Daemon is local, assuming trusted")); + m_trusted_daemon = true; + } + } + catch (const std::exception &e) { } + m_http_client.set_server(m_wallet->get_daemon_address(), m_wallet->get_daemon_login()); + m_wallet->callback(this); return true; } //---------------------------------------------------------------------------------------------------- @@ -1450,9 +1196,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_generate_from_view_key = command_line::get_arg(vm, arg_generate_from_view_key); m_generate_from_keys = command_line::get_arg(vm, arg_generate_from_keys); m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json); - 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); m_electrum_seed = command_line::get_arg(vm, arg_electrum_seed); m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); @@ -1467,22 +1210,23 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::try_connect_to_daemon(bool silent) +bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) { - bool same_version = false; - if (!m_wallet->check_connection(&same_version)) + uint32_t version_ = 0; + if (!version) + version = &version_; + if (!m_wallet->check_connection(version)) { if (!silent) - fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_daemon_address << ". " << + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << tr("Daemon either is not started or wrong port was passed. " "Please make sure daemon is running or restart the wallet with the correct daemon address."); return false; } - if (!m_allow_mismatched_daemon_version && !same_version) + if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) { if (!silent) - fail_msg_writer() << tr("Daemon uses a different RPC version that the wallet: ") << m_daemon_address << ". " << - tr("Either update one of them, or use --allow-mismatched-daemon-version."); + fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address(); return false; } return true; @@ -1522,18 +1266,38 @@ std::string simple_wallet::get_mnemonic_language() fail_msg_writer() << tr("invalid language choice passed. Please try again.\n"); } } - catch (std::exception &e) + catch (const std::exception &e) { fail_msg_writer() << tr("invalid language choice passed. Please try again.\n"); } } return language_list[language_number]; } +//---------------------------------------------------------------------------------------------------- +boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const +{ + auto pwd_container = tools::wallet2::password_prompt(m_wallet_file.empty()); + if (!pwd_container) + return boost::none; + if (!m_wallet->verify_password(pwd_container->password())) + { + fail_msg_writer() << tr("invalid password"); + return boost::none; + } + return pwd_container; +} //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key, - bool recover, bool two_random, bool testnet, const std::string &old_language) +bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, + const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language) { + auto rc = tools::wallet2::make_new(vm); + m_wallet = std::move(rc.first); + if (!m_wallet) + { + return false; + } + bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed)); @@ -1547,7 +1311,7 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string if (was_deprecated_wallet) { // The user had used an older version of the wallet with old style mnemonics. - message_writer(epee::log_space::console_color_green, false) << "\n" << tr("You had been using " + message_writer(console_color_green, false) << "\n" << tr("You had been using " "a deprecated version of the wallet. Please use the new seed that we provide.\n"); } mnemonic_language = get_mnemonic_language(); @@ -1555,11 +1319,6 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string return false; } - - m_wallet_file=wallet_file; - - m_wallet.reset(new tools::wallet2(testnet)); - m_wallet->callback(this); m_wallet->set_seed_language(mnemonic_language); // for a totally new account, we don't care about older blocks. @@ -1575,8 +1334,8 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string crypto::secret_key recovery_val; try { - recovery_val = m_wallet->generate(wallet_file, password, recovery_key, recover, two_random); - message_writer(epee::log_space::console_color_white, true) << tr("Generated new wallet: ") + recovery_val = m_wallet->generate(m_wallet_file, std::move(rc.second).password(), recovery_key, recover, two_random); + message_writer(console_color_white, true) << tr("Generated new wallet: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); std::cout << tr("View key: ") << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << ENDL; } @@ -1586,8 +1345,6 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string return false; } - m_wallet->init(m_daemon_address); - // convert rng value to electrum-style word list std::string electrum_words; @@ -1612,48 +1369,30 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password, const cryptonote::account_public_address& address, - const crypto::secret_key& viewkey, bool testnet) +bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, + const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, + const crypto::secret_key& viewkey) { - m_wallet_file=wallet_file; - - m_wallet.reset(new tools::wallet2(testnet)); - m_wallet->callback(this); - if (m_restore_height) - m_wallet->set_refresh_from_block_height(m_restore_height); - - try + auto rc = tools::wallet2::make_new(vm); + m_wallet = std::move(rc.first); + if (!m_wallet) { - m_wallet->generate(wallet_file, password, address, viewkey); - message_writer(epee::log_space::console_color_white, true) << tr("Generated new watch-only wallet: ") - << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); - std::cout << tr("View key: ") << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << ENDL; - } - catch (const std::exception& e) - { - fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); return false; } - - m_wallet->init(m_daemon_address); - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password, const cryptonote::account_public_address& address, - const crypto::secret_key& spendkey, const crypto::secret_key& viewkey, bool testnet) -{ - m_wallet_file=wallet_file; - - m_wallet.reset(new tools::wallet2(testnet)); - m_wallet->callback(this); if (m_restore_height) m_wallet->set_refresh_from_block_height(m_restore_height); try { - m_wallet->generate(wallet_file, password, address, spendkey, viewkey); - message_writer(epee::log_space::console_color_white, true) << tr("Generated new wallet: ") + if (spendkey) + { + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), address, *spendkey, viewkey); + } + else + { + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), address, viewkey); + } + message_writer(console_color_white, true) << tr("Generated new wallet: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); } catch (const std::exception& e) @@ -1662,27 +1401,29 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string return false; } - m_wallet->init(m_daemon_address); return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::open_wallet(const string &wallet_file, const std::string& password, bool testnet) +bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { - if (!tools::wallet2::wallet_valid_path_format(wallet_file)) + if (!tools::wallet2::wallet_valid_path_format(m_wallet_file)) { - fail_msg_writer() << tr("wallet file path not valid: ") << wallet_file; + fail_msg_writer() << tr("wallet file path not valid: ") << m_wallet_file; return false; } - - m_wallet_file=wallet_file; - m_wallet.reset(new tools::wallet2(testnet)); - m_wallet->callback(this); - + std::string password; try { - m_wallet->load(m_wallet_file, password); - message_writer(epee::log_space::console_color_white, true) << + auto rc = tools::wallet2::make_from_file(vm, m_wallet_file); + m_wallet = std::move(rc.first); + password = std::move(rc.second).password(); + if (!m_wallet) + { + return false; + } + + message_writer(console_color_white, true) << (m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); // If the wallet file is deprecated, we should ask for mnemonic language again and store @@ -1692,7 +1433,7 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa { if (m_wallet->is_deterministic()) { - message_writer(epee::log_space::console_color_green, false) << "\n" << tr("You had been using " + message_writer(console_color_green, false) << "\n" << tr("You had been using " "a deprecated version of the wallet. Please proceed to upgrade your wallet.\n"); std::string mnemonic_language = get_mnemonic_language(); if (mnemonic_language.empty()) @@ -1707,9 +1448,9 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa } else { - message_writer(epee::log_space::console_color_green, false) << "\n" << tr("You had been using " + message_writer(console_color_green, false) << "\n" << tr("You had been using " "a deprecated version of the wallet. Your wallet file format is being upgraded now.\n"); - m_wallet->rewrite(m_wallet_file, password); + m_wallet->rewrite(m_wallet_file, password); } } } @@ -1717,13 +1458,10 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa { fail_msg_writer() << tr("failed to load wallet: ") << e.what(); // only suggest removing cache if the password was actually correct - if (m_wallet->verify_password(password)) - fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % wallet_file; + if (m_wallet && m_wallet->verify_password(password)) + fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; return false; } - - m_wallet->init(m_daemon_address); - success_msg_writer() << "**********************************************************************\n" << tr("Use \"help\" command to see the list of available commands.\n") << @@ -1782,29 +1520,15 @@ bool simple_wallet::save(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - bool success = false; - tools::password_container pwd_container(m_wallet_file.empty()); + const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet")); - success = pwd_container.read_password(tr("Password for the new watch-only wallet")); - if (!success) + if (!pwd_container) { fail_msg_writer() << tr("failed to read wallet password"); return true; } - std::string password = pwd_container.password(); - success = pwd_container.read_password(tr("Enter new password again")); - if (!success) - { - fail_msg_writer() << tr("failed to read wallet password"); - return true; - } - if (password != pwd_container.password()) - { - fail_msg_writer() << tr("passwords do not match"); - return true; - } - m_wallet->write_watch_only_wallet(m_wallet_file, pwd_container.password()); + m_wallet->write_watch_only_wallet(m_wallet_file, pwd_container->password()); return true; } @@ -1820,16 +1544,16 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; - COMMAND_RPC_START_MINING::request req; + assert(m_wallet); + COMMAND_RPC_START_MINING::request req = AUTO_VAL_INIT(req); req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); bool ok = true; size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2)); - if (0 == args.size()) - { - req.threads_count = 1; - } - else if (1 == args.size()) + size_t arg_size = args.size(); + if(arg_size >= 3) req.ignore_battery = args[2] == "true"; + if(arg_size >= 2) req.do_background_mining = args[1] == "true"; + if(arg_size >= 1) { uint16_t num = 1; ok = string_tools::get_xtype_from_string(num, args[0]); @@ -1838,18 +1562,18 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) } else { - ok = false; + req.threads_count = 1; } if (!ok) { - fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>], " + fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>] [do_bg_mining] [ignore_battery], " "<number_of_threads> should be from 1 to ") << max_mining_threads_count; 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); + bool r = net_utils::invoke_http_json("/start_mining", req, res, m_http_client); std::string err = interpret_rpc_response(r, res.status); if (err.empty()) success_msg_writer() << tr("Mining started in daemon"); @@ -1863,9 +1587,10 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; + assert(m_wallet); 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); + bool r = net_utils::invoke_http_json("/stop_mining", req, res, m_http_client); std::string err = interpret_rpc_response(r, res.status); if (err.empty()) success_msg_writer() << tr("Mining stopped in daemon"); @@ -1879,9 +1604,10 @@ bool simple_wallet::save_bc(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; + assert(m_wallet); COMMAND_RPC_SAVE_BC::request req; COMMAND_RPC_SAVE_BC::response res; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/save_bc", req, res, m_http_client); + bool r = net_utils::invoke_http_json("/save_bc", req, res, m_http_client); std::string err = interpret_rpc_response(r, res.status); if (err.empty()) success_msg_writer() << tr("Blockchain saved"); @@ -1896,11 +1622,11 @@ 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, uint64_t amount) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) { - message_writer(epee::log_space::console_color_green, false) << "\r" << + message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << - tr("transaction ") << get_transaction_hash(tx) << ", " << + tr("transaction ") << txid << ", " << tr("received ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -1908,11 +1634,16 @@ void simple_wallet::on_money_received(uint64_t height, const cryptonote::transac m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) +void simple_wallet::on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) { - message_writer(epee::log_space::console_color_magenta, false) << "\r" << + // Not implemented in CLI wallet +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) +{ + message_writer(console_color_magenta, false) << "\r" << tr("Height ") << height << ", " << - tr("transaction ") << get_transaction_hash(spend_tx) << ", " << + tr("transaction ") << txid << ", " << tr("spent ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -1920,11 +1651,11 @@ void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transactio m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) +void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) { - message_writer(epee::log_space::console_color_red, true) << "\r" << + message_writer(console_color_red, true) << "\r" << tr("Height ") << height << ", " << - tr("transaction ") << get_transaction_hash(tx) << ", " << + tr("transaction ") << txid << ", " << tr("unsupported transaction format"); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -2036,18 +1767,23 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args bool filter = false; bool available = false; - if (!args.empty()) + bool verbose = false; + for (const auto &arg: args) { - if (args[0] == "available") + if (arg == "available") { filter = true; available = true; } - else if (args[0] == "unavailable") + else if (arg == "unavailable") { filter = true; available = false; } + else if (arg == "verbose") + { + verbose = true; + } } tools::wallet2::transfer_container transfers; @@ -2060,17 +1796,24 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (!transfers_found) { - message_writer() << boost::format("%21s%8s%12s%8s%16s%68s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id"); + std::string verbose_string; + if (verbose) + verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str(); + message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % verbose_string; transfers_found = true; } - message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << - boost::format("%21s%8s%12s%8s%16u%68s") % + std::string verbose_string; + if (verbose) + verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); + message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << + boost::format("%21s%8s%12s%8s%16u%68s%s") % print_money(td.amount()) % (td.m_spent ? tr("T") : tr("F")) % (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % (td.is_rct() ? tr("RingCT") : tr("-")) % td.m_global_output_index % - td.m_txid; + td.m_txid % + verbose_string; } } @@ -2146,9 +1889,14 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- uint64_t simple_wallet::get_daemon_blockchain_height(std::string& err) { + if (!m_wallet) + { + throw std::runtime_error("simple_wallet null wallet"); + } + 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); + bool r = net_utils::invoke_http_json("/getheight", req, res, m_http_client); err = interpret_rpc_response(r, res.status); return res.height; } @@ -2214,78 +1962,111 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id) +bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr) { - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), str)) + uint32_t version; + if (!try_connect_to_daemon(false, &version)) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return false; + } + // available for RPC version 1.4 or higher + if (version < MAKE_CORE_RPC_VERSION(1, 4)) + return true; + std::string err; + uint64_t blockchain_height = get_daemon_blockchain_height(err); + if (!err.empty()) + { + fail_msg_writer() << tr("failed to get blockchain height: ") << err; + return false; + } + // for each transaction + for (size_t n = 0; n < ptx_vector.size(); ++n) + { + const cryptonote::transaction& tx = ptx_vector[n].tx; + const tools::wallet2::tx_construction_data& construction_data = ptx_vector[n].construction_data; + ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx); + // for each input + std::vector<int> spent_key_height(tx.vin.size()); + std::vector<crypto::hash> spent_key_txid (tx.vin.size()); + for (size_t i = 0; i < tx.vin.size(); ++i) { - // if treating as an address fails, try as url - bool dnssec_ok = false; - std::string url = str; - - // attempt to get address from dns query - auto addresses_from_dns = tools::wallet2::addresses_from_url(url, dnssec_ok); - - // for now, move on only if one address found - if (addresses_from_dns.size() == 1) + if (tx.vin[i].type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key& in_key = boost::get<cryptonote::txin_to_key>(tx.vin[i]); + const cryptonote::tx_source_entry& source = construction_data.sources[i]; + ostr << boost::format(tr("\nInput %llu/%llu: amount=%s")) % (i + 1) % tx.vin.size() % print_money(source.amount); + // convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount + std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets); + // get block heights from which those ring member keys originated + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) { - if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), addresses_from_dns[0])) - { - // if it was an address, prompt user for confirmation. - // inform user of DNSSEC validation status as well. - - std::string dnssec_str; - if (dnssec_ok) - { - dnssec_str = tr("DNSSEC validation passed"); - } - else - { - dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); - } - std::stringstream prompt; - prompt << tr("For URL: ") << url - << ", " << dnssec_str << std::endl - << tr(" Monero Address = ") << addresses_from_dns[0] - << std::endl - << tr("Is this OK? (Y/n) ") - ; - - // prompt the user for confirmation given the dns query and dnssec status - std::string confirm_dns_ok = command_line::input_line(prompt.str()); - if (std::cin.eof()) - { - return false; - } - if (confirm_dns_ok != "Y" && confirm_dns_ok != "y" && confirm_dns_ok != "Yes" && confirm_dns_ok != "yes" - && confirm_dns_ok != tr("yes") && confirm_dns_ok != tr("no")) - { - fail_msg_writer() << tr("you have cancelled the transfer request"); - return false; - } - } - else + req.outputs[j].amount = in_key.amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r = net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client); + err = interpret_rpc_response(r, res.status); + if (!err.empty()) + { + fail_msg_writer() << tr("failed to get output: ") << err; + return false; + } + // make sure that returned block heights are less than blockchain height + for (auto& res_out : res.outs) + { + if (res_out.height >= blockchain_height) { - fail_msg_writer() << tr("failed to get a Monero address from: ") << url; + fail_msg_writer() << tr("output key's originating block height shouldn't be higher than the blockchain height"); return false; } } - else if (addresses_from_dns.size() > 1) + ostr << tr("\nOriginating block heights: "); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + ostr << tr(j == source.real_output ? " *" : " ") << res.outs[j].height; + spent_key_height[i] = res.outs[source.real_output].height; + spent_key_txid [i] = res.outs[source.real_output].txid; + // visualize the distribution, using the code by moneroexamples onion-monero-viewer + const uint64_t resolution = 79; + std::string ring_str(resolution, '_'); + for (size_t j = 0; j < absolute_offsets.size(); ++j) { - fail_msg_writer() << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url; - return false; + uint64_t pos = (res.outs[j].height * resolution) / blockchain_height; + ring_str[pos] = 'o'; } - else + uint64_t pos = (res.outs[source.real_output].height * resolution) / blockchain_height; + ring_str[pos] = '*'; + ostr << tr("\n|") << ring_str << tr("|\n"); + } + // warn if rings contain keys originating from the same tx or temporally very close block heights + bool are_keys_from_same_tx = false; + bool are_keys_from_close_height = false; + for (size_t i = 0; i < tx.vin.size(); ++i) { + for (size_t j = i + 1; j < tx.vin.size(); ++j) { - fail_msg_writer() << tr("wrong address: ") << url; - return false; + if (spent_key_txid[i] == spent_key_txid[j]) + are_keys_from_same_tx = true; + if (std::abs(spent_key_height[i] - spent_key_height[j]) < 5) + are_keys_from_close_height = true; } } - - return true; + if (are_keys_from_same_tx || are_keys_from_close_height) + { + ostr + << tr("\nWarning: Some input keys being spent are from ") + << tr(are_keys_from_same_tx ? "the same transaction" : "blocks that are temporally very close") + << tr(", which can break the anonymity of ring signature. Make sure this is intentional!"); + } + ostr << ENDL; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; @@ -2293,6 +2074,18 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri std::vector<std::string> local_args = args_; + int priority = 0; + if(local_args.size() > 0) { + auto priority_pos = std::find( + allowed_priority_strings.begin(), + allowed_priority_strings.end(), + local_args[0]); + if(priority_pos != allowed_priority_strings.end()) { + local_args.erase(local_args.begin()); + priority = std::distance(allowed_priority_strings.begin(), priority_pos); + } + } + size_t fake_outs_count; if(local_args.size() > 0) { if(!epee::string_tools::get_xtype_from_string(fake_outs_count, local_args[0])) @@ -2307,21 +2100,17 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } } - if(local_args.size() < 2) + const size_t min_args = (transfer_type == TransferLocked) ? 3 : 2; + if(local_args.size() < min_args) { fail_msg_writer() << tr("wrong number of arguments"); return true; } - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - std::vector<uint8_t> extra; bool payment_id_seen = false; - if (1 == local_args.size() % 2) + bool expect_even = (transfer_type == TransferLocked); + if ((expect_even ? 0 : 1) == local_args.size() % 2) { std::string payment_id_str = local_args.back(); local_args.pop_back(); @@ -2354,14 +2143,37 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri payment_id_seen = true; } + uint64_t locked_blocks = 0; + if (transfer_type == TransferLocked) + { + try + { + locked_blocks = boost::lexical_cast<uint64_t>(local_args.back()); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("bad locked_blocks parameter:") << " " << local_args.back(); + return true; + } + if (locked_blocks > 1000000) + { + fail_msg_writer() << tr("Locked blocks too high, max 1000000 (˜4 yrs)"); + return true; + } + local_args.pop_back(); + } + vector<cryptonote::tx_destination_entry> dsts; for (size_t i = 0; i < local_args.size(); i += 2) { cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if (!get_address_from_str(local_args[i], de.addr, has_payment_id, new_payment_id)) + if (!cryptonote::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i])) + { + fail_msg_writer() << tr("failed to parse address"); return true; + } if (has_payment_id) { @@ -2379,6 +2191,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); return true; } + payment_id_seen = true; } bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); @@ -2392,31 +2205,67 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri dsts.push_back(de); } + // prompt is there is no payment id and confirmation is required + if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) + { + std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + if (std::cin.eof()) + return true; + if (!command_line::is_yes(accepted)) + { + fail_msg_writer() << tr("transaction cancelled."); + + return true; + } + } + try { // figure out what tx will be necessary std::vector<tools::wallet2::pending_tx> ptx_vector; + uint64_t bc_height, unlock_block = 0; + std::string err; switch (transfer_type) { + case TransferLocked: + bc_height = get_daemon_blockchain_height(err); + if (!err.empty()) + { + fail_msg_writer() << tr("failed to get blockchain height: ") << err; + return true; + } + unlock_block = bc_height + locked_blocks; + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_trusted_daemon); + break; case TransferNew: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); break; default: LOG_ERROR("Unknown transfer method, using original"); case TransferOriginal: - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); break; } + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No outputs found, or daemon is not ready"); + return true; + } + // if more than one tx necessary, prompt user to confirm if (m_wallet->always_confirm_transfers() || ptx_vector.size() > 1) { + uint64_t total_sent = 0; uint64_t total_fee = 0; uint64_t dust_not_in_fee = 0; uint64_t dust_in_fee = 0; for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; + for (auto i: ptx_vector[n].selected_transfers) + total_sent += m_wallet->get_transfer_details(i).amount(); + total_sent -= ptx_vector[n].change_dts.amount + ptx_vector[n].fee; if (ptx_vector[n].dust_added_to_fee) dust_in_fee += ptx_vector[n].dust; @@ -2425,6 +2274,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } std::stringstream prompt; + prompt << boost::format(tr("Sending %s. ")) % print_money(total_sent); if (ptx_vector.size() > 1) { prompt << boost::format(tr("Your transaction needs to be split into %llu transactions. " @@ -2439,23 +2289,43 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (dust_in_fee != 0) prompt << boost::format(tr(", of which %s is dust from change")) % print_money(dust_in_fee); if (dust_not_in_fee != 0) prompt << tr(".") << ENDL << boost::format(tr("A total of %s from dust change will be sent to dust address")) % print_money(dust_not_in_fee); - prompt << tr(".") << ENDL << tr("Is this okay? (Y/Yes/N/No)"); + if (transfer_type == TransferLocked) + { + float days = locked_blocks / 720.0f; + prompt << boost::format(tr(".\nThis transaction will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % ((unsigned long long)unlock_block) % days; + } + if (m_wallet->print_ring_members()) + { + if (!print_ring_members(ptx_vector, prompt)) + return true; + } + prompt << ENDL << tr("Is this okay? (Y/Yes/N/No): "); std::string accepted = command_line::input_line(prompt.str()); if (std::cin.eof()) return true; - if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") + if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - // would like to return false, because no tx made, but everything else returns true - // and I don't know what returning false might adversely affect. *sigh* return true; } } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2478,18 +2348,25 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri LOG_ERROR("RPC error: " << e.to_string()); fail_msg_writer() << tr("RPC error: ") << e.what(); } - catch (const tools::error::get_random_outs_error&) + catch (const tools::error::get_random_outs_error &e) { - fail_msg_writer() << tr("failed to get random outputs to mix"); + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -2557,17 +2434,18 @@ bool simple_wallet::transfer_new(const std::vector<std::string> &args_) return transfer_main(TransferNew, args_); } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) +{ + return transfer_main(TransferLocked, args_); +} +//---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - LOCK_IDLE_SCOPE(); try { @@ -2585,39 +2463,46 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; - for (const auto &vin: ptx_vector[n].tx.vin) - { - if (vin.type() == typeid(txin_to_key)) - total_unmixable += boost::get<txin_to_key>(vin).amount; - } + for (auto i: ptx_vector[n].selected_transfers) + total_unmixable += m_wallet->get_transfer_details(i).amount(); } std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable); if (ptx_vector.size() > 1) { - prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % print_money(total_unmixable) % ((unsigned long long)ptx_vector.size()) % print_money(total_fee)).str(); } else { - prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % print_money(total_unmixable) % print_money(total_fee)).str(); } std::string accepted = command_line::input_line(prompt_str); if (std::cin.eof()) return true; - if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") + if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - // would like to return false, because no tx made, but everything else returns true - // and I don't know what returning false might adversely affect. *sigh* return true; } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2640,18 +2525,25 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) LOG_ERROR("RPC error: " << e.to_string()); fail_msg_writer() << tr("RPC error: ") << e.what(); } - catch (const tools::error::get_random_outs_error&) + catch (const tools::error::get_random_outs_error &e) { - fail_msg_writer() << tr("failed to get random outputs to mix"); + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -2709,17 +2601,12 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::sweep_all(const std::vector<std::string> &args_) +bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &args_) { + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; - if(m_wallet->watch_only()) - { - fail_msg_writer() << tr("this is a watch only wallet"); - return true; - } - std::vector<std::string> local_args = args_; size_t fake_outs_count; @@ -2780,8 +2667,11 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) bool has_payment_id; crypto::hash8 new_payment_id; cryptonote::account_public_address address; - if (!get_address_from_str(local_args[0], address, has_payment_id, new_payment_id)) + if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0])) + { + fail_msg_writer() << tr("failed to parse address"); return true; + } if (has_payment_id) { @@ -2799,16 +2689,31 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); return true; } + payment_id_seen = true; + } + + // prompt is there is no payment id and confirmation is required + if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) + { + std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + if (std::cin.eof()) + return true; + if (!command_line::is_yes(accepted)) + { + fail_msg_writer() << tr("transaction cancelled."); + + return true; + } } try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(address, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + auto ptx_vector = m_wallet->create_transactions_all(below, address, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); if (ptx_vector.empty()) { - fail_msg_writer() << tr("No outputs found"); + fail_msg_writer() << tr("No outputs found, or daemon is not ready"); return true; } @@ -2817,39 +2722,48 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; - for (const auto &vin: ptx_vector[n].tx.vin) - { - if (vin.type() == typeid(txin_to_key)) - total_sent += boost::get<txin_to_key>(vin).amount; - } + for (auto i: ptx_vector[n].selected_transfers) + total_sent += m_wallet->get_transfer_details(i).amount(); } - std::string prompt_str; + std::ostringstream prompt; + if (!print_ring_members(ptx_vector, prompt)) + return true; if (ptx_vector.size() > 1) { - prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % print_money(total_sent) % ((unsigned long long)ptx_vector.size()) % - print_money(total_fee)).str(); + print_money(total_fee); } else { - prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % print_money(total_sent) % - print_money(total_fee)).str(); + print_money(total_fee); } - std::string accepted = command_line::input_line(prompt_str); + std::string accepted = command_line::input_line(prompt.str()); if (std::cin.eof()) return true; - if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") + if (!command_line::is_yes(accepted)) { fail_msg_writer() << tr("transaction cancelled."); - // would like to return false, because no tx made, but everything else returns true - // and I don't know what returning false might adversely affect. *sigh* return true; } // actually commit the transactions - while (!ptx_vector.empty()) + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else while (!ptx_vector.empty()) { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); @@ -2872,18 +2786,25 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) LOG_ERROR("RPC error: " << e.to_string()); fail_msg_writer() << tr("RPC error: ") << e.what(); } - catch (const tools::error::get_random_outs_error&) + catch (const tools::error::get_random_outs_error &e) { - fail_msg_writer() << tr("failed to get random outputs to mix"); + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } catch (const tools::error::not_enough_money& e) { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees"); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -2941,6 +2862,316 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::sweep_all(const std::vector<std::string> &args_) +{ + return sweep_main(0, args_); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sweep_below(const std::vector<std::string> &args_) +{ + uint64_t below = 0; + if (args_.size() < 1) + { + fail_msg_writer() << tr("missing amount threshold"); + return true; + } + if (!cryptonote::parse_amount(below, args_[0])) + { + fail_msg_writer() << tr("invalid amount threshold"); + return true; + } + return sweep_main(below, std::vector<std::string>(++args_.begin(), args_.end())); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::donate(const std::vector<std::string> &args_) +{ + std::vector<std::string> local_args = args_; + if(local_args.empty() || local_args.size() > 3) + { + fail_msg_writer() << tr("wrong number of arguments"); + return true; + } + std::string mixin_str; + // Hardcode Monero's donation address (see #1447) + const std::string address_str = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; + std::string amount_str; + std::string payment_id_str; + // check payment id + crypto::hash payment_id; + crypto::hash8 payment_id8; + if (tools::wallet2::parse_long_payment_id (local_args.back(), payment_id ) || + tools::wallet2::parse_short_payment_id(local_args.back(), payment_id8)) + { + payment_id_str = local_args.back(); + local_args.pop_back(); + } + // check mixin + if (local_args.size() > 1) + { + mixin_str = local_args[0]; + local_args.erase(local_args.begin()); + } + amount_str = local_args[0]; + // refill args as necessary + local_args.clear(); + if (!mixin_str.empty()) + local_args.push_back(mixin_str); + local_args.push_back(address_str); + local_args.push_back(amount_str); + if (!payment_id_str.empty()) + local_args.push_back(payment_id_str); + message_writer() << tr("Donating ") << amount_str << " XMR to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A)."; + transfer_new(local_args); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message) +{ + // gather info to ask the user + uint64_t amount = 0, amount_to_dests = 0, change = 0; + size_t min_mixin = ~0; + std::unordered_map<std::string, uint64_t> dests; + const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + int first_known_non_zero_change_index = -1; + for (size_t n = 0; n < get_num_txes(); ++n) + { + const tools::wallet2::tx_construction_data &cd = get_tx(n); + for (size_t s = 0; s < cd.sources.size(); ++s) + { + amount += cd.sources[s].amount; + size_t mixin = cd.sources[s].outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address = get_account_address_as_str(m_wallet->testnet(), entry.addr); + std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address); + if (i == dests.end()) + dests.insert(std::make_pair(address, entry.amount)); + else + i->second += entry.amount; + amount_to_dests += entry.amount; + } + if (cd.change_dts.amount > 0) + { + std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); + if (it == dests.end()) + { + fail_msg_writer() << tr("Claimed change does not go to a paid address"); + return false; + } + if (it->second < cd.change_dts.amount) + { + fail_msg_writer() << tr("Claimed change is larger than payment to the change address"); + return false; + } + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + if (memcmp(&cd.change_dts.addr, &get_tx(first_known_non_zero_change_index).change_dts.addr, sizeof(cd.change_dts.addr))) + { + fail_msg_writer() << tr("Change goes to more than one address"); + return false; + } + } + change += cd.change_dts.amount; + it->second -= cd.change_dts.amount; + if (it->second == 0) + dests.erase(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); + } + } + std::string dest_string; + for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); ) + { + dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second) % i->first).str(); + ++i; + if (i != dests.end()) + dest_string += ", "; + } + if (dest_string.empty()) + dest_string = tr("with no destinations"); + + std::string change_string; + if (change > 0) + { + std::string address = get_account_address_as_str(m_wallet->testnet(), get_tx(0).change_dts.addr); + change_string += (boost::format(tr("%s change to %s")) % print_money(change) % address).str(); + } + else + change_string += tr("no change"); + + uint64_t fee = amount - amount_to_dests; + std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %sIs this okay? (Y/Yes/N/No): ")) % (unsigned long)get_num_txes() % print_money(amount) % print_money(fee) % dest_string % change_string % (unsigned long)min_mixin % extra_message).str(); + return command_line::is_yes(command_line::input_line(prompt_str)); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) +{ + std::string extra_message; + if (!txs.transfers.empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.size()).str(); + return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) +{ + std::string extra_message; + if (!txs.key_images.empty()) + extra_message = (boost::format("%u key images to import. ") % (unsigned)txs.key_images.size()).str(); + return accept_loaded_tx([&txs](){return txs.ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.ptx[n].construction_data;}, extra_message); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) +{ + if(m_wallet->watch_only()) + { + fail_msg_writer() << tr("This is a watch only wallet"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::vector<tools::wallet2::pending_tx> ptx; + try + { + bool r = m_wallet->sign_tx("unsigned_monero_tx", "signed_monero_tx", ptx, [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign transaction"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to sign transaction: ") << e.what(); + return true; + } + + std::string txids_as_text; + for (const auto &t: ptx) + { + if (!txids_as_text.empty()) + txids_as_text += (", "); + txids_as_text += epee::string_tools::pod_to_hex(get_transaction_hash(t.tx)); + } + success_msg_writer(true) << tr("Transaction successfully signed to file ") << "signed_monero_tx" << ", txid " << txids_as_text; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) +{ + if (!try_connect_to_daemon()) + return true; + + try + { + std::vector<tools::wallet2::pending_tx> ptx_vector; + bool r = m_wallet->load_tx("signed_monero_tx", ptx_vector, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load transaction from file"); + return true; + } + + // actually commit the transactions + while (!ptx_vector.empty()) + { + auto & ptx = ptx_vector.back(); + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx); + + // if no exception, remove element from vector + ptx_vector.pop_back(); + } + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try later"); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("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() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error &e) + { + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); + } + catch (const tools::error::not_enough_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; + for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("Failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; @@ -2949,12 +3180,13 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) fail_msg_writer() << tr("usage: get_tx_key <txid>"); return true; } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) { fail_msg_writer() << tr("failed to parse txid"); - return false; + return true; } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); @@ -2986,8 +3218,9 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) if (!try_connect_to_daemon()) return true; + assert(m_wallet); cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) { fail_msg_writer() << tr("failed to parse txid"); return true; @@ -3003,7 +3236,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) } crypto::secret_key tx_key; cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) { fail_msg_writer() << tr("failed to parse tx key"); return true; @@ -3013,7 +3246,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2])) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2])) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3022,7 +3255,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client) || + if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) { fail_msg_writer() << tr("failed to get transaction from daemon"); @@ -3255,8 +3488,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) m_wallet->get_payments_out(payments, min_height, max_height); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::confirmed_transfer_details &pd = i->second; - uint64_t fee = pd.m_amount_in - pd.m_amount_out; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + uint64_t fee = pd.m_amount_in - pd.m_amount_out; std::string dests; for (const auto &d: pd.m_dests) { if (!dests.empty()) @@ -3273,7 +3506,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) // print in and out sorted by height for (std::map<uint64_t, std::pair<bool, std::string>>::const_iterator i = output.begin(); i != output.end(); ++i) { - message_writer(i->second.first ? epee::log_space::console_color_green : epee::log_space::console_color_magenta, false) << + message_writer(i->second.first ? console_color_green : console_color_magenta, false) << boost::format("%8.8llu %6.6s %s") % ((unsigned long long)i->first) % (i->second.first ? tr("in") : tr("out")) % i->second.second; } @@ -3306,7 +3539,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; uint64_t amount = pd.m_amount_in; - uint64_t fee = amount - pd.m_amount_out - pd.m_change; + uint64_t fee = amount - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); @@ -3321,6 +3554,124 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) +{ + if(!args_.empty() && args_.size() != 2) { + fail_msg_writer() << tr("usage: unspent_outputs [<min_amount> <max_amount>]"); + return true; + } + uint64_t min_amount = 0; + uint64_t max_amount = std::numeric_limits<uint64_t>::max(); + if (args_.size() == 2) + { + if (!cryptonote::parse_amount(min_amount, args_[0]) || !cryptonote::parse_amount(max_amount, args_[1])) + { + fail_msg_writer() << tr("amount is wrong: ") << args_[0] << ' ' << args_[1] << + ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max()); + return true; + } + if (min_amount > max_amount) + { + fail_msg_writer() << tr("<min_amount> should be smaller than <max_amount>"); + return true; + } + } + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + if (transfers.empty()) + { + success_msg_writer() << "There is no unspent output in this wallet."; + return true; + } + std::map<uint64_t, tools::wallet2::transfer_container> amount_to_tds; + uint64_t min_height = std::numeric_limits<uint64_t>::max(); + uint64_t max_height = 0; + uint64_t found_min_amount = std::numeric_limits<uint64_t>::max(); + uint64_t found_max_amount = 0; + uint64_t count = 0; + for (const auto& td : transfers) + { + uint64_t amount = td.amount(); + if (td.m_spent || amount < min_amount || amount > max_amount) + continue; + amount_to_tds[amount].push_back(td); + if (min_height > td.m_block_height) min_height = td.m_block_height; + if (max_height < td.m_block_height) max_height = td.m_block_height; + if (found_min_amount > amount) found_min_amount = amount; + if (found_max_amount < amount) found_max_amount = amount; + ++count; + } + for (const auto& amount_tds : amount_to_tds) + { + auto& tds = amount_tds.second; + success_msg_writer() << tr("\nAmount: ") << print_money(amount_tds.first) << tr(", number of keys: ") << tds.size(); + for (size_t i = 0; i < tds.size(); ) + { + std::ostringstream oss; + for (size_t j = 0; j < 8 && i < tds.size(); ++i, ++j) + oss << tds[i].m_block_height << tr(" "); + success_msg_writer() << oss.str(); + } + } + success_msg_writer() + << tr("\nMin block height: ") << min_height + << tr("\nMax block height: ") << max_height + << tr("\nMin amount found: ") << print_money(found_min_amount) + << tr("\nMax amount found: ") << print_money(found_max_amount) + << tr("\nTotal count: ") << count; + const size_t histogram_height = 10; + const size_t histogram_width = 50; + double bin_size = (max_height - min_height + 1.0) / histogram_width; + size_t max_bin_count = 0; + std::vector<size_t> histogram(histogram_width, 0); + for (const auto& amount_tds : amount_to_tds) + { + for (auto& td : amount_tds.second) + { + uint64_t bin_index = (td.m_block_height - min_height + 1) / bin_size; + if (bin_index >= histogram_width) + bin_index = histogram_width - 1; + histogram[bin_index]++; + if (max_bin_count < histogram[bin_index]) + max_bin_count = histogram[bin_index]; + } + } + for (size_t x = 0; x < histogram_width; ++x) + { + double bin_count = histogram[x]; + if (max_bin_count > histogram_height) + bin_count *= histogram_height / (double)max_bin_count; + if (histogram[x] > 0 && bin_count < 1.0) + bin_count = 1.0; + histogram[x] = bin_count; + } + std::vector<std::string> histogram_line(histogram_height, std::string(histogram_width, ' ')); + for (size_t y = 0; y < histogram_height; ++y) + { + for (size_t x = 0; x < histogram_width; ++x) + { + if (y < histogram[x]) + histogram_line[y][x] = '*'; + } + } + double count_per_star = max_bin_count / (double)histogram_height; + if (count_per_star < 1) + count_per_star = 1; + success_msg_writer() + << tr("\nBin size: ") << bin_size + << tr("\nOutputs per *: ") << count_per_star; + ostringstream histogram_str; + histogram_str << tr("count\n ^\n"); + for (size_t y = histogram_height; y > 0; --y) + histogram_str << tr(" |") << histogram_line[y - 1] << tr("|\n"); + histogram_str + << tr(" +") << std::string(histogram_width, '-') << tr("+--> block height\n") + << tr(" ^") << std::string(histogram_width - 2, ' ') << tr("^\n") + << tr(" ") << min_height << std::string(histogram_width - 8, ' ') << max_height; + success_msg_writer() << histogram_str.str(); + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { return refresh_main(0, true); @@ -3365,7 +3716,7 @@ bool simple_wallet::run() m_idle_thread = boost::thread([&]{wallet_idle_thread();}); std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6); - message_writer(epee::log_space::console_color_green, false) << "Background refresh thread started"; + message_writer(console_color_green, false) << "Background refresh thread started"; return m_cmd_binder.run_handling(std::string("[") + tr("wallet") + " " + addr_start + "]: ", ""); } //---------------------------------------------------------------------------------------------------- @@ -3428,6 +3779,86 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + if (args.size() == 0) + { + } + else if (args.size() == 1 || (args[0] != "add" && args[0] != "delete")) + { + fail_msg_writer() << tr("usage: address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)]"); + return true; + } + else if (args[0] == "add") + { + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id8; + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1])) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + crypto::hash payment_id = null_hash; + size_t description_start = 2; + if (has_payment_id) + { + memcpy(payment_id.data, payment_id8.data, 8); + } + else if (!has_payment_id && args.size() >= 4 && args[2] == "pid") + { + if (tools::wallet2::parse_long_payment_id(args[3], payment_id)) + { + description_start += 2; + } + else if (tools::wallet2::parse_short_payment_id(args[3], payment_id8)) + { + memcpy(payment_id.data, payment_id8.data, 8); + description_start += 2; + } + else + { + fail_msg_writer() << tr("failed to parse payment ID"); + return true; + } + } + std::string description; + for (size_t i = description_start; i < args.size(); ++i) + { + if (i > description_start) + description += " "; + description += args[i]; + } + m_wallet->add_address_book_row(address, payment_id, description); + } + else + { + size_t row_id; + if(!epee::string_tools::get_xtype_from_string(row_id, args[1])) + { + fail_msg_writer() << tr("failed to parse index"); + return true; + } + m_wallet->delete_address_book_row(row_id); + } + auto address_book = m_wallet->get_address_book(); + if (address_book.empty()) + { + success_msg_writer() << tr("Address book is empty."); + } + else + { + for (size_t i = 0; i < address_book.size(); ++i) { + auto& row = address_book[i]; + success_msg_writer() << tr("Index: ") << i; + success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_address); + success_msg_writer() << tr("Payment ID: ") << row.m_payment_id; + success_msg_writer() << tr("Description: ") << row.m_description << "\n"; + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_tx_note(const std::vector<std::string> &args) { if (args.size() == 0) @@ -3437,10 +3868,10 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args) } cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) { fail_msg_writer() << tr("failed to parse txid"); - return false; + return true; } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); @@ -3465,10 +3896,10 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) } cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) { fail_msg_writer() << tr("failed to parse txid"); - return false; + return true; } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); @@ -3484,7 +3915,8 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) bool simple_wallet::status(const std::vector<std::string> &args) { uint64_t local_height = m_wallet->get_blockchain_current_height(); - if (!try_connect_to_daemon()) + uint32_t version = 0; + if (!m_wallet->check_connection(&version)) { success_msg_writer() << "Refreshed " << local_height << "/?, no daemon connected"; return true; @@ -3495,7 +3927,8 @@ bool simple_wallet::status(const std::vector<std::string> &args) if (err.empty()) { bool synced = local_height == bc_height; - success_msg_writer() << "Refreshed " << local_height << "/" << bc_height << ", " << (synced ? "synced" : "syncing"); + success_msg_writer() << "Refreshed " << local_height << "/" << bc_height << ", " << (synced ? "synced" : "syncing") + << ", daemon RPC v" << get_version_string(version); } else { @@ -3516,6 +3949,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("wallet is watch-only and cannot sign"); return true; } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); @@ -3551,7 +3985,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), address_string)) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3581,28 +4015,18 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("wallet is watch-only and cannot export key images"); return true; } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; try { - std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images(); - std::string data(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); - const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; - data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); - data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); - for (const auto &i: ski) - { - data += std::string((const char *)&i.first, sizeof(crypto::key_image)); - data += std::string((const char *)&i.second, sizeof(crypto::signature)); - } - bool r = epee::file_io_utils::save_string_to_file(filename, data); - if (!r) + if (!m_wallet->export_key_images(filename)) { fail_msg_writer() << tr("failed to save file ") << filename; return true; } } - catch (std::exception &e) + catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); fail_msg_writer() << "Error exporting key images: " << e.what(); @@ -3622,6 +4046,78 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) } std::string filename = args[0]; + try + { + uint64_t spent = 0, unspent = 0; + uint64_t height = m_wallet->import_key_images(filename, spent, unspent); + if (height > 0) + { + success_msg_writer() << "Signed key images imported to height " << height << ", " + << print_money(spent) << " spent, " << print_money(unspent) << " unspent"; + } else { + fail_msg_writer() << "Failed to import key images"; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to import key images: " << e.what(); + return true; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::export_outputs(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_outputs <filename>"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + std::string filename = args[0]; + + try + { + std::vector<tools::wallet2::transfer_details> outs = m_wallet->export_outputs(); + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << outs; + + std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); + bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting outputs: " << e.what()); + fail_msg_writer() << "Error exporting outputs: " << e.what(); + return true; + } + + success_msg_writer() << tr("Outputs exported to ") << filename; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::import_outputs(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: import_outputs <filename>"); + return true; + } + std::string filename = args[0]; + std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); if (!r) @@ -3629,58 +4125,187 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("failed to read file ") << filename; return true; } - const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); - if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << "Bad output export file magic in " << filename; + return true; + } + + try { - fail_msg_writer() << "Bad key image export file magic in " << filename; + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); return true; } - const size_t headerlen = magiclen + 2 * sizeof(crypto::public_key); + + const size_t headerlen = 2 * sizeof(crypto::public_key); if (data.size() < headerlen) { fail_msg_writer() << "Bad data size from file " << filename; return true; } - const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[magiclen]; - const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[magiclen + sizeof(crypto::public_key)]; + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { - fail_msg_writer() << "Key images from " << filename << " are for a different account"; + fail_msg_writer() << "Outputs from " << filename << " are for a different account"; + return true; + } + + try + { + std::string body(data, headerlen); + std::stringstream iss; + iss << body; + std::vector<tools::wallet2::transfer_details> outputs; + try + { + boost::archive::portable_binary_iarchive ar(iss); + ar >> outputs; + } + catch (...) + { + iss.str(""); + iss << body; + boost::archive::binary_iarchive ar(iss); + ar >> outputs; + } + size_t n_outputs = m_wallet->import_outputs(outputs); + success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported"; + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to import outputs: " << e.what(); return true; } - const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); - if ((data.size() - headerlen) % record_size) + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::show_transfer(const std::vector<std::string> &args) +{ + if (args.size() != 1) { - fail_msg_writer() << "Bad data size from file " << filename; + fail_msg_writer() << tr("usage: show_transfer <txid>"); return true; } - size_t nki = (data.size() - headerlen) / record_size; - std::vector<std::pair<crypto::key_image, crypto::signature>> ski; - ski.reserve(nki); - for (size_t n = 0; n < nki; ++n) + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) { - crypto::key_image key_image = *reinterpret_cast<const crypto::key_image*>(&data[headerlen + n * record_size]); - crypto::signature signature = *reinterpret_cast<const crypto::signature*>(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); + fail_msg_writer() << tr("failed to parse txid"); + return true; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - ski.push_back(std::make_pair(key_image, signature)); + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + m_wallet->get_payments(payments, 0); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second; + if (pd.m_tx_hash == txid) { + std::string payment_id = string_tools::pod_to_hex(i->first); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + success_msg_writer() << "Incoming transaction found"; + success_msg_writer() << "txid: " << txid; + success_msg_writer() << "Height: " << pd.m_block_height; + success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Amount: " << print_money(pd.m_amount); + success_msg_writer() << "Payment ID: " << payment_id; + success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); + return true; + } + } + + std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out; + m_wallet->get_payments_out(payments_out, 0); + for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) { + if (i->first == txid) + { + const tools::wallet2::confirmed_transfer_details &pd = i->second; + uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + uint64_t fee = pd.m_amount_in - pd.m_amount_out; + std::string dests; + for (const auto &d: pd.m_dests) { + if (!dests.empty()) + dests += ", "; + dests += get_account_address_as_str(m_wallet->testnet(), d.addr) + ": " + print_money(d.amount); + } + std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + success_msg_writer() << "Outgoing transaction found"; + success_msg_writer() << "txid: " << txid; + success_msg_writer() << "Height: " << pd.m_block_height; + success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Amount: " << print_money(pd.m_amount_in - change - fee); + success_msg_writer() << "Payment ID: " << payment_id; + success_msg_writer() << "Change: " << print_money(change); + success_msg_writer() << "Fee: " << print_money(fee); + success_msg_writer() << "Destinations: " << dests; + success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); + return true; + } } try { - uint64_t spent = 0, unspent = 0; - uint64_t height = m_wallet->import_key_images(ski, spent, unspent); - success_msg_writer() << "Signed key images imported to height " << height << ", " - << print_money(spent) << " spent, " << print_money(unspent) << " unspent"; + m_wallet->update_pool_state(); + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments; + m_wallet->get_unconfirmed_payments(pool_payments); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second; + if (pd.m_tx_hash == txid) + { + std::string payment_id = string_tools::pod_to_hex(i->first); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + success_msg_writer() << "Unconfirmed incoming transaction found in the txpool"; + success_msg_writer() << "txid: " << txid; + success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Amount: " << print_money(pd.m_amount); + success_msg_writer() << "Payment ID: " << payment_id; + success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); + return true; + } + } } - catch (const std::exception &e) + catch (...) { - fail_msg_writer() << "Failed to import key images: " << e.what(); - return true; + fail_msg_writer() << "Failed to get pool state"; + } + + std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; + m_wallet->get_unconfirmed_payments_out(upayments); + for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + if (i->first == txid) + { + const tools::wallet2::unconfirmed_transfer_details &pd = i->second; + uint64_t amount = pd.m_amount_in; + uint64_t fee = amount - pd.m_amount_out; + std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; + + success_msg_writer() << (is_failed ? "Failed" : "Pending") << " outgoing transaction found"; + success_msg_writer() << "txid: " << txid; + success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); + success_msg_writer() << "Amount: " << print_money(amount - pd.m_change - fee); + success_msg_writer() << "Payment ID: " << payment_id; + success_msg_writer() << "Change: " << print_money(pd.m_change); + success_msg_writer() << "Fee: " << print_money(fee); + success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); + return true; + } } + fail_msg_writer() << tr("Transaction ID not found"); return true; } //---------------------------------------------------------------------------------------------------- @@ -3703,258 +4328,69 @@ void simple_wallet::interrupt() //---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { -#ifdef WIN32 - _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); -#endif - - //TRY_ENTRY(); - - std::string lang = i18n_get_language(); - tools::sanitize_locale(); - tools::set_strict_default_file_permissions(true); - - string_tools::set_module_name_and_folder(argv[0]); - - po::options_description desc_general(sw::tr("General options")); - command_line::add_arg(desc_general, command_line::arg_help); - command_line::add_arg(desc_general, command_line::arg_version); - - po::options_description desc_params(sw::tr("Wallet options")); + po::options_description desc_params(wallet_args::tr("Wallet options")); + tools::wallet2::init_options(desc_params); command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_generate_new_wallet); command_line::add_arg(desc_params, arg_generate_from_view_key); command_line::add_arg(desc_params, arg_generate_from_keys); command_line::add_arg(desc_params, arg_generate_from_json); - command_line::add_arg(desc_params, arg_password); - command_line::add_arg(desc_params, arg_password_file); - command_line::add_arg(desc_params, arg_daemon_address); - command_line::add_arg(desc_params, arg_daemon_host); - 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); - command_line::add_arg(desc_params, arg_max_concurrency); - - bf::path default_log {log_space::log_singletone::get_default_log_folder()}; - std::string log_file_name = log_space::log_singletone::get_default_log_file(); - if (log_file_name.empty()) - { - // Sanity check: File path should also be empty if file name is. If not, - // this would be a problem in epee's discovery of current process's file - // path. - if (! default_log.empty()) - { - fail_msg_writer() << sw::tr("unexpected empty log file name in presence of non-empty file path"); - return false; - } - // epee didn't find path to executable from argv[0], so use this default file name. - log_file_name = "monero-wallet-cli.log"; - // The full path will use cwd because epee also returned an empty default log folder. - } - default_log /= log_file_name; - - command_line::add_arg(desc_params, arg_log_file, default_log.string()); command_line::add_arg(desc_params, arg_restore_deterministic_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); - command_line::add_arg(desc_params, arg_testnet); - command_line::add_arg(desc_params, arg_restricted); command_line::add_arg(desc_params, arg_trusted_daemon); command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); - tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); - i18n_set_language("translations", "monero", lang); + const auto vm = wallet_args::main( + argc, argv, + "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", + desc_params, + positional_options, + "monero-wallet-cli.log" + ); - po::options_description desc_all; - desc_all.add(desc_general).add(desc_params); - cryptonote::simple_wallet w; - po::variables_map vm; - bool r = command_line::handle_error_helper(desc_all, [&]() + if (!vm) { - po::store(command_line::parse_command_line(argc, argv, desc_general, true), vm); - - if (command_line::get_arg(vm, command_line::arg_help)) - { - success_msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - success_msg_writer() << sw::tr("Usage:") << " monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]"; - success_msg_writer() << desc_all; - return false; - } - else if (command_line::get_arg(vm, command_line::arg_version)) - { - success_msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return false; - } - - auto parser = po::command_line_parser(argc, argv).options(desc_params).positional(positional_options); - po::store(parser.run(), vm); - po::notify(vm); - return true; - }); - if (!r) - return 0; - - // log_file_path - // default: < argv[0] directory >/monero-wallet-cli.log - // so if ran as "monero-wallet-cli" (no path), log file will be in cwd - // - // if log-file argument given: - // absolute path - // relative path: relative to cwd - - // Set log file - bf::path log_file_path {bf::absolute(command_line::get_arg(vm, arg_log_file))}; - - // Set up logging options - int log_level = LOG_LEVEL_2; - log_space::get_set_log_detalisation_level(true, log_level); - //log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); - log_space::log_singletone::add_logger(LOGGER_FILE, - log_file_path.filename().string().c_str(), - log_file_path.parent_path().string().c_str(), - LOG_LEVEL_4 - ); - - if(command_line::has_arg(vm, arg_max_concurrency)) - tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); - - message_writer(epee::log_space::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + return 1; + } - if(command_line::has_arg(vm, arg_log_level)) - log_level = command_line::get_arg(vm, arg_log_level); - LOG_PRINT_L0("Setting log level = " << log_level); - LOG_PRINT_L0(sw::tr("default_log: ") << default_log.string()); - message_writer(epee::log_space::console_color_white, true) << boost::format(sw::tr("Logging at log level %d to %s")) % - log_level % log_file_path.string(); - log_space::get_set_log_detalisation_level(true, log_level); + cryptonote::simple_wallet w; + const bool r = w.init(*vm); + CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet")); - if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) + std::vector<std::string> command = command_line::get_arg(*vm, arg_command); + if (!command.empty()) { - 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(sw::tr("Wallet file not set.")); - return 1; - } - if(!command_line::has_arg(vm, arg_daemon_address) ) - { - LOG_ERROR(sw::tr("Daemon address not set.")); - return 1; - } - - bool testnet = command_line::get_arg(vm, arg_testnet); - bool restricted = command_line::get_arg(vm, arg_restricted); - std::string wallet_file = command_line::get_arg(vm, arg_wallet_file); - - tools::password_container pwd_container(wallet_file.empty()); - if (!cryptonote::simple_wallet::get_password(vm, false, pwd_container)) - return 1; - 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 = testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT; - if (daemon_address.empty()) - daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - - std::string password; - const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); - if (!gfj.empty()) { - if (!w.generate_from_json(vm, wallet_file, password)) - return 1; - } - else { - password = pwd_container.password(); - } - - tools::wallet2 wal(testnet,restricted); - bool quit = false; - tools::signal_handler::install([&wal, &quit](int) { - quit = true; - wal.stop(); - }); - try - { - LOG_PRINT_L0(sw::tr("Loading wallet...")); - wal.load(wallet_file, password); - wal.init(daemon_address); - wal.refresh(); - // if we ^C during potentially length load/refresh, there's no server loop yet - if (quit) - { - LOG_PRINT_L0(sw::tr("Storing wallet...")); - wal.store(); - LOG_PRINT_GREEN(sw::tr("Stored ok"), LOG_LEVEL_0); - return 1; - } - LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); - } - catch (const std::exception& e) - { - LOG_ERROR(sw::tr("Wallet initialization failed: ") << e.what()); - return 1; - } - tools::wallet_rpc_server wrpc(wal); - bool r = wrpc.init(vm); - CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet rpc server")); - tools::signal_handler::install([&wrpc, &wal](int) { - wrpc.send_stop_signal(); - }); - LOG_PRINT_L0(sw::tr("Starting wallet rpc server")); - wrpc.run(); - LOG_PRINT_L0(sw::tr("Stopped wallet rpc server")); - try - { - LOG_PRINT_L0(sw::tr("Storing wallet...")); - wal.store(); - LOG_PRINT_GREEN(sw::tr("Stored ok"), LOG_LEVEL_0); - } - catch (const std::exception& e) - { - LOG_ERROR(sw::tr("Failed to store wallet: ") << e.what()); - return 1; - } - }else + w.process_command(command); + w.stop(); + w.deinit(); + } + else { - //runs wallet with console interface - r = w.init(vm); - CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet")); - - std::vector<std::string> command = command_line::get_arg(vm, arg_command); - if (!command.empty()) - { - w.process_command(command); - w.stop(); - w.deinit(); - } - else - { - tools::signal_handler::install([&w](int type) { + tools::signal_handler::install([&w](int type) { #ifdef WIN32 - if (type == CTRL_C_EVENT) + if (type == CTRL_C_EVENT) #else - if (type == SIGINT) + if (type == SIGINT) #endif - { - // if we're pressing ^C when refreshing, just stop refreshing - w.interrupt(); - } - else - { - w.stop(); - } - }); - w.run(); + { + // if we're pressing ^C when refreshing, just stop refreshing + w.interrupt(); + } + else + { + w.stop(); + } + }); + w.run(); - w.deinit(); - } + w.deinit(); } return 0; //CATCH_ENTRY_L0("main", 1); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6eb18ed9a..1b58ff32a 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -37,15 +37,19 @@ #include <memory> +#include <boost/optional/optional.hpp> #include <boost/program_options/variables_map.hpp> -#include "cryptonote_core/account.h" -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "wallet/wallet2.h" #include "console_handler.h" -#include "password_container.h" +#include "common/password.h" #include "crypto/crypto.h" // for definition of crypto::secret_key +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.simplewallet" + /*! * \namespace cryptonote * \brief Holds cryptonote related classes and helpers. @@ -58,7 +62,6 @@ namespace cryptonote class simple_wallet : public tools::i_wallet2_callback { public: - static bool get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container); static const char *tr(const char *str) { return i18n_translate(str, "cryptonote::simple_wallet"); } public: @@ -70,7 +73,6 @@ namespace cryptonote bool run(); void stop(); void interrupt(); - bool generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password); //wallet *create_wallet(); bool process_command(const std::vector<std::string> &args); @@ -82,13 +84,14 @@ namespace cryptonote void wallet_idle_thread(); - bool new_wallet(const std::string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key, - bool recover, bool two_random, bool testnet, const std::string &old_language); - bool new_wallet(const std::string &wallet_file, const std::string& password, const cryptonote::account_public_address& address, - const crypto::secret_key& spendkey, const crypto::secret_key& viewkey, bool testnet); - bool new_wallet(const std::string &wallet_file, const std::string& password, const cryptonote::account_public_address& address, - const crypto::secret_key& viewkey, bool testnet); - bool open_wallet(const std::string &wallet_file, const std::string& password, bool testnet); + //! \return Prompts user for password and verifies against local file. Logs on error and returns `none` + boost::optional<tools::password_container> get_and_verify_password() const; + + bool new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, + bool recover, bool two_random, const std::string &old_language); + bool new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, + const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); + bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); @@ -106,10 +109,17 @@ namespace cryptonote */ bool seed_set_language(const std::vector<std::string> &args = std::vector<std::string>()); bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_print_ring_members(const std::vector<std::string> &args = std::vector<std::string>()); bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>()); bool set_default_mixin(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>()); bool set_refresh_type(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_confirm_missing_payment_id(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_ask_password(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_unit(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_min_output_count(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_min_output_value(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_merge_destinations(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); @@ -123,13 +133,20 @@ namespace cryptonote bool transfer_main(int transfer_type, const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); bool transfer_new(const std::vector<std::string> &args); + bool locked_transfer(const std::vector<std::string> &args); + bool sweep_main(uint64_t below, const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); + bool sweep_below(const std::vector<std::string> &args); bool sweep_unmixable(const std::vector<std::string> &args); + bool donate(const std::vector<std::string> &args); + bool sign_transfer(const std::vector<std::string> &args); + bool submit_transfer(const std::vector<std::string> &args); std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits ); bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>()); + bool address_book(const std::vector<std::string> &args = std::vector<std::string>()); bool save(const std::vector<std::string> &args); bool save_watch_only(const std::vector<std::string> &args); bool set_variable(const std::vector<std::string> &args); @@ -138,6 +155,7 @@ namespace cryptonote bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); + bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); bool refresh_main(uint64_t start_height, bool reset = false); bool set_tx_note(const std::vector<std::string> &args); @@ -148,11 +166,18 @@ namespace cryptonote bool verify(const std::vector<std::string> &args); bool export_key_images(const std::vector<std::string> &args); bool import_key_images(const std::vector<std::string> &args); + bool export_outputs(const std::vector<std::string> &args); + bool import_outputs(const std::vector<std::string> &args); + bool show_transfer(const std::vector<std::string> &args); + bool change_password(const std::vector<std::string>& args); uint64_t get_daemon_blockchain_height(std::string& err); - bool try_connect_to_daemon(bool silent = false); + bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); bool ask_wallet_create_if_needed(); - bool get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id); + bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message = std::string()); + bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs); + bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); + bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); /*! * \brief Prints the seed with a nice message @@ -171,9 +196,10 @@ namespace cryptonote //----------------- 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, uint64_t amount); - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx); - virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount); + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount); + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx); + virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); //---------------------------------------------------------- friend class refresh_progress_reporter_t; @@ -247,10 +273,6 @@ namespace cryptonote bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional - std::string m_daemon_address; - std::string m_daemon_host; - int m_daemon_port; - epee::console_handlers_binder m_cmd_binder; std::unique_ptr<tools::wallet2> m_wallet; diff --git a/src/version.cmake b/src/version.cmake index d60673ae2..623d3cf1f 100644 --- a/src/version.cmake +++ b/src/version.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # diff --git a/src/version.h.in b/src/version.h.in index c1eca54b8..852c8023b 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define MONERO_VERSION_TAG "@VERSIONTAG@" -#define MONERO_VERSION "0.10.0.0" +#define MONERO_VERSION "0.10.3.1" #define MONERO_RELEASE_NAME "Wolfram Warptangent" #define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 506eaef85..2e7610b64 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2016, The Monero Project +# Copyright (c) 2014-2017, The Monero Project # # All rights reserved. # @@ -32,13 +32,16 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(wallet_sources wallet2.cpp - wallet_rpc_server.cpp + wallet_args.cpp + node_rpc_proxy.cpp api/wallet.cpp api/wallet_manager.cpp api/transaction_info.cpp api/transaction_history.cpp api/pending_transaction.cpp - api/utils.cpp) + api/utils.cpp + api/address_book.cpp + api/unsigned_transaction.cpp) set(wallet_api_headers wallet2_api.h) @@ -46,25 +49,30 @@ set(wallet_api_headers set(wallet_private_headers wallet2.h + wallet_args.h wallet_errors.h wallet_rpc_server.h wallet_rpc_server_commands_defs.h wallet_rpc_server_error_codes.h + node_rpc_proxy.h api/wallet.h api/wallet_manager.h api/transaction_info.h api/transaction_history.h api/pending_transaction.h - api/common_defines.h) + api/common_defines.h + api/address_book.h + api/unsigned_transaction.h) -bitmonero_private_headers(wallet +monero_private_headers(wallet ${wallet_private_headers}) -bitmonero_add_library(wallet +monero_add_library(wallet ${wallet_sources} ${wallet_api_headers} ${wallet_private_headers}) target_link_libraries(wallet PUBLIC + common cryptonote_core mnemonics p2p @@ -76,17 +84,61 @@ target_link_libraries(wallet ${Boost_REGEX_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) +add_dependencies(wallet version) + +if (NOT BUILD_GUI_DEPS) + set(wallet_rpc_sources + wallet_rpc_server.cpp) + + set(wallet_rpc_headers) + + set(wallet_rpc_private_headers + wallet_rpc_server.h) + + monero_private_headers(wallet_rpc_server + ${wallet_rpc_private_headers}) + monero_add_executable(wallet_rpc_server + ${wallet_rpc_sources} + ${wallet_rpc_headers} + ${wallet_rpc_private_headers}) + + target_link_libraries(wallet_rpc_server + PRIVATE + wallet + epee + rpc + cryptonote_core + crypto + common + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) + add_dependencies(wallet_rpc_server version) + set_property(TARGET wallet_rpc_server + PROPERTY + OUTPUT_NAME "monero-wallet-rpc") + install(TARGETS wallet_rpc_server DESTINATION bin) +endif() + # build and install libwallet_merged only if we building for GUI if (BUILD_GUI_DEPS) - set(libs_to_merge wallet cryptonote_core mnemonics common crypto ringct) + set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common crypto ringct) foreach(lib ${libs_to_merge}) list(APPEND objlibs $<TARGET_OBJECTS:obj_${lib}>) # matches naming convention in src/CMakeLists.txt endforeach() add_library(wallet_merged STATIC ${objlibs}) + if(IOS) + set(lib_folder lib-${ARCH}) + else() + set(lib_folder lib) + endif() install(TARGETS wallet_merged - ARCHIVE DESTINATION lib) + ARCHIVE DESTINATION ${lib_folder}) install(FILES ${wallet_api_headers} DESTINATION include/wallet) diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp new file mode 100644 index 000000000..28f835ebd --- /dev/null +++ b/src/wallet/api/address_book.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + + +#include "address_book.h" +#include "wallet.h" +#include "crypto/hash.h" +#include "wallet/wallet2.h" +#include "common_defines.h" + +#include <vector> + +namespace Monero { + +AddressBook::~AddressBook() {} + +AddressBookImpl::AddressBookImpl(WalletImpl *wallet) + : m_wallet(wallet) {} + +bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description) +{ + clearStatus(); + + cryptonote::account_public_address addr; + bool has_short_pid; + crypto::hash8 payment_id_short; + if(!cryptonote::get_account_integrated_address_from_str(addr, has_short_pid, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) { + m_errorString = tr("Invalid destination address"); + m_errorCode = Invalid_Address; + return false; + } + + crypto::hash payment_id = cryptonote::null_hash; + bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); + + // Short payment id provided + if(payment_id_str.length() == 16) { + m_errorString = tr("Invalid payment ID. Short payment ID should only be used in an integrated address"); + m_errorCode = Invalid_Payment_Id; + return false; + } + + // long payment id provided but not valid + if(!payment_id_str.empty() && !has_long_pid) { + m_errorString = tr("Invalid payment ID"); + m_errorCode = Invalid_Payment_Id; + return false; + } + + // integrated + long payment id provided + if(has_long_pid && has_short_pid) { + m_errorString = tr("Integrated address and long payment id can't be used at the same time"); + m_errorCode = Invalid_Payment_Id; + return false; + } + + // Pad short pid with zeros + if (has_short_pid) + { + memcpy(payment_id.data, payment_id_short.data, 8); + } + + bool r = m_wallet->m_wallet->add_address_book_row(addr,payment_id,description); + if (r) + refresh(); + else + m_errorCode = General_Error; + return r; +} + +void AddressBookImpl::refresh() +{ + LOG_PRINT_L2("Refreshing addressbook"); + + clearRows(); + + // Fetch from Wallet2 and create vector of AddressBookRow objects + std::vector<tools::wallet2::address_book_row> rows = m_wallet->m_wallet->get_address_book(); + for (size_t i = 0; i < rows.size(); ++i) { + tools::wallet2::address_book_row * row = &rows.at(i); + + std::string payment_id = (row->m_payment_id == cryptonote::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id); + std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(),row->m_address); + // convert the zero padded short payment id to integrated address + if (payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) { + payment_id = payment_id.substr(0,16); + crypto::hash8 payment_id_short; + if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) { + address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->testnet(), row->m_address, payment_id_short); + // Don't show payment id when integrated address is used + payment_id = ""; + } + } + AddressBookRow * abr = new AddressBookRow(i, address, payment_id, row->m_description); + m_rows.push_back(abr); + } + +} + +bool AddressBookImpl::deleteRow(std::size_t rowId) +{ + LOG_PRINT_L2("Deleting address book row " << rowId); + bool r = m_wallet->m_wallet->delete_address_book_row(rowId); + if (r) + refresh(); + return r; +} + +int AddressBookImpl::lookupPaymentID(const std::string &payment_id) const +{ + // turn short ones into long ones for comparison + const std::string long_payment_id = payment_id + std::string(64 - payment_id.size(), '0'); + + int idx = -1; + for (const auto &row: m_rows) { + ++idx; + // this does short/short and long/long + if (payment_id == row->getPaymentId()) + return idx; + // short/long + if (long_payment_id == row->getPaymentId()) + return idx; + // one case left: payment_id was long, row's is short + const std::string long_row_payment_id = row->getPaymentId() + std::string(64 - row->getPaymentId().size(), '0'); + if (payment_id == long_row_payment_id) + return idx; + } + return -1; +} + +void AddressBookImpl::clearRows() { + for (auto r : m_rows) { + delete r; + } + m_rows.clear(); +} + +void AddressBookImpl::clearStatus(){ + m_errorString = ""; + m_errorCode = 0; +} + +std::vector<AddressBookRow*> AddressBookImpl::getAll() const +{ + return m_rows; +} + + +AddressBookImpl::~AddressBookImpl() +{ + clearRows(); +} + +} // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h new file mode 100644 index 000000000..25f59128b --- /dev/null +++ b/src/wallet/api/address_book.h @@ -0,0 +1,70 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "wallet/wallet2_api.h" +#include "wallet/wallet2.h" + +namespace Monero { + +class WalletImpl; + +class AddressBookImpl : public AddressBook +{ +public: + AddressBookImpl(WalletImpl * wallet); + ~AddressBookImpl(); + + // Fetches addresses from Wallet2 + void refresh(); + std::vector<AddressBookRow*> getAll() const; + bool addRow(const std::string &dst_addr , const std::string &payment_id, const std::string &description); + bool deleteRow(std::size_t rowId); + + // Error codes. See AddressBook:ErrorCode enum in wallet2_api.h + std::string errorString() const {return m_errorString;} + int errorCode() const {return m_errorCode;} + + int lookupPaymentID(const std::string &payment_id) const; + +private: + void clearRows(); + void clearStatus(); + +private: + WalletImpl *m_wallet; + std::vector<AddressBookRow*> m_rows; + std::string m_errorString; + int m_errorCode; +}; + +} + +namespace Bitmonero = Monero; + diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 26ce9fc7e..9798d66c6 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,9 +32,8 @@ #include "wallet.h" #include "common_defines.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "cryptonote_core/cryptonote_basic_impl.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include <memory> #include <vector> @@ -43,7 +42,7 @@ using namespace std; -namespace Bitmonero { +namespace Monero { PendingTransaction::~PendingTransaction() {} @@ -51,7 +50,7 @@ PendingTransaction::~PendingTransaction() {} PendingTransactionImpl::PendingTransactionImpl(WalletImpl &wallet) : m_wallet(wallet) { - + m_status = Status_Ok; } PendingTransactionImpl::~PendingTransactionImpl() @@ -69,19 +68,47 @@ string PendingTransactionImpl::errorString() const return m_errorString; } -bool PendingTransactionImpl::commit() +std::vector<std::string> PendingTransactionImpl::txid() const +{ + std::vector<std::string> txid; + for (const auto &pt: m_pending_tx) + txid.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(pt.tx))); + return txid; +} + +bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) { - LOG_PRINT_L0("m_pending_tx size: " << m_pending_tx.size()); - assert(m_pending_tx.size() == 1); + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + try { + // Save tx to file + if (!filename.empty()) { + boost::system::error_code ignore; + bool tx_file_exists = boost::filesystem::exists(filename, ignore); + if(tx_file_exists && !overwrite){ + m_errorString = string(tr("Attempting to save transaction to file, but specified file(s) exist. Exiting to not risk overwriting. File:")) + filename; + m_status = Status_Error; + LOG_ERROR(m_errorString); + return false; + } + bool r = m_wallet.m_wallet->save_tx(m_pending_tx, filename); + if (!r) { + m_errorString = tr("Failed to write transaction(s) to file"); + m_status = Status_Error; + } else { + m_status = Status_Ok; + } + } + // Commit tx + else { while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); m_wallet.m_wallet->commit_tx(ptx); - // success_msg_writer(true) << tr("Money successfully sent, transaction ") << get_transaction_hash(ptx.tx); // if no exception, remove element from vector m_pending_tx.pop_back(); } // TODO: extract method; + } } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? m_errorString = tr("daemon is busy. Please try again later."); @@ -92,8 +119,12 @@ bool PendingTransactionImpl::commit() } catch (const tools::error::tx_rejected& e) { std::ostringstream writer(m_errorString); writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); m_status = Status_Error; - } catch (std::exception &e) { + m_errorString = writer.str(); + if (!reason.empty()) + m_errorString += string(tr(". Reason: ")) + reason; + } catch (const std::exception &e) { m_errorString = string(tr("Unknown exception: ")) + e.what(); m_status = Status_Error; } catch (...) { @@ -128,11 +159,18 @@ uint64_t PendingTransactionImpl::dust() const uint64_t PendingTransactionImpl::fee() const { uint64_t result = 0; - for (const auto ptx : m_pending_tx) { + for (const auto &ptx : m_pending_tx) { result += ptx.fee; } return result; } +uint64_t PendingTransactionImpl::txCount() const +{ + return m_pending_tx.size(); } +} + +namespace Bitmonero = Monero; + diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 8e09bec91..0c3e95a85 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -35,7 +35,7 @@ #include <vector> -namespace Bitmonero { +namespace Monero { class WalletImpl; class PendingTransactionImpl : public PendingTransaction @@ -45,10 +45,12 @@ public: ~PendingTransactionImpl(); int status() const; std::string errorString() const; - bool commit(); + bool commit(const std::string &filename = "", bool overwrite = false); uint64_t amount() const; uint64_t dust() const; uint64_t fee() const; + std::vector<std::string> txid() const; + uint64_t txCount() const; // TODO: continue with interface; private: @@ -62,3 +64,5 @@ private: } + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 95aafb04f..85f2b05ce 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -42,7 +42,7 @@ using namespace epee; -namespace Bitmonero { +namespace Monero { TransactionHistory::~TransactionHistory() {} @@ -55,37 +55,60 @@ TransactionHistoryImpl::TransactionHistoryImpl(WalletImpl *wallet) TransactionHistoryImpl::~TransactionHistoryImpl() { - + for (auto t : m_history) + delete t; } int TransactionHistoryImpl::count() const { - return m_history.size(); + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + int result = m_history.size(); + return result; +} + +TransactionInfo *TransactionHistoryImpl::transaction(int index) const +{ + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + // sanity check + if (index < 0) + return nullptr; + unsigned index_ = static_cast<unsigned>(index); + return index_ < m_history.size() ? m_history[index_] : nullptr; } TransactionInfo *TransactionHistoryImpl::transaction(const std::string &id) const { - return nullptr; + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); + auto itr = std::find_if(m_history.begin(), m_history.end(), + [&](const TransactionInfo * ti) { + return ti->hash() == id; + }); + return itr != m_history.end() ? *itr : nullptr; } std::vector<TransactionInfo *> TransactionHistoryImpl::getAll() const { + boost::shared_lock<boost::shared_mutex> lock(m_historyMutex); return m_history; } void TransactionHistoryImpl::refresh() { + // multithreaded access: + // boost::lock_guard<boost::mutex> guarg(m_historyMutex); + // for "write" access, locking exclusively + boost::unique_lock<boost::shared_mutex> lock(m_historyMutex); + // TODO: configurable values; uint64_t min_height = 0; uint64_t max_height = (uint64_t)-1; + uint64_t wallet_height = m_wallet->blockChainHeight(); // delete old transactions; for (auto t : m_history) delete t; m_history.clear(); - - // transactions are stored in wallet2: // - confirmed_transfer_details - out transfers // - unconfirmed_transfer_details - pending out transfers @@ -101,15 +124,14 @@ void TransactionHistoryImpl::refresh() std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); - // TODO TransactionInfoImpl * ti = new TransactionInfoImpl(); ti->m_paymentid = payment_id; ti->m_amount = pd.m_amount; ti->m_direction = TransactionInfo::Direction_In; ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; - // TODO: - // ti->m_timestamp = pd.m_timestamp; + ti->m_timestamp = pd.m_timestamp; + ti->m_confirmations = wallet_height - pd.m_block_height; m_history.push_back(ti); /* output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s") @@ -135,8 +157,8 @@ void TransactionHistoryImpl::refresh() const crypto::hash &hash = i->first; const tools::wallet2::confirmed_transfer_details &pd = i->second; - uint64_t fee = pd.m_amount_in - pd.m_amount_out; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + uint64_t fee = pd.m_amount_in - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); @@ -151,6 +173,8 @@ void TransactionHistoryImpl::refresh() ti->m_direction = TransactionInfo::Direction_Out; ti->m_hash = string_tools::pod_to_hex(hash); ti->m_blockheight = pd.m_block_height; + ti->m_timestamp = pd.m_timestamp; + ti->m_confirmations = wallet_height - pd.m_block_height; // single output transaction might contain multiple transfers for (const auto &d: pd.m_dests) { @@ -160,9 +184,9 @@ void TransactionHistoryImpl::refresh() } // unconfirmed output transactions - std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; - m_wallet->m_wallet->get_unconfirmed_payments_out(upayments); - for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments_out; + m_wallet->m_wallet->get_unconfirmed_payments_out(upayments_out); + for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments_out.begin(); i != upayments_out.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; const crypto::hash &hash = i->first; uint64_t amount = pd.m_amount_in; @@ -180,14 +204,36 @@ void TransactionHistoryImpl::refresh() ti->m_failed = is_failed; ti->m_pending = true; ti->m_hash = string_tools::pod_to_hex(hash); + ti->m_timestamp = pd.m_timestamp; + ti->m_confirmations = 0; m_history.push_back(ti); } - + + + // unconfirmed payments (tx pool) + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments; + m_wallet->m_wallet->get_unconfirmed_payments(upayments); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second; + std::string payment_id = string_tools::pod_to_hex(i->first); + if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) + payment_id = payment_id.substr(0,16); + TransactionInfoImpl * ti = new TransactionInfoImpl(); + ti->m_paymentid = payment_id; + ti->m_amount = pd.m_amount; + ti->m_direction = TransactionInfo::Direction_In; + ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); + ti->m_blockheight = pd.m_block_height; + ti->m_pending = true; + ti->m_timestamp = pd.m_timestamp; + ti->m_confirmations = 0; + m_history.push_back(ti); + + LOG_PRINT_L1(__FUNCTION__ << ": Unconfirmed payment found " << pd.m_amount); + } + } -TransactionInfo *TransactionHistoryImpl::transaction(int index) const -{ - return nullptr; -} +} // namespace -} +namespace Bitmonero = Monero; diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 171fd2210..4987bdab2 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -29,10 +29,10 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "wallet/wallet2_api.h" +#include <boost/thread/shared_mutex.hpp> -namespace Bitmonero { +namespace Monero { -class TransactionInfo; class WalletImpl; class TransactionHistoryImpl : public TransactionHistory @@ -51,7 +51,10 @@ private: // TransactionHistory is responsible of memory management std::vector<TransactionInfo*> m_history; WalletImpl *m_wallet; + mutable boost::shared_mutex m_historyMutex; }; } +namespace Bitmonero = Monero; + diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index f25c53a90..79a8fe9b5 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -33,7 +33,7 @@ using namespace std; -namespace Bitmonero { +namespace Monero { TransactionInfo::~TransactionInfo() {} @@ -49,6 +49,7 @@ TransactionInfoImpl::TransactionInfoImpl() , m_fee(0) , m_blockheight(0) , m_timestamp(0) + , m_confirmations(0) { } @@ -109,4 +110,11 @@ const std::vector<TransactionInfo::Transfer> &TransactionInfoImpl::transfers() c return m_transfers; } +uint64_t TransactionInfoImpl::confirmations() const +{ + return m_confirmations; +} + } // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index 82ab2cc6b..16fa5da7a 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,7 +32,7 @@ #include <string> #include <ctime> -namespace Bitmonero { +namespace Monero { class TransactionHistoryImpl; @@ -55,6 +55,7 @@ public: virtual std::time_t timestamp() const; virtual std::string paymentId() const; virtual const std::vector<Transfer> &transfers() const; + virtual uint64_t confirmations() const; private: int m_direction; @@ -67,9 +68,12 @@ private: std::time_t m_timestamp; std::string m_paymentid; std::vector<Transfer> m_transfers; + uint64_t m_confirmations; friend class TransactionHistoryImpl; }; } // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp new file mode 100644 index 000000000..1d9ef5d7c --- /dev/null +++ b/src/wallet/api/unsigned_transaction.cpp @@ -0,0 +1,284 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "unsigned_transaction.h" +#include "wallet.h" +#include "common_defines.h" + +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" + +#include <memory> +#include <vector> +#include <sstream> +#include <boost/format.hpp> + +using namespace std; + +namespace Monero { + +UnsignedTransaction::~UnsignedTransaction() {} + + +UnsignedTransactionImpl::UnsignedTransactionImpl(WalletImpl &wallet) + : m_wallet(wallet) +{ + m_status = Status_Ok; +} + +UnsignedTransactionImpl::~UnsignedTransactionImpl() +{ + LOG_PRINT_L3("Unsigned tx deleted"); +} + +int UnsignedTransactionImpl::status() const +{ + return m_status; +} + +string UnsignedTransactionImpl::errorString() const +{ + return m_errorString; +} + +bool UnsignedTransactionImpl::sign(const std::string &signedFileName) +{ + if(m_wallet.watchOnly()) + { + m_errorString = tr("This is a watch only wallet"); + m_status = Status_Error; + return false; + } + std::vector<tools::wallet2::pending_tx> ptx; + try + { + bool r = m_wallet.m_wallet->sign_tx(m_unsigned_tx_set, signedFileName, ptx); + if (!r) + { + m_errorString = tr("Failed to sign transaction"); + m_status = Status_Error; + return false; + } + } + catch (const std::exception &e) + { + m_errorString = string(tr("Failed to sign transaction")) + e.what(); + m_status = Status_Error; + return false; + } + return true; +} + +//---------------------------------------------------------------------------------------------------- +bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message) +{ + // gather info to ask the user + uint64_t amount = 0, amount_to_dests = 0, change = 0; + size_t min_mixin = ~0; + std::unordered_map<std::string, uint64_t> dests; + const std::string wallet_address = m_wallet.m_wallet->get_account().get_public_address_str(m_wallet.m_wallet->testnet()); + int first_known_non_zero_change_index = -1; + for (size_t n = 0; n < get_num_txes(); ++n) + { + const tools::wallet2::tx_construction_data &cd = get_tx(n); + for (size_t s = 0; s < cd.sources.size(); ++s) + { + amount += cd.sources[s].amount; + size_t mixin = cd.sources[s].outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), entry.addr); + std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address); + if (i == dests.end()) + dests.insert(std::make_pair(address, entry.amount)); + else + i->second += entry.amount; + amount_to_dests += entry.amount; + } + if (cd.change_dts.amount > 0) + { + std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr)); + if (it == dests.end()) + { + m_status = Status_Error; + m_errorString = tr("Claimed change does not go to a paid address"); + return false; + } + if (it->second < cd.change_dts.amount) + { + m_status = Status_Error; + m_errorString = tr("Claimed change is larger than payment to the change address"); + return false; + } + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + if (memcmp(&cd.change_dts.addr, &get_tx(first_known_non_zero_change_index).change_dts.addr, sizeof(cd.change_dts.addr))) + { + m_status = Status_Error; + m_errorString = tr("Change goes to more than one address"); + return false; + } + } + change += cd.change_dts.amount; + it->second -= cd.change_dts.amount; + if (it->second == 0) + dests.erase(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr)); + } + } + std::string dest_string; + for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); ) + { + dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second) % i->first).str(); + ++i; + if (i != dests.end()) + dest_string += ", "; + } + if (dest_string.empty()) + dest_string = tr("with no destinations"); + + std::string change_string; + if (change > 0) + { + std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).change_dts.addr); + change_string += (boost::format(tr("%s change to %s")) % cryptonote::print_money(change) % address).str(); + } + else + change_string += tr("no change"); + uint64_t fee = amount - amount_to_dests; + m_confirmationMessage = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %s")) % (unsigned long)get_num_txes() % cryptonote::print_money(amount) % cryptonote::print_money(fee) % dest_string % change_string % (unsigned long)min_mixin % extra_message).str(); + return true; +} + +std::vector<uint64_t> UnsignedTransactionImpl::amount() const +{ + std::vector<uint64_t> result; + for (const auto &utx : m_unsigned_tx_set.txes) { + for (const auto &unsigned_dest : utx.dests) { + result.push_back(unsigned_dest.amount); + } + } + return result; +} + +std::vector<uint64_t> UnsignedTransactionImpl::fee() const +{ + std::vector<uint64_t> result; + for (const auto &utx : m_unsigned_tx_set.txes) { + uint64_t fee = 0; + for (const auto &i: utx.sources) fee += i.amount; + for (const auto &i: utx.splitted_dsts) fee -= i.amount; + result.push_back(fee); + } + return result; +} + +std::vector<uint64_t> UnsignedTransactionImpl::mixin() const +{ + std::vector<uint64_t> result; + for (const auto &utx: m_unsigned_tx_set.txes) { + size_t min_mixin = ~0; + // TODO: Is this loop needed or is sources[0] ? + for (size_t s = 0; s < utx.sources.size(); ++s) { + size_t mixin = utx.sources[s].outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + result.push_back(min_mixin); + } + return result; +} + +uint64_t UnsignedTransactionImpl::txCount() const +{ + return m_unsigned_tx_set.txes.size(); +} + +std::vector<std::string> UnsignedTransactionImpl::paymentId() const +{ + std::vector<string> result; + for (const auto &utx: m_unsigned_tx_set.txes) { + crypto::hash payment_id = cryptonote::null_hash; + cryptonote::tx_extra_nonce extra_nonce; + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + cryptonote::parse_tx_extra(utx.extra, tx_extra_fields); + if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash8 payment_id8 = cryptonote::null_hash8; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + // We can't decrypt short pid without recipient key. + memcpy(payment_id.data, payment_id8.data, 8); + } + else if (!cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + payment_id = cryptonote::null_hash; + } + } + if(payment_id != cryptonote::null_hash) + result.push_back(epee::string_tools::pod_to_hex(payment_id)); + else + result.push_back(""); + } + return result; +} + +std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const +{ + // TODO: return integrated address if short payment ID exists + std::vector<string> result; + for (const auto &utx: m_unsigned_tx_set.txes) { + result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].addr)); + } + return result; +} + +uint64_t UnsignedTransactionImpl::minMixinCount() const +{ + uint64_t min_mixin = ~0; + for (const auto &utx: m_unsigned_tx_set.txes) { + for (size_t s = 0; s < utx.sources.size(); ++s) { + size_t mixin = utx.sources[s].outputs.size() - 1; + if (mixin < min_mixin) + min_mixin = mixin; + } + } + return min_mixin; +} + +} // namespace + +namespace Bitmonero = Monero; + diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h new file mode 100644 index 000000000..9c442f503 --- /dev/null +++ b/src/wallet/api/unsigned_transaction.h @@ -0,0 +1,76 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "wallet/wallet2_api.h" +#include "wallet/wallet2.h" + +#include <string> +#include <vector> + + +namespace Monero { + +class WalletImpl; +class UnsignedTransactionImpl : public UnsignedTransaction +{ +public: + UnsignedTransactionImpl(WalletImpl &wallet); + ~UnsignedTransactionImpl(); + int status() const; + std::string errorString() const; + std::vector<uint64_t> amount() const; + std::vector<uint64_t> dust() const; + std::vector<uint64_t> fee() const; + std::vector<uint64_t> mixin() const; + std::vector<std::string> paymentId() const; + std::vector<std::string> recipientAddress() const; + uint64_t txCount() const; + // sign txs and save to file + bool sign(const std::string &signedFileName); + std::string confirmationMessage() const {return m_confirmationMessage;} + uint64_t minMixinCount() const; + +private: + // Callback function to check all loaded tx's and generate confirmationMessage + bool checkLoadedTx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message); + + friend class WalletImpl; + WalletImpl &m_wallet; + + int m_status; + std::string m_errorString; + tools::wallet2::unsigned_tx_set m_unsigned_tx_set; + std::string m_confirmationMessage; +}; + + +} + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index aa85323f0..a9646e038 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,49 +31,26 @@ #include "include_base_utils.h" // LOG_PRINT_x -#include "net/http_client.h" // epee::net_utils::... -#include <boost/asio.hpp> +#include "common/util.h" using namespace std; -namespace Bitmonero { +namespace Monero { namespace Utils { - -// copy-pasted from simplewallet. - bool isAddressLocal(const std::string &address) -{ - // extract host - epee::net_utils::http::url_content u_c; - if (!epee::net_utils::parse_url(address, u_c)) - { - LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); - return false; - } - if (u_c.host.empty()) - { - LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); +{ + try { + return tools::is_local_address(address); + } catch (const std::exception &e) { + MERROR("error: " << e.what()); return false; } - - // resolve to IP - boost::asio::io_service io_service; - boost::asio::ip::tcp::resolver resolver(io_service); - boost::asio::ip::tcp::resolver::query query(u_c.host, ""); - boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); - while (i != boost::asio::ip::tcp::resolver::iterator()) - { - const boost::asio::ip::tcp::endpoint &ep = *i; - if (ep.address().is_loopback()) - return true; - ++i; - } - - return false; } } } // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index eefb49e95..21760ac49 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,17 +31,23 @@ #include "wallet.h" #include "pending_transaction.h" +#include "unsigned_transaction.h" #include "transaction_history.h" +#include "address_book.h" #include "common_defines.h" #include "mnemonics/electrum-words.h" #include <boost/format.hpp> #include <sstream> +#include <unordered_map> using namespace std; using namespace cryptonote; -namespace Bitmonero { +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" + +namespace Monero { namespace { // copy-pasted from simplewallet @@ -49,13 +55,18 @@ namespace { static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10; // limit maximum refresh interval as one minute static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; + // Default refresh interval when connected to remote node + static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10; + // Connection timeout 30 sec + static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; } struct Wallet2CallbackImpl : public tools::i_wallet2_callback { - Wallet2CallbackImpl() + Wallet2CallbackImpl(WalletImpl * wallet) : m_listener(nullptr) + , m_wallet(wallet) { } @@ -77,48 +88,69 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback virtual void on_new_block(uint64_t height, const cryptonote::block& block) { - LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); - - if (m_listener) { - m_listener->newBlock(height); - // m_listener->updated(); + // Don't flood the GUI with signals. On fast refresh - send signal every 1000th block + // get_refresh_from_block_height() returns the blockheight from when the wallet was + // created or the restore height specified when wallet was recovered + if(height >= m_wallet->m_wallet->get_refresh_from_block_height() || height % 1000 == 0) { + // LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); + if (m_listener) { + m_listener->newBlock(height); + } } } - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) { - std::string tx_hash = epee::string_tools::pod_to_hex(get_transaction_hash(tx)); + std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount)); - if (m_listener) { + // do not signal on received tx if wallet is not syncronized completely + if (m_listener && m_wallet->synchronized()) { m_listener->moneyReceived(tx_hash, amount); m_listener->updated(); } } - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) + { + + std::string tx_hash = epee::string_tools::pod_to_hex(txid); + + LOG_PRINT_L3(__FUNCTION__ << ": unconfirmed money received. height: " << height + << ", tx: " << tx_hash + << ", amount: " << print_money(amount)); + // do not signal on received tx if wallet is not syncronized completely + if (m_listener && m_wallet->synchronized()) { + m_listener->unconfirmedMoneyReceived(tx_hash, amount); + m_listener->updated(); + } + } + + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount, const cryptonote::transaction& spend_tx) { // TODO; - std::string tx_hash = epee::string_tools::pod_to_hex(get_transaction_hash(spend_tx)); + std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount)); - if (m_listener) { + // do not signal on sent tx if wallet is not syncronized completely + if (m_listener && m_wallet->synchronized()) { m_listener->moneySpent(tx_hash, amount); m_listener->updated(); } } - virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) + virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid) { // TODO; } WalletListener * m_listener; + WalletImpl * m_wallet; }; Wallet::~Wallet() {} @@ -154,8 +186,71 @@ std::string Wallet::genPaymentId() bool Wallet::paymentIdValid(const string &paiment_id) { - crypto::hash8 pid; - return tools::wallet2::parse_short_payment_id(paiment_id, pid); + crypto::hash8 pid8; + if (tools::wallet2::parse_short_payment_id(paiment_id, pid8)) + return true; + crypto::hash pid; + if (tools::wallet2::parse_long_payment_id(paiment_id, pid)) + return true; + return false; +} + +bool Wallet::addressValid(const std::string &str, bool testnet) +{ + bool has_payment_id; + cryptonote::account_public_address address; + crypto::hash8 pid; + return get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str); +} + +bool Wallet::keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error) +{ + bool has_payment_id; + cryptonote::account_public_address address; + crypto::hash8 pid; + if(!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, address_string)) { + error = tr("Failed to parse address"); + return false; + } + + cryptonote::blobdata key_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(secret_key_string, key_data) || key_data.size() != sizeof(crypto::secret_key)) + { + error = tr("Failed to parse key"); + return false; + } + crypto::secret_key key = *reinterpret_cast<const crypto::secret_key*>(key_data.data()); + + // check the key match the given address + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(key, pkey)) { + error = tr("failed to verify key"); + return false; + } + bool matchAddress = false; + if(isViewKey) + matchAddress = address.m_view_public_key == pkey; + else + matchAddress = address.m_spend_public_key == pkey; + + if(!matchAddress) { + error = tr("key does not match address"); + return false; + } + + return true; +} + +std::string Wallet::paymentIdFromAddress(const std::string &str, bool testnet) +{ + bool has_payment_id; + cryptonote::account_public_address address; + crypto::hash8 pid; + if (!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str)) + return ""; + if (!has_payment_id) + return ""; + return epee::string_tools::pod_to_hex(pid); } uint64_t Wallet::maximumAllowedAmount() @@ -163,18 +258,34 @@ uint64_t Wallet::maximumAllowedAmount() return std::numeric_limits<uint64_t>::max(); } +void Wallet::init(const char *argv0, const char *default_log_base_name) { + epee::string_tools::set_module_name_and_folder(argv0); + mlog_configure(mlog_get_default_log_path(default_log_base_name), true); +} + +void Wallet::debug(const std::string &str) { + MDEBUG(str); +} ///////////////////////// WalletImpl implementation //////////////////////// WalletImpl::WalletImpl(bool testnet) - :m_wallet(nullptr), m_status(Wallet::Status_Ok), m_trustedDaemon(false), - m_wallet2Callback(nullptr) + :m_wallet(nullptr) + , m_status(Wallet::Status_Ok) + , m_trustedDaemon(false) + , m_wallet2Callback(nullptr) + , m_recoveringFromSeed(false) + , m_synchronized(false) + , m_rebuildWalletCache(false) + , m_is_connected(false) { m_wallet = new tools::wallet2(testnet); m_history = new TransactionHistoryImpl(this); - m_wallet2Callback = new Wallet2CallbackImpl; + m_wallet2Callback = new Wallet2CallbackImpl(this); m_wallet->callback(m_wallet2Callback); m_refreshThreadDone = false; m_refreshEnabled = false; + m_addressBook = new AddressBookImpl(this); + m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS; @@ -186,21 +297,29 @@ WalletImpl::WalletImpl(bool testnet) WalletImpl::~WalletImpl() { + + LOG_PRINT_L1(__FUNCTION__); + // Pause refresh thread - prevents refresh from starting again + pauseRefresh(); + // Close wallet - stores cache and stops ongoing refresh operation + close(); + // Stop refresh thread stopRefresh(); + delete m_wallet2Callback; delete m_history; + delete m_addressBook; delete m_wallet; - delete m_wallet2Callback; + LOG_PRINT_L1(__FUNCTION__ << " finished"); } bool WalletImpl::create(const std::string &path, const std::string &password, const std::string &language) { clearStatus(); - + m_recoveringFromSeed = false; bool keys_file_exists; bool wallet_file_exists; tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); - // TODO: figure out how to setup logger; LOG_PRINT_L3("wallet_path: " << path << ""); LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); @@ -210,7 +329,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co if (keys_file_exists || wallet_file_exists) { m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; LOG_ERROR(m_errorString); - m_status = Status_Error; + m_status = Status_Critical; return false; } // TODO: validate language @@ -222,25 +341,166 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co m_status = Status_Ok; } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); + m_status = Status_Critical; + m_errorString = e.what(); + return false; + } + + return true; +} + +bool WalletImpl::createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const +{ + clearStatus(); + std::unique_ptr<tools::wallet2> view_wallet(new tools::wallet2(m_wallet->testnet())); + + // Store same refresh height as original wallet + view_wallet->set_refresh_from_block_height(m_wallet->get_refresh_from_block_height()); + + bool keys_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + LOG_PRINT_L3("wallet_path: " << path << ""); + LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha + << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); + + // add logic to error out if new wallet requested but named wallet file exists + if (keys_file_exists || wallet_file_exists) { + m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(m_errorString); + m_status = Status_Error; + return false; + } + // TODO: validate language + view_wallet->set_seed_language(language); + + const crypto::secret_key viewkey = m_wallet->get_account().get_keys().m_view_secret_key; + const cryptonote::account_public_address address = m_wallet->get_account().get_keys().m_account_address; + + try { + view_wallet->generate(path, password, address, viewkey); + m_status = Status_Ok; + } catch (const std::exception &e) { + LOG_ERROR("Error creating view only wallet: " << e.what()); m_status = Status_Error; m_errorString = e.what(); return false; } + return true; +} + +bool WalletImpl::recoverFromKeys(const std::string &path, + const std::string &language, + const std::string &address_string, + const std::string &viewkey_string, + const std::string &spendkey_string) +{ + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 new_payment_id; + if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, m_wallet->testnet(), address_string)) + { + m_errorString = tr("failed to parse address"); + m_status = Status_Error; + return false; + } + + // parse optional spend key + crypto::secret_key spendkey; + bool has_spendkey = false; + if (!spendkey_string.empty()) { + cryptonote::blobdata spendkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) + { + m_errorString = tr("failed to parse secret spend key"); + m_status = Status_Error; + return false; + } + has_spendkey = true; + spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); + } + + // parse view secret key + if (viewkey_string.empty()) { + m_errorString = tr("No view key supplied, cancelled"); + m_status = Status_Error; + return false; + } + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) + { + m_errorString = tr("failed to parse secret view key"); + m_status = Status_Error; + return false; + } + crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); + + // check the spend and view keys match the given address + crypto::public_key pkey; + if(has_spendkey) { + if (!crypto::secret_key_to_public_key(spendkey, pkey)) { + m_errorString = tr("failed to verify secret spend key"); + m_status = Status_Error; + return false; + } + if (address.m_spend_public_key != pkey) { + m_errorString = tr("spend key does not match address"); + m_status = Status_Error; + return false; + } + } + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + m_errorString = tr("failed to verify secret view key"); + m_status = Status_Error; + return false; + } + if (address.m_view_public_key != pkey) { + m_errorString = tr("view key does not match address"); + m_status = Status_Error; + return false; + } + try + { + if (has_spendkey) { + m_wallet->generate(path, "", address, spendkey, viewkey); + LOG_PRINT_L1("Generated new wallet from keys"); + } + else { + m_wallet->generate(path, "", address, viewkey); + LOG_PRINT_L1("Generated new view only wallet from keys"); + } + + } + catch (const std::exception& e) { + m_errorString = string(tr("failed to generate new wallet: ")) + e.what(); + m_status = Status_Error; + return false; + } return true; } + bool WalletImpl::open(const std::string &path, const std::string &password) { clearStatus(); + m_recoveringFromSeed = false; try { // TODO: handle "deprecated" + // Check if wallet cache exists + bool keys_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + if(!wallet_file_exists){ + // Rebuilding wallet cache, using refresh height from .keys file + m_rebuildWalletCache = true; + } m_wallet->load(path, password); m_password = password; } catch (const std::exception &e) { LOG_ERROR("Error opening wallet: " << e.what()); - m_status = Status_Error; + m_status = Status_Critical; m_errorString = e.what(); } return m_status == Status_Ok; @@ -257,6 +517,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return false; } + m_recoveringFromSeed = true; crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { @@ -268,9 +529,9 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) try { m_wallet->set_seed_language(old_language); m_wallet->generate(path, "", recovery_key, true, false); - // TODO: wallet->init(daemon_address); + } catch (const std::exception &e) { - m_status = Status_Error; + m_status = Status_Critical; m_errorString = e.what(); } return m_status == Status_Ok; @@ -280,19 +541,22 @@ bool WalletImpl::close() { bool result = false; - LOG_PRINT_L3("closing wallet..."); + LOG_PRINT_L1("closing wallet..."); try { - // do not store wallet with invalid status - if (status() == Status_Ok) + // Do not store wallet with invalid status + // Status Critical refers to errors on opening or creating wallets. + if (status() != Status_Critical) m_wallet->store(); - LOG_PRINT_L3("wallet::store done"); - LOG_PRINT_L3("Calling wallet::stop..."); + else + LOG_ERROR("Status_Critical - not storing wallet"); + LOG_PRINT_L1("wallet::store done"); + LOG_PRINT_L1("Calling wallet::stop..."); m_wallet->stop(); - LOG_PRINT_L3("wallet::stop done"); + LOG_PRINT_L1("wallet::stop done"); result = true; clearStatus(); } catch (const std::exception &e) { - m_status = Status_Error; + m_status = Status_Critical; m_errorString = e.what(); LOG_ERROR("Error closing wallet: " << e.what()); } @@ -349,11 +613,21 @@ std::string WalletImpl::integratedAddress(const std::string &payment_id) const { crypto::hash8 pid; if (!tools::wallet2::parse_short_payment_id(payment_id, pid)) { - pid = crypto::rand<crypto::hash8>(); + return ""; } return m_wallet->get_account().get_public_integrated_address_str(pid, m_wallet->testnet()); } +std::string WalletImpl::privateViewKey() const +{ + return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key); +} + +std::string WalletImpl::path() const +{ + return m_wallet->path(); +} + bool WalletImpl::store(const std::string &path) { clearStatus(); @@ -382,32 +656,23 @@ string WalletImpl::keysFilename() const return m_wallet->get_keys_file(); } -bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) +bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password) { clearStatus(); - - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } - bool result = this->refresh(); - // enabling background refresh thread - startRefresh(); - return result; - + if(daemon_username != "") + m_daemon_login.emplace(daemon_username, daemon_password); + return doInit(daemon_address, upper_transaction_size_limit); } -void WalletImpl::initAsync(const string &daemon_address, uint64_t upper_transaction_size_limit) +void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) { - clearStatus(); - m_wallet->init(daemon_address, upper_transaction_size_limit); - if (Utils::isAddressLocal(daemon_address)) { - this->setTrustedDaemon(true); - } - startRefresh(); + m_wallet->set_refresh_from_block_height(refresh_from_block_height); } - +void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed) +{ + m_recoveringFromSeed = recoveringFromSeed; +} uint64_t WalletImpl::balance() const { @@ -423,9 +688,14 @@ uint64_t WalletImpl::blockChainHeight() const { return m_wallet->get_blockchain_current_height(); } - +uint64_t WalletImpl::approximateBlockChainHeight() const +{ + return m_wallet->get_approximate_blockchain_height(); +} uint64_t WalletImpl::daemonBlockChainHeight() const { + if (!m_is_connected) + return 0; std::string err; uint64_t result = m_wallet->get_daemon_blockchain_height(err); if (!err.empty()) { @@ -441,6 +711,41 @@ uint64_t WalletImpl::daemonBlockChainHeight() const return result; } +uint64_t WalletImpl::daemonBlockChainTargetHeight() const +{ + if (!m_is_connected) + return 0; + std::string err; + uint64_t result = m_wallet->get_daemon_blockchain_target_height(err); + if (!err.empty()) { + LOG_ERROR(__FUNCTION__ << ": " << err); + result = 0; + m_errorString = err; + m_status = Status_Error; + + } else { + m_status = Status_Ok; + m_errorString = ""; + } + // Target height can be 0 when daemon is synced. Use blockchain height instead. + if(result == 0) + result = daemonBlockChainHeight(); + return result; +} + +bool WalletImpl::daemonSynced() const +{ + if(connected() == Wallet::ConnectionStatus_Disconnected) + return false; + uint64_t blockChainHeight = daemonBlockChainHeight(); + return (blockChainHeight >= daemonBlockChainTargetHeight() && blockChainHeight > 1); +} + +bool WalletImpl::synchronized() const +{ + return m_synchronized; +} + bool WalletImpl::refresh() { clearStatus(); @@ -450,7 +755,7 @@ bool WalletImpl::refresh() void WalletImpl::refreshAsync() { - LOG_PRINT_L3(__FUNCTION__ << ": Refreshing asyncronously.."); + LOG_PRINT_L3(__FUNCTION__ << ": Refreshing asynchronously.."); clearStatus(); m_refreshCV.notify_one(); } @@ -471,6 +776,93 @@ int WalletImpl::autoRefreshInterval() const return m_refreshIntervalMillis; } +UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) { + clearStatus(); + UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); + if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ + m_errorString = tr("Failed to load unsigned transactions"); + m_status = Status_Error; + } + + // Check tx data and construct confirmation message + std::string extra_message; + if (!transaction->m_unsigned_tx_set.transfers.empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str(); + transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); + m_status = transaction->status(); + m_errorString = transaction->errorString(); + + return transaction; +} + +bool WalletImpl::submitTransaction(const string &fileName) { + clearStatus(); + std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this)); + + bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); + if (!r) { + m_errorString = tr("Failed to load transaction from file"); + m_status = Status_Ok; + return false; + } + + if(!transaction->commit()) { + m_errorString = transaction->m_errorString; + m_status = Status_Error; + return false; + } + + return true; +} + +bool WalletImpl::exportKeyImages(const string &filename) +{ + if (m_wallet->watch_only()) + { + m_errorString = tr("Wallet is view only"); + m_status = Status_Error; + return false; + } + + try + { + if (!m_wallet->export_key_images(filename)) + { + m_errorString = tr("failed to save file ") + filename; + m_status = Status_Error; + return false; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + m_errorString = e.what(); + m_status = Status_Error; + return false; + } + return true; +} + +bool WalletImpl::importKeyImages(const string &filename) +{ + try + { + uint64_t spent = 0, unspent = 0; + uint64_t height = m_wallet->import_key_images(filename, spent, unspent); + LOG_PRINT_L2("Signed key images imported to height " << height << ", " + << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + m_errorString = string(tr("Failed to import key images: ")) + e.what(); + m_status = Status_Error; + return false; + } + + return true; +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -481,13 +873,15 @@ int WalletImpl::autoRefreshInterval() const // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, uint64_t amount, uint32_t mixin_count, +PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority priority) { clearStatus(); - vector<cryptonote::tx_destination_entry> dsts; - cryptonote::tx_destination_entry de; + // Pause refresh thread while creating transaction + pauseRefresh(); + + cryptonote::account_public_address addr; // indicates if dst_addr is integrated address (address + payment_id) bool has_payment_id; @@ -500,7 +894,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { - if(!cryptonote::get_account_integrated_address_from_str(de.addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) { + if(!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 m_status = Status_Error; m_errorString = "Invalid destination address"; @@ -533,15 +927,129 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const break; } } + else if (has_payment_id) { + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short); + bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + if (!r) { + m_status = Status_Error; + m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(payment_id_short); + break; + } + } + - de.amount = amount; - dsts.push_back(de); //std::vector<tools::wallet2::pending_tx> ptx_vector; try { - transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, - static_cast<uint32_t>(priority), - extra, m_trustedDaemon); + if (amount) { + vector<cryptonote::tx_destination_entry> dsts; + cryptonote::tx_destination_entry de; + de.addr = addr; + de.amount = *amount; + dsts.push_back(de); + transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, + static_cast<uint32_t>(priority), + extra, m_trustedDaemon); + } else { + transaction->m_pending_tx = m_wallet->create_transactions_all(0, addr, fake_outs_count, 0 /* unlock_time */, + static_cast<uint32_t>(priority), + extra, m_trustedDaemon); + } + + } catch (const tools::error::daemon_busy&) { + // TODO: make it translatable with "tr"? + m_errorString = tr("daemon is busy. Please try again later."); + m_status = Status_Error; + } catch (const tools::error::no_connection_to_daemon&) { + m_errorString = tr("no connection to daemon. Please make sure daemon is running."); + m_status = Status_Error; + } catch (const tools::error::wallet_rpc_error& e) { + m_errorString = tr("RPC error: ") + e.to_string(); + m_status = Status_Error; + } catch (const tools::error::get_random_outs_error &e) { + m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); + m_status = Status_Error; + + } catch (const tools::error::not_enough_money& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + + } catch (const tools::error::tx_not_possible& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee()); + m_errorString = writer.str(); + + } catch (const tools::error::not_enough_outs_to_mix& e) { + std::ostringstream writer; + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; + for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; + } + m_errorString = writer.str(); + m_status = Status_Error; + } catch (const tools::error::tx_not_constructed&) { + m_errorString = tr("transaction was not constructed"); + m_status = Status_Error; + } catch (const tools::error::tx_rejected& e) { + std::ostringstream writer; + writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + m_errorString = writer.str(); + m_status = Status_Error; + } catch (const tools::error::tx_sum_overflow& e) { + m_errorString = e.what(); + m_status = Status_Error; + } catch (const tools::error::zero_destination&) { + m_errorString = tr("one of destinations is zero"); + m_status = Status_Error; + } catch (const tools::error::tx_too_big& e) { + m_errorString = tr("failed to find a suitable way to split transactions"); + m_status = Status_Error; + } catch (const tools::error::transfer_error& e) { + m_errorString = string(tr("unknown transfer error: ")) + e.what(); + m_status = Status_Error; + } catch (const tools::error::wallet_internal_error& e) { + m_errorString = string(tr("internal error: ")) + e.what(); + m_status = Status_Error; + } catch (const std::exception& e) { + m_errorString = string(tr("unexpected error: ")) + e.what(); + m_status = Status_Error; + } catch (...) { + m_errorString = tr("unknown error"); + m_status = Status_Error; + } + } while (false); + + transaction->m_status = m_status; + transaction->m_errorString = m_errorString; + // Resume refresh thread + startRefresh(); + return transaction; +} + +PendingTransaction *WalletImpl::createSweepUnmixableTransaction() + +{ + clearStatus(); + vector<cryptonote::tx_destination_entry> dsts; + cryptonote::tx_destination_entry de; + + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + do { + try { + transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(m_trustedDaemon); } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? @@ -561,6 +1069,15 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const m_status = Status_Error; std::ostringstream writer; + writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + + } catch (const tools::error::tx_not_possible& e) { + m_status = Status_Error; + std::ostringstream writer; + writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % @@ -623,6 +1140,11 @@ TransactionHistory *WalletImpl::history() const return m_history; } +AddressBook *WalletImpl::addressBook() const +{ + return m_addressBook; +} + void WalletImpl::setListener(WalletListener *l) { // TODO thread synchronization; @@ -639,10 +1161,67 @@ void WalletImpl::setDefaultMixin(uint32_t arg) m_wallet->default_mixin(arg); } +bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + return false; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + m_wallet->set_tx_note(htxid, note); + return true; +} + +std::string WalletImpl::getUserNote(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + return ""; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + return m_wallet->get_tx_note(htxid); +} + +std::string WalletImpl::getTxKey(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + return ""; + } + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + crypto::secret_key tx_key; + if (m_wallet->get_tx_key(htxid, tx_key)) + { + return epee::string_tools::pod_to_hex(tx_key); + } + else + { + return ""; + } +} + +std::string WalletImpl::signMessage(const std::string &message) +{ + return m_wallet->sign(message); +} + +bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const +{ + cryptonote::account_public_address addr; + bool has_payment_id; + crypto::hash8 payment_id; + + if (!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), address)) + return false; + + return m_wallet->verify(message, addr, signature); +} bool WalletImpl::connectToDaemon() { - bool result = m_wallet->check_connection(); + bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); m_status = result ? Status_Ok : Status_Error; if (!result) { m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); @@ -652,9 +1231,15 @@ bool WalletImpl::connectToDaemon() return result; } -bool WalletImpl::connected() const +Wallet::ConnectionStatus WalletImpl::connected() const { - return m_wallet->check_connection(); + uint32_t version = 0; + m_is_connected = m_wallet->check_connection(&version, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + if (!m_is_connected) + return Wallet::ConnectionStatus_Disconnected; + if ((version >> 16) != CORE_RPC_VERSION_MAJOR) + return Wallet::ConnectionStatus_WrongVersion; + return Wallet::ConnectionStatus_Connected; } void WalletImpl::setTrustedDaemon(bool arg) @@ -667,7 +1252,12 @@ bool WalletImpl::trustedDaemon() const return m_trustedDaemon; } -void WalletImpl::clearStatus() +bool WalletImpl::watchOnly() const +{ + return m_wallet->watch_only(); +} + +void WalletImpl::clearStatus() const { m_status = Status_Ok; m_errorString.clear(); @@ -695,7 +1285,7 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); - if (m_refreshEnabled /*&& m_status == Status_Ok*/) { + if (m_refreshEnabled) { LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); doRefresh(); } @@ -708,7 +1298,22 @@ void WalletImpl::doRefresh() // synchronizing async and sync refresh calls boost::lock_guard<boost::mutex> guarg(m_refreshMutex2); try { - m_wallet->refresh(); + // Syncing daemon and refreshing wallet simultaneously is very resource intensive. + // Disable refresh if wallet is disconnected or daemon isn't synced. + if (daemonSynced()) { + m_wallet->refresh(); + if (!m_synchronized) { + m_synchronized = true; + } + // assuming if we have empty history, it wasn't initialized yet + // for futher history changes client need to update history in + // "on_money_received" and "on_money_sent" callbacks + if (m_history->count() == 0) { + m_history->refresh(); + } + } else { + LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); + } } catch (const std::exception &e) { m_status = Status_Error; m_errorString = e.what(); @@ -722,6 +1327,7 @@ void WalletImpl::doRefresh() void WalletImpl::startRefresh() { if (!m_refreshEnabled) { + LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed..."); m_refreshEnabled = true; m_refreshCV.notify_one(); } @@ -741,6 +1347,7 @@ void WalletImpl::stopRefresh() void WalletImpl::pauseRefresh() { + LOG_PRINT_L2(__FUNCTION__ << ": refresh paused..."); // TODO synchronize access if (!m_refreshThreadDone) { m_refreshEnabled = false; @@ -748,4 +1355,76 @@ void WalletImpl::pauseRefresh() } +bool WalletImpl::isNewWallet() const +{ + // in case wallet created without daemon connection, closed and opened again, + // it's the same case as if it created from scratch, i.e. we need "fast sync" + // with the daemon (pull hashes instead of pull blocks). + // If wallet cache is rebuilt, creation height stored in .keys is used. + // Watch only wallet is a copy of an existing wallet. + return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_rebuildWalletCache) && !watchOnly(); +} + +bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit) +{ + if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit)) + return false; + + // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) + // If daemon isn't synced a calculated block height will be used instead + if (isNewWallet() && daemonSynced()) { + LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight()); + m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); + } + + if (m_rebuildWalletCache) + LOG_PRINT_L2(__FUNCTION__ << ": Rebuilding wallet cache, fast refresh until block " << m_wallet->get_refresh_from_block_height()); + + if (Utils::isAddressLocal(daemon_address)) { + this->setTrustedDaemon(true); + m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS; + } else { + this->setTrustedDaemon(false); + m_refreshIntervalMillis = DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS; + } + return true; +} + +bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) +{ + return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); +} + +bool WalletImpl::rescanSpent() +{ + clearStatus(); + if (!trustedDaemon()) { + m_status = Status_Error; + m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + return false; + } + try { + m_wallet->rescan_spent(); + } catch (const std::exception &e) { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + m_status = Status_Error; + m_errorString = e.what(); + return false; + } + return true; +} + + +void WalletImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const +{ + m_wallet->get_hard_fork_info(version, earliest_height); +} + +bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const +{ + return m_wallet->use_fork_rules(version,early_blocks); +} + } // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index d97a8f3b3..c376dd6c1 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -40,9 +40,11 @@ #include <boost/thread/condition_variable.hpp> -namespace Bitmonero { +namespace Monero { class TransactionHistoryImpl; class PendingTransactionImpl; +class UnsignedTransactionImpl; +class AddressBookImpl; struct Wallet2CallbackImpl; class WalletImpl : public Wallet @@ -52,8 +54,15 @@ public: ~WalletImpl(); bool create(const std::string &path, const std::string &password, const std::string &language); + bool createWatchOnly(const std::string &path, const std::string &password, + const std::string &language) const; bool open(const std::string &path, const std::string &password); bool recover(const std::string &path, const std::string &seed); + bool recoverFromKeys(const std::string &path, + const std::string &language, + const std::string &address_string, + const std::string &viewkey_string, + const std::string &spendkey_string = ""); bool close(); std::string seed() const; std::string getSeedLanguage() const; @@ -64,47 +73,74 @@ public: bool setPassword(const std::string &password); std::string address() const; std::string integratedAddress(const std::string &payment_id) const; + std::string privateViewKey() const; + std::string path() const; bool store(const std::string &path); std::string filename() const; std::string keysFilename() const; - bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit); - void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit); + bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = ""); bool connectToDaemon(); - bool connected() const; + ConnectionStatus connected() const; void setTrustedDaemon(bool arg); bool trustedDaemon() const; uint64_t balance() const; uint64_t unlockedBalance() const; uint64_t blockChainHeight() const; + uint64_t approximateBlockChainHeight() const; uint64_t daemonBlockChainHeight() const; + uint64_t daemonBlockChainTargetHeight() const; + bool synchronized() const; bool refresh(); void refreshAsync(); void setAutoRefreshInterval(int millis); int autoRefreshInterval() const; - - + void setRefreshFromBlockHeight(uint64_t refresh_from_block_height); + void setRecoveringFromSeed(bool recoveringFromSeed); + bool watchOnly() const; + bool rescanSpent(); + bool testnet() const {return m_wallet->testnet();} + void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const; + bool useForkRules(uint8_t version, int64_t early_blocks) const; PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - uint64_t amount, uint32_t mixin_count, + optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low); + virtual PendingTransaction * createSweepUnmixableTransaction(); + bool submitTransaction(const std::string &fileName); + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename); + bool exportKeyImages(const std::string &filename); + bool importKeyImages(const std::string &filename); virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; + virtual AddressBook * addressBook() const; virtual void setListener(WalletListener * l); virtual uint32_t defaultMixin() const; virtual void setDefaultMixin(uint32_t arg); + virtual bool setUserNote(const std::string &txid, const std::string ¬e); + virtual std::string getUserNote(const std::string &txid) const; + virtual std::string getTxKey(const std::string &txid) const; + virtual std::string signMessage(const std::string &message); + virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; + virtual void startRefresh(); + virtual void pauseRefresh(); + virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error); private: - void clearStatus(); + void clearStatus() const; void refreshThreadFunc(); void doRefresh(); - void startRefresh(); + bool daemonSynced() const; void stopRefresh(); - void pauseRefresh(); + bool isNewWallet() const; + bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit); private: friend class PendingTransactionImpl; + friend class UnsignedTransactionImpl; friend class TransactionHistoryImpl; + friend struct Wallet2CallbackImpl; + friend class AddressBookImpl; tools::wallet2 * m_wallet; mutable std::atomic<int> m_status; @@ -114,6 +150,7 @@ private: bool m_trustedDaemon; WalletListener * m_walletListener; Wallet2CallbackImpl * m_wallet2Callback; + AddressBookImpl * m_addressBook; // multi-threaded refresh stuff std::atomic<bool> m_refreshEnabled; @@ -126,11 +163,21 @@ private: boost::mutex m_refreshMutex2; boost::condition_variable m_refreshCV; boost::thread m_refreshThread; - + // flag indicating wallet is recovering from seed + // so it shouldn't be considered as new and pull blocks (slow-refresh) + // instead of pulling hashes (fast-refresh) + std::atomic<bool> m_recoveringFromSeed; + std::atomic<bool> m_synchronized; + std::atomic<bool> m_rebuildWalletCache; + // cache connection status to avoid unnecessary RPC calls + mutable std::atomic<bool> m_is_connected; + boost::optional<epee::net_utils::http::login> m_daemon_login{}; }; } // namespace +namespace Bitmonero = Monero; + #endif diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index ca83806ff..b2f947972 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,16 +31,33 @@ #include "wallet_manager.h" #include "wallet.h" +#include "common_defines.h" +#include "common/dns_utils.h" +#include "common/util.h" +#include "common/updates.h" +#include "version.h" +#include "net/http_client.h" #include <boost/filesystem.hpp> #include <boost/regex.hpp> +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" namespace epee { unsigned int g_test_dbg_lock_sleep = 0; } -namespace Bitmonero { +namespace { + template<typename Request, typename Response> + bool connect_and_invoke(const std::string& address, const std::string& path, const Request& request, Response& response) + { + epee::net_utils::http::http_simple_client client{}; + return client.set_server(address, boost::none) && epee::net_utils::invoke_http_json(path, request, response, client); + } +} + +namespace Monero { Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet) @@ -54,16 +71,37 @@ Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string { WalletImpl * wallet = new WalletImpl(testnet); wallet->open(path, password); + //Refresh addressBook + wallet->addressBook()->refresh(); return wallet; } -Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &memo, bool testnet) +Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &memo, bool testnet, uint64_t restoreHeight) { WalletImpl * wallet = new WalletImpl(testnet); + if(restoreHeight > 0){ + wallet->setRefreshFromBlockHeight(restoreHeight); + } wallet->recover(path, memo); return wallet; } +Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, + const std::string &language, + bool testnet, + uint64_t restoreHeight, + const std::string &addressString, + const std::string &viewKeyString, + const std::string &spendKeyString) +{ + WalletImpl * wallet = new WalletImpl(testnet); + if(restoreHeight > 0){ + wallet->setRefreshFromBlockHeight(restoreHeight); + } + wallet->recoverFromKeys(path, language, addressString, viewKeyString, spendKeyString); + return wallet; +} + bool WalletManagerImpl::closeWallet(Wallet *wallet) { WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); @@ -78,6 +116,12 @@ bool WalletManagerImpl::closeWallet(Wallet *wallet) bool WalletManagerImpl::walletExists(const std::string &path) { + bool keys_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + if(keys_file_exists){ + return true; + } return false; } @@ -85,10 +129,13 @@ bool WalletManagerImpl::walletExists(const std::string &path) std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) { std::vector<std::string> result; + boost::filesystem::path work_dir(path); + // return empty result if path doesn't exist + if(!boost::filesystem::is_directory(path)){ + return result; + } const boost::regex wallet_rx("(.*)\\.(keys)$"); // searching for <wallet_name>.keys files boost::filesystem::recursive_directory_iterator end_itr; // Default ctor yields past-the-end - boost::filesystem::path work_dir(path); - for (boost::filesystem::recursive_directory_iterator itr(path); itr != end_itr; ++itr) { // Skip if not a file if (!boost::filesystem::is_regular_file(itr->status())) @@ -116,11 +163,295 @@ std::string WalletManagerImpl::errorString() const return m_errorString; } -void WalletManagerImpl::setDaemonHost(const std::string &hostname) +void WalletManagerImpl::setDaemonAddress(const std::string &address) +{ + m_daemonAddress = address; +} + +bool WalletManagerImpl::connected(uint32_t *version) const +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_version"; + if (!connect_and_invoke(m_daemonAddress, "/json_rpc", req_t, resp_t)) + return false; + + if (version) + *version = resp_t.result.version; + return true; +} + +bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const +{ + error = ""; + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + error = tr("failed to parse txid"); + return false; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + if (txkey_text.size() < 64 || txkey_text.size() % 64) + { + error = tr("failed to parse tx key"); + return false; + } + crypto::secret_key tx_key; + cryptonote::blobdata tx_key_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data) || tx_key_data.size() != sizeof(crypto::hash)) + { + error = tr("failed to parse tx key"); + return false; + } + tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); + + bool testnet = address_text[0] != '4'; + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id; + if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text)) + { + error = tr("failed to parse address"); + return false; + } + + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + if (!connect_and_invoke(m_daemonAddress, "/gettransactions", req, res) || + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + { + error = tr("failed to get transaction from daemon"); + return false; + } + cryptonote::blobdata tx_data; + bool ok; + if (res.txs.size() == 1) + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) + { + error = tr("failed to parse transaction from daemon"); + return false; + } + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + { + error = tr("failed to validate transaction from daemon"); + return false; + } + if (tx_hash != txid) + { + error = tr("failed to get the right transaction from daemon"); + return false; + } + + crypto::key_derivation derivation; + if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + { + error = tr("failed to generate key derivation from supplied parameters"); + return false; + } + + received = 0; + try { + for (size_t n = 0; n < tx.vout.size(); ++n) + { + if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) + continue; + const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); + crypto::public_key pubkey; + derive_public_key(derivation, n, address.m_spend_public_key, pubkey); + if (pubkey == tx_out_to_key.key) + { + uint64_t amount; + if (tx.version == 1) + { + amount = tx.vout[n].amount; + } + else + { + try + { + rct::key Ctmp; + //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); + if (!r) + { + LOG_ERROR("Failed to generate key derivation to decode rct output " << n); + amount = 0; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + } + catch (...) { amount = 0; } + } + received += amount; + } + } + } + catch(const std::exception &e) + { + LOG_ERROR("error: " << e.what()); + error = std::string(tr("error: ")) + e.what(); + return false; + } + + if (received > 0) + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); + } + else + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid); + } + if (res.txs.front().in_pool) + { + height = 0; + } + else + { + height = res.txs.front().block_height; + } + + return true; +} + +uint64_t WalletManagerImpl::blockchainHeight() const +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + + if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + return 0; + return ires.height; +} + +uint64_t WalletManagerImpl::blockchainTargetHeight() const +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + + if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + return 0; + return ires.target_height >= ires.height ? ires.target_height : ires.height; +} + +uint64_t WalletManagerImpl::networkDifficulty() const +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + + if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + return 0; + return ires.difficulty; +} + +double WalletManagerImpl::miningHashRate() const +{ + cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; + cryptonote::COMMAND_RPC_MINING_STATUS::response mres; + + epee::net_utils::http::http_simple_client http_client; + if (!connect_and_invoke(m_daemonAddress, "/mining_status", mreq, mres)) + return 0.0; + if (!mres.active) + return 0.0; + return mres.speed; +} + +uint64_t WalletManagerImpl::blockTarget() const +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + + if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + return 0; + return ires.target; +} + +bool WalletManagerImpl::isMining() const +{ + cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; + cryptonote::COMMAND_RPC_MINING_STATUS::response mres; + + if (!connect_and_invoke(m_daemonAddress, "/mining_status", mreq, mres)) + return false; + return mres.active; +} + +bool WalletManagerImpl::startMining(const std::string &address, uint32_t threads, bool background_mining, bool ignore_battery) { + cryptonote::COMMAND_RPC_START_MINING::request mreq; + cryptonote::COMMAND_RPC_START_MINING::response mres; + + mreq.miner_address = address; + mreq.threads_count = threads; + mreq.ignore_battery = ignore_battery; + mreq.do_background_mining = background_mining; + + if (!connect_and_invoke(m_daemonAddress, "/start_mining", mreq, mres)) + return false; + return mres.status == CORE_RPC_STATUS_OK; +} + +bool WalletManagerImpl::stopMining() +{ + cryptonote::COMMAND_RPC_STOP_MINING::request mreq; + cryptonote::COMMAND_RPC_STOP_MINING::response mres; + + if (!connect_and_invoke(m_daemonAddress, "/stop_mining", mreq, mres)) + return false; + return mres.status == CORE_RPC_STATUS_OK; +} +std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool &dnssec_valid) const +{ + std::vector<std::string> addresses = tools::dns_utils::addresses_from_url(address, dnssec_valid); + if (addresses.empty()) + return ""; + return addresses.front(); } +std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, const std::string &subdir) +{ +#ifdef BUILD_TAG + static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); +#else + static const char buildtag[] = "source"; +#endif + + std::string version, hash; + MDEBUG("Checking for a new " << software << " version for " << buildtag); + if (!tools::check_updates(software, buildtag, version, hash)) + return std::make_tuple(false, "", "", "", ""); + + if (tools::vercmp(version.c_str(), MONERO_VERSION) > 0) + { + std::string user_url = tools::get_update_url(software, subdir, buildtag, version, true); + std::string auto_url = tools::get_update_url(software, subdir, buildtag, version, false); + MGINFO("Version " << version << " of " << software << " for " << buildtag << " is available: " << user_url << ", SHA256 hash " << hash); + return std::make_tuple(true, version, hash, user_url, auto_url); + } + return std::make_tuple(false, "", "", "", ""); +} ///////////////////// WalletManagerFactory implementation ////////////////////// @@ -130,7 +461,6 @@ WalletManager *WalletManagerFactory::getWalletManager() static WalletManagerImpl * g_walletManager = nullptr; if (!g_walletManager) { - epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_MAX); g_walletManager = new WalletManagerImpl(); } @@ -139,9 +469,16 @@ WalletManager *WalletManagerFactory::getWalletManager() void WalletManagerFactory::setLogLevel(int level) { - epee::log_space::log_singletone::get_set_log_detalisation_level(true, level); + mlog_set_log_level(level); +} + +void WalletManagerFactory::setLogCategories(const std::string &categories) +{ + mlog_set_log(categories.c_str()); } } + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 7585c1af7..033e8108f 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,7 +32,7 @@ #include "wallet/wallet2_api.h" #include <string> -namespace Bitmonero { +namespace Monero { class WalletManagerImpl : public WalletManager { @@ -40,17 +40,38 @@ public: Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet); Wallet * openWallet(const std::string &path, const std::string &password, bool testnet); - virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet); + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet, uint64_t restoreHeight); + virtual Wallet * createWalletFromKeys(const std::string &path, + const std::string &language, + bool testnet, + uint64_t restoreHeight, + const std::string &addressString, + const std::string &viewKeyString, + const std::string &spendKeyString = ""); virtual bool closeWallet(Wallet *wallet); bool walletExists(const std::string &path); std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; - void setDaemonHost(const std::string &hostname); + void setDaemonAddress(const std::string &address); + bool connected(uint32_t *version = NULL) const; + bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; + uint64_t blockchainHeight() const; + uint64_t blockchainTargetHeight() const; + uint64_t networkDifficulty() const; + double miningHashRate() const; + uint64_t blockTarget() const; + bool isMining() const; + bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true); + bool stopMining(); + std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const; private: WalletManagerImpl() {} friend struct WalletManagerFactory; + std::string m_daemonAddress; std::string m_errorString; }; } // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp new file mode 100644 index 000000000..03e1bbd98 --- /dev/null +++ b/src/wallet/node_rpc_proxy.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "node_rpc_proxy.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "common/json_util.h" +#include "storages/http_abstract_invoke.h" + +using namespace epee; + +namespace tools +{ + +static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); + +NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::mutex &mutex) + : m_http_client(http_client) + , m_daemon_rpc_mutex(mutex) + , m_height(0) + , m_height_time(0) + , m_earliest_height() + , m_dynamic_per_kb_fee_estimate(0) + , m_dynamic_per_kb_fee_estimate_cached_height(0) + , m_dynamic_per_kb_fee_estimate_grace_blocks(0) + , m_rpc_version(0) +{} + +void NodeRPCProxy::invalidate() +{ + m_height = 0; + m_height_time = 0; + for (size_t n = 0; n < 256; ++n) + m_earliest_height[n] = 0; + m_dynamic_per_kb_fee_estimate = 0; + m_dynamic_per_kb_fee_estimate_cached_height = 0; + m_dynamic_per_kb_fee_estimate_grace_blocks = 0; + m_rpc_version = 0; +} + +boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) +{ + const time_t now = time(NULL); + if (m_rpc_version == 0) + { + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_version"; + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get daemon RPC version"); + m_rpc_version = resp_t.result.version; + } + rpc_version = m_rpc_version; + return boost::optional<std::string>(); +} + +boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) +{ + const time_t now = time(NULL); + if (m_height == 0 || now >= m_height_time + 30) // re-cache every 30 seconds + { + cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res); + + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json("/getheight", req, res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, res.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, res.status, "Failed to get current blockchain height"); + m_height = res.height; + m_height_time = now; + } + height = m_height; + return boost::optional<std::string>(); +} + +void NodeRPCProxy::set_height(uint64_t h) +{ + m_height = h; +} + +boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) +{ + if (m_earliest_height[version] == 0) + { + epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "hard_fork_info"; + req_t.params.version = version; + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get hard fork status"); + m_earliest_height[version] = resp_t.result.earliest_height; + } + + earliest_height = m_earliest_height[version]; + return boost::optional<std::string>(); +} + +boost::optional<std::string> NodeRPCProxy::get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks, uint64_t &fee) +{ + uint64_t height; + + boost::optional<std::string> result = get_height(height); + if (result) + return result; + + if (m_dynamic_per_kb_fee_estimate_cached_height != height || m_dynamic_per_kb_fee_estimate_grace_blocks != grace_blocks) + { + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_fee_estimate"; + req_t.params.grace_blocks = grace_blocks; + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get fee estimate"); + m_dynamic_per_kb_fee_estimate = resp_t.result.fee; + m_dynamic_per_kb_fee_estimate_cached_height = height; + m_dynamic_per_kb_fee_estimate_grace_blocks = grace_blocks; + } + + fee = m_dynamic_per_kb_fee_estimate; + return boost::optional<std::string>(); +} + +} diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h new file mode 100644 index 000000000..02d1d8d93 --- /dev/null +++ b/src/wallet/node_rpc_proxy.h @@ -0,0 +1,65 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> +#include <boost/thread/mutex.hpp> +#include "include_base_utils.h" +#include "net/http_client.h" + +namespace tools +{ + +class NodeRPCProxy +{ +public: + NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::mutex &mutex); + + void invalidate(); + + boost::optional<std::string> get_rpc_version(uint32_t &version); + boost::optional<std::string> get_height(uint64_t &height); + void set_height(uint64_t h); + boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height); + boost::optional<std::string> get_dynamic_per_kb_fee_estimate(uint64_t grace_blocks, uint64_t &fee); + +private: + epee::net_utils::http::http_simple_client &m_http_client; + boost::mutex &m_daemon_rpc_mutex; + + uint64_t m_height; + time_t m_height_time; + uint64_t m_earliest_height[256]; + uint64_t m_dynamic_per_kb_fee_estimate; + uint64_t m_dynamic_per_kb_fee_estimate_cached_height; + uint64_t m_dynamic_per_kb_fee_estimate_grace_blocks; + uint32_t m_rpc_version; +}; + +} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ed4ab93de..9069789ca 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,32 +30,34 @@ #include <random> #include <tuple> -#include <boost/archive/binary_oarchive.hpp> -#include <boost/archive/binary_iarchive.hpp> - +#include <boost/format.hpp> +#include <boost/optional/optional.hpp> #include <boost/utility/value_init.hpp> #include "include_base_utils.h" using namespace epee; #include "cryptonote_config.h" #include "wallet2.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "wallet2_api.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "common/boost_serialization_helper.h" +#include "common/command_line.h" #include "profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" #include "cryptonote_protocol/blobdatatype.h" #include "mnemonics/electrum-words.h" -#include "common/dns_utils.h" +#include "common/i18n.h" #include "common/util.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include "common/json_util.h" #include "common/base58.h" +#include "common/scoped_message_writer.h" #include "ringct/rctSigs.h" extern "C" @@ -65,6 +67,9 @@ extern "C" } using namespace cryptonote; +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" + // used to choose when to stop adding outputs to a tx #define APPROXIMATE_INPUT_BYTES 80 @@ -74,6 +79,16 @@ using namespace cryptonote; // arbitrary, used to generate different hashes from the same input #define CHACHA8_KEY_TAIL 0x8c +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003" +#define SIGNED_TX_PREFIX "Monero signed tx set\003" + +#define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone +#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) + +#define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks + +#define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -82,8 +97,22 @@ using namespace cryptonote; ioservice.stop(); \ } while(0) +#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" + namespace { +// Create on-demand to prevent static initialization order fiasco issues. +struct options { + const command_line::arg_descriptor<std::string> daemon_address = {"daemon-address", tools::wallet2::tr("Use daemon instance at <host>:<port>"), ""}; + const command_line::arg_descriptor<std::string> daemon_host = {"daemon-host", tools::wallet2::tr("Use daemon instance at host <arg> instead of localhost"), ""}; + const command_line::arg_descriptor<std::string> password = {"password", tools::wallet2::tr("Wallet password (escape/quote as needed)"), "", true}; + const command_line::arg_descriptor<std::string> password_file = {"password-file", tools::wallet2::tr("Wallet password file"), "", true}; + const command_line::arg_descriptor<int> daemon_port = {"daemon-port", tools::wallet2::tr("Use daemon instance at port <arg> instead of 18081"), 0}; + const command_line::arg_descriptor<std::string> daemon_login = {"daemon-login", tools::wallet2::tr("Specify username[:password] for daemon RPC client"), "", true}; + const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; + const command_line::arg_descriptor<bool> restricted = {"restricted-rpc", tools::wallet2::tr("Restricts to view-only commands"), false}; +}; + void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) { keys_file = file_path; @@ -109,6 +138,294 @@ uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, ui return calculate_fee(fee_per_kb, blob.size(), fee_multiplier); } +std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts) +{ + const bool testnet = command_line::get_arg(vm, opts.testnet); + const bool restricted = command_line::get_arg(vm, opts.restricted); + + auto daemon_address = command_line::get_arg(vm, opts.daemon_address); + auto daemon_host = command_line::get_arg(vm, opts.daemon_host); + auto daemon_port = command_line::get_arg(vm, opts.daemon_port); + + if (!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port) + { + tools::fail_msg_writer() << tools::wallet2::tr("can't specify daemon host or port more than once"); + return nullptr; + } + + boost::optional<epee::net_utils::http::login> login{}; + if (command_line::has_arg(vm, opts.daemon_login)) + { + auto parsed = tools::login::parse( + command_line::get_arg(vm, opts.daemon_login), false, "Daemon client password" + ); + if (!parsed) + return nullptr; + + login.emplace(std::move(parsed->username), std::move(parsed->password).password()); + } + + if (daemon_host.empty()) + daemon_host = "localhost"; + + if (!daemon_port) + { + daemon_port = testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT; + } + + if (daemon_address.empty()) + daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); + + std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(testnet, restricted)); + wallet->init(std::move(daemon_address), std::move(login)); + return wallet; +} + +boost::optional<tools::password_container> get_password(const boost::program_options::variables_map& vm, const options& opts, const bool verify) +{ + if (command_line::has_arg(vm, opts.password) && command_line::has_arg(vm, opts.password_file)) + { + tools::fail_msg_writer() << tools::wallet2::tr("can't specify more than one of --password and --password-file"); + return boost::none; + } + + if (command_line::has_arg(vm, opts.password)) + { + return tools::password_container{command_line::get_arg(vm, opts.password)}; + } + + if (command_line::has_arg(vm, opts.password_file)) + { + std::string password; + bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, opts.password_file), + password); + if (!r) + { + tools::fail_msg_writer() << tools::wallet2::tr("the password file specified could not be read"); + return boost::none; + } + + // Remove line breaks the user might have inserted + boost::trim_right_if(password, boost::is_any_of("\r\n")); + return {tools::password_container{std::move(password)}}; + } + + return tools::wallet2::password_prompt(verify); +} + +std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts) +{ + const bool testnet = command_line::get_arg(vm, opts.testnet); + + /* GET_FIELD_FROM_JSON_RETURN_ON_ERROR Is a generic macro that can return + false. Gcc will coerce this into unique_ptr(nullptr), but clang correctly + fails. This large wrapper is for the use of that macro */ + std::unique_ptr<tools::wallet2> wallet; + const auto do_generate = [&]() -> bool { + std::string buf; + if (!epee::file_io_utils::load_file_to_string(json_file, buf)) { + tools::fail_msg_writer() << tools::wallet2::tr("Failed to load file ") << json_file; + return false; + } + + rapidjson::Document json; + if (json.Parse(buf.c_str()).HasParseError()) { + tools::fail_msg_writer() << tools::wallet2::tr("Failed to parse JSON"); + return false; + } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true, 0); + const int current_version = 1; + if (field_version > current_version) { + tools::fail_msg_writer() << boost::format(tools::wallet2::tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; + return false; + } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false, 0); + const bool recover = field_scan_from_height_found; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false, std::string()); + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false, std::string()); + crypto::secret_key viewkey; + if (field_viewkey_found) + { + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) + { + tools::fail_msg_writer() << tools::wallet2::tr("failed to parse view key secret key"); + return false; + } + viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); + return false; + } + } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false, std::string()); + crypto::secret_key spendkey; + if (field_spendkey_found) + { + cryptonote::blobdata spendkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) + { + tools::fail_msg_writer() << tools::wallet2::tr("failed to parse spend key secret key"); + return false; + } + spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(spendkey, pkey)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); + return false; + } + } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false, std::string()); + std::string old_language; + crypto::secret_key recovery_key; + bool restore_deterministic_wallet = false; + if (field_seed_found) + { + if (!crypto::ElectrumWords::words_to_bytes(field_seed, recovery_key, old_language)) + { + tools::fail_msg_writer() << tools::wallet2::tr("Electrum-style word list failed verification"); + return false; + } + restore_deterministic_wallet = true; + } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); + + // compatibility checks + if (!field_seed_found && !field_viewkey_found) + { + tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified"); + return false; + } + if (field_seed_found && (field_viewkey_found || field_spendkey_found)) + { + tools::fail_msg_writer() << tools::wallet2::tr("Both Electrum-style word list and private key(s) specified"); + return false; + } + + // if an address was given, we check keys against it, and deduce the spend + // public key if it was not given + if (field_address_found) + { + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 new_payment_id; + if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address)) + { + tools::fail_msg_writer() << tools::wallet2::tr("invalid address"); + return false; + } + if (field_viewkey_found) + { + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); + return false; + } + if (address.m_view_public_key != pkey) { + tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address"); + return false; + } + } + if (field_spendkey_found) + { + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(spendkey, pkey)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); + return false; + } + if (address.m_spend_public_key != pkey) { + tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address"); + return false; + } + } + } + + const bool deprecated_wallet = restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || + crypto::ElectrumWords::get_is_old_style_seed(field_seed)); + if (deprecated_wallet) { + tools::fail_msg_writer() << tools::wallet2::tr("Cannot create deprecated wallets from JSON"); + return false; + } + + wallet.reset(make_basic(vm, opts).release()); + wallet->set_refresh_from_block_height(field_scan_from_height); + + try + { + if (!field_seed.empty()) + { + wallet->generate(field_filename, field_password, recovery_key, recover, false); + } + else + { + cryptonote::account_public_address address; + if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); + return false; + } + + if (field_spendkey.empty()) + { + // if we have an addres but no spend key, we can deduce the spend public key + // from the address + if (field_address_found) + { + cryptonote::account_public_address address2; + bool has_payment_id; + crypto::hash8 new_payment_id; + get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address); + address.m_spend_public_key = address2.m_spend_public_key; + } + wallet->generate(field_filename, field_password, address, viewkey); + } + else + { + if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { + tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); + return false; + } + wallet->generate(field_filename, field_password, address, spendkey, viewkey); + } + } + } + catch (const std::exception& e) + { + tools::fail_msg_writer() << tools::wallet2::tr("failed to generate new wallet: ") << e.what(); + return false; + } + return true; + }; + + if (do_generate()) + { + return wallet; + } + return nullptr; +} + +static void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) +{ + // no error + if (!status) + return; + + // empty string -> not connection + THROW_WALLET_EXCEPTION_IF(status->empty(), tools::error::no_connection_to_daemon, method); + + THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method); + THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, *status); +} + } //namespace namespace tools @@ -116,11 +433,88 @@ namespace tools // for now, limit to 30 attempts. TODO: discuss a good number to limit to. const size_t MAX_SPLIT_ATTEMPTS = 30; +constexpr const std::chrono::seconds wallet2::rpc_timeout; +const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } + +bool wallet2::has_testnet_option(const boost::program_options::variables_map& vm) +{ + return command_line::get_arg(vm, options().testnet); +} + +void wallet2::init_options(boost::program_options::options_description& desc_params) +{ + const options opts{}; + command_line::add_arg(desc_params, opts.daemon_address); + command_line::add_arg(desc_params, opts.daemon_host); + command_line::add_arg(desc_params, opts.password); + command_line::add_arg(desc_params, opts.password_file); + command_line::add_arg(desc_params, opts.daemon_port); + command_line::add_arg(desc_params, opts.daemon_login); + command_line::add_arg(desc_params, opts.testnet); + command_line::add_arg(desc_params, opts.restricted); +} + +boost::optional<password_container> wallet2::password_prompt(const bool new_password) +{ + auto pwd_container = tools::password_container::prompt( + new_password, (new_password ? tr("Enter new wallet password") : tr("Wallet password")) + ); + if (!pwd_container) + { + tools::fail_msg_writer() << tr("failed to read wallet password"); + } + return pwd_container; +} + +std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file) +{ + const options opts{}; + return generate_from_json(json_file, vm, opts); +} + +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( + const boost::program_options::variables_map& vm, const std::string& wallet_file) +{ + const options opts{}; + auto pwd = get_password(vm, opts, false); + if (!pwd) + { + return {nullptr, password_container{}}; + } + auto wallet = make_basic(vm, opts); + if (wallet) + { + wallet->load(wallet_file, pwd->password()); + } + return {std::move(wallet), std::move(*pwd)}; +} + +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm) +{ + const options opts{}; + auto pwd = get_password(vm, opts, true); + if (!pwd) + { + return {nullptr, password_container{}}; + } + return {make_basic(vm, opts), std::move(*pwd)}; +} + +std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm) +{ + const options opts{}; + return make_basic(vm, opts); +} + //---------------------------------------------------------------------------------------------------- -void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit) +bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit) { + if(m_http_client.is_connected()) + m_http_client.disconnect(); m_upper_transaction_size_limit = upper_transaction_size_limit; - m_daemon_address = daemon_address; + m_daemon_address = std::move(daemon_address); + m_daemon_login = std::move(daemon_login); + return m_http_client.set_server(get_daemon_address(), get_daemon_login()); } //---------------------------------------------------------------------------------------------------- bool wallet2::is_deterministic() const @@ -173,21 +567,23 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_spent(transfer_details &td, uint64_t height) +void wallet2::set_spent(size_t idx, uint64_t height) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting SPENT at " << height << ": ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = true; td.m_spent_height = height; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_unspent(transfer_details &td) +void wallet2::set_unspent(size_t idx) { + transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Setting UNSPENT: ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); td.m_spent = false; td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const +void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const { if (o.target.type() != typeid(txout_to_key)) { @@ -195,7 +591,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp LOG_ERROR("wrong type id in transaction out"); return; } - received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i); + received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i); if(received) { money_transfered = o.amount; // may be 0 for ringct outputs @@ -207,7 +603,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp error = false; } //---------------------------------------------------------------------------------------------------- -static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask) +static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask) { crypto::key_derivation derivation; bool r = crypto::generate_key_derivation(pub, sec, derivation); @@ -238,28 +634,20 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki) { - class lazy_txid_getter - { - const cryptonote::transaction &tx; - crypto::hash lazy_txid; - bool computed; - public: - lazy_txid_getter(const transaction &tx): tx(tx), computed(false) {} - const crypto::hash &operator()() - { - if (!computed) - { - lazy_txid = cryptonote::get_transaction_hash(tx); - computed = true; - } - return lazy_txid; - } - } txid(tx); + if (!cryptonote::generate_key_image_helper(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +{ + // In this function, tx (probably) only contains the base information + // (that is, the prunable stuff may or may not be included) if (!miner_tx) - process_unconfirmed(tx, height); + process_unconfirmed(txid, tx, height); std::vector<size_t> outs; uint64_t tx_money_got_in_outs = 0; crypto::public_key tx_pub_key = null_pkey; @@ -268,18 +656,23 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s if(!parse_tx_extra(tx.extra, tx_extra_fields)) { // Extra may only be partially parsed, it's OK if tx_extra_fields contains public key - LOG_PRINT_L0("Transaction extra has unsupported format: " << txid()); + LOG_PRINT_L0("Transaction extra has unsupported format: " << txid); } // Don't try to extract tx public key if tx has no ouputs - if (!tx.vout.empty()) + size_t pk_index = 0; + while (!tx.vout.empty()) { + // if tx.vout is not empty, we loop through all tx pubkeys + tx_extra_pub_key pub_key_field; - if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field)) + if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) { - LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid()); + if (pk_index > 1) + break; + LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid); if(0 != m_callback) - m_callback->on_skip_transaction(height, tx); + m_callback->on_skip_transaction(height, txid, tx); return; } @@ -291,6 +684,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s std::deque<uint64_t> amount(tx.vout.size()); std::deque<rct::key> mask(tx.vout.size()); int threads = tools::get_max_concurrency(); + const cryptonote::account_keys& keys = m_account.get_keys(); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { // assume coinbase isn't for us @@ -299,7 +695,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, received, money_transfered, error); if (error) { r = false; @@ -309,14 +705,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s // this assumes that the miner tx pays a single address if (received) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + wallet_generate_key_image_helper(keys, tx_pub_key, 0, in_ephemeral[0], ki[0]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(0); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]); } amount[0] = money_transfered; @@ -332,14 +727,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -352,14 +746,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -380,13 +773,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -400,14 +792,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -422,7 +813,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, received, money_transfered, error); if (error) { r = false; @@ -432,14 +823,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { if (received) { - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } amount[i] = money_transfered; @@ -462,17 +852,17 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s " not match with daemon response size=" + std::to_string(o_indices.size())); } - BOOST_FOREACH(size_t o, outs) + for(size_t o: outs) { THROW_WALLET_EXCEPTION_IF(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())); - auto kit = m_key_images.find(ki[o]); - THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(), - error::wallet_internal_error, std::string("Unexpected transfer index from key image: ") - + "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + auto kit = m_pub_keys.find(in_ephemeral[o].pub); + THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), + error::wallet_internal_error, std::string("Unexpected transfer index from public key: ") + + "got " + (kit == m_pub_keys.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size())); - if (kit == m_key_images.end()) + if (kit == m_pub_keys.end()) { if (!pool) { @@ -482,9 +872,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_internal_output_index = o; td.m_global_output_index = o_indices[o]; td.m_tx = (const cryptonote::transaction_prefix&)tx; - td.m_txid = txid(); + td.m_txid = txid; td.m_key_image = ki[o]; + td.m_key_image_known = !m_watch_only; td.m_amount = tx.vout[o].amount; + td.m_pk_index = pk_index - 1; if (td.m_amount == 0) { td.m_mask = mask[o]; @@ -501,23 +893,24 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - set_unspent(td); + set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); + m_pub_keys[in_ephemeral[o].pub] = m_transfers.size()-1; + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, tx, td.m_amount); + m_callback->on_money_received(height, txid, tx, td.m_amount); } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(ki) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " " << print_money(m_transfers[kit->second].amount()) << ", received output ignored"); } else { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(ki) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one @@ -530,8 +923,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_internal_output_index = o; td.m_global_output_index = o_indices[o]; td.m_tx = (const cryptonote::transaction_prefix&)tx; - td.m_txid = txid(); + td.m_txid = txid; td.m_amount = tx.vout[o].amount; + td.m_pk_index = pk_index - 1; if (td.m_amount == 0) { td.m_mask = mask[o]; @@ -548,12 +942,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images"); + THROW_WALLET_EXCEPTION_IF(td.get_public_key() != in_ephemeral[o].pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, tx, td.m_amount); + m_callback->on_money_received(height, txid, tx, td.m_amount); } } } @@ -562,7 +956,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s uint64_t tx_money_spent_in_ins = 0; // check all outputs for spending (compare key images) - BOOST_FOREACH(auto& in, tx.vin) + for(auto& in: tx.vin) { if(in.type() != typeid(cryptonote::txin_to_key)) continue; @@ -578,17 +972,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s std::string(", expected ") + print_money(td.amount())); } amount = td.amount(); - LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); tx_money_spent_in_ins += amount; - set_spent(td, height); - if (0 != m_callback) - m_callback->on_money_spent(height, tx, amount, tx); + if (!pool) + { + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid); + set_spent(it->second, height); + if (0 != m_callback) + m_callback->on_money_spent(height, txid, tx, amount, tx); + } } } if (tx_money_spent_in_ins > 0) { - process_outgoing(tx, height, ts, tx_money_spent_in_ins, tx_money_got_in_outs); + process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, tx_money_got_in_outs); } uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; @@ -634,25 +1031,27 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } payment_details payment; - payment.m_tx_hash = txid(); + payment.m_tx_hash = txid; payment.m_amount = received; payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; payment.m_timestamp = ts; - if (pool) + if (pool) { m_unconfirmed_payments.emplace(payment_id, payment); + if (0 != m_callback) + m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount); + } else m_payments.emplace(payment_id, payment); LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t height) +void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height) { if (m_unconfirmed_txs.empty()) return; - crypto::hash txid = get_transaction_hash(tx); auto unconf_it = m_unconfirmed_txs.find(txid); if(unconf_it != m_unconfirmed_txs.end()) { if (store_tx_info()) { @@ -668,9 +1067,8 @@ void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t he } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received) +void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received) { - crypto::hash txid = get_transaction_hash(tx); std::pair<std::unordered_map<crypto::hash, confirmed_transfer_details>::iterator, bool> entry = m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details())); // fill with the info we know, some info might already be there if (entry.second) @@ -684,6 +1082,17 @@ void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t heigh else entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee; entry.first->second.m_change = received; + + std::vector<tx_extra_field> tx_extra_fields; + if(parse_tx_extra(tx.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); + } + } } entry.first->second.m_block_height = height; entry.first->second.m_timestamp = ts; @@ -702,16 +1111,19 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); - BOOST_FOREACH(auto& txblob, bche.txs) + THROW_WALLET_EXCEPTION_IF(bche.txs.size() != b.tx_hashes.size(), error::wallet_internal_error, "Wrong amount of transactions for block"); + size_t idx = 0; + for (const auto& txblob: bche.txs) { cryptonote::transaction tx; - bool r = parse_and_validate_tx_from_blob(txblob, tx); + bool r = parse_and_validate_tx_base_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); + process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); + ++idx; } 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"); @@ -767,9 +1179,37 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); req.block_ids = short_chain_history; + uint32_t rpc_version; + boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); + // no error + if (!!result) + { + // empty string -> not connection + THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); + THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); + if (*result != CORE_RPC_STATUS_OK) + { + MDEBUG("Cannot determined daemon RPC version, not asking for pruned blocks"); + req.prune = false; // old daemon + } + } + else + { + if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 7)) + { + MDEBUG("Daemon is recent enough, asking for pruned blocks"); + req.prune = true; + } + else + { + MDEBUG("Daemon is too old, not asking for pruned blocks"); + req.prune = false; + } + } + req.start_height = start_height; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getblocks.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); @@ -791,7 +1231,7 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, req.start_height = start_height; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/gethashes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gethashes.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gethashes.bin"); @@ -875,7 +1315,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: } else { - BOOST_FOREACH(auto& bl_entry, blocks) + for(auto& bl_entry: blocks) { cryptonote::block bl; bool r = cryptonote::parse_and_validate_block_from_blob(bl_entry.block, bl); @@ -949,25 +1389,28 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei //---------------------------------------------------------------------------------------------------- void wallet2::update_pool_state() { + MDEBUG("update_pool_state start"); + // get the pool state - cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res; + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response res; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/get_transaction_pool", req, res, m_http_client, 200000); + bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool"); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + MDEBUG("update_pool_state got pool"); // remove any pending tx that's not in the pool std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); while (it != m_unconfirmed_txs.end()) { - const std::string txid = epee::string_tools::pod_to_hex(it->first); + const crypto::hash &txid = it->first; bool found = false; - for (auto it2: res.transactions) + for (const auto &it2: res.tx_hashes) { - if (it2.id_hash == txid) + if (it2 == txid) { found = true; break; @@ -997,12 +1440,13 @@ void wallet2::update_pool_state() if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) { txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); - for (auto &td: m_transfers) + for (size_t i = 0; i < m_transfers.size(); ++i) { + const transfer_details &td = m_transfers[i]; if (td.m_key_image == tx_in_to_key.k_image) { LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); break; } } @@ -1011,16 +1455,17 @@ void wallet2::update_pool_state() } } } + MDEBUG("update_pool_state done first loop"); // remove pool txes to us that aren't in the pool anymore std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { - const std::string txid = string_tools::pod_to_hex(uit->first); + const crypto::hash &txid = uit->second.m_tx_hash; bool found = false; - for (auto it2: res.transactions) + for (const auto &it2: res.tx_hashes) { - if (it2.id_hash == txid) + if (it2 == txid) { found = true; break; @@ -1029,102 +1474,122 @@ void wallet2::update_pool_state() auto pit = uit++; if (!found) { + MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool"); m_unconfirmed_payments.erase(pit); } } + MDEBUG("update_pool_state done second loop"); - // add new pool txes to us - for (auto it: res.transactions) + // gather txids of new pool txes to us + std::vector<crypto::hash> txids; + for (const auto &txid: res.tx_hashes) { - cryptonote::blobdata txid_data; - if(epee::string_tools::parse_hexstr_to_binbuff(it.id_hash, txid_data)) + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + LOG_PRINT_L2("Already seen " << txid << ", skipped"); + continue; + } + if (m_unconfirmed_payments.find(txid) == m_unconfirmed_payments.end()) { - const crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - if (m_unconfirmed_payments.find(txid) == m_unconfirmed_payments.end()) + LOG_PRINT_L1("Found new pool tx: " << txid); + bool found = false; + for (const auto &i: m_unconfirmed_txs) { - LOG_PRINT_L1("Found new pool tx: " << txid); - bool found = false; - for (const auto &i: m_unconfirmed_txs) + if (i.first == txid) { - if (i.first == txid) - { - found = true; - break; - } + found = true; + break; } - if (!found) + } + if (!found) + { + // not one of those we sent ourselves + txids.push_back(txid); + } + else + { + LOG_PRINT_L1("We sent that one"); + } + } + else + { + LOG_PRINT_L1("Already saw that one, it's for us"); + } + } + + // get those txes + if (!txids.empty()) + { + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + for (const auto &txid: txids) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + MDEBUG("asking for " << txids.size() << " transactions"); + req.decode_as_json = false; + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + MDEBUG("Got " << r << " and " << res.status); + if (r && res.status == CORE_RPC_STATUS_OK) + { + if (res.txs.size() == txids.size()) + { + size_t n = 0; + for (const auto &txid: txids) { - // not one of those we sent ourselves - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(it.id_hash); - req.decode_as_json = false; - m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client, 200000); - m_daemon_rpc_mutex.unlock(); - if (r && res.status == CORE_RPC_STATUS_OK) + // might have just been put in a block + if (res.txs[n].in_pool) { - if (res.txs.size() == 1) + cryptonote::transaction tx; + cryptonote::blobdata bd; + crypto::hash tx_hash, tx_prefix_hash; + if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[n].as_hex, bd)) { - // might have just been put in a block - if (res.txs[0].in_pool) + if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - cryptonote::transaction tx; - cryptonote::blobdata bd; - crypto::hash tx_hash, tx_prefix_hash; - if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd)) + if (tx_hash == txid) { - if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) + process_new_transaction(txid, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + m_scanned_pool_txs[0].insert(txid); + if (m_scanned_pool_txs[0].size() > 5000) { - if (tx_hash == txid) - { - process_new_transaction(tx, std::vector<uint64_t>(), 0, time(NULL), false, true); - } - else - { - LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); - } - } - else - { - LOG_PRINT_L0("failed to validate transaction from daemon"); + std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]); + m_scanned_pool_txs[0].clear(); } } else { - LOG_PRINT_L0("Failed to parse tx " << txid); + LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); } } else { - LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + LOG_PRINT_L0("failed to validate transaction from daemon"); } } else { - LOG_PRINT_L0("Expected 1 tx, got " << res.txs.size()); + LOG_PRINT_L0("Failed to parse tx " << txid); } } else { - LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status); + LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); } - } - else - { - LOG_PRINT_L1("We sent that one"); + ++n; } } else { - LOG_PRINT_L1("Already saw that one"); + LOG_PRINT_L0("Expected " << txids.size() << " tx(es), got " << res.txs.size()); } } else { - LOG_PRINT_L0("Failed to parse txid"); + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << res.status); } } + MDEBUG("update_pool_state end"); } //---------------------------------------------------------------------------------------------------- void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history) @@ -1155,7 +1620,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, } } current_index = blocks_start_height; - BOOST_FOREACH(auto& bl_id, hashes) + for(auto& bl_id: hashes) { if(current_index >= m_blockchain.size()) { @@ -1182,6 +1647,30 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, } } + +bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description) +{ + wallet2::address_book_row a; + a.m_address = address; + a.m_payment_id = payment_id; + a.m_description = description; + + auto old_size = m_address_book.size(); + m_address_book.push_back(a); + if(m_address_book.size() == old_size+1) + return true; + return false; +} + +bool wallet2::delete_address_book_row(std::size_t row_id) { + if(m_address_book.size() <= row_id) + return false; + + m_address_book.erase(m_address_book.begin()+row_id); + + return true; +} + //---------------------------------------------------------------------------------------------------- void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money) { @@ -1211,6 +1700,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // and then fall through to regular refresh processing } + // If stop() is called during fast refresh we don't need to continue + if(!m_run.load(std::memory_order_relaxed)) + return; pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices); // always reset start_height to 0 to force short_chain_ history to be used on // subsequent pulls in this refresh. @@ -1230,8 +1722,11 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re process_blocks(blocks_start_height, blocks, o_indices, added_blocks); blocks_fetched += added_blocks; pull_thread.join(); - if(!added_blocks) + if(blocks_start_height == next_blocks_start_height) + { + m_node_rpc_proxy.set_height(m_blockchain.size()); break; + } // switch to the new blocks from the daemon blocks_start_height = next_blocks_start_height; @@ -1266,7 +1761,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re try { - update_pool_state(); + // If stop() is called we don't need to check pending transactions + if(m_run.load(std::memory_order_relaxed)) + update_pool_state(); } catch (...) { @@ -1301,7 +1798,7 @@ void wallet2::detach_blockchain(uint64_t height) if (td.m_spent && td.m_spent_height >= height) { LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); - set_unspent(td); + set_unspent(i); } } @@ -1313,7 +1810,13 @@ void wallet2::detach_blockchain(uint64_t height) auto it_ki = m_key_images.find(m_transfers[i].m_key_image); THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); m_key_images.erase(it_ki); - ++transfers_detached; + } + + for(size_t i = i_start; i!= m_transfers.size();i++) + { + auto it_pk = m_pub_keys.find(m_transfers[i].get_public_key()); + THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); + m_pub_keys.erase(it_pk); } m_transfers.erase(it, m_transfers.end()); @@ -1350,6 +1853,7 @@ bool wallet2::clear() m_blockchain.clear(); m_transfers.clear(); m_key_images.clear(); + m_pub_keys.clear(); m_unconfirmed_txs.clear(); m_payments.clear(); m_tx_keys.clear(); @@ -1395,6 +1899,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); + value2.SetInt(m_print_ring_members ? 1 :0); + json.AddMember("print_ring_members", value2, json.GetAllocator()); + value2.SetInt(m_store_tx_info ? 1 :0); json.AddMember("store_tx_info", value2, json.GetAllocator()); @@ -1413,6 +1920,24 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p value2.SetUint64(m_refresh_from_block_height); json.AddMember("refresh_height", value2, json.GetAllocator()); + value2.SetInt(m_confirm_missing_payment_id ? 1 :0); + json.AddMember("confirm_missing_payment_id", value2, json.GetAllocator()); + + value2.SetInt(m_ask_password ? 1 :0); + json.AddMember("ask_password", value2, json.GetAllocator()); + + value2.SetUint(m_min_output_count); + json.AddMember("min_output_count", value2, json.GetAllocator()); + + value2.SetUint64(m_min_output_value); + json.AddMember("min_output_value", value2, json.GetAllocator()); + + value2.SetInt(cryptonote::get_default_decimal_point()); + json.AddMember("default_decimal_point", value2, json.GetAllocator()); + + value2.SetInt(m_merge_destinations ? 1 :0); + json.AddMember("merge_destinations", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -1474,10 +1999,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa is_old_file_format = true; m_watch_only = false; m_always_confirm_transfers = false; + m_print_ring_members = false; m_default_mixin = 0; m_default_priority = 0; m_auto_refresh = true; m_refresh_type = RefreshType::RefreshDefault; + m_confirm_missing_payment_id = true; + m_ask_password = true; + m_min_output_count = 0; + m_min_output_value = 0; + m_merge_destinations = false; } else { @@ -1501,8 +2032,10 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); m_watch_only = field_watch_only; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, false); - m_always_confirm_transfers = field_always_confirm_transfers_found && field_always_confirm_transfers; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); + m_always_confirm_transfers = field_always_confirm_transfers; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); + m_print_ring_members = field_print_ring_members; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true); m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0)); @@ -1533,8 +2066,19 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default"); } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0); - if (field_refresh_height_found) - m_refresh_from_block_height = field_refresh_height; + m_refresh_from_block_height = field_refresh_height; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_missing_payment_id, int, Int, false, true); + m_confirm_missing_payment_id = field_confirm_missing_payment_id; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, int, Int, false, true); + m_ask_password = field_ask_password; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_decimal_point, int, Int, false, CRYPTONOTE_DISPLAY_DECIMAL_POINT); + cryptonote::set_default_decimal_point(field_default_decimal_point); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, min_output_count, uint32_t, Uint, false, 0); + m_min_output_count = field_min_output_count; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, min_output_value, uint64_t, Uint64, false, 0); + m_min_output_value = field_min_output_value; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, merge_destinations, int, Int, false, false); + m_merge_destinations = field_merge_destinations; } const cryptonote::account_keys& keys = m_account.get_keys(); @@ -1590,7 +2134,8 @@ bool wallet2::verify_password(const std::string& password) const const cryptonote::account_keys& keys = account_data_check.get_keys(); 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(!m_watch_only) + r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -1618,11 +2163,24 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; + if(m_refresh_from_block_height == 0 && !recover){ + // Wallets created offline don't know blockchain height. + // Set blockchain height calculated from current date/time + // -1 month for fluctuations in block time and machine date/time setup. + // avg seconds per block + const int seconds_per_block = DIFFICULTY_TARGET_V2; + // ~num blocks per month + const uint64_t blocks_per_month = 60*60*24*30/seconds_per_block; + uint64_t approx_blockchain_height = get_approximate_blockchain_height(); + if(approx_blockchain_height > 0) { + m_refresh_from_block_height = approx_blockchain_height - blocks_per_month; + } + } bool r = store_keys(m_keys_file, password, false); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) LOG_PRINT_RED_L0("String with address text not saved"); + if(!r) MERROR("String with address text not saved"); cryptonote::block b; generate_genesis(b); @@ -1657,7 +2215,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) LOG_PRINT_RED_L0("String with address text not saved"); + if(!r) MERROR("String with address text not saved"); cryptonote::block b; generate_genesis(b); @@ -1692,7 +2250,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) LOG_PRINT_RED_L0("String with address text not saved"); + if(!r) MERROR("String with address text not saved"); cryptonote::block b; generate_genesis(b); @@ -1711,7 +2269,7 @@ void wallet2::rewrite(const std::string& wallet_name, const std::string& passwor prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); - bool r = store_keys(m_keys_file, password, false); + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); } /*! @@ -1785,42 +2343,49 @@ bool wallet2::parse_payment_id(const std::string& payment_id_str, crypto::hash& return false; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_default_decimal_point(unsigned int decimal_point) +{ + cryptonote::set_default_decimal_point(decimal_point); +} +//---------------------------------------------------------------------------------------------------- +unsigned int wallet2::get_default_decimal_point() const +{ + return cryptonote::get_default_decimal_point(); +} +//---------------------------------------------------------------------------------------------------- bool wallet2::prepare_file_names(const std::string& file_path) { do_prepare_file_names(file_path, m_keys_file, m_wallet_file); return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(bool *same_version) +bool wallet2::check_connection(uint32_t *version, uint32_t timeout) { boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); if(!m_http_client.is_connected()) { - net_utils::http::url_content u; - net_utils::parse_url(m_daemon_address, u); - - if(!u.port) - { - u.port = m_testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT; - } - - if (!m_http_client.connect(u.host, std::to_string(u.port), WALLET_RCP_CONNECTION_TIMEOUT)) + m_node_rpc_proxy.invalidate(); + if (!m_http_client.connect(std::chrono::milliseconds(timeout))) return false; } - if (same_version) + if (version) { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_version"; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); - if (!r || resp_t.result.status != CORE_RPC_STATUS_OK) - *same_version = false; + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client); + if(!r) { + *version = 0; + return false; + } + if (resp_t.result.status != CORE_RPC_STATUS_OK) + *version = 0; else - *same_version = resp_t.result.version == CORE_RPC_VERSION; + *version = resp_t.result.version; } return true; @@ -1884,16 +2449,38 @@ void wallet2::load(const std::string& wallet_, const std::string& password) std::stringstream iss; iss << cache_data; - boost::archive::binary_iarchive ar(iss); - ar >> *this; + try { + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (...) + { + LOG_PRINT_L0("Failed to open portable binary, trying unportable"); + boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + iss.str(""); + iss << cache_data; + boost::archive::binary_iarchive ar(iss); + ar >> *this; + } } catch (...) { LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); std::stringstream iss; iss << buf; - boost::archive::binary_iarchive ar(iss); - ar >> *this; + try { + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (...) + { + LOG_PRINT_L0("Failed to open portable binary, trying unportable"); + boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + iss.str(""); + iss << buf; + boost::archive::binary_iarchive ar(iss); + ar >> *this; + } } THROW_WALLET_EXCEPTION_IF( m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key || @@ -1918,11 +2505,16 @@ void wallet2::load(const std::string& wallet_, const std::string& password) } //---------------------------------------------------------------------------------------------------- void wallet2::check_genesis(const crypto::hash& genesis_hash) const { - std::string what("Genesis block missmatch. You probably use wallet without testnet flag with blockchain from test network or vice versa"); + std::string what("Genesis block mismatch. You probably use wallet without testnet flag with blockchain from test network or vice versa"); THROW_WALLET_EXCEPTION_IF(genesis_hash != m_blockchain[0], error::wallet_internal_error, what); } //---------------------------------------------------------------------------------------------------- +std::string wallet2::path() const +{ + return m_wallet_file; +} +//---------------------------------------------------------------------------------------------------- void wallet2::store() { store_to("", ""); @@ -1962,7 +2554,7 @@ void wallet2::store_to(const std::string &path, const std::string &password) } // preparing wallet data std::stringstream oss; - boost::archive::binary_oarchive ar(oss); + boost::archive::portable_binary_oarchive ar(oss); ar << *this; wallet2::cache_file_data cache_file_data = boost::value_initialized<wallet2::cache_file_data>(); @@ -2022,7 +2614,7 @@ void wallet2::store_to(const std::string &path, const std::string &password) uint64_t wallet2::unlocked_balance() const { uint64_t amount = 0; - BOOST_FOREACH(const transfer_details& td, m_transfers) + for(const transfer_details& td: m_transfers) if(!td.m_spent && is_transfer_unlocked(td)) amount += td.amount(); @@ -2032,12 +2624,12 @@ uint64_t wallet2::unlocked_balance() const uint64_t wallet2::balance() const { uint64_t amount = 0; - BOOST_FOREACH(auto& td, m_transfers) + for(auto& td: m_transfers) if(!td.m_spent) amount += td.amount(); - BOOST_FOREACH(auto& utx, m_unconfirmed_txs) + for(auto& utx: m_unconfirmed_txs) if (utx.second.m_state != wallet2::unconfirmed_transfer_details::failed) amount+= utx.second.m_change; @@ -2097,44 +2689,50 @@ void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2: //---------------------------------------------------------------------------------------------------- void wallet2::rescan_spent() { - std::vector<std::string> key_images; - - // make a list of key images for all our outputs - for (size_t i = 0; i < m_transfers.size(); ++i) + // This is RPC call that can take a long time if there are many outputs, + // so we call it several times, in stripes, so we don't time out spuriously + std::vector<int> spent_status; + spent_status.reserve(m_transfers.size()); + const size_t chunk_size = 1000; + for (size_t start_offset = 0; start_offset < m_transfers.size(); start_offset += chunk_size) { - const transfer_details& td = m_transfers[i]; - key_images.push_back(string_tools::pod_to_hex(td.m_key_image)); + const size_t n_outputs = std::min<size_t>(chunk_size, m_transfers.size() - start_offset); + MDEBUG("Calling is_key_image_spent on " << start_offset << " - " << (start_offset + n_outputs - 1) << ", out of " << m_transfers.size()); + COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + for (size_t n = start_offset; n < start_offset + n_outputs; ++n) + req.key_images.push_back(string_tools::pod_to_hex(m_transfers[n].m_key_image)); + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status); + THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, + "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + + std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs)); + std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status)); } - COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req); - COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - req.key_images = key_images; - m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/is_key_image_spent", req, daemon_resp, m_http_client, 200000); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status); - THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != key_images.size(), error::wallet_internal_error, - "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + - std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(key_images.size())); - // update spent status for (size_t i = 0; i < m_transfers.size(); ++i) { transfer_details& td = m_transfers[i]; - if (td.m_spent != (daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) + // a view wallet may not know about key images + if (!td.m_key_image_known) + continue; + if (td.m_spent != (spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { if (td.m_spent) { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as unspent, it was marked as spent"); - set_unspent(td); + set_unspent(i); td.m_spent_height = 0; } else { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as spent, it was marked as unspent"); - set_spent(td, td.m_spent_height); + set_spent(i, td.m_spent_height); // unknown height, if this gets reorged, it might still be missed } } @@ -2227,6 +2825,19 @@ namespace vec.pop_back(); return res; } + + template<typename T> + void pop_if_present(std::vector<T>& vec, T e) + { + for (size_t i = 0; i < vec.size(); ++i) + { + if (e == vec[i]) + { + pop_index (vec, i); + return; + } + } + } } //---------------------------------------------------------------------------------------------------- // This returns a handwavy estimation of how much two outputs are related @@ -2260,7 +2871,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -2268,9 +2879,9 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (const auto &i: selected_transfers) + for (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { - float r = get_output_relatedness(candidate, *i); + float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) { relatedness = r; @@ -2288,20 +2899,37 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve if (relatedness == best_relatedness) candidates.push_back(n); } - size_t idx = crypto::rand<size_t>() % candidates.size(); + + // we have all the least related outputs in candidates, so we can pick either + // the smallest, or a random one, depending on request + size_t idx; + if (smallest) + { + idx = 0; + for (size_t n = 0; n < candidates.size(); ++n) + { + const transfer_details &td = transfers[unused_indices[candidates[n]]]; + if (td.amount() < transfers[unused_indices[candidates[idx]]].amount()) + idx = n; + } + } + else + { + idx = crypto::rand<size_t>() % candidates.size(); + } return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const { - return pop_best_value_from(m_transfers, unused_indices, selected_transfers); + return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest); } //---------------------------------------------------------------------------------------------------- // Select random input sources for transaction. // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; while (found_money < needed_money && !unused_transfers_indices.empty()) @@ -2309,7 +2937,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); transfer_container::iterator it = m_transfers.begin() + idx; - selected_transfers.push_back(it); + selected_transfers.push_back(idx); found_money += it->amount(); } @@ -2323,6 +2951,7 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo utd.m_amount_out = 0; for (const auto &d: dests) utd.m_amount_out += d.amount; + utd.m_amount_out += change_amount; // dests does not contain change utd.m_change = change_amount; utd.m_sent_time = time(NULL); utd.m_tx = (const cryptonote::transaction_prefix&)tx; @@ -2395,74 +3024,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( return retVal; } } // anonymous namespace - -/** - * @brief gets a monero address from the TXT record of a DNS entry - * - * gets the monero address from the TXT record of the DNS entry associated - * with <url>. If this lookup fails, or the TXT record does not contain an - * XMR address in the correct format, returns an empty string. <dnssec_valid> - * will be set true or false according to whether or not the DNS query passes - * DNSSEC validation. - * - * @param url the url to look up - * @param dnssec_valid return-by-reference for DNSSEC status of query - * - * @return a monero address (as a string) or an empty string - */ -std::vector<std::string> wallet2::addresses_from_url(const std::string& url, bool& dnssec_valid) -{ - std::vector<std::string> addresses; - // get txt records - bool dnssec_available, dnssec_isvalid; - std::string oa_addr = tools::DNSResolver::instance().get_dns_format_from_oa_address(url); - auto records = tools::DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid); - - // TODO: update this to allow for conveying that dnssec was not available - if (dnssec_available && dnssec_isvalid) - { - dnssec_valid = true; - } - else dnssec_valid = false; - - // for each txt record, try to find a monero address in it. - for (auto& rec : records) - { - std::string addr = address_from_txt_record(rec); - if (addr.size()) - { - addresses.push_back(addr); - } - } - - return addresses; -} - //---------------------------------------------------------------------------------------------------- -// TODO: parse the string in a less stupid way, probably with regex -std::string wallet2::address_from_txt_record(const std::string& s) -{ - // make sure the txt record has "oa1:xmr" and find it - auto pos = s.find("oa1:xmr"); - - // search from there to find "recipient_address=" - pos = s.find("recipient_address=", pos); - - pos += 18; // move past "recipient_address=" - - // find the next semicolon - auto pos2 = s.find(";", pos); - if (pos2 != std::string::npos) - { - // length of address == 95, we can at least validate that much here - if (pos2 - pos == 95) - { - return s.substr(pos, 95); - } - } - return std::string(); -} - crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; @@ -2487,6 +3049,24 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const } return payment_id; } + +crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const +{ + crypto::hash8 payment_id8 = null_hash8; + std::vector<tx_extra_field> tx_extra_fields; + if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) + return payment_id8; + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); + } + } + return payment_id8; +} + //---------------------------------------------------------------------------------------------------- // take a pending tx and actually send it to the daemon void wallet2::commit_tx(pending_tx& ptx) @@ -2499,12 +3079,19 @@ void wallet2::commit_tx(pending_tx& ptx) req.do_not_relay = false; COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000); + bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction"); THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason); + // sanity checks + for (size_t idx: ptx.selected_transfers) + { + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, + "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx)); + } + txid = get_transaction_hash(ptx.tx); crypto::hash payment_id = cryptonote::null_hash; std::vector<cryptonote::tx_destination_entry> dests; @@ -2513,8 +3100,8 @@ void wallet2::commit_tx(pending_tx& ptx) { payment_id = get_payment_id(ptx); dests = ptx.dests; - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - amount_in += it->amount(); + for(size_t idx: ptx.selected_transfers) + amount_in += m_transfers[idx].amount(); } add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); if (store_tx_info()) @@ -2524,13 +3111,13 @@ void wallet2::commit_tx(pending_tx& ptx) LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + for(size_t idx: ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } //fee includes dust if dust policy specified it. - LOG_PRINT_L0("Transaction successfully sent. <" << txid << ">" << ENDL + LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL << "Balance: " << print_money(balance()) << ENDL << "Unlocked: " << print_money(unlocked_balance()) << ENDL @@ -2544,26 +3131,310 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) commit_tx(ptx); } } +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) +{ + LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); + unsigned_tx_set txs; + for (auto &tx: ptx_vector) + { + tx_construction_data construction_data = tx.construction_data; + // Short payment id is encrypted with tx_key. + // Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID + // Get decrypted payment id from pending_tx + crypto::hash8 payment_id = get_short_payment_id(tx); + if (payment_id != null_hash8) + { + // Remove encrypted + remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); + // Add decrypted + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce)) + { + LOG_ERROR("Failed to add decrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Decrypted payment ID: " << payment_id); + } + // Save tx construction_data to unsigned_tx_set + txs.txes.push_back(construction_data); + } + + txs.transfers = m_transfers; + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << txs; + } + catch (...) + { + return false; + } + LOG_PRINT_L2("Saving unsigned tx data: " << oss.str()); + return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str()); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) +{ + std::string s; + boost::system::error_code errcode; -uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const + if (!boost::filesystem::exists(unsigned_filename, errcode)) + { + LOG_PRINT_L0("File " << unsigned_filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(unsigned_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << unsigned_filename); + return false; + } + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << unsigned_filename); + return false; + } + s = s.substr(magiclen); + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + } + catch (...) + { + LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + return false; + } + LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions"); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func) +{ + unsigned_tx_set exported_txs; + if(!load_unsigned_tx(unsigned_filename, exported_txs)) + return false; + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + return sign_tx(exported_txs, signed_filename, txs); +} + +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs) +{ + import_outputs(exported_txs.transfers); + + // sign the transactions + signed_tx_set signed_txes; + for (size_t n = 0; n < exported_txs.txes.size(); ++n) + { + const tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1)); + signed_txes.ptx.push_back(pending_tx()); + tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); + crypto::secret_key tx_key; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, sd.splitted_dsts, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); + // we don't test tx size, because we don't know the current limit, due to not having a blockchain, + // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, + // and if we really go over limit, the daemon will reject when it gets submitted. Chances are it's + // OK anyway since it was generated in the first place, and rerolling should be within a few bytes. + + // normally, the tx keys are saved in commit_tx, when the tx is actually sent to the daemon. + // we can't do that here since the tx will be sent from the compromised wallet, which we don't want + // to see that info, so we save it here + if (store_tx_info()) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + m_tx_keys.insert(std::make_pair(txid, tx_key)); + } + + std::string key_images; + bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.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; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, ptx.tx); + + ptx.key_images = key_images; + ptx.fee = 0; + for (const auto &i: sd.sources) ptx.fee += i.amount; + for (const auto &i: sd.splitted_dsts) ptx.fee -= i.amount; + ptx.dust = 0; + ptx.dust_added_to_fee = false; + ptx.change_dts = sd.change_dts; + ptx.selected_transfers = sd.selected_transfers; + ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet + ptx.dests = sd.dests; + ptx.construction_data = sd; + + txs.push_back(ptx); + } + + // add key images + signed_txes.key_images.resize(m_transfers.size()); + for (size_t i = 0; i < m_transfers.size(); ++i) + { + if (!m_transfers[i].m_key_image_known) + LOG_PRINT_L0("WARNING: key image not known in signing wallet at index " << i); + signed_txes.key_images[i] = m_transfers[i].m_key_image; + } + + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << signed_txes; + } + catch(...) + { + return false; + } + LOG_PRINT_L3("Saving signed tx data: " << oss.str()); + return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str()); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) +{ + std::string s; + boost::system::error_code errcode; + signed_tx_set signed_txs; + + if (!boost::filesystem::exists(signed_filename, errcode)) + { + LOG_PRINT_L0("File " << signed_filename << " does not exist: " << errcode); + return false; + } + + if (!epee::file_io_utils::load_file_to_string(signed_filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << signed_filename); + return false; + } + const size_t magiclen = strlen(SIGNED_TX_PREFIX); + if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << signed_filename); + return false; + } + s = s.substr(magiclen); + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> signed_txs; + } + catch (...) + { + LOG_PRINT_L0("Failed to parse data from " << signed_filename); + return false; + } + LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); + for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + + if (accept_func && !accept_func(signed_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + // import key images + if (signed_txs.key_images.size() > m_transfers.size()) + { + LOG_PRINT_L1("More key images returned that we know outputs for"); + return false; + } + for (size_t i = 0; i < signed_txs.key_images.size(); ++i) + { + transfer_details &td = m_transfers[i]; + if (td.m_key_image_known && td.m_key_image != signed_txs.key_images[i]) + LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one"); + td.m_key_image = signed_txs.key_images[i]; + m_key_images[m_transfers[i].m_key_image] = i; + td.m_key_image_known = true; + m_pub_keys[m_transfers[i].get_public_key()] = i; + } + + ptx = signed_txs.ptx; + + return true; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const { static const uint64_t old_multipliers[3] = {1, 2, 3}; static const uint64_t new_multipliers[3] = {1, 20, 166}; + static const uint64_t newer_multipliers[4] = {1, 4, 20, 166}; - // 0 -> default (here, x1) + // 0 -> default (here, x1 till fee algorithm 2, x4 from it) if (priority == 0) priority = m_default_priority; if (priority == 0) - priority = 1; + { + if (fee_algorithm >= 2) + priority = 2; + else + priority = 1; + } - // 1 to 3 are allowed as priorities - if (priority >= 1 && priority <= 3) - return (use_new_fee ? new_multipliers : old_multipliers)[priority-1]; + // 1 to 3/4 are allowed as priorities + uint32_t max_priority = (fee_algorithm >= 2) ? 4 : 3; + if (priority >= 1 && priority <= max_priority) + { + switch (fee_algorithm) + { + case 0: return old_multipliers[priority-1]; + case 1: return new_multipliers[priority-1]; + case 2: return newer_multipliers[priority-1]; + default: THROW_WALLET_EXCEPTION_IF (true, error::invalid_priority); + } + } THROW_WALLET_EXCEPTION_IF (false, error::invalid_priority); return 1; } +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_dynamic_per_kb_fee_estimate() +{ + uint64_t fee; + boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_per_kb_fee_estimate(FEE_ESTIMATE_GRACE_BLOCKS, fee); + if (!result) + return fee; + LOG_PRINT_L1("Failed to query per kB fee, using " << print_money(FEE_PER_KB)); + return FEE_PER_KB; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_per_kb_fee() +{ + bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 1); + if (!use_dyn_fee) + return FEE_PER_KB; + return get_dynamic_per_kb_fee_estimate(); +} +//---------------------------------------------------------------------------------------------------- +int wallet2::get_fee_algorithm() +{ + // changes at v3 and v5 + if (use_fork_rules(5, 0)) + return 2; + if (use_fork_rules(3, -720 * 14)) + return 1; + return 0; +} //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -2571,11 +3442,10 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const // transactions will be required std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) { - const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, trusted_daemon); + const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true, trusted_daemon); - const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; - const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); + const uint64_t fee_per_kb = get_per_kb_fee(); + const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); // failsafe split attempt counter size_t attempt_count = 0; @@ -2613,9 +3483,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto ptx_vector.push_back(ptx); // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + for(size_t idx: ptx.selected_transfers) { - set_spent(*it, 0); + set_spent(idx, 0); } } @@ -2625,9 +3495,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + for(size_t idx2: ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2644,9 +3514,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + for(size_t idx2: ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2663,9 +3533,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto for (auto & ptx : ptx_vector) { // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + for(size_t idx2: ptx.selected_transfers) { - set_unspent(*it2); + set_unspent(idx2); } } @@ -2674,8 +3544,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } } -template<typename entry> -void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -2688,13 +3557,14 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_output_histogram"; - for(auto it: selected_transfers) - req_t.params.amounts.push_back(it->is_rct() ? 0 : it->amount()); + for(size_t idx: selected_transfers) + req_t.params.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); std::sort(req_t.params.amounts.begin(), req_t.params.amounts.end()); auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); req_t.params.unlocked = true; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + req_t.params.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); @@ -2705,31 +3575,49 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count); // generate output indices to request - COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); - COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - for(transfer_container::iterator it: selected_transfers) + size_t num_selected_transfers = 0; + for(size_t idx: selected_transfers) { - const uint64_t amount = it->is_rct() ? 0 : it->amount(); + ++num_selected_transfers; + const transfer_details &td = m_transfers[idx]; + const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set<uint64_t> seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. - uint64_t num_outs = 0; + uint64_t num_outs = 0, num_recent_outs = 0; for (auto he: resp_t.result.histogram) { if (he.amount == amount) { - num_outs = he.instances; + LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " + << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); + num_outs = he.unlocked_instances; + num_recent_outs = he.recent_instances; break; } } - LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount)); + LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, - "histogram reports no outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); + "histogram reports no unlocked outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); + THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, + "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + + // X% of those outs are to be taken from recent outputs + size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO; + if (recent_outputs_count == 0) + recent_outputs_count = 1; // ensure we have at least one, if possible + if (recent_outputs_count > num_recent_outs) + recent_outputs_count = num_recent_outs; + if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) + --recent_outputs_count; // if the real out is recent, pick one less recent fake out + LOG_PRINT_L1("Using " << recent_outputs_count << " recent outputs"); if (num_outs <= requested_outputs_count) { @@ -2745,8 +3633,9 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr { // start with real one uint64_t num_found = 1; - seen_indices.emplace(it->m_global_output_index); - req.outputs.push_back({amount, it->m_global_output_index}); + seen_indices.emplace(td.m_global_output_index); + req.outputs.push_back({amount, td.m_global_output_index}); + LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount)); // while we still need more mixins while (num_found < requested_outputs_count) @@ -2759,13 +3648,29 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr // return to the top of the loop and try again, otherwise add it to the // list of output indices we've seen. - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - uint64_t i = (uint64_t)(frac*num_outs); - // just in case rounding up to 1 occurs after sqrt - if (i == num_outs) - --i; + uint64_t i; + if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_recent_outs) + num_outs - num_recent_outs; + // just in case rounding up to 1 occurs after calc + if (i == num_outs) + --i; + LOG_PRINT_L2("picking " << i << " as recent"); + } + else + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_outs); + // just in case rounding up to 1 occurs after calc + if (i == num_outs) + --i; + LOG_PRINT_L2("picking " << i << " as triangular"); + } if (seen_indices.count(i)) continue; @@ -2778,7 +3683,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr // sort the subsection, to ensure the daemon doesn't know wich output is ours std::sort(req.outputs.begin() + start, req.outputs.end(), - [](const COMMAND_RPC_GET_OUTPUTS::out &a, const COMMAND_RPC_GET_OUTPUTS::out &b) { return a.index < b.index; }); + [](const get_outputs_out &a, const get_outputs_out &b) { return a.index < b.index; }); } for (auto i: req.outputs) @@ -2786,7 +3691,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr // get the keys for those m_daemon_rpc_mutex.lock(); - r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_outs.bin", req, daemon_resp, m_http_client, 200000); + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); @@ -2797,16 +3702,34 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; - outs.reserve(selected_transfers.size()); - for(transfer_container::iterator it: selected_transfers) + outs.reserve(num_selected_transfers); + for(size_t idx: selected_transfers) { - size_t requested_outputs_count = base_requested_outputs_count + (it->is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); - outs.push_back(std::vector<entry>()); + const transfer_details &td = m_transfers[idx]; + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); + outs.push_back(std::vector<get_outs_entry>()); outs.back().reserve(fake_outputs_count + 1); - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + + // make sure the real outputs we asked for are really included, along + // with the correct key and mask: this guards against an active attack + // where the node sends dummy data for all outputs, and we then send + // the real one, which the node can then tell from the fake outputs, + // as it has different data than the dummy data it had sent earlier + bool real_out_found = false; + for (size_t n = 0; n < requested_outputs_count; ++n) + { + size_t i = base + n; + if (req.outputs[i].index == td.m_global_output_index) + if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) + if (daemon_resp.outs[i].mask == mask) + real_out_found = true; + } + THROW_WALLET_EXCEPTION_IF(!real_out_found, error::wallet_internal_error, + "Daemon response did not include the requested real output"); // pick real out first (it will be sorted when done) - outs.back().push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); // then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution @@ -2816,12 +3739,12 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr order[n] = n; std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>())); - LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(it->is_rct() ? 0 : it->amount())); + LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(td.is_rct() ? 0 : td.amount())); for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) { size_t i = base + order[o]; - LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << it->m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); - if (req.outputs[i].index == it->m_global_output_index) // don't re-add real one + LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); + if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one continue; if (!daemon_resp.outs[i].unlocked) // don't add locked outs continue; @@ -2832,12 +3755,12 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } if (outs.back().size() < fake_outputs_count + 1) { - scanty_outs[it->is_rct() ? 0 : it->amount()] = outs.back().size(); + scanty_outs[td.is_rct() ? 0 : td.amount()] = outs.back().size(); } else { // sort the subsection, so any spares are reset in order - std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return std::get<0>(a) < std::get<0>(b); }); + std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); }); } base += requested_outputs_count; } @@ -2845,18 +3768,20 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<tr } else { - for (transfer_container::iterator it: selected_transfers) + for (size_t idx: selected_transfers) { - std::vector<entry> v; - const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); - v.push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); + const transfer_details &td = m_transfers[idx]; + std::vector<get_outs_entry> v; + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); outs.push_back(v); } } } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2869,7 +3794,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t - BOOST_FOREACH(auto& dt, dsts) + for(auto& dt: dsts) { THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); needed_money += dt.amount; @@ -2878,27 +3803,27 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + for(size_t idx: selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); - typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry; - std::vector<std::vector<entry>> outs; - get_outs(outs, selected_transfers, fake_outputs_count); // may throw + if (outs.empty()) + get_outs(outs, selected_transfers, fake_outputs_count); // may throw //prepare inputs + LOG_PRINT_L2("preparing outputs"); typedef cryptonote::tx_source_entry::output_entry tx_output_entry; size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste keys (fake and real) @@ -2927,12 +3852,13 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); src.real_output = it_to_replace - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; detail::print_source_entry(src); ++out_index; } + LOG_PRINT_L2("outputs prepared"); cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); if (needed_money < found_money) @@ -2944,18 +3870,20 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent std::vector<cryptonote::tx_destination_entry> splitted_dsts, dust_dsts; uint64_t dust = 0; destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust_dsts); - BOOST_FOREACH(auto& d, dust_dsts) { + for(auto& d: dust_dsts) { THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < d.amount, error::wallet_internal_error, "invalid dust value: dust = " + std::to_string(d.amount) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); } - BOOST_FOREACH(auto& d, dust_dsts) { + for(auto& d: dust_dsts) { if (!dust_policy.add_to_fee) splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust)); dust += d.amount; } crypto::secret_key tx_key; + LOG_PRINT_L2("constructing tx"); bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key); + LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); @@ -2983,9 +3911,19 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.splitted_dsts = splitted_dsts; + ptx.construction_data.selected_transfers = selected_transfers; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; + ptx.construction_data.dests = dsts; + LOG_PRINT_L2("transfer_selected done"); } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2994,11 +3932,14 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); uint64_t needed_money = fee; - LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); + LOG_PRINT_L2("transfer_selected_rct: starting with fee " << print_money (needed_money)); + LOG_PRINT_L0("selected transfers: "); + for (auto t: selected_transfers) + LOG_PRINT_L2(" " << t); // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t - BOOST_FOREACH(auto& dt, dsts) + for(auto& dt: dsts) { THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); needed_money += dt.amount; @@ -3007,26 +3948,26 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) + for(size_t idx: selected_transfers) { - found_money += it->amount(); + found_money += m_transfers[idx].amount(); } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); - typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry; - std::vector<std::vector<entry>> outs; - get_outs(outs, selected_transfers, fake_outputs_count); // may throw + if (outs.empty()) + get_outs(outs, selected_transfers, fake_outputs_count); // may throw //prepare inputs + LOG_PRINT_L2("preparing outputs"); size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = td.is_rct(); //paste mixin transaction @@ -3055,29 +3996,45 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); src.real_output = it_to_replace - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; src.mask = td.m_mask; detail::print_source_entry(src); ++out_index; } + LOG_PRINT_L2("outputs prepared"); // we still keep a copy, since we want to keep dsts free of change for user feedback purposes std::vector<cryptonote::tx_destination_entry> splitted_dsts = dsts; cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); - if (needed_money < found_money) + change_dts.amount = found_money - needed_money; + if (change_dts.amount == 0) + { + // If the change is 0, send it to a random address, to avoid confusing + // the sender with a 0 amount output. We send a 0 amount in order to avoid + // letting the destination be able to work out which of the inputs is the + // real one in our rings + LOG_PRINT_L2("generating dummy address for 0 change"); + cryptonote::account_base dummy; + dummy.generate(); + change_dts.addr = dummy.get_keys().m_account_address; + LOG_PRINT_L2("generated dummy address for 0 change"); + } + else { change_dts.addr = m_account.get_keys().m_account_address; - change_dts.amount = found_money - needed_money; - splitted_dsts.push_back(change_dts); } + splitted_dsts.push_back(change_dts); crypto::secret_key tx_key; + LOG_PRINT_L2("constructing tx"); bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key, true); + LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); + LOG_PRINT_L2("gathering key images"); 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 { @@ -3086,6 +4043,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry return true; }); THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); + LOG_PRINT_L2("gathered key images"); ptx.key_images = key_images; ptx.fee = fee; @@ -3096,6 +4054,15 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.splitted_dsts = splitted_dsts; + ptx.construction_data.selected_transfers = selected_transfers; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = true; + ptx.construction_data.dests = dsts; + LOG_PRINT_L2("transfer_selected_rct done"); } static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) @@ -3143,12 +4110,20 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) return size; } -std::vector<size_t> wallet2::pick_prefered_rct_inputs(uint64_t needed_money) const +static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs) +{ + if (use_rct) + return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1); + else + return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES; +} + +std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) const { std::vector<size_t> picks; float current_output_relatdness = 1.0f; - LOG_PRINT_L2("pick_prefered_rct_inputs: needed_money " << print_money(needed_money)); + LOG_PRINT_L2("pick_preferred_rct_inputs: needed_money " << print_money(needed_money)); // try to find a rct input of enough size for (size_t i = 0; i < m_transfers.size(); ++i) @@ -3203,6 +4178,60 @@ std::vector<size_t> wallet2::pick_prefered_rct_inputs(uint64_t needed_money) con return picks; } +bool wallet2::should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const +{ + if (!use_rct) + return false; + if (n_transfers > 1) + return false; + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) + return false; + // we want at least one free rct output to avoid a corner case where + // we'd choose a non rct output which doesn't have enough "siblings" + // value-wise on the chain, and thus can't be mixed + bool found = false; + for (auto i: unused_dust_indices) + { + if (m_transfers[i].is_rct()) + { + found = true; + break; + } + } + if (!found) for (auto i: unused_transfers_indices) + { + if (m_transfers[i].is_rct()) + { + found = true; + break; + } + } + if (!found) + return false; + return true; +} + +std::vector<size_t> wallet2::get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const +{ + std::vector<size_t> indices; + for (size_t n: unused_dust_indices) + if (m_transfers[n].is_rct()) + indices.push_back(n); + for (size_t n: unused_transfers_indices) + if (m_transfers[n].is_rct()) + indices.push_back(n); + return indices; +} + +static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &transfers, const std::vector<size_t> &indices, uint64_t threshold) +{ + uint32_t count = 0; + for (size_t idx: indices) + if (transfers[idx].amount() >= threshold) + ++count; + return count; +} + // Another implementation of transaction creation that is hopefully better // While there is anything left to pay, it goes through random outputs and tries // to fill the next destination/amount. If it fully fills it, it will use the @@ -3225,19 +4254,29 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; size_t bytes; - void add(const account_public_address &addr, uint64_t amount) { - std::vector<cryptonote::tx_destination_entry>::iterator i; - i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); }); - if (i == dsts.end()) - dsts.push_back(tx_destination_entry(amount,addr)); - else + void add(const account_public_address &addr, uint64_t amount, unsigned int original_output_index, bool merge_destinations) { + if (merge_destinations) + { + std::vector<cryptonote::tx_destination_entry>::iterator i; + i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); }); + if (i == dsts.end()) + dsts.push_back(tx_destination_entry(0,addr)); i->amount += amount; + } + else + { + THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error, "original_output_index too large"); + if (original_output_index == dsts.size()) + dsts.push_back(tx_destination_entry(0,addr)); + THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &addr, sizeof(addr)), error::wallet_internal_error, "Mismatched destination address"); + dsts[original_output_index].amount += amount; + } } }; std::vector<TX> txes; @@ -3246,9 +4285,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); const bool use_rct = use_fork_rules(4, 0); - const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; - const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); + const uint64_t fee_per_kb = get_per_kb_fee(); + const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); @@ -3256,7 +4294,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t needed_money = 0; - BOOST_FOREACH(auto& dt, dsts) + for(auto& dt: dsts) { THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); needed_money += dt.amount; @@ -3281,6 +4319,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); + // early out if we know we can't make it anyway + // we could also check for being within FEE_PER_KB, but if the fee calculation + // ever changes, this might be missed, so let this go through + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(), error::not_enough_money, + unlocked_balance(), needed_money, 0); + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) return std::vector<wallet2::pending_tx>(); @@ -3291,12 +4335,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp accumulated_change = 0; adding_fee = false; needed_fee = 0; + std::vector<std::vector<tools::wallet2::get_outs_entry>> outs; // for rct, since we don't see the amounts, we will try to make all transactions // look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as // this prevents linking to another by provenance analysis, but two is ok if we // try to pick outputs not from the same block. We will get two outputs, one for // the destination, and one for change. + LOG_PRINT_L2("checking preferred"); std::vector<size_t> prefered_inputs; uint64_t rct_outs_needed = 2 * (fake_outs_count + 1); rct_outs_needed += 100; // some fudge factor since we don't know how many are locked @@ -3305,37 +4351,84 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); - prefered_inputs = pick_prefered_rct_inputs(needed_money + estimated_fee); + prefered_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee); if (!prefered_inputs.empty()) { string s; - for (auto i: prefered_inputs) s += print_money(m_transfers[i].amount()) + " "; + for (auto i: prefered_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); } } - - // while we have something to send - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { + LOG_PRINT_L2("done checking preferred"); + + // while: + // - we have something to send + // - or we need to gather more fee + // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2) + unsigned int original_output_index = 0; + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), unused_transfers_indices, unused_dust_indices)) { TX &tx = txes.back(); + LOG_PRINT_L2("Start of loop with " << unused_transfers_indices.size() << " " << unused_dust_indices.size()); + LOG_PRINT_L2("unused_transfers_indices:"); + for (auto t: unused_transfers_indices) + LOG_PRINT_L2(" " << t); + LOG_PRINT_L2("unused_dust_indices:"); + for (auto t: unused_dust_indices) + LOG_PRINT_L2(" " << t); + LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount)); + LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct); + // if we need to spend money and don't have any left, we fail if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { LOG_PRINT_L2("No more outputs to choose from"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) // This could be more clever, but maybe at the cost of making probabilistic inferences easier - size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !unused_transfers_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : pop_best_value(unused_dust_indices, tx.selected_transfers); + size_t idx; + if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { + // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too + std::vector<size_t> indices = get_only_rct(unused_dust_indices, unused_transfers_indices); + idx = pop_best_value(indices, tx.selected_transfers, true); + + // we might not want to add it if it's a large output and we don't have many left + if (m_transfers[idx].amount() >= m_min_output_value) { + if (get_count_above(m_transfers, unused_transfers_indices, m_min_output_value) < m_min_output_count) { + LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(m_min_output_value) << ", not adding"); + break; + } + } + + // since we're trying to add a second output which is not strictly needed, + // we only add it if it's unrelated enough to the first one + float relatedness = get_output_relatedness(m_transfers[idx], m_transfers[tx.selected_transfers.front()]); + if (relatedness > SECOND_OUTPUT_RELATEDNESS_THRESHOLD) + { + LOG_PRINT_L2("Second output was not strictly needed, and relatedness " << relatedness << ", not adding"); + break; + } + pop_if_present(unused_transfers_indices, idx); + pop_if_present(unused_dust_indices, idx); + } else if (!prefered_inputs.empty()) { + idx = pop_back(prefered_inputs); + pop_if_present(unused_transfers_indices, idx); + pop_if_present(unused_dust_indices, idx); + } else + idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers); const transfer_details &td = m_transfers[idx]; - LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); + LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; + // clear any fake outs we'd already gathered, since we'll need a new set + outs.clear(); + if (adding_fee) { LOG_PRINT_L2("We need more fee, adding it to fee"); @@ -3343,22 +4436,23 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << " for " << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, dsts[0].amount); + tx.add(dsts[0].addr, dsts[0].amount, original_output_index, m_merge_destinations); available_amount -= dsts[0].amount; dsts[0].amount = 0; pop_index(dsts, 0); + ++original_output_index; } - if (available_amount > 0 && !dsts.empty()) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, available_amount); + tx.add(dsts[0].addr, available_amount, original_output_index, m_merge_destinations); dsts[0].amount -= available_amount; available_amount = 0; } @@ -3375,11 +4469,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - size_t estimated_rct_tx_size; - if (use_rct) - estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); - else - estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES; + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()); try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); } @@ -3392,15 +4482,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); if (needed_fee > available_for_fee && dsts[0].amount > 0) @@ -3433,13 +4523,19 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - test_tx, test_ptx); - else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); - txBlob = t_serializable_object_to_blob(test_ptx.tx); + do { + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + txBlob = t_serializable_object_to_blob(test_ptx.tx); + needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); + LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + " fee and " << print_money(test_ptx.change_dts.amount) << " change"); + } while (needed_fee > test_ptx.fee); + LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -3461,7 +4557,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (adding_fee) { LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); } LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << @@ -3472,8 +4568,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3485,13 +4581,36 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp return ptx_vector; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; + const bool use_rct = use_fork_rules(4, 0); + + // gather all our dust and non dust outputs + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + { + if (below == 0 || td.amount() < below) + { + if (td.is_rct() || is_valid_decomposed_amount(td.amount())) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + } + } + } + + return create_transactions_from(address, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); +} + +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +{ uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -3500,24 +4619,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono std::vector<TX> txes; uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - const bool use_rct = use_fork_rules(4, 0); + std::vector<std::vector<get_outs_entry>> outs; - const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; - const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); + const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); + const uint64_t fee_per_kb = get_per_kb_fee(); + const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - // gather all our dust and non dust outputs - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) - { - if (td.is_rct() || is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); - else - unused_dust_indices.push_back(i); - } - } LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); if (unused_dust_indices.empty() && unused_transfers_indices.empty()) @@ -3543,18 +4650,17 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); + tx.selected_transfers.push_back(idx); uint64_t available_amount = td.amount(); accumulated_outputs += available_amount; + // clear any fake outs we'd already gathered, since we'll need a new set + outs.clear(); + // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - size_t estimated_rct_tx_size; - if (use_rct) - estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); - else - estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES; + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); if (try_tx) { @@ -3568,15 +4674,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); @@ -3585,10 +4691,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); @@ -3620,8 +4726,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono { TX &tx = *i; uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); + for (size_t idx: tx.selected_transfers) + tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << @@ -3647,151 +4753,22 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const } return money; } - -template<typename T> -void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t> &extra, cryptonote::transaction& tx, pending_tx &ptx) -{ - using namespace cryptonote; - - uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - - // select all dust inputs for transaction - // throw if there are none - uint64_t money = 0; - std::list<transfer_container::iterator> selected_transfers; -#if 1 - for (size_t n = 0; n < outs.size(); ++n) - { - const transfer_details& td = m_transfers[outs[n]]; - if (!td.m_spent) - { - selected_transfers.push_back (m_transfers.begin() + outs[n]); - money += td.amount(); - if (selected_transfers.size() >= num_outputs) - break; - } - } -#else - for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) - { - const transfer_details& td = *i; - if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td)) - { - selected_transfers.push_back (i); - money += td.amount(); - if (selected_transfers.size() >= num_outputs) - break; - } - } -#endif - - // we don't allow no output to self, easier, but one may want to burn the dust if = fee - THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee); - - typedef cryptonote::tx_source_entry::output_entry tx_output_entry; - - //prepare inputs - size_t i = 0; - std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) - { - sources.resize(sources.size()+1); - cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; - src.amount = td.amount(); - src.rct = td.is_rct(); - - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - return a.first >= td.m_global_output_index; - }); - tx_output_entry real_oe; - real_oe.first = td.m_global_output_index; - real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); - real_oe.second.mask = rct::commit(td.amount(), td.m_mask); - auto interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - detail::print_source_entry(src); - ++i; - } - - cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); - - std::vector<cryptonote::tx_destination_entry> dsts; - uint64_t money_back = money - needed_fee; - if (dust_policy.dust_threshold > 0) - money_back = money_back - money_back % dust_policy.dust_threshold; - dsts.push_back(cryptonote::tx_destination_entry(money_back, m_account_public_address)); - std::vector<cryptonote::tx_destination_entry> splitted_dsts, dust; - destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust); - BOOST_FOREACH(auto& d, dust) { - THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < d.amount, error::wallet_internal_error, "invalid dust value: dust = " + - std::to_string(d.amount) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); - } - - crypto::secret_key tx_key; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); - THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, 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 - { - CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); - key_images += boost::to_string(in.k_image) + " "; - return true; - }); - THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); - - ptx.key_images = key_images; - ptx.fee = money - money_back; - ptx.dust = 0; - ptx.tx = tx; - ptx.change_dts = change_dts; - ptx.selected_transfers = selected_transfers; - ptx.tx_key = tx_key; - ptx.dests = dsts; -} - //---------------------------------------------------------------------------------------------------- void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); - - m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "hard_fork_info"; - req_t.params.version = version; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); - m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to connect to daemon"); - CHECK_AND_ASSERT_THROW_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, "Failed to connect to daemon"); - CHECK_AND_ASSERT_THROW_MES(resp_t.result.status == CORE_RPC_STATUS_OK, "Failed to get hard fork status"); - - earliest_height = resp_t.result.earliest_height; + boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); + throw_on_rpc_response_error(result, "get_hard_fork_info"); } //---------------------------------------------------------------------------------------------------- bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) { - cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res); + uint64_t height, earliest_height; + boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); + throw_on_rpc_response_error(result, "get_info"); + result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); + throw_on_rpc_response_error(result, "get_hard_fork_info"); - m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); - m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get current blockchain height"); - - uint64_t earliest_height; - get_hard_fork_info(version, earliest_height); // can throw - - bool close_enough = res.height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand + bool close_enough = height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand if (close_enough) LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else @@ -3803,7 +4780,7 @@ uint64_t wallet2::get_upper_tranaction_size_limit() { if (m_upper_transaction_size_limit > 0) return m_upper_transaction_size_limit; - uint64_t full_reward_zone = use_fork_rules(2, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; + uint64_t full_reward_zone = use_fork_rules(5, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : use_fork_rules(2, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- @@ -3829,7 +4806,7 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() for (const auto &td: m_transfers) { if (!td.m_spent) - set.insert(td.amount()); + set.insert(td.is_rct() ? 0 : td.amount()); } std::vector<uint64_t> vector; vector.reserve(set.size()); @@ -3840,7 +4817,7 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() return vector; } //---------------------------------------------------------------------------------------------------- -std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool trusted_daemon) +std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct, bool trusted_daemon) { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); @@ -3853,9 +4830,9 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co req_t.params.min_count = count; req_t.params.max_count = 0; req_t.params.unlocked = unlocked; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_unmixable_outputs"); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram"); THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); @@ -3865,10 +4842,10 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co mixable.insert(i.amount); } - return select_available_outputs([mixable, atleast](const transfer_details &td) { - if (td.is_rct()) + return select_available_outputs([mixable, atleast, allow_rct](const transfer_details &td) { + if (!allow_rct && td.is_rct()) return false; - const uint64_t amount = td.amount(); + const uint64_t amount = td.is_rct() ? 0 : td.amount(); if (atleast) { if (mixable.find(amount) != mixable.end()) return true; @@ -3892,7 +4869,7 @@ uint64_t wallet2::get_num_rct_outputs() req_t.params.amounts.push_back(0); req_t.params.min_count = 0; req_t.params.max_count = 0; - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs"); THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); @@ -3900,19 +4877,27 @@ uint64_t wallet2::get_num_rct_outputs() THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); - return resp_t.result.histogram[0].instances; + return resp_t.result.histogram[0].total_instances; +} +//---------------------------------------------------------------------------------------------------- +const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const +{ + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Bad transfer index"); + return m_transfers[idx]; } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances - return select_available_outputs_from_histogram(3, false, true, trusted_daemon); + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 + return select_available_outputs_from_histogram(min_mixin + 1, false, true, false, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with - return select_available_outputs_from_histogram(3, true, true, trusted_daemon); + const size_t min_mixin = use_fork_rules(5, 10) ? 4 : 2; // v5 increases min mixin from 2 to 4 + return select_available_outputs_from_histogram(min_mixin + 1, true, true, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) @@ -3921,8 +4906,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); - const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); // may throw std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); @@ -3933,100 +4917,17 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo return std::vector<wallet2::pending_tx>(); } - // failsafe split attempt counter - size_t attempt_count = 0; - - for(attempt_count = 1; ;attempt_count++) + // split in "dust" and "non dust" to make it easier to select outputs + std::vector<size_t> unmixable_transfer_outputs, unmixable_dust_outputs; + for (auto n: unmixable_outputs) { - size_t num_tx = 0.5 + pow(1.7,attempt_count-1); - size_t num_outputs_per_tx = (num_dust_outputs + num_tx - 1) / num_tx; - - std::vector<pending_tx> ptx_vector; - try - { - // for each new tx - for (size_t i=0; i<num_tx;++i) - { - cryptonote::transaction tx; - pending_tx ptx; - std::vector<uint8_t> extra; - - // loop until fee is met without increasing tx size to next KB boundary. - uint64_t needed_fee = 0; - do - { - transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); - auto txBlob = t_serializable_object_to_blob(ptx.tx); - needed_fee = calculate_fee(fee_per_kb, txBlob, 1); - - // reroll the tx with the actual amount minus the fee - // if there's not enough for the fee, it'll throw - transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); - txBlob = t_serializable_object_to_blob(ptx.tx); - needed_fee = calculate_fee(fee_per_kb, txBlob, 1); - } while (ptx.fee < needed_fee); - - ptx_vector.push_back(ptx); - - // mark transfers to be used as "spent" - BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - { - set_spent(*it, 0); - } - } - - // if we made it this far, we've selected our transactions. committing them will mark them spent, - // so this is a failsafe in case they don't go through - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - { - set_unspent(*it2); - } - } - - // if we made it this far, we're OK to actually send the transactions - return ptx_vector; - - } - // only catch this here, other exceptions need to pass through to the calling function - catch (const tools::error::tx_too_big& e) - { - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - { - set_unspent(*it2); - } - } - - if (attempt_count >= MAX_SPLIT_ATTEMPTS) - { - throw; - } - } - catch (...) - { - // in case of some other exception, make sure any tx in queue are marked unspent again - - // unmark pending tx transfers as spent - for (auto & ptx : ptx_vector) - { - // mark transfers to be used as not spent - BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - { - set_unspent(*it2); - } - } - - throw; - } + if (m_transfers[n].amount() < fee_per_kb) + unmixable_dust_outputs.push_back(n); + else + unmixable_transfer_outputs.push_back(n); } + + return create_transactions_from(m_account_public_address, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon); } bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const @@ -4055,23 +4956,38 @@ std::string wallet2::get_daemon_address() const uint64_t wallet2::get_daemon_blockchain_height(string &err) { - // XXX: DRY violation. copy-pasted from simplewallet.cpp:get_daemon_blockchain_height() - // consider to move it from simplewallet to wallet2 ? - COMMAND_RPC_GET_HEIGHT::request req; - COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>(); + uint64_t height; + + boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); + if (result) + { + err = *result; + return 0; + } + + err = ""; + return height; +} + +uint64_t wallet2::get_daemon_blockchain_target_height(string &err) +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - bool ok = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_info"; + bool ok = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); - // XXX: DRY violation. copy-pasted from simplewallet.cpp:interpret_rpc_response() if (ok) { - if (res.status == CORE_RPC_STATUS_BUSY) + if (resp_t.result.status == CORE_RPC_STATUS_BUSY) { err = "daemon is busy. Please try again later."; } - else if (res.status != CORE_RPC_STATUS_OK) + else if (resp_t.result.status != CORE_RPC_STATUS_OK) { - err = res.status; + err = resp_t.result.status; } else // success, cleaning up error message { @@ -4082,7 +4998,22 @@ uint64_t wallet2::get_daemon_blockchain_height(string &err) { err = "possibly lost connection to daemon"; } - return res.height; + return resp_t.result.target_height; +} + +uint64_t wallet2::get_approximate_blockchain_height() const +{ + if (m_testnet) return 0; + // time of v2 fork + const time_t fork_time = 1458748658; + // v2 fork block + const uint64_t fork_block = 1009827; + // avg seconds per block + const int seconds_per_block = DIFFICULTY_TARGET_V2; + // Calculated blockchain height + uint64_t approx_blockchain_height = fork_block + (time(NULL) - fork_time)/seconds_per_block; + LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); + return approx_blockchain_height; } void wallet2::set_tx_note(const crypto::hash &txid, const std::string ¬e) @@ -4131,6 +5062,74 @@ bool wallet2::verify(const std::string &data, const cryptonote::account_public_a return crypto::check_signature(hash, address.m_spend_public_key, s); } //---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const +{ + std::vector<tx_extra_field> tx_extra_fields; + if(!parse_tx_extra(td.m_tx.extra, tx_extra_fields)) + { + // Extra may only be partially parsed, it's OK if tx_extra_fields contains public key + } + + // Due to a previous bug, there might be more than one tx pubkey in extra, one being + // the result of a previously discarded signature. + // For speed, since scanning for outputs is a slow process, we check whether extra + // contains more than one pubkey. If not, the first one is returned. If yes, they're + // checked for whether they yield at least one output + tx_extra_pub_key pub_key_field; + THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 0), error::wallet_internal_error, + "Public key wasn't found in the transaction extra"); + const crypto::public_key tx_pub_key = pub_key_field.pub_key; + bool two_found = find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 1); + if (!two_found) { + // easy case, just one found + return tx_pub_key; + } + + // more than one, loop and search + const cryptonote::account_keys& keys = m_account.get_keys(); + size_t pk_index = 0; + while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) { + const crypto::public_key tx_pub_key = pub_key_field.pub_key; + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + + for (size_t i = 0; i < td.m_tx.vout.size(); ++i) + { + uint64_t money_transfered = 0; + bool error = false, received = false; + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, td.m_tx.vout[i], derivation, i, received, money_transfered, error); + if (!error && received) + return tx_pub_key; + } + } + + // we found no key yielding an output + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, + "Public key yielding at least one output wasn't found in the transaction extra"); + return cryptonote::null_pkey; +} + +bool wallet2::export_key_images(const std::string filename) +{ + std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images(); + std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + + std::string data; + data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + for (const auto &i: ski) + { + data += std::string((const char *)&i.first, sizeof(crypto::key_image)); + data += std::string((const char *)&i.second, sizeof(crypto::signature)); + } + + // encrypt data, keep magic plaintext + std::string ciphertext = encrypt_with_view_secret_key(data); + return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); +} + +//---------------------------------------------------------------------------------------------------- std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key_images() const { std::vector<std::pair<crypto::key_image, crypto::signature>> ski; @@ -4156,16 +5155,15 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key { // Extra may only be partially parsed, it's OK if tx_extra_fields contains public key } - tx_extra_pub_key pub_key_field; - THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field), error::wallet_internal_error, - "Public key wasn't found in the transaction extra"); - crypto::public_key tx_pub_key = pub_key_field.pub_key; + + crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); // generate ephemeral secret key crypto::key_image ki; cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); - THROW_WALLET_EXCEPTION_IF(ki != td.m_key_image, + + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -4181,6 +5179,70 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key } return ski; } + +uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) +{ + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return 0; + } + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << "Bad key image export file magic in " << filename; + return 0; + } + + try + { + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); + return 0; + } + + const size_t headerlen = 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + fail_msg_writer() << "Bad data size from file " << filename; + return 0; + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + fail_msg_writer() << "Key images from " << filename << " are for a different account"; + return 0; + } + + const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); + if ((data.size() - headerlen) % record_size) + { + fail_msg_writer() << "Bad data size from file " << filename; + return 0; + } + size_t nki = (data.size() - headerlen) / record_size; + + std::vector<std::pair<crypto::key_image, crypto::signature>> ski; + ski.reserve(nki); + for (size_t n = 0; n < nki; ++n) + { + crypto::key_image key_image = *reinterpret_cast<const crypto::key_image*>(&data[headerlen + n * record_size]); + crypto::signature signature = *reinterpret_cast<const crypto::signature*>(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); + + ski.push_back(std::make_pair(key_image, signature)); + } + + return import_key_images(ski, spent, unspent); +} + //---------------------------------------------------------------------------------------------------- uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent) { @@ -4221,10 +5283,14 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } for (size_t n = 0; n < signed_key_images.size(); ++n) + { m_transfers[n].m_key_image = signed_key_images[n].first; + m_key_images[m_transfers[n].m_key_image] = n; + m_transfers[n].m_key_image_known = true; + } m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/is_key_image_spent", req, daemon_resp, m_http_client, 200000); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); @@ -4252,6 +5318,336 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag return m_transfers[signed_key_images.size() - 1].m_block_height; } //---------------------------------------------------------------------------------------------------- +std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const +{ + std::vector<tools::wallet2::transfer_details> outs; + + outs.reserve(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + const transfer_details &td = m_transfers[n]; + + outs.push_back(td); + } + + return outs; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs) +{ + m_transfers.clear(); + m_transfers.reserve(outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) + { + transfer_details td = outputs[i]; + + // the hot wallet wouldn't have known about key images (except if we already exported them) + cryptonote::keypair in_ephemeral; + std::vector<tx_extra_field> tx_extra_fields; + tx_extra_pub_key pub_key_field; + + THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i)); + THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, + "Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i)); + crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); + + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image); + td.m_key_image_known = true; + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i)); + + m_key_images[td.m_key_image] = m_transfers.size(); + m_pub_keys[td.get_public_key()] = m_transfers.size(); + m_transfers.push_back(td); + } + + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + std::string ciphertext; + crypto::chacha8_iv iv = crypto::rand<crypto::chacha8_iv>(); + ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); + crypto::chacha8(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + memcpy(&ciphertext[0], &iv, sizeof(iv)); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + crypto::signature &signature = *(crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, skey, signature); + } + return ciphertext; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const +{ + return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const +{ + const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); + THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size, + error::wallet_internal_error, "Unexpected ciphertext size"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + std::string plaintext; + plaintext.resize(ciphertext.size() - prefix_size); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), + error::wallet_internal_error, "Failed to authenticate criphertext"); + } + crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); + return plaintext; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const +{ + return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) +{ + cryptonote::account_public_address tmp_address; + bool has_payment_id; + crypto::hash8 new_payment_id; + if(!get_account_integrated_address_from_str(tmp_address, has_payment_id, new_payment_id, testnet(), address)) + { + error = std::string("wrong address: ") + address; + return std::string(); + } + + // we want only one payment id + if (has_payment_id && !payment_id.empty()) + { + error = "A single payment id is allowed"; + return std::string(); + } + + if (!payment_id.empty()) + { + crypto::hash pid32; + crypto::hash8 pid8; + if (!wallet2::parse_long_payment_id(payment_id, pid32) && !wallet2::parse_short_payment_id(payment_id, pid8)) + { + error = "Invalid payment id"; + return std::string(); + } + } + + std::string uri = "monero:" + address; + unsigned int n_fields = 0; + + if (!payment_id.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("tx_payment_id=") + payment_id; + } + + if (amount > 0) + { + // URI encoded amount is in decimal units, not atomic units + uri += (n_fields++ ? "&" : "?") + std::string("tx_amount=") + cryptonote::print_money(amount); + } + + if (!recipient_name.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name); + } + + if (!tx_description.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description); + } + + return uri; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) +{ + if (uri.substr(0, 7) != "monero:") + { + error = std::string("URI has wrong scheme (expected \"monero:\"): ") + uri; + return false; + } + + std::string remainder = uri.substr(7); + const char *ptr = strchr(remainder.c_str(), '?'); + address = ptr ? remainder.substr(0, ptr-remainder.c_str()) : remainder; + + cryptonote::account_public_address addr; + bool has_payment_id; + crypto::hash8 new_payment_id; + if(!get_account_integrated_address_from_str(addr, has_payment_id, new_payment_id, testnet(), address)) + { + error = std::string("URI has wrong address: ") + address; + return false; + } + if (!strchr(remainder.c_str(), '?')) + return true; + + std::vector<std::string> arguments; + std::string body = remainder.substr(address.size() + 1); + if (body.empty()) + return true; + boost::split(arguments, body, boost::is_any_of("&")); + std::set<std::string> have_arg; + for (const auto &arg: arguments) + { + std::vector<std::string> kv; + boost::split(kv, arg, boost::is_any_of("=")); + if (kv.size() != 2) + { + error = std::string("URI has wrong parameter: ") + arg; + return false; + } + if (have_arg.find(kv[0]) != have_arg.end()) + { + error = std::string("URI has more than one instance of " + kv[0]); + return false; + } + have_arg.insert(kv[0]); + + if (kv[0] == "tx_amount") + { + amount = 0; + if (!cryptonote::parse_amount(amount, kv[1])) + { + error = std::string("URI has invalid amount: ") + kv[1]; + return false; + } + } + else if (kv[0] == "tx_payment_id") + { + if (has_payment_id) + { + error = "Separate payment id given with an integrated address"; + return false; + } + crypto::hash hash; + crypto::hash8 hash8; + if (!wallet2::parse_long_payment_id(kv[1], hash) && !wallet2::parse_short_payment_id(kv[1], hash8)) + { + error = "Invalid payment id: " + kv[1]; + return false; + } + payment_id = kv[1]; + } + else if (kv[0] == "recipient_name") + { + recipient_name = epee::net_utils::convert_from_url_format(kv[1]); + } + else if (kv[0] == "tx_description") + { + tx_description = epee::net_utils::convert_from_url_format(kv[1]); + } + else + { + unknown_parameters.push_back(arg); + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day) +{ + uint32_t version; + if (!check_connection(&version)) + { + throw std::runtime_error("failed to connect to daemon: " + get_daemon_address()); + } + if (version < MAKE_CORE_RPC_VERSION(1, 6)) + { + throw std::runtime_error("this function requires RPC version 1.6 or higher"); + } + std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 }; + date.tm_year = year - 1900; + date.tm_mon = month - 1; + date.tm_mday = day; + if (date.tm_mon < 0 || 11 < date.tm_mon || date.tm_mday < 1 || 31 < date.tm_mday) + { + throw std::runtime_error("month or day out of range"); + } + uint64_t timestamp_target = std::mktime(&date); + std::string err; + uint64_t height_min = 0; + uint64_t height_max = get_daemon_blockchain_height(err) - 1; + if (!err.empty()) + { + throw std::runtime_error("failed to get blockchain height"); + } + while (true) + { + COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request req; + COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response res; + uint64_t height_mid = (height_min + height_max) / 2; + req.heights = + { + height_min, + height_mid, + height_max + }; + bool r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, m_http_client, rpc_timeout); + if (!r || res.status != CORE_RPC_STATUS_OK) + { + std::ostringstream oss; + oss << "failed to get blocks by heights: "; + for (auto height : req.heights) + oss << height << ' '; + oss << endl << "reason: "; + if (!r) + oss << "possibly lost connection to daemon"; + else if (res.status == CORE_RPC_STATUS_BUSY) + oss << "daemon is busy"; + else + oss << res.status; + throw std::runtime_error(oss.str()); + } + cryptonote::block blk_min, blk_mid, blk_max; + if (!parse_and_validate_block_from_blob(res.blocks[0].block, blk_min)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_min)); + if (!parse_and_validate_block_from_blob(res.blocks[1].block, blk_mid)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_mid)); + if (!parse_and_validate_block_from_blob(res.blocks[2].block, blk_max)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_max)); + uint64_t timestamp_min = blk_min.timestamp; + uint64_t timestamp_mid = blk_mid.timestamp; + uint64_t timestamp_max = blk_max.timestamp; + if (!(timestamp_min <= timestamp_mid && timestamp_mid <= timestamp_max)) + { + // the timestamps are not in the chronological order. + // assuming they're sufficiently close to each other, simply return the smallest height + return std::min({height_min, height_mid, height_max}); + } + if (timestamp_target > timestamp_max) + { + throw std::runtime_error("specified date is in the future"); + } + if (timestamp_target <= timestamp_min + 2 * 24 * 60 * 60) // two days of "buffer" period + { + return height_min; + } + if (timestamp_target <= timestamp_mid) + height_max = height_mid; + else + height_min = height_mid; + if (height_max - height_min <= 2 * 24 * 30) // don't divide the height range finer than two days + { + return height_min; + } + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index dd7cd19dc..03c6a431b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -31,19 +31,22 @@ #pragma once #include <memory> -#include <boost/archive/binary_iarchive.hpp> + +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> #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" -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/account_boost_serialization.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "net/http_client.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_tx_utils.h" #include "common/unordered_containers_boost_serialization.h" #include "crypto/chacha8.h" #include "crypto/hash.h" @@ -51,9 +54,15 @@ #include "ringct/rctOps.h" #include "wallet_errors.h" +#include "common/password.h" +#include "node_rpc_proxy.h" #include <iostream> -#define WALLET_RCP_CONNECTION_TIMEOUT 200000 + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" + +class Serialization_portability_wallet_Test; namespace tools { @@ -61,9 +70,10 @@ namespace tools { 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, uint64_t amount) {} - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {} - virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) {} + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) {} + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {} + virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} virtual ~i_wallet2_callback() {} }; @@ -83,7 +93,10 @@ namespace tools class wallet2 { + friend class ::Serialization_portability_wallet_Test; public: + static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); + enum RefreshType { RefreshFull, RefreshOptimizeCoinbase, @@ -92,10 +105,32 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} public: - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} + static const char* tr(const char* str); + + static bool has_testnet_option(const boost::program_options::variables_map& vm); + static void init_options(boost::program_options::options_description& desc_params); + + //! \return Password retrieved from prompt. Logs error on failure. + static boost::optional<password_container> password_prompt(const bool new_password); + + //! Uses stdin and stdout. Returns a wallet2 if no errors. + static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file); + + //! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors. + static std::pair<std::unique_ptr<wallet2>, password_container> + make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file); + + //! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors. + static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm); + + //! Just parses variables. + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm); + + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + struct transfer_details { uint64_t m_block_height; @@ -109,9 +144,28 @@ namespace tools rct::key m_mask; uint64_t m_amount; bool m_rct; + bool m_key_image_known; + size_t m_pk_index; bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } + const crypto::public_key &get_public_key() const { return boost::get<const cryptonote::txout_to_key>(m_tx.vout[m_internal_output_index].target).key; } + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_block_height) + FIELD(m_tx) + FIELD(m_txid) + FIELD(m_internal_output_index) + FIELD(m_global_output_index) + FIELD(m_spent) + FIELD(m_spent_height) + FIELD(m_key_image) + FIELD(m_mask) + FIELD(m_amount) + FIELD(m_rct) + FIELD(m_key_image_known) + FIELD(m_pk_index) + END_SERIALIZE() }; struct payment_details @@ -151,19 +205,50 @@ namespace tools m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) {} }; + struct tx_construction_data + { + std::vector<cryptonote::tx_source_entry> sources; + cryptonote::tx_destination_entry change_dts; + std::vector<cryptonote::tx_destination_entry> splitted_dsts; // split, includes change + std::list<size_t> selected_transfers; + std::vector<uint8_t> extra; + uint64_t unlock_time; + bool use_rct; + std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change + }; + typedef std::vector<transfer_details> transfer_container; typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; + // The convention for destinations is: + // dests does not include change + // splitted_dsts (in construction_data) does struct pending_tx { cryptonote::transaction tx; uint64_t dust, fee; bool dust_added_to_fee; cryptonote::tx_destination_entry change_dts; - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; std::vector<cryptonote::tx_destination_entry> dests; + + tx_construction_data construction_data; + }; + + // The term "Unsigned tx" is not really a tx since it's not signed yet. + // It doesnt have tx hash, key and the integrated address is not separated into addr + payment id. + struct unsigned_tx_set + { + std::vector<tx_construction_data> txes; + wallet2::transfer_container transfers; + }; + + struct signed_tx_set + { + std::vector<pending_tx> ptx; + std::vector<crypto::key_image> key_images; }; struct keys_file_data @@ -187,6 +272,16 @@ namespace tools FIELD(cache_data) END_SERIALIZE() }; + + // GUI Address book + struct address_book_row + { + cryptonote::account_public_address m_address; + crypto::hash m_payment_id; + std::string m_description; + }; + + typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; /*! * \brief Generates a wallet or restores one. @@ -235,6 +330,8 @@ namespace tools */ void store_to(const std::string &path, const std::string &password); + std::string path() const; + /*! * \brief verifies given password is correct for default wallet keys file */ @@ -243,14 +340,16 @@ namespace tools const cryptonote::account_base& get_account()const{return m_account;} void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} + uint64_t get_refresh_from_block_height() const {return m_refresh_from_block_height;} // upper_transaction_size_limit as defined below is set to // approximately 125% of the fixed minimum allowable penalty // free block size. TODO: fix this so that it actually takes // into account the current median block size rather than // the minimum block size. - void init(const std::string& daemon_address = "http://localhost:8080", uint64_t upper_transaction_size_limit = 0); bool deinit(); + bool init(std::string daemon_address = "http://localhost:8080", + boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0); void stop() { m_run.store(false, std::memory_order_relaxed); } @@ -296,20 +395,29 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); template<typename T> - void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); - template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); - void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, + std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); + bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet + bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL); + // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI + bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx); + // load unsigned_tx_set from file. + bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs); + bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); - bool check_connection(bool *same_version = NULL); + bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const; void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; @@ -352,9 +460,39 @@ namespace tools a & m_tx_notes; if(ver < 13) return; - a & m_unconfirmed_payments; + if (ver < 17) + { + // we're loading an old version, where m_unconfirmed_payments was a std::map + std::unordered_map<crypto::hash, payment_details> m; + a & m; + for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i) + m_unconfirmed_payments.insert(*i); + } if(ver < 14) return; + if(ver < 15) + { + // we're loading an older wallet without a pubkey map, rebuild it + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; + const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); + m_pub_keys.emplace(o.key, i); + } + return; + } + a & m_pub_keys; + if(ver < 16) + return; + a & m_address_book; + if(ver < 17) + return; + a & m_unconfirmed_payments; + if(ver < 18) + return; + a & m_scanned_pool_txs[0]; + a & m_scanned_pool_txs[1]; } /*! @@ -370,17 +508,14 @@ namespace tools * \return Whether path is valid format */ static bool wallet_valid_path_format(const std::string& file_path); - static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); - static std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid); - - static std::string address_from_txt_record(const std::string& s); - bool always_confirm_transfers() const { return m_always_confirm_transfers; } void always_confirm_transfers(bool always) { m_always_confirm_transfers = always; } + bool print_ring_members() const { return m_print_ring_members; } + void print_ring_members(bool value) { m_print_ring_members = value; } bool store_tx_info() const { return m_store_tx_info; } void store_tx_info(bool store) { m_store_tx_info = store; } uint32_t default_mixin() const { return m_default_mixin; } @@ -389,26 +524,52 @@ namespace tools void set_default_priority(uint32_t p) { m_default_priority = p; } bool auto_refresh() const { return m_auto_refresh; } void auto_refresh(bool r) { m_auto_refresh = r; } + bool confirm_missing_payment_id() const { return m_confirm_missing_payment_id; } + void confirm_missing_payment_id(bool always) { m_confirm_missing_payment_id = always; } + bool ask_password() const { return m_ask_password; } + void ask_password(bool always) { m_ask_password = always; } + void set_default_decimal_point(unsigned int decimal_point); + unsigned int get_default_decimal_point() const; + void set_min_output_count(uint32_t count) { m_min_output_count = count; } + uint32_t get_min_output_count() const { return m_min_output_count; } + void set_min_output_value(uint64_t value) { m_min_output_value = value; } + uint64_t get_min_output_value() const { return m_min_output_value; } + void merge_destinations(bool merge) { m_merge_destinations = merge; } + bool merge_destinations() const { return m_merge_destinations; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; + /*! + * \brief GUI Address book get/store + */ + std::vector<address_book_row> get_address_book() const { return m_address_book; } + bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description); + bool delete_address_book_row(std::size_t row_id); + uint64_t get_num_rct_outputs(); + const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); bool use_fork_rules(uint8_t version, int64_t early_blocks = 0); + int get_fee_algorithm(); std::string get_wallet_file() const; std::string get_keys_file() const; std::string get_daemon_address() const; + const boost::optional<epee::net_utils::http::login>& get_daemon_login() const { return m_daemon_login; } uint64_t get_daemon_blockchain_height(std::string& err); - - std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool trusted_daemon); + uint64_t get_daemon_blockchain_target_height(std::string& err); + /*! + * \brief Calculates the approximate blockchain height from current date/time. + */ + uint64_t get_approximate_blockchain_height() const; + std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct, bool trusted_daemon); std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f); std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -416,10 +577,26 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + std::vector<tools::wallet2::transfer_details> export_outputs() const; + size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs); + + bool export_key_images(const std::string filename); std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const; uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent); + uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); void update_pool_state(); + + std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; + std::string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const; + + std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error); + bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error); + + uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31 + private: /*! * \brief Stores wallet information to wallet file. @@ -435,7 +612,7 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; @@ -446,28 +623,35 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon); + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); - void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); - void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); + void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); + void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; + crypto::hash8 get_short_payment_id(const pending_tx &ptx) const; + void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); - uint64_t get_fee_multiplier(uint32_t priority, bool use_new_fee) const; + uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm) const; + uint64_t get_dynamic_per_kb_fee_estimate(); + uint64_t get_per_kb_fee(); float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; - std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; - void set_spent(transfer_details &td, uint64_t height); - void set_unspent(transfer_details &td); - template<typename entry> - void get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count); + std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money) const; + void set_spent(size_t idx, uint64_t height); + void set_unspent(size_t idx); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); + crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; + bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; + std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; cryptonote::account_base m_account; + boost::optional<epee::net_utils::http::login> m_daemon_login; std::string m_daemon_address; std::string m_wallet_file; std::string m_keys_file; @@ -476,14 +660,16 @@ namespace tools std::atomic<uint64_t> m_local_bc_height; //temporary workaround std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; - std::unordered_map<crypto::hash, payment_details> m_unconfirmed_payments; + std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments; std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; transfer_container m_transfers; payment_container m_payments; std::unordered_map<crypto::key_image, size_t> m_key_images; + std::unordered_map<crypto::public_key, size_t> m_pub_keys; cryptonote::account_public_address m_account_public_address; std::unordered_map<crypto::hash, std::string> m_tx_notes; + std::vector<tools::wallet2::address_book_row> m_address_book; 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; @@ -497,30 +683,43 @@ namespace tools bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ bool m_watch_only; /*!< no spend key */ bool m_always_confirm_transfers; + bool m_print_ring_members; bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ uint32_t m_default_mixin; uint32_t m_default_priority; RefreshType m_refresh_type; bool m_auto_refresh; uint64_t m_refresh_from_block_height; + bool m_confirm_missing_payment_id; + bool m_ask_password; + uint32_t m_min_output_count; + uint64_t m_min_output_value; + bool m_merge_destinations; + NodeRPCProxy m_node_rpc_proxy; + std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; }; } -BOOST_CLASS_VERSION(tools::wallet2, 14) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 4) +BOOST_CLASS_VERSION(tools::wallet2, 18) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 7) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 5) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) +BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3) +BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16) +BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0) namespace boost { namespace serialization { template <class Archive> - inline void initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) + inline typename std::enable_if<!Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { } - template<> - inline void initialize_transfer_details(boost::archive::binary_iarchive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) + template <class Archive> + inline typename std::enable_if<Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { if (ver < 1) { @@ -535,6 +734,14 @@ namespace boost { x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; } + if (ver < 6) + { + x.m_key_image_known = true; + } + if (ver < 7) + { + x.m_pk_index = 0; + } } template <class Archive> @@ -582,6 +789,26 @@ namespace boost return; } a & x.m_rct; + if (ver < 5) + { + initialize_transfer_details(a, x, ver); + return; + } + if (ver < 6) + { + // v5 did not properly initialize + uint8_t u; + a & u; + x.m_key_image_known = true; + return; + } + a & x.m_key_image_known; + if (ver < 7) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_pk_index; } template <class Archive> @@ -613,6 +840,14 @@ namespace boost return; a & x.m_amount_in; a & x.m_amount_out; + if (ver < 6) + { + // v<6 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. + // We convert it to include change from v6 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + x.m_amount_out += x.m_change; + } } template <class Archive> @@ -629,6 +864,20 @@ namespace boost if (ver < 2) return; a & x.m_timestamp; + if (ver < 3) + { + // v<3 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. Whether it got added + // or not depends on whether it came from a unconfirmed_transfer_details + // (not included) or not (included). We can't reliably tell here, so we + // check whether either yields a "negative" fee, or use the other if so. + // We convert it to include change from v3 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + { + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; + } + } } template <class Archive> @@ -649,6 +898,56 @@ namespace boost a & x.amount; a & x.addr; } + + template <class Archive> + inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) + { + a & x.m_address; + a & x.m_payment_id; + a & x.m_description; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) + { + a & x.txes; + a & x.transfers; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::signed_tx_set &x, const boost::serialization::version_type ver) + { + a & x.ptx; + a & x.key_images; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::tx_construction_data &x, const boost::serialization::version_type ver) + { + a & x.sources; + a & x.change_dts; + a & x.splitted_dsts; + a & x.selected_transfers; + a & x.extra; + a & x.unlock_time; + a & x.use_rct; + a & x.dests; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver) + { + a & x.tx; + a & x.dust; + a & x.fee; + a & x.dust_added_to_fee; + a & x.change_dts; + a & x.selected_transfers; + a & x.key_images; + a & x.tx_key; + a & x.dests; + a & x.construction_data; + } } } @@ -665,7 +964,7 @@ namespace tools splitted_dsts.clear(); dust_dsts.clear(); - BOOST_FOREACH(auto& de, dsts) + for(auto& de: dsts) { cryptonote::decompose_amount_into_digits(de.amount, 0, [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, de.addr)); }, @@ -728,7 +1027,7 @@ namespace tools // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t - BOOST_FOREACH(auto& dt, dsts) + for(auto& dt: dsts) { THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); needed_money += dt.amount; @@ -737,7 +1036,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than amount available to send - std::list<transfer_container::iterator> selected_transfers; + std::list<size_t> selected_transfers; uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); @@ -749,8 +1048,9 @@ namespace tools { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); 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) + for(size_t idx: selected_transfers) { + const transfer_container::const_iterator it = m_transfers.begin() + idx; THROW_WALLET_EXCEPTION_IF(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())); @@ -758,7 +1058,7 @@ namespace tools } m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", req, daemon_resp, m_http_client, 200000); + bool r = epee::net_utils::invoke_http_bin("/getrandom_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); @@ -768,7 +1068,7 @@ namespace tools std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(selected_transfers.size())); std::unordered_map<uint64_t, uint64_t> scanty_outs; - BOOST_FOREACH(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs, daemon_resp.outs) + for(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs: daemon_resp.outs) { if (amount_outs.outs.size() < fake_outputs_count) { @@ -781,18 +1081,18 @@ namespace tools //prepare inputs size_t i = 0; std::vector<cryptonote::tx_source_entry> sources; - BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); cryptonote::tx_source_entry& src = sources.back(); - transfer_details& td = *it; + const transfer_details& td = m_transfers[idx]; src.amount = td.amount(); src.rct = false; //paste mixin transaction if(daemon_resp.outs.size()) { daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index;}); - BOOST_FOREACH(out_entry& daemon_oe, daemon_resp.outs[i].outs) + for(out_entry& daemon_oe: daemon_resp.outs[i].outs) { if(td.m_global_output_index == daemon_oe.global_amount_index) continue; @@ -834,11 +1134,11 @@ namespace tools std::vector<cryptonote::tx_destination_entry> splitted_dsts, dust_dsts; uint64_t dust = 0; destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust_dsts); - BOOST_FOREACH(auto& d, dust_dsts) { + for(auto& d: dust_dsts) { THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < d.amount, error::wallet_internal_error, "invalid dust value: dust = " + std::to_string(d.amount) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); } - BOOST_FOREACH(auto& d, dust_dsts) { + for(auto& d: dust_dsts) { if (!dust_policy.add_to_fee) splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust)); dust += d.amount; @@ -872,6 +1172,14 @@ namespace tools ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; ptx.dests = dsts; + ptx.construction_data.sources = sources; + ptx.construction_data.change_dts = change_dts; + ptx.construction_data.splitted_dsts = splitted_dsts; + ptx.construction_data.selected_transfers = selected_transfers; + ptx.construction_data.extra = tx.extra; + ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.use_rct = false; + ptx.construction_data.dests = dsts; } diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 08e2ae16b..17d0caf7d 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -34,13 +34,28 @@ #include <string> #include <vector> #include <ctime> +#include <iostream> // Public interface for libwallet library -namespace Bitmonero { +namespace Monero { namespace Utils { bool isAddressLocal(const std::string &hostaddr); } + + template<typename T> + class optional { + public: + optional(): set(false) {} + optional(const T &t): t(t), set(true) {} + const T &operator*() const { return t; } + T &operator*() { return t; } + operator bool() const { return set; } + private: + T t; + bool set; + }; + /** * @brief Transaction-like interface for sending money */ @@ -48,7 +63,8 @@ struct PendingTransaction { enum Status { Status_Ok, - Status_Error + Status_Error, + Status_Critical }; enum Priority { @@ -61,10 +77,59 @@ struct PendingTransaction virtual ~PendingTransaction() = 0; virtual int status() const = 0; virtual std::string errorString() const = 0; - virtual bool commit() = 0; + // commit transaction or save to file if filename is provided. + virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; virtual uint64_t amount() const = 0; virtual uint64_t dust() const = 0; virtual uint64_t fee() const = 0; + virtual std::vector<std::string> txid() const = 0; + /*! + * \brief txCount - number of transactions current transaction will be splitted to + * \return + */ + virtual uint64_t txCount() const = 0; +}; + +/** + * @brief Transaction-like interface for sending money + */ +struct UnsignedTransaction +{ + enum Status { + Status_Ok, + Status_Error, + Status_Critical + }; + + enum Priority { + Priority_Low = 1, + Priority_Medium = 2, + Priority_High = 3, + Priority_Last + }; + + virtual ~UnsignedTransaction() = 0; + virtual int status() const = 0; + virtual std::string errorString() const = 0; + virtual std::vector<uint64_t> amount() const = 0; + virtual std::vector<uint64_t> fee() const = 0; + virtual std::vector<uint64_t> mixin() const = 0; + // returns a string with information about all transactions. + virtual std::string confirmationMessage() const = 0; + virtual std::vector<std::string> paymentId() const = 0; + virtual std::vector<std::string> recipientAddress() const = 0; + virtual uint64_t minMixinCount() const = 0; + /*! + * \brief txCount - number of transactions current transaction will be splitted to + * \return + */ + virtual uint64_t txCount() const = 0; + /*! + * @brief sign - Sign txs and saves to file + * @param signedFileName + * return - true on success + */ + virtual bool sign(const std::string &signedFileName) = 0; }; /** @@ -90,6 +155,7 @@ struct TransactionInfo virtual uint64_t amount() const = 0; virtual uint64_t fee() const = 0; virtual uint64_t blockHeight() const = 0; + virtual uint64_t confirmations() const = 0; //! transaction_id virtual std::string hash() const = 0; virtual std::time_t timestamp() const = 0; @@ -110,6 +176,51 @@ struct TransactionHistory virtual void refresh() = 0; }; +/** + * @brief AddressBookRow - provides functions to manage address book + */ +struct AddressBookRow { +public: + AddressBookRow(std::size_t _rowId, const std::string &_address, const std::string &_paymentId, const std::string &_description): + m_rowId(_rowId), + m_address(_address), + m_paymentId(_paymentId), + m_description(_description) {} + +private: + std::size_t m_rowId; + std::string m_address; + std::string m_paymentId; + std::string m_description; +public: + std::string extra; + std::string getAddress() const {return m_address;} + std::string getDescription() const {return m_description;} + std::string getPaymentId() const {return m_paymentId;} + std::size_t getRowId() const {return m_rowId;} +}; + +/** + * @brief The AddressBook - interface for +Book + */ +struct AddressBook +{ + enum ErrorCode { + Status_Ok, + General_Error, + Invalid_Address, + Invalid_Payment_Id + }; + virtual ~AddressBook() = 0; + virtual std::vector<AddressBookRow*> getAll() const = 0; + virtual bool addRow(const std::string &dst_addr , const std::string &payment_id, const std::string &description) = 0; + virtual bool deleteRow(std::size_t rowId) = 0; + virtual void refresh() = 0; + virtual std::string errorString() const = 0; + virtual int errorCode() const = 0; + virtual int lookupPaymentID(const std::string &payment_id) const = 0; +}; struct WalletListener { @@ -127,6 +238,13 @@ struct WalletListener * @param amount - amount */ virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0; + + /** + * @brief unconfirmedMoneyReceived - called when payment arrived in tx pool + * @param txId - transaction id + * @param amount - amount + */ + virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) = 0; /** * @brief newBlock - called when new block received @@ -156,7 +274,14 @@ struct Wallet enum Status { Status_Ok, - Status_Error + Status_Error, + Status_Critical + }; + + enum ConnectionStatus { + ConnectionStatus_Disconnected, + ConnectionStatus_Connected, + ConnectionStatus_WrongVersion }; virtual ~Wallet() = 0; @@ -169,7 +294,12 @@ struct Wallet virtual std::string errorString() const = 0; virtual bool setPassword(const std::string &password) = 0; virtual std::string address() const = 0; - + virtual std::string path() const = 0; + virtual bool testnet() const = 0; + //! returns current hard fork info + virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; + //! check if hard fork rules should be used + virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; /*! * \brief integratedAddress - returns integrated address for current wallet address and given payment_id. * if passed "payment_id" param is an empty string or not-valid payment id string @@ -181,6 +311,12 @@ struct Wallet */ virtual std::string integratedAddress(const std::string &payment_id) const = 0; + /*! + * \brief privateViewKey - returns private view key + * \return - private view key + */ + virtual std::string privateViewKey() const = 0; + /*! * \brief store - stores wallet to file. * \param path - main filename to store wallet to. additionally stores address file and keys file. @@ -199,25 +335,38 @@ struct Wallet */ virtual std::string keysFilename() const = 0; /*! - * \brief init - initializes wallet with daemon connection params. implicitly connects to the daemon - * and refreshes the wallet. "refreshed" callback will be invoked. if daemon_address is - * local address, "trusted daemon" will be set to true forcibly - * - * \param daemon_address - daemon address in "hostname:port" format - * \param upper_transaction_size_limit - * \return - true if initialized and refreshed successfully - */ - virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0; - - /*! - * \brief init - initalizes wallet asynchronously. logic is the same as "init" but returns immediately. - * "refreshed" callback will be invoked. + * \brief init - initializes wallet with daemon connection params. + * if daemon_address is local address, "trusted daemon" will be set to true forcibly + * startRefresh() should be called when wallet is initialized. * * \param daemon_address - daemon address in "hostname:port" format * \param upper_transaction_size_limit - * \return - true if initialized and refreshed successfully + * \return - true on success */ - virtual void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0; + virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username = "", const std::string &daemon_password = "") = 0; + + /*! + * \brief createWatchOnly - Creates a watch only wallet + * \param path - where to store the wallet + * \param password + * \param language + * \return - true if created successfully + */ + virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; + + /*! + * \brief setRefreshFromBlockHeight - start refresh from block height on recover + * + * \param refresh_from_block_height - blockchain start height + */ + virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; + + /*! + * \brief setRecoveringFromSeed - set state recover form seed + * + * \param recoveringFromSeed - true/false + */ + virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; /** * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed @@ -229,12 +378,18 @@ struct Wallet * @brief connected - checks if the wallet connected to the daemon * @return - true if connected */ - virtual bool connected() const = 0; + virtual ConnectionStatus connected() const = 0; virtual void setTrustedDaemon(bool arg) = 0; virtual bool trustedDaemon() const = 0; virtual uint64_t balance() const = 0; virtual uint64_t unlockedBalance() const = 0; + /** + * @brief watchOnly - checks if wallet is watch only + * @return - true if watch only + */ + virtual bool watchOnly() const = 0; + /** * @brief blockChainHeight - returns current blockchain height * @return @@ -242,19 +397,52 @@ struct Wallet virtual uint64_t blockChainHeight() const = 0; /** + * @brief approximateBlockChainHeight - returns approximate blockchain height calculated from date/time + * @return + */ + virtual uint64_t approximateBlockChainHeight() const = 0; + + /** * @brief daemonBlockChainHeight - returns daemon blockchain height * @return 0 - in case error communicating with the daemon. * status() will return Status_Error and errorString() will return verbose error description */ virtual uint64_t daemonBlockChainHeight() const = 0; + /** + * @brief daemonBlockChainTargetHeight - returns daemon blockchain target height + * @return 0 - in case error communicating with the daemon. + * status() will return Status_Error and errorString() will return verbose error description + */ + virtual uint64_t daemonBlockChainTargetHeight() const = 0; + + /** + * @brief synchronized - checks if wallet was ever synchronized + * @return + */ + virtual bool synchronized() const = 0; static std::string displayAmount(uint64_t amount); static uint64_t amountFromString(const std::string &amount); static uint64_t amountFromDouble(double amount); static std::string genPaymentId(); static bool paymentIdValid(const std::string &paiment_id); + static bool addressValid(const std::string &str, bool testnet); + static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error); + static std::string paymentIdFromAddress(const std::string &str, bool testnet); static uint64_t maximumAllowedAmount(); + // Easylogger wrapper + static void init(const char *argv0, const char *default_log_base_name); + static void debug(const std::string &str); + + /** + * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) + */ + virtual void startRefresh() = 0; + /** + * @brief pauseRefresh - pause refresh thread + */ + virtual void pauseRefresh() = 0; /** * @brief refresh - refreshes the wallet, updating transactions from daemon @@ -292,15 +480,54 @@ struct Wallet */ virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - uint64_t amount, uint32_t mixin_count, + optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0; /*! + * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. + * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() + * after object returned + */ + + virtual PendingTransaction * createSweepUnmixableTransaction() = 0; + + /*! + * \brief loadUnsignedTx - creates transaction from unsigned tx file + * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() + * after object returned + */ + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; + + /*! + * \brief submitTransaction - submits transaction in signed tx file + * \return - true on success + */ + virtual bool submitTransaction(const std::string &fileName) = 0; + + + /*! * \brief disposeTransaction - destroys transaction object * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; */ virtual void disposeTransaction(PendingTransaction * t) = 0; + + /*! + * \brief exportKeyImages - exports key images to file + * \param filename + * \return - true on success + */ + virtual bool exportKeyImages(const std::string &filename) = 0; + + /*! + * \brief importKeyImages - imports key images from file + * \param filename + * \return - true on success + */ + virtual bool importKeyImages(const std::string &filename) = 0; + + virtual TransactionHistory * history() const = 0; + virtual AddressBook * addressBook() const = 0; virtual void setListener(WalletListener *) = 0; /*! * \brief defaultMixin - returns number of mixins used in transactions @@ -312,6 +539,43 @@ struct Wallet * \param arg */ virtual void setDefaultMixin(uint32_t arg) = 0; + + /*! + * \brief setUserNote - attach an arbitrary string note to a txid + * \param txid - the transaction id to attach the note to + * \param note - the note + * \return true if succesful, false otherwise + */ + virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; + /*! + * \brief getUserNote - return an arbitrary string note attached to a txid + * \param txid - the transaction id to attach the note to + * \return the attached note, or empty string if there is none + */ + virtual std::string getUserNote(const std::string &txid) const = 0; + virtual std::string getTxKey(const std::string &txid) const = 0; + + /* + * \brief signMessage - sign a message with the spend private key + * \param message - the message to sign (arbitrary byte data) + * \return the signature + */ + virtual std::string signMessage(const std::string &message) = 0; + /*! + * \brief verifySignedMessage - verify a signature matches a given message + * \param message - the message (arbitrary byte data) + * \param address - the address the signature claims to be made with + * \param signature - the signature + * \return true if the signature verified, false otherwise + */ + virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + + virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0; + /* + * \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon + * \return true on success + */ + virtual bool rescanSpent() = 0; }; /** @@ -341,9 +605,30 @@ struct WalletManager * \brief recovers existing wallet using memo (electrum seed) * \param path Name of wallet file to be created * \param memo memo (25 words electrum seed) + * \param testnet testnet + * \param restoreHeight restore from start height * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ - virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet = false) = 0; + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo, bool testnet = false, uint64_t restoreHeight = 0) = 0; + + /*! + * \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted + * \param path Name of wallet file to be created + * \param language language + * \param testnet testnet + * \param restoreHeight restore from start height + * \param addressString public address + * \param viewKeyString view key + * \param spendKeyString spend key (optional) + * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) + */ + virtual Wallet * createWalletFromKeys(const std::string &path, + const std::string &language, + bool testnet, + uint64_t restoreHeight, + const std::string &addressString, + const std::string &viewKeyString, + const std::string &spendKeyString = "") = 0; /*! * \brief Closes wallet. In case operation succeded, wallet object deleted. in case operation failed, wallet object not deleted @@ -370,9 +655,57 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; + /*! + * \brief checkPayment - checks a payment was made using a txkey + * \param address - the address the payment was sent to + * \param txid - the transaction id for that payment + * \param txkey - the transaction's secret key + * \param daemon_address - the address (host and port) to the daemon to request transaction data + * \param received - if succesful, will hold the amount of monero received + * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) + * \param error - if unsuccesful, will hold an error string with more information about the error + * \return - true is succesful, false otherwise + */ + virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; + //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; + //! set the daemon address (hostname and port) + virtual void setDaemonAddress(const std::string &address) = 0; + + //! returns whether the daemon can be reached, and its version number + virtual bool connected(uint32_t *version = NULL) const = 0; + + //! returns current blockchain height + virtual uint64_t blockchainHeight() const = 0; + + //! returns current blockchain target height + virtual uint64_t blockchainTargetHeight() const = 0; + + //! returns current network difficulty + virtual uint64_t networkDifficulty() const = 0; + + //! returns current mining hash rate (0 if not mining) + virtual double miningHashRate() const = 0; + + //! returns current block target + virtual uint64_t blockTarget() const = 0; + + //! returns true iff mining + virtual bool isMining() const = 0; + + //! starts mining with the set number of threads + virtual bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true) = 0; + + //! stops mining + virtual bool stopMining() = 0; + + //! resolves an OpenAlias address to a monero address + virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; + + //! checks for an update and returns version, hash and url + static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); }; @@ -392,8 +725,11 @@ struct WalletManagerFactory static WalletManager * getWalletManager(); static void setLogLevel(int level); + static void setLogCategories(const std::string &categories); }; } +namespace Bitmonero = Monero; + diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp new file mode 100644 index 000000000..1508f3791 --- /dev/null +++ b/src/wallet/wallet_args.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "wallet/wallet_args.h" + +#include <boost/filesystem/path.hpp> +#include <boost/format.hpp> +#include "common/i18n.h" +#include "common/scoped_message_writer.h" +#include "common/util.h" +#include "misc_log_ex.h" +#include "string_tools.h" +#include "version.h" + +#if defined(WIN32) +#include <crtdbg.h> +#endif + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" + +// workaround for a suspected bug in pthread/kernel on MacOS X +#ifdef __APPLE__ +#define DEFAULT_MAX_CONCURRENCY 1 +#else +#define DEFAULT_MAX_CONCURRENCY 0 +#endif + + +namespace wallet_args +{ + // Create on-demand to prevent static initialization order fiasco issues. + command_line::arg_descriptor<std::string> arg_generate_from_json() + { + return {"generate-from-json", wallet_args::tr("Generate wallet from JSON format file"), ""}; + } + command_line::arg_descriptor<std::string> arg_wallet_file() + { + return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""}; + } + + const char* tr(const char* str) + { + return i18n_translate(str, "wallet_args"); + } + + boost::optional<boost::program_options::variables_map> main( + int argc, char** argv, + const char* const usage, + boost::program_options::options_description desc_params, + const boost::program_options::positional_options_description& positional_options, + const char *default_log_name, + bool log_to_console) + + { + namespace bf = boost::filesystem; + namespace po = boost::program_options; +#ifdef WIN32 + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); +#endif + + const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; + const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", wallet_args::tr("Max number of threads to use for a parallel job"), DEFAULT_MAX_CONCURRENCY}; + const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", wallet_args::tr("Specify log file"), ""}; + const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", wallet_args::tr("Config file"), "", true}; + + + std::string lang = i18n_get_language(); + tools::sanitize_locale(); + tools::set_strict_default_file_permissions(true); + + epee::string_tools::set_module_name_and_folder(argv[0]); + + po::options_description desc_general(wallet_args::tr("General options")); + command_line::add_arg(desc_general, command_line::arg_help); + command_line::add_arg(desc_general, command_line::arg_version); + + command_line::add_arg(desc_params, arg_log_file, ""); + command_line::add_arg(desc_params, arg_log_level); + command_line::add_arg(desc_params, arg_max_concurrency); + command_line::add_arg(desc_params, arg_config_file); + + i18n_set_language("translations", "monero", lang); + + po::options_description desc_all; + desc_all.add(desc_general).add(desc_params); + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_all, [&]() + { + auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); + po::store(parser.run(), vm); + + if(command_line::has_arg(vm, arg_config_file)) + { + std::string config = command_line::get_arg(vm, arg_config_file); + bf::path config_path(config); + boost::system::error_code ec; + if (bf::exists(config_path, ec)) + { + po::store(po::parse_config_file<char>(config_path.string<std::string>().c_str(), desc_params), vm); + } + else + { + tools::fail_msg_writer() << wallet_args::tr("Can't find config file ") << config; + return false; + } + } + + po::notify(vm); + return true; + }); + if (!r) + return boost::none; + + std::string log_path; + if (!vm["log-file"].defaulted()) + log_path = command_line::get_arg(vm, arg_log_file); + else + log_path = mlog_get_default_log_path(default_log_name); + mlog_configure(log_path, log_to_console); + if (!vm["log-level"].defaulted()) + { + mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); + } + + if (command_line::get_arg(vm, command_line::arg_help)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + tools::msg_writer() << wallet_args::tr("Usage:") << ' ' << usage; + tools::msg_writer() << desc_all; + return boost::none; + } + else if (command_line::get_arg(vm, command_line::arg_version)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + return boost::none; + } + + if(command_line::has_arg(vm, arg_max_concurrency)) + tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); + + tools::scoped_message_writer(epee::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + + if (!vm["log-level"].defaulted()) + MINFO("Setting log level = " << command_line::get_arg(vm, arg_log_level)); + else + MINFO("Setting log levels = " << getenv("MONERO_LOGS")); + MINFO(wallet_args::tr("Logging to: ") << log_path); + tools::scoped_message_writer(epee::console_color_white, true) << boost::format(wallet_args::tr("Logging to %s")) % log_path; + + return {std::move(vm)}; + } +} diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h new file mode 100644 index 000000000..cf23ffded --- /dev/null +++ b/src/wallet/wallet_args.h @@ -0,0 +1,54 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <boost/optional/optional.hpp> +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/positional_options.hpp> +#include <boost/program_options/variables_map.hpp> + +#include "common/command_line.h" + +namespace wallet_args +{ + command_line::arg_descriptor<std::string> arg_generate_from_json(); + command_line::arg_descriptor<std::string> arg_wallet_file(); + + const char* tr(const char* str); + + /*! Processes command line arguments (`argc` and `argv`) using `desc_params` + and `positional_options`, while adding parameters for log files and + concurrency. Log file and concurrency arguments are handled, along with basic + global init for the wallet process. + + \return The list of parsed options, iff there are no errors.*/ + boost::optional<boost::program_options::variables_map> main( + int argc, char** argv, + const char* const usage, + boost::program_options::options_description desc_params, + const boost::program_options::positional_options_description& positional_options, + const char *default_log_name, bool log_to_console = false); +} diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index c5590d79c..3e3578149 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -35,7 +35,7 @@ #include <string> #include <vector> -#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "rpc/core_rpc_server_commands_defs.h" #include "include_base_utils.h" @@ -68,6 +68,7 @@ namespace tools // transfer_error * // get_random_outs_general_error // not_enough_money + // tx_not_possible // not_enough_outs_to_mix // tx_not_constructed // tx_rejected @@ -351,6 +352,32 @@ namespace tools : transfer_error(std::move(loc), "not enough money") , m_available(availbable) , m_tx_amount(tx_amount) + { + } + + uint64_t available() const { return m_available; } + uint64_t tx_amount() const { return m_tx_amount; } + + 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); + return ss.str(); + } + + private: + uint64_t m_available; + uint64_t m_tx_amount; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_not_possible : public transfer_error + { + explicit tx_not_possible(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + : transfer_error(std::move(loc), "tx not possible") + , m_available(availbable) + , m_tx_amount(tx_amount) , m_fee(fee) { } @@ -599,6 +626,18 @@ namespace tools std::string m_request; }; //---------------------------------------------------------------------------------------------------- + struct wallet_generic_rpc_error : public wallet_rpc_error + { + explicit wallet_generic_rpc_error(std::string&& loc, const std::string& request, const std::string& status) + : wallet_rpc_error(std::move(loc), std::string("error in ") + request + " RPC: " + status, request), + m_status(status) + { + } + const std::string& status() const { return m_status; } + private: + const std::string m_status; + }; + //---------------------------------------------------------------------------------------------------- struct daemon_busy : public wallet_rpc_error { explicit daemon_busy(std::string&& loc, const std::string& request) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index faa40e166..f2b3dcaf5 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -27,42 +27,74 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - +#include <boost/asio/ip/address.hpp> +#include <boost/filesystem/operations.hpp> +#include <cstdint> #include "include_base_utils.h" using namespace epee; #include "wallet_rpc_server.h" +#include "wallet/wallet_args.h" #include "common/command_line.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "cryptonote_core/account.h" +#include "common/i18n.h" +#include "common/util.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/account.h" #include "wallet_rpc_server_commands_defs.h" #include "misc_language.h" +#include "string_coding.h" #include "string_tools.h" #include "crypto/hash.h" +#include "mnemonics/electrum-words.h" +#include "rpc/rpc_args.h" +#include "rpc/core_rpc_server_commands_defs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" + +namespace +{ + const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; + const command_line::arg_descriptor<bool> arg_disable_rpc_login = {"disable-rpc-login", "Disable HTTP authentication for RPC connections served by this process"}; + const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", "Enable commands which rely on a trusted daemon", false}; + const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; + + constexpr const char default_rpc_username[] = "monero"; +} 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"}; - const command_line::arg_descriptor<std::string> wallet_rpc_server::arg_user_agent = {"user-agent", "Restrict RPC to clients using this user agent", ""}; + const char* wallet_rpc_server::tr(const char* str) + { + return i18n_translate(str, "tools::wallet_rpc_server"); + } - void wallet_rpc_server::init_options(boost::program_options::options_description& desc) + //------------------------------------------------------------------------------------------------------------------------------ + wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_filename(), m_stop(false), m_trusted_daemon(false) + { + } + //------------------------------------------------------------------------------------------------------------------------------ + wallet_rpc_server::~wallet_rpc_server() { - command_line::add_arg(desc, arg_rpc_bind_ip); - command_line::add_arg(desc, arg_rpc_bind_port); - command_line::add_arg(desc, arg_user_agent); + try + { + boost::system::error_code ec{}; + boost::filesystem::remove(rpc_login_filename, ec); + } + catch (...) {} } //------------------------------------------------------------------------------------------------------------------------------ - wallet_rpc_server::wallet_rpc_server(wallet2& w):m_wallet(w) - {} + void wallet_rpc_server::set_wallet(wallet2 *cr) + { + m_wallet = cr; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::run() { m_stop = false; m_net_server.add_idle_handler([this](){ try { - m_wallet.refresh(); + if (m_wallet) m_wallet->refresh(); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } @@ -81,30 +113,196 @@ namespace tools 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) + void wallet_rpc_server::stop() { - m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); - m_port = command_line::get_arg(vm, arg_rpc_bind_port); - m_user_agent = command_line::get_arg(vm, arg_user_agent); - return true; + if (m_wallet) + { + m_wallet->store(); + delete m_wallet; + m_wallet = NULL; + } } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::init(const boost::program_options::variables_map& vm) + bool wallet_rpc_server::init(const boost::program_options::variables_map *vm) { + auto rpc_config = cryptonote::rpc_args::process(*vm); + if (!rpc_config) + return false; + + m_vm = vm; + tools::wallet2 *walvars; + std::unique_ptr<tools::wallet2> tmpwal; + + if (m_wallet) + walvars = m_wallet; + else + { + tmpwal = tools::wallet2::make_dummy(*m_vm); + walvars = tmpwal.get(); + } + boost::optional<epee::net_utils::http::login> http_login{}; + std::string bind_port = command_line::get_arg(*m_vm, arg_rpc_bind_port); + const bool disable_auth = command_line::get_arg(*m_vm, arg_disable_rpc_login); + m_trusted_daemon = command_line::get_arg(*m_vm, arg_trusted_daemon); + if (!command_line::has_arg(*m_vm, arg_trusted_daemon)) + { + if (tools::is_local_address(walvars->get_daemon_address())) + { + MINFO(tr("Daemon is local, assuming trusted")); + m_trusted_daemon = true; + } + } + if (command_line::has_arg(*m_vm, arg_wallet_dir)) + { + m_wallet_dir = command_line::get_arg(*m_vm, arg_wallet_dir); +#ifdef _WIN32 +#define MKDIR(path, mode) mkdir(path) +#else +#define MKDIR(path, mode) mkdir(path, mode) +#endif + MKDIR(m_wallet_dir.c_str(), 0700); + } + + if (disable_auth) + { + if (rpc_config->login) + { + const cryptonote::rpc_args::descriptors arg{}; + LOG_ERROR(tr("Cannot specify --") << arg_disable_rpc_login.name << tr(" and --") << arg.rpc_login.name); + return false; + } + } + else // auth enabled + { + if (!rpc_config->login) + { + std::array<std::uint8_t, 16> rand_128bit{{}}; + crypto::rand(rand_128bit.size(), rand_128bit.data()); + http_login.emplace( + default_rpc_username, + string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size()) + ); + } + else + { + http_login.emplace( + std::move(rpc_config->login->username), std::move(rpc_config->login->password).password() + ); + } + assert(bool(http_login)); + + std::string temp = "monero-wallet-rpc." + bind_port + ".login"; + const auto cookie = tools::create_private_file(temp); + if (!cookie) + { + LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); + return false; + } + rpc_login_filename.swap(temp); // nothrow guarantee destructor cleanup + temp = rpc_login_filename; + std::fputs(http_login->username.c_str(), cookie.get()); + std::fputc(':', cookie.get()); + std::fputs(http_login->password.c_str(), cookie.get()); + std::fflush(cookie.get()); + if (std::ferror(cookie.get())) + { + LOG_ERROR(tr("Error writing to file ") << temp); + return false; + } + LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); + } // end auth enabled + + m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); + 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, m_user_agent); + return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( + std::move(bind_port), std::move(rpc_config->bind_ip), std::move(http_login) + ); + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::not_open(epee::json_rpc::error& er) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_OPEN; + er.message = "No wallet file"; + return false; + } + //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) + { + entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); + entry.payment_id = string_tools::pod_to_hex(payment_id); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = pd.m_block_height; + entry.timestamp = pd.m_timestamp; + entry.amount = pd.m_amount; + entry.fee = 0; // TODO + entry.note = m_wallet->get_tx_note(pd.m_tx_hash); + entry.type = "in"; + } + //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) + { + entry.txid = string_tools::pod_to_hex(txid); + entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = pd.m_block_height; + entry.timestamp = pd.m_timestamp; + entry.fee = pd.m_amount_in - pd.m_amount_out; + uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known + entry.amount = pd.m_amount_in - change - entry.fee; + entry.note = m_wallet->get_tx_note(txid); + + for (const auto &d: pd.m_dests) { + entry.destinations.push_back(wallet_rpc::transfer_destination()); + wallet_rpc::transfer_destination &td = entry.destinations.back(); + td.amount = d.amount; + td.address = get_account_address_as_str(m_wallet->testnet(), d.addr); + } + + entry.type = "out"; + } + //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) + { + bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; + entry.txid = string_tools::pod_to_hex(txid); + entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); + entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = 0; + entry.timestamp = pd.m_timestamp; + entry.fee = pd.m_amount_in - pd.m_amount_out; + entry.amount = pd.m_amount_in - pd.m_change - entry.fee; + entry.note = m_wallet->get_tx_note(txid); + entry.type = is_failed ? "failed" : "pending"; + } + //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) + { + entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); + entry.payment_id = string_tools::pod_to_hex(payment_id); + if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) + entry.payment_id = entry.payment_id.substr(0,16); + entry.height = 0; + entry.timestamp = pd.m_timestamp; + entry.amount = pd.m_amount; + entry.fee = 0; // TODO + entry.note = m_wallet->get_tx_note(pd.m_tx_hash); + entry.type = "pool"; } //------------------------------------------------------------------------------------------------------------------------------ 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) { + if (!m_wallet) return not_open(er); try { - res.balance = m_wallet.balance(); - res.unlocked_balance = m_wallet.unlocked_balance(); + res.balance = m_wallet->balance(); + res.unlocked_balance = m_wallet->unlocked_balance(); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -115,11 +313,12 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); try { - res.address = m_wallet.get_account().get_public_address_str(m_wallet.testnet()); + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -130,11 +329,12 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); try { - res.height = m_wallet.get_blockchain_current_height(); + res.height = m_wallet->get_blockchain_current_height(); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -152,7 +352,7 @@ namespace tools cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(de.addr, has_payment_id, new_payment_id, m_wallet.testnet(), it->address)) + if(!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), it->address, false)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; @@ -195,7 +395,7 @@ namespace tools cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, long_payment_id); } /* or short payment ID */ - else if (!wallet2::parse_short_payment_id(payment_id_str, short_payment_id)) { + else if (wallet2::parse_short_payment_id(payment_id_str, short_payment_id)) { cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, short_payment_id); } else { @@ -222,7 +422,9 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dsts; std::vector<uint8_t> extra; - if (m_wallet.restricted()) + LOG_PRINT_L3("on_transfer_split starts"); + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -238,11 +440,11 @@ namespace tools try { uint64_t mixin = req.mixin; - if (mixin < 2 && m_wallet.use_fork_rules(2, 10)) { + if (mixin < 2 && m_wallet->use_fork_rules(2, 10)) { LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); mixin = 2; } - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) @@ -252,7 +454,7 @@ namespace tools return false; } - m_wallet.commit_tx(ptx_vector); + m_wallet->commit_tx(ptx_vector); // populate response with tx hash res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); @@ -260,6 +462,7 @@ namespace tools { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); } + res.fee = ptx_vector.back().fee; return true; } catch (const tools::error::daemon_busy& e) @@ -289,7 +492,8 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dsts; std::vector<uint8_t> extra; - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -305,14 +509,18 @@ namespace tools try { uint64_t mixin = req.mixin; - if (mixin < 2 && m_wallet.use_fork_rules(2, 10)) { + if (mixin < 2 && m_wallet->use_fork_rules(2, 10)) { LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); mixin = 2; } std::vector<wallet2::pending_tx> ptx_vector; - ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.trusted_daemon); + LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); + ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - m_wallet.commit_tx(ptx_vector); + LOG_PRINT_L2("on_transfer_split calling commit_txyy"); + m_wallet->commit_tx(ptx_vector); + LOG_PRINT_L2("on_transfer_split called commit_txyy"); // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -322,6 +530,7 @@ namespace tools { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } + res.fee_list.push_back(ptx.fee); } return true; @@ -349,7 +558,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -358,9 +568,9 @@ namespace tools try { - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_unmixable_sweep_transactions(req.trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - m_wallet.commit_tx(ptx_vector); + m_wallet->commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -370,6 +580,7 @@ namespace tools { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } + res.fee_list.push_back(ptx.fee); } return true; @@ -400,7 +611,8 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dsts; std::vector<uint8_t> extra; - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -419,9 +631,9 @@ namespace tools try { - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions_all(dsts[0].addr, req.mixin, req.unlock_time, req.priority, extra, req.trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, req.mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); - m_wallet.commit_tx(ptx_vector); + m_wallet->commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -431,6 +643,7 @@ namespace tools { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } + res.fee_list.push_back(ptx.fee); } return true; @@ -458,6 +671,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); try { crypto::hash8 payment_id; @@ -475,11 +689,11 @@ namespace tools } } - res.integrated_address = m_wallet.get_account().get_public_integrated_address_str(payment_id, m_wallet.testnet()); + res.integrated_address = m_wallet->get_account().get_public_integrated_address_str(payment_id, m_wallet->testnet()); res.payment_id = epee::string_tools::pod_to_hex(payment_id); return true; } - catch (std::exception &e) + catch (const std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -490,13 +704,14 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); try { cryptonote::account_public_address address; crypto::hash8 payment_id; bool has_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet.testnet(), req.integrated_address)) + if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), req.integrated_address)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = "Invalid address"; @@ -508,11 +723,11 @@ namespace tools er.message = "Address is not an integrated address"; return false; } - res.standard_address = get_account_address_as_str(m_wallet.testnet(),address); + res.standard_address = get_account_address_as_str(m_wallet->testnet(),address); res.payment_id = epee::string_tools::pod_to_hex(payment_id); return true; } - catch (std::exception &e) + catch (const std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -523,7 +738,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ 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) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -532,9 +748,9 @@ namespace tools try { - m_wallet.store(); + m_wallet->store(); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -545,13 +761,14 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); crypto::hash payment_id; crypto::hash8 payment_id8; cryptonote::blobdata payment_id_blob; if(!epee::string_tools::parse_hexstr_to_binbuff(req.payment_id, payment_id_blob)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment ID has invald format"; + er.message = "Payment ID has invalid format"; return false; } @@ -574,7 +791,7 @@ namespace tools res.payments.clear(); std::list<wallet2::payment_details> payment_list; - m_wallet.get_payments(payment_id, payment_list); + m_wallet->get_payments(payment_id, payment_list); for (auto & payment : payment_list) { wallet_rpc::payment_details rpc_payment; @@ -592,12 +809,13 @@ namespace tools bool wallet_rpc_server::on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er) { res.payments.clear(); + if (!m_wallet) return not_open(er); /* If the payment ID list is empty, we get payments to any payment ID (or lack thereof) */ if (req.payment_ids.empty()) { std::list<std::pair<crypto::hash,wallet2::payment_details>> payment_list; - m_wallet.get_payments(payment_list, req.min_block_height); + m_wallet->get_payments(payment_list, req.min_block_height); for (auto & payment : payment_list) { @@ -646,7 +864,7 @@ namespace tools } std::list<wallet2::payment_details> payment_list; - m_wallet.get_payments(payment_id, payment_list, req.min_block_height); + m_wallet->get_payments(payment_id, payment_list, req.min_block_height); for (auto & payment : payment_list) { @@ -665,6 +883,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_incoming_transfers(const wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); if(req.transfer_type.compare("all") != 0 && req.transfer_type.compare("available") != 0 && req.transfer_type.compare("unavailable") != 0) { er.code = WALLET_RPC_ERROR_CODE_TRANSFER_TYPE; @@ -686,7 +905,7 @@ namespace tools } wallet2::transfer_container transfers; - m_wallet.get_transfers(transfers); + m_wallet->get_transfers(transfers); bool transfers_found = false; for (const auto& td : transfers) @@ -713,7 +932,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -722,7 +942,7 @@ namespace tools if (req.key_type.compare("mnemonic") == 0) { - if (!m_wallet.get_seed(res.key)) + if (!m_wallet->get_seed(res.key)) { er.message = "The wallet is non-deterministic. Cannot display seed."; return false; @@ -730,7 +950,7 @@ namespace tools } else if(req.key_type.compare("view_key") == 0) { - res.key = string_tools::pod_to_hex(m_wallet.get_account().get_keys().m_view_secret_key); + res.key = string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key); } else { @@ -743,7 +963,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_rescan_blockchain(const wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::request& req, wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -752,9 +973,9 @@ namespace tools try { - m_wallet.rescan_blockchain(); + m_wallet->rescan_blockchain(); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -765,20 +986,22 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } - res.signature = m_wallet.sign(req.data); + res.signature = m_wallet->sign(req.data); return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -788,20 +1011,21 @@ namespace tools cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet.testnet(), req.address)) + if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), req.address, false)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = ""; return false; } - res.good = m_wallet.verify(req.data, address, req.signature); + res.good = m_wallet->verify(req.data, address, req.signature); return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_stop_wallet(const wallet_rpc::COMMAND_RPC_STOP_WALLET::request& req, wallet_rpc::COMMAND_RPC_STOP_WALLET::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -810,10 +1034,10 @@ namespace tools try { - m_wallet.store(); + m_wallet->store(); m_stop.store(true, std::memory_order_relaxed); } - catch (std::exception& e) + catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); @@ -824,6 +1048,14 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_set_tx_notes(const wallet_rpc::COMMAND_RPC_SET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_SET_TX_NOTES::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (req.txids.size() != req.notes.size()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -836,7 +1068,7 @@ namespace tools while (i != req.txids.end()) { cryptonote::blobdata txid_blob; - if(!epee::string_tools::parse_hexstr_to_binbuff(*i++, txid_blob)) + if(!epee::string_tools::parse_hexstr_to_binbuff(*i++, txid_blob) || txid_blob.size() != sizeof(crypto::hash)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; er.message = "TX ID has invalid format"; @@ -851,7 +1083,7 @@ namespace tools std::list<std::string>::const_iterator in = req.notes.begin(); while (il != txids.end()) { - m_wallet.set_tx_note(*il++, *in++); + m_wallet->set_tx_note(*il++, *in++); } return true; @@ -860,13 +1092,14 @@ namespace tools bool wallet_rpc_server::on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er) { res.notes.clear(); + if (!m_wallet) return not_open(er); std::list<crypto::hash> txids; std::list<std::string>::const_iterator i = req.txids.begin(); while (i != req.txids.end()) { cryptonote::blobdata txid_blob; - if(!epee::string_tools::parse_hexstr_to_binbuff(*i++, txid_blob)) + if(!epee::string_tools::parse_hexstr_to_binbuff(*i++, txid_blob) || txid_blob.size() != sizeof(crypto::hash)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; er.message = "TX ID has invalid format"; @@ -880,14 +1113,15 @@ namespace tools std::list<crypto::hash>::const_iterator il = txids.begin(); while (il != txids.end()) { - res.notes.push_back(m_wallet.get_tx_note(*il++)); + res.notes.push_back(m_wallet->get_tx_note(*il++)); } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er) { - if (m_wallet.restricted()) + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; @@ -904,109 +1138,135 @@ namespace tools if (req.in) { std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet.get_payments(payments, min_height, max_height); + m_wallet->get_payments(payments, min_height, max_height); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { - res.in.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.in.back(); - const tools::wallet2::payment_details &pd = i->second; - - entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); - entry.payment_id = string_tools::pod_to_hex(i->first); - if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) - entry.payment_id = entry.payment_id.substr(0,16); - entry.height = pd.m_block_height; - entry.timestamp = pd.m_timestamp; - entry.amount = pd.m_amount; - entry.fee = 0; // TODO - entry.note = m_wallet.get_tx_note(pd.m_tx_hash); + res.in.push_back(wallet_rpc::transfer_entry()); + fill_transfer_entry(res.in.back(), i->second.m_tx_hash, i->first, i->second); } } if (req.out) { std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments; - m_wallet.get_payments_out(payments, min_height, max_height); + m_wallet->get_payments_out(payments, min_height, max_height); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { - res.out.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.out.back(); - const tools::wallet2::confirmed_transfer_details &pd = i->second; - - entry.txid = string_tools::pod_to_hex(i->first); - entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id); - if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) - entry.payment_id = entry.payment_id.substr(0,16); - entry.height = pd.m_block_height; - entry.timestamp = pd.m_timestamp; - entry.fee = pd.m_amount_in - pd.m_amount_out; - uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known - entry.amount = pd.m_amount_in - change - entry.fee; - entry.note = m_wallet.get_tx_note(i->first); - - for (const auto &d: pd.m_dests) { - entry.destinations.push_back(wallet_rpc::transfer_destination()); - wallet_rpc::transfer_destination &td = entry.destinations.back(); - td.amount = d.amount; - td.address = get_account_address_as_str(m_wallet.testnet(), d.addr); - } + res.out.push_back(wallet_rpc::transfer_entry()); + fill_transfer_entry(res.out.back(), i->first, i->second); } } if (req.pending || req.failed) { std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; - m_wallet.get_unconfirmed_payments_out(upayments); + m_wallet->get_unconfirmed_payments_out(upayments); for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; if (!((req.failed && is_failed) || (!is_failed && req.pending))) continue; - std::list<wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry> &entries = is_failed ? res.failed : res.pending; - entries.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = entries.back(); - - entry.txid = string_tools::pod_to_hex(i->first); - entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id); - entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id); - if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) - entry.payment_id = entry.payment_id.substr(0,16); - entry.height = 0; - entry.timestamp = pd.m_timestamp; - entry.fee = pd.m_amount_in - pd.m_amount_out; - entry.amount = pd.m_amount_in - pd.m_change - entry.fee; - entry.note = m_wallet.get_tx_note(i->first); + std::list<wallet_rpc::transfer_entry> &entries = is_failed ? res.failed : res.pending; + entries.push_back(wallet_rpc::transfer_entry()); + fill_transfer_entry(entries.back(), i->first, i->second); } } if (req.pool) { - m_wallet.update_pool_state(); + m_wallet->update_pool_state(); std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet.get_unconfirmed_payments(payments); + m_wallet->get_unconfirmed_payments(payments); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { - res.pool.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry()); - wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.pool.back(); - const tools::wallet2::payment_details &pd = i->second; - - entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); - entry.payment_id = string_tools::pod_to_hex(i->first); - if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) - entry.payment_id = entry.payment_id.substr(0,16); - entry.height = 0; - entry.timestamp = pd.m_timestamp; - entry.amount = pd.m_amount; - entry.fee = 0; // TODO - entry.note = m_wallet.get_tx_note(pd.m_tx_hash); + res.pool.push_back(wallet_rpc::transfer_entry()); + fill_transfer_entry(res.pool.back(), i->first, i->second); } } return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + crypto::hash txid; + cryptonote::blobdata txid_blob; + if(!epee::string_tools::parse_hexstr_to_binbuff(req.txid, txid_blob)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "Transaction ID has invalid format"; + return false; + } + + if(sizeof(txid) == txid_blob.size()) + { + txid = *reinterpret_cast<const crypto::hash*>(txid_blob.data()); + } + else + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "Transaction ID has invalid size: " + req.txid; + return false; + } + + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + m_wallet->get_payments(payments, 0); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + if (i->second.m_tx_hash == txid) + { + fill_transfer_entry(res.transfer, i->second.m_tx_hash, i->first, i->second); + return true; + } + } + + std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out; + m_wallet->get_payments_out(payments_out, 0); + for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) { + if (i->first == txid) + { + fill_transfer_entry(res.transfer, i->first, i->second); + return true; + } + } + + std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; + m_wallet->get_unconfirmed_payments_out(upayments); + for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + if (i->first == txid) + { + fill_transfer_entry(res.transfer, i->first, i->second); + return true; + } + } + + m_wallet->update_pool_state(); + + std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments; + m_wallet->get_unconfirmed_payments(pool_payments); + for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { + if (i->second.m_tx_hash == txid) + { + fill_transfer_entry(res.transfer, i->first, i->second); + return true; + } + } + + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "Transaction not found."; + return false; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); try { - std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet.export_key_images(); + std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images(); res.signed_key_images.resize(ski.size()); for (size_t n = 0; n < ski.size(); ++n) { @@ -1027,6 +1287,14 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er) { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + try { std::vector<std::pair<crypto::key_image, crypto::signature>> ski; @@ -1035,7 +1303,7 @@ namespace tools { cryptonote::blobdata bd; - if(!epee::string_tools::parse_hexstr_to_binbuff(req.signed_key_images[n].key_image, bd)) + if(!epee::string_tools::parse_hexstr_to_binbuff(req.signed_key_images[n].key_image, bd) || bd.size() != sizeof(crypto::key_image)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; er.message = "failed to parse key image"; @@ -1043,7 +1311,7 @@ namespace tools } ski[n].first = *reinterpret_cast<const crypto::key_image*>(bd.data()); - if(!epee::string_tools::parse_hexstr_to_binbuff(req.signed_key_images[n].signature, bd)) + if(!epee::string_tools::parse_hexstr_to_binbuff(req.signed_key_images[n].signature, bd) || bd.size() != sizeof(crypto::signature)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE; er.message = "failed to parse signature"; @@ -1052,7 +1320,7 @@ namespace tools ski[n].second = *reinterpret_cast<const crypto::signature*>(bd.data()); } uint64_t spent = 0, unspent = 0; - uint64_t height = m_wallet.import_key_images(ski, spent, unspent); + uint64_t height = m_wallet->import_key_images(ski, spent, unspent); res.spent = spent; res.unspent = unspent; res.height = height; @@ -1068,5 +1336,482 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er) + { + std::string error; + std::string uri = m_wallet->make_uri(req.address, req.payment_id, req.amount, req.tx_description, req.recipient_name, error); + if (uri.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_URI; + er.message = std::string("Cannot make URI from supplied parameters: ") + error; + return false; + } + + res.uri = uri; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_parse_uri(const wallet_rpc::COMMAND_RPC_PARSE_URI::request& req, wallet_rpc::COMMAND_RPC_PARSE_URI::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + std::string error; + if (!m_wallet->parse_uri(req.uri, res.uri.address, res.uri.payment_id, res.uri.amount, res.uri.tx_description, res.uri.recipient_name, res.unknown_parameters, error)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_URI; + er.message = "Error parsing URI: " + error; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + const auto ab = m_wallet->get_address_book(); + if (req.entries.empty()) + { + uint64_t idx = 0; + for (const auto &entry: ab) + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + } + else + { + for (uint64_t idx: req.entries) + { + if (idx >= ab.size()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_INDEX; + er.message = "Index out of range: " + std::to_string(idx); + return false; + } + const auto &entry = ab[idx]; + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + } + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id8; + crypto::hash payment_id = cryptonote::null_hash; + if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), req.address, false)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; + return false; + } + if (has_payment_id) + { + memcpy(payment_id.data, payment_id8.data, 8); + memset(payment_id.data + 8, 0, 24); + } + if (!req.payment_id.empty()) + { + if (has_payment_id) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Separate payment ID given with integrated address"; + return false; + } + + crypto::hash long_payment_id; + crypto::hash8 short_payment_id; + + if (!wallet2::parse_long_payment_id(req.payment_id, payment_id)) + { + if (!wallet2::parse_short_payment_id(req.payment_id, payment_id8)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 16 or 64 character string"; + return false; + } + else + { + memcpy(payment_id.data, payment_id8.data, 8); + memset(payment_id.data + 8, 0, 24); + } + } + } + if (!m_wallet->add_address_book_row(address, payment_id, req.description)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to add address book entry"; + return false; + } + res.index = m_wallet->get_address_book().size(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + const auto ab = m_wallet->get_address_book(); + if (req.index >= ab.size()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_INDEX; + er.message = "Index out of range: " + std::to_string(req.index); + return false; + } + if (!m_wallet->delete_address_book_row(req.index)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to delete address book entry"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + try + { + m_wallet->rescan_spent(); + return true; + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (!m_trusted_daemon) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "This command requires a trusted daemon."; + return false; + } + + size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2)); + if (req.threads_count < 1 || max_mining_threads_count < req.threads_count) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "The specified number of threads is inappropriate."; + return false; + } + + cryptonote::COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req); + daemon_req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + daemon_req.threads_count = req.threads_count; + daemon_req.do_background_mining = req.do_background_mining; + daemon_req.ignore_battery = req.ignore_battery; + + cryptonote::COMMAND_RPC_START_MINING::response daemon_res; + bool r = net_utils::invoke_http_json("/start_mining", daemon_req, daemon_res, m_http_client); + if (!r || daemon_res.status != CORE_RPC_STATUS_OK) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Couldn't start mining due to unknown error."; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er) + { + cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req; + cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res; + bool r = net_utils::invoke_http_json("/stop_mining", daemon_req, daemon_res, m_http_client); + if (!r || daemon_res.status != CORE_RPC_STATUS_OK) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Couldn't stop mining due to unknown error."; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er) + { + crypto::ElectrumWords::get_language_list(res.languages); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er) + { + if (m_wallet_dir.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "No wallet dir configured"; + return false; + } + + namespace po = boost::program_options; + po::variables_map vm2; + const char *ptr = strchr(req.filename.c_str(), '/'); +#ifdef _WIN32 + if (!ptr) + ptr = strchr(req.filename.c_str(), '\\'); + if (!ptr) + ptr = strchr(req.filename.c_str(), ':'); +#endif + if (ptr) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Invalid filename"; + return false; + } + std::string wallet_file = m_wallet_dir + "/" + req.filename; + { + std::vector<std::string> languages; + crypto::ElectrumWords::get_language_list(languages); + std::vector<std::string>::iterator it; + std::string wallet_file; + char *ptr; + + it = std::find(languages.begin(), languages.end(), req.language); + if (it == languages.end()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Unknown language"; + return false; + } + } + { + po::options_description desc("dummy"); + const command_line::arg_descriptor<std::string, true> arg_password = {"password", "password"}; + const char *argv[4]; + int argc = 3; + argv[0] = "wallet-rpc"; + argv[1] = "--password"; + argv[2] = req.password.c_str(); + argv[3] = NULL; + vm2 = *m_vm; + command_line::add_arg(desc, arg_password); + po::store(po::parse_command_line(argc, argv, desc), vm2); + } + std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2).first; + if (!wal) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to create wallet"; + return false; + } + wal->set_seed_language(req.language); + cryptonote::COMMAND_RPC_GET_HEIGHT::request hreq; + cryptonote::COMMAND_RPC_GET_HEIGHT::response hres; + hres.height = 0; + bool r = net_utils::invoke_http_json("/getheight", hreq, hres, m_http_client); + wal->set_refresh_from_block_height(hres.height); + crypto::secret_key dummy_key; + try { + wal->generate(wallet_file, req.password, dummy_key, false, false); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to generate wallet"; + return false; + } + if (m_wallet) + delete m_wallet; + m_wallet = wal.release(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er) + { + if (m_wallet_dir.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "No wallet dir configured"; + return false; + } + + namespace po = boost::program_options; + po::variables_map vm2; + const char *ptr = strchr(req.filename.c_str(), '/'); +#ifdef _WIN32 + if (!ptr) + ptr = strchr(req.filename.c_str(), '\\'); + if (!ptr) + ptr = strchr(req.filename.c_str(), ':'); +#endif + if (ptr) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Invalid filename"; + return false; + } + std::string wallet_file = m_wallet_dir + "/" + req.filename; + { + po::options_description desc("dummy"); + const command_line::arg_descriptor<std::string, true> arg_password = {"password", "password"}; + const char *argv[4]; + int argc = 3; + argv[0] = "wallet-rpc"; + argv[1] = "--password"; + argv[2] = req.password.c_str(); + argv[3] = NULL; + vm2 = *m_vm; + command_line::add_arg(desc, arg_password); + po::store(po::parse_command_line(argc, argv, desc), vm2); + } + std::unique_ptr<tools::wallet2> wal; + try { + wal = tools::wallet2::make_from_file(vm2, wallet_file).first; + } + catch (const std::exception& e) + { + wal = nullptr; + } + if (!wal) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to open wallet"; + return false; + } + if (m_wallet) + delete m_wallet; + m_wallet = wal.release(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } +int main(int argc, char** argv) { + namespace po = boost::program_options; + + const auto arg_wallet_file = wallet_args::arg_wallet_file(); + const auto arg_from_json = wallet_args::arg_generate_from_json(); + + po::options_description desc_params(wallet_args::tr("Wallet options")); + tools::wallet2::init_options(desc_params); + command_line::add_arg(desc_params, arg_rpc_bind_port); + command_line::add_arg(desc_params, arg_disable_rpc_login); + command_line::add_arg(desc_params, arg_trusted_daemon); + cryptonote::rpc_args::init_options(desc_params); + command_line::add_arg(desc_params, arg_wallet_file); + command_line::add_arg(desc_params, arg_from_json); + command_line::add_arg(desc_params, arg_wallet_dir); + + + const auto vm = wallet_args::main( + argc, argv, + "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", + desc_params, + po::positional_options_description(), + "monero-wallet-rpc.log", + true + ); + if (!vm) + { + return 1; + } + + std::unique_ptr<tools::wallet2> wal; + try + { + const auto wallet_file = command_line::get_arg(*vm, arg_wallet_file); + const auto from_json = command_line::get_arg(*vm, arg_from_json); + const auto wallet_dir = command_line::get_arg(*vm, arg_wallet_dir); + + if(!wallet_file.empty() && !from_json.empty()) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Can't specify more than one of --wallet-file and --generate-from-json")); + return 1; + } + + if (!wallet_dir.empty()) + { + wal = NULL; + goto just_dir; + } + + if (wallet_file.empty() && from_json.empty()) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Must specify --wallet-file or --generate-from-json or --wallet-dir")); + return 1; + } + + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); + if(!wallet_file.empty()) + { + wal = tools::wallet2::make_from_file(*vm, wallet_file).first; + } + else + { + wal = tools::wallet2::make_from_json(*vm, from_json); + } + if (!wal) + { + return 1; + } + + bool quit = false; + tools::signal_handler::install([&wal, &quit](int) { + assert(wal); + quit = true; + wal->stop(); + }); + + wal->refresh(); + // if we ^C during potentially length load/refresh, there's no server loop yet + if (quit) + { + MINFO(tools::wallet_rpc_server::tr("Storing wallet...")); + wal->store(); + MINFO(tools::wallet_rpc_server::tr("Stored ok")); + return 1; + } + MINFO(tools::wallet_rpc_server::tr("Loaded ok")); + } + catch (const std::exception& e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Wallet initialization failed: ") << e.what()); + return 1; + } +just_dir: + tools::wallet_rpc_server wrpc; + if (wal) wrpc.set_wallet(wal.release()); + bool r = wrpc.init(&(vm.get())); + CHECK_AND_ASSERT_MES(r, 1, tools::wallet_rpc_server::tr("Failed to initialize wallet rpc server")); + tools::signal_handler::install([&wrpc, &wal](int) { + wrpc.send_stop_signal(); + }); + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet rpc server")); + wrpc.run(); + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet rpc server")); + try + { + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Storing wallet...")); + wrpc.stop(); + LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stored ok")); + } + catch (const std::exception& e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Failed to store wallet: ") << e.what()); + return 1; + } + return 0; +} diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index b3e95c18a..230dcee5b 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -32,10 +32,14 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> +#include <string> #include "net/http_server_impl_base.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" -#include "common/command_line.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" + namespace tools { /************************************************************************/ @@ -46,16 +50,16 @@ namespace tools 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; - const static command_line::arg_descriptor<std::string> arg_user_agent; + static const char* tr(const char* str); + wallet_rpc_server(); + ~wallet_rpc_server(); - static void init_options(boost::program_options::options_description& desc); - bool init(const boost::program_options::variables_map& vm); + bool init(const boost::program_options::variables_map *vm); bool run(); + void stop(); + void set_wallet(wallet2 *cr); + private: CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map @@ -81,10 +85,22 @@ namespace tools MAP_JON_RPC_WE("set_tx_notes", on_set_tx_notes, wallet_rpc::COMMAND_RPC_SET_TX_NOTES) MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES) MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS) + MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY) MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES) MAP_JON_RPC_WE("import_key_images", on_import_key_images, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES) + MAP_JON_RPC_WE("make_uri", on_make_uri, wallet_rpc::COMMAND_RPC_MAKE_URI) + MAP_JON_RPC_WE("parse_uri", on_parse_uri, wallet_rpc::COMMAND_RPC_PARSE_URI) + MAP_JON_RPC_WE("get_address_book", on_get_address_book, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY) + MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY) + MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY) + MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT) + MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING) + MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING) + MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) + MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) + MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) END_JSON_RPC_MAP() END_URI_MAP2() @@ -108,20 +124,39 @@ namespace tools bool on_set_tx_notes(const wallet_rpc::COMMAND_RPC_SET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_SET_TX_NOTES::response& res, epee::json_rpc::error& er); bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er); bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er); + bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er); bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er); bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); bool on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); - - bool handle_command_line(const boost::program_options::variables_map& vm); + bool on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er); + bool on_parse_uri(const wallet_rpc::COMMAND_RPC_PARSE_URI::request& req, wallet_rpc::COMMAND_RPC_PARSE_URI::response& res, epee::json_rpc::error& er); + bool on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); + bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); + bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); + bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er); + bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er); + bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er); + bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); + bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); + bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); - wallet2& m_wallet; - std::string m_port; - std::string m_bind_ip; - std::string m_user_agent; + // helpers + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd); + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd); + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); + bool not_open(epee::json_rpc::error& er); + + wallet2 *m_wallet; + std::string m_wallet_dir; + std::string rpc_login_filename; std::atomic<bool> m_stop; + bool m_trusted_daemon; + epee::net_utils::http::http_simple_client m_http_client; + const boost::program_options::variables_map *m_vm; }; } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index cde9863a2..3c10dc41f 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,9 +30,13 @@ #pragma once #include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "crypto/hash.h" #include "wallet_rpc_server_error_codes.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" + namespace tools { namespace wallet_rpc @@ -115,7 +119,6 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool get_tx_key; - bool trusted_daemon; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -124,7 +127,6 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_key) - KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; @@ -133,11 +135,13 @@ namespace wallet_rpc std::string tx_hash; std::string tx_key; std::list<std::string> amount_keys; + uint64_t fee; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount_keys) + KV_SERIALIZE(fee) END_KV_SERIALIZE_MAP() }; }; @@ -152,7 +156,6 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool get_tx_keys; - bool trusted_daemon; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -161,7 +164,6 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_keys) - KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; @@ -178,10 +180,12 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + std::list<uint64_t> fee_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(fee_list) END_KV_SERIALIZE_MAP() }; }; @@ -191,11 +195,9 @@ namespace wallet_rpc struct request { bool get_tx_keys; - bool trusted_daemon; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) - KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; @@ -212,10 +214,12 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + std::list<uint64_t> fee_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(fee_list) END_KV_SERIALIZE_MAP() }; }; @@ -230,7 +234,7 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool get_tx_keys; - bool trusted_daemon; + uint64_t below_amount; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -239,7 +243,7 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_keys) - KV_SERIALIZE(trusted_daemon) + KV_SERIALIZE(below_amount) END_KV_SERIALIZE_MAP() }; @@ -256,10 +260,12 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + std::list<uint64_t> fee_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(fee_list) END_KV_SERIALIZE_MAP() }; }; @@ -517,6 +523,31 @@ namespace wallet_rpc }; }; + struct transfer_entry + { + std::string txid; + std::string payment_id; + uint64_t height; + uint64_t timestamp; + uint64_t amount; + uint64_t fee; + std::string note; + std::list<transfer_destination> destinations; + std::string type; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid); + KV_SERIALIZE(payment_id); + KV_SERIALIZE(height); + KV_SERIALIZE(timestamp); + KV_SERIALIZE(amount); + KV_SERIALIZE(fee); + KV_SERIALIZE(note); + KV_SERIALIZE(destinations); + KV_SERIALIZE(type); + END_KV_SERIALIZE_MAP() + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request @@ -543,43 +574,41 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; - struct entry + struct response + { + std::list<transfer_entry> in; + std::list<transfer_entry> out; + std::list<transfer_entry> pending; + std::list<transfer_entry> failed; + std::list<transfer_entry> pool; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(in); + KV_SERIALIZE(out); + KV_SERIALIZE(pending); + KV_SERIALIZE(failed); + KV_SERIALIZE(pool); + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_TRANSFER_BY_TXID + { + struct request { std::string txid; - std::string payment_id; - uint64_t height; - uint64_t timestamp; - uint64_t amount; - uint64_t fee; - std::string note; - std::list<transfer_destination> destinations; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(txid); - KV_SERIALIZE(payment_id); - KV_SERIALIZE(height); - KV_SERIALIZE(timestamp); - KV_SERIALIZE(amount); - KV_SERIALIZE(fee); - KV_SERIALIZE(note); - KV_SERIALIZE(destinations); END_KV_SERIALIZE_MAP() }; struct response { - std::list<entry> in; - std::list<entry> out; - std::list<entry> pending; - std::list<entry> failed; - std::list<entry> pool; + transfer_entry transfer; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(in); - KV_SERIALIZE(out); - KV_SERIALIZE(pending); - KV_SERIALIZE(failed); - KV_SERIALIZE(pool); + KV_SERIALIZE(transfer); END_KV_SERIALIZE_MAP() }; }; @@ -695,5 +724,248 @@ namespace wallet_rpc }; }; + struct uri_spec + { + std::string address; + std::string payment_id; + uint64_t amount; + std::string tx_description; + std::string recipient_name; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address); + KV_SERIALIZE(payment_id); + KV_SERIALIZE(amount); + KV_SERIALIZE(tx_description); + KV_SERIALIZE(recipient_name); + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_MAKE_URI + { + struct request: public uri_spec + { + }; + + struct response + { + std::string uri; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(uri) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_PARSE_URI + { + struct request + { + std::string uri; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(uri) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uri_spec uri; + std::vector<std::string> unknown_parameters; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(uri); + KV_SERIALIZE(unknown_parameters); + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY + { + struct request + { + std::string address; + std::string payment_id; + std::string description; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(description) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index); + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY + { + struct request + { + std::list<uint64_t> entries; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(entries) + END_KV_SERIALIZE_MAP() + }; + + struct entry + { + uint64_t index; + std::string address; + std::string payment_id; + std::string description; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index) + KV_SERIALIZE(address) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(description) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<entry> entries; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(entries) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY + { + struct request + { + uint64_t index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_RESCAN_SPENT + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_START_MINING + { + struct request + { + uint64_t threads_count; + bool do_background_mining; + bool ignore_battery; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(threads_count) + KV_SERIALIZE(do_background_mining) + KV_SERIALIZE(ignore_battery) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_STOP_MINING + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_LANGUAGES + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + struct response + { + std::vector<std::string> languages; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(languages) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CREATE_WALLET + { + struct request + { + std::string filename; + std::string password; + std::string language; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(filename) + KV_SERIALIZE(password) + KV_SERIALIZE(language) + END_KV_SERIALIZE_MAP() + }; + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_OPEN_WALLET + { + struct request + { + std::string filename; + std::string password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(filename) + KV_SERIALIZE(password) + 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 index 4617a1449..3c79c0ac3 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2016, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -41,3 +41,6 @@ #define WALLET_RPC_ERROR_CODE_WRONG_TXID -8 #define WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE -9 #define WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE -10 +#define WALLET_RPC_ERROR_CODE_WRONG_URI -11 +#define WALLET_RPC_ERROR_CODE_WRONG_INDEX -12 +#define WALLET_RPC_ERROR_CODE_NOT_OPEN -13 |