diff options
Diffstat (limited to 'src')
83 files changed, 1912 insertions, 858 deletions
diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp index 3eb24494f..d138a1e7e 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.cpp +++ b/src/blockchain_db/berkeleydb/db_bdb.cpp @@ -1654,36 +1654,6 @@ tx_out_index BlockchainBDB::get_output_tx_and_index(const uint64_t& amount, cons return indices[0]; } -std::vector<uint64_t> BlockchainBDB::get_tx_output_indices(const crypto::hash& h) const -{ - LOG_PRINT_L3("BlockchainBDB::" << __func__); - check_open(); - std::vector<uint64_t> index_vec; - - bdb_cur cur(DB_DEFAULT_TX, m_tx_outputs); - - Dbt_copy<crypto::hash> k(h); - Dbt_copy<uint32_t> v; - auto result = cur->get(&k, &v, DB_SET); - if (result == DB_NOTFOUND) - throw1(OUTPUT_DNE("Attempting to get an output by tx hash and tx index, but output not found")); - else if (result) - throw0(DB_ERROR("DB error attempting to get an output")); - - db_recno_t num_elems = 0; - cur->count(&num_elems, 0); - - for (uint64_t i = 0; i < num_elems; ++i) - { - index_vec.push_back(v); - cur->get(&k, &v, DB_NEXT_DUP); - } - - cur.close(); - - return index_vec; -} - std::vector<uint64_t> BlockchainBDB::get_tx_amount_output_indices(const crypto::hash& h) const { LOG_PRINT_L3("BlockchainBDB::" << __func__); diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index 04a33d7c6..3ae90efe1 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -312,7 +312,6 @@ public: virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index); virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices); - virtual std::vector<uint64_t> get_tx_output_indices(const crypto::hash& h) const; virtual std::vector<uint64_t> get_tx_amount_output_indices(const crypto::hash& h) const; virtual bool has_key_image(const crypto::key_image& img) const; diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index d772bf4bb..2b039f557 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -211,8 +211,6 @@ uint64_t BlockchainDB::add_block( const std::pair<block, blobdata>& blck if (blk.tx_hashes.size() != txs.size()) throw std::runtime_error("Inconsistent tx/hashes sizes"); - block_txn_start(false); - TIME_MEASURE_START(time1); crypto::hash blk_hash = get_block_hash(blk); TIME_MEASURE_FINISH(time1); @@ -252,8 +250,6 @@ uint64_t BlockchainDB::add_block( const std::pair<block, blobdata>& blck m_hardfork->add(blk, prev_height); - block_txn_stop(); - ++num_calls; return prev_height; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 2c40b5a78..b6b8c6c3e 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -754,6 +754,21 @@ public: virtual void batch_stop() = 0; /** + * @brief aborts a batch transaction + * + * If the subclass implements batching, this function should abort the + * batch it is currently on. + * + * If no batch is 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 any of this cannot be done, the subclass should throw the corresponding + * subclass of DB_EXCEPTION + */ + virtual void batch_abort() = 0; + + /** * @brief sets whether or not to batch transactions * * If the subclass implements batching, this function tells it to begin @@ -770,9 +785,12 @@ public: */ virtual void set_batch_transactions(bool) = 0; - virtual void block_txn_start(bool readonly=false) = 0; - virtual void block_txn_stop() = 0; - virtual void block_txn_abort() = 0; + virtual void block_wtxn_start() = 0; + virtual void block_wtxn_stop() = 0; + virtual void block_wtxn_abort() = 0; + virtual bool block_rtxn_start() const = 0; + virtual void block_rtxn_stop() const = 0; + virtual void block_rtxn_abort() const = 0; virtual void set_hard_fork(HardFork* hf); @@ -1699,6 +1717,52 @@ public: }; // class BlockchainDB +class db_txn_guard +{ +public: + db_txn_guard(BlockchainDB *db, bool readonly): db(db), readonly(readonly), active(false) + { + if (readonly) + { + active = db->block_rtxn_start(); + } + else + { + db->block_wtxn_start(); + active = true; + } + } + virtual ~db_txn_guard() + { + if (active) + stop(); + } + void stop() + { + if (readonly) + db->block_rtxn_stop(); + else + db->block_wtxn_stop(); + active = false; + } + void abort() + { + if (readonly) + db->block_rtxn_abort(); + else + db->block_wtxn_abort(); + active = false; + } + +private: + BlockchainDB *db; + bool readonly; + bool active; +}; + +class db_rtxn_guard: public db_txn_guard { public: db_rtxn_guard(BlockchainDB *db): db_txn_guard(db, true) {} }; +class db_wtxn_guard: public db_txn_guard { public: db_wtxn_guard(BlockchainDB *db): db_txn_guard(db, false) {} }; + BlockchainDB *new_db(const std::string& db_type); } // namespace cryptonote diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index a07e9ac55..d0fd787db 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -770,8 +770,8 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l bi.bi_timestamp = blk.timestamp; bi.bi_coins = coins_generated; bi.bi_weight = block_weight; - bi.bi_diff_hi = (cumulative_difficulty >> 64).convert_to<uint64_t>(); - bi.bi_diff_lo = (cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); + bi.bi_diff_hi = ((cumulative_difficulty >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); + bi.bi_diff_lo = (cumulative_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); bi.bi_hash = blk_hash; bi.bi_cum_rct = num_rct_outs; if (blk.major_version >= 4) @@ -1077,11 +1077,11 @@ void BlockchainLMDB::add_tx_amount_output_indices(const uint64_t tx_id, int result = 0; - int num_outputs = amount_output_indices.size(); + size_t num_outputs = amount_output_indices.size(); MDB_val_set(k_tx_id, tx_id); MDB_val v; - v.mv_data = (void *)amount_output_indices.data(); + v.mv_data = num_outputs ? (void *)amount_output_indices.data() : (void*)""; v.mv_size = sizeof(uint64_t) * num_outputs; // LOG_PRINT_L1("tx_outputs[tx_hash] size: " << v.mv_size); @@ -1953,7 +1953,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) TIME_MEASURE_START(t); - size_t n_total_records = 0, n_prunable_records = 0, n_pruned_records = 0; + size_t n_total_records = 0, n_prunable_records = 0, n_pruned_records = 0, commit_counter = 0; uint64_t n_bytes = 0; mdb_txn_safe txn; @@ -2056,6 +2056,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { MDEBUG("Pruning at height " << block_height << "/" << blockchain_height); ++n_pruned_records; + ++commit_counter; n_bytes += k.mv_size + v.mv_size; result = mdb_cursor_del(c_txs_prunable, 0); if (result) @@ -2065,6 +2066,25 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) result = mdb_cursor_del(c_txs_prunable_tip, 0); if (result) throw0(DB_ERROR(lmdb_error("Failed to delete transaction tip data: ", result).c_str())); + + if (mode != prune_mode_check && commit_counter >= 4096) + { + MDEBUG("Committing txn at checkpoint..."); + txn.commit(); + 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())); + result = mdb_cursor_open(txn, m_txs_pruned, &c_txs_pruned); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_pruned: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable, &c_txs_prunable); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable_tip, &c_txs_prunable_tip); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable_tip: ", result).c_str())); + commit_counter = 0; + } } } } @@ -2134,6 +2154,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) result = mdb_cursor_del(c_txs_prunable, 0); if (result) throw0(DB_ERROR(lmdb_error("Failed to delete transaction prunable data: ", result).c_str())); + ++commit_counter; } } } @@ -2150,6 +2171,34 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) ", seed " << epee::string_tools::to_string_hex(pruning_seed)); } } + + if (mode != prune_mode_check && commit_counter >= 4096) + { + MDEBUG("Committing txn at checkpoint..."); + txn.commit(); + 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())); + result = mdb_cursor_open(txn, m_txs_pruned, &c_txs_pruned); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_pruned: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable, &c_txs_prunable); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable_tip, &c_txs_prunable_tip); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable_tip: ", result).c_str())); + result = mdb_cursor_open(txn, m_tx_indices, &c_tx_indices); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for tx_indices: ", result).c_str())); + MDB_val val; + val.mv_size = sizeof(ti); + val.mv_data = (void *)&ti; + result = mdb_cursor_get(c_tx_indices, (MDB_val*)&zerokval, &val, MDB_GET_BOTH); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to restore cursor for tx_indices: ", result).c_str())); + commit_counter = 0; + } } mdb_cursor_close(c_tx_indices); } @@ -3611,16 +3660,15 @@ void BlockchainLMDB::block_rtxn_stop() const memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); } -void BlockchainLMDB::block_txn_start(bool readonly) +bool BlockchainLMDB::block_rtxn_start() const { - if (readonly) - { - MDB_txn *mtxn; - mdb_txn_cursors *mcur; - block_rtxn_start(&mtxn, &mcur); - return; - } + MDB_txn *mtxn; + mdb_txn_cursors *mcur; + return block_rtxn_start(&mtxn, &mcur); +} +void BlockchainLMDB::block_wtxn_start() +{ LOG_PRINT_L3("BlockchainLMDB::" << __func__); // Distinguish the exceptions here from exceptions that would be thrown while // using the txn and committing it. @@ -3652,10 +3700,13 @@ void BlockchainLMDB::block_txn_start(bool readonly) 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() +void BlockchainLMDB::block_wtxn_stop() { LOG_PRINT_L3("BlockchainLMDB::" << __func__); - if (m_write_txn && m_writer == boost::this_thread::get_id()) + if (!m_write_txn) + throw0(DB_ERROR_TXN_START((std::string("Attempted to stop write txn when no such txn exists in ")+__FUNCTION__).c_str())); + if (m_writer != boost::this_thread::get_id()) + throw0(DB_ERROR_TXN_START((std::string("Attempted to stop write txn from the wrong thread in ")+__FUNCTION__).c_str())); { if (! m_batch_active) { @@ -3669,40 +3720,31 @@ void BlockchainLMDB::block_txn_stop() memset(&m_wcursors, 0, sizeof(m_wcursors)); } } - else if (m_tinfo->m_ti_rtxn) - { - mdb_txn_reset(m_tinfo->m_ti_rtxn); - memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - } } -void BlockchainLMDB::block_txn_abort() +void BlockchainLMDB::block_wtxn_abort() { LOG_PRINT_L3("BlockchainLMDB::" << __func__); - if (m_write_txn && m_writer == boost::this_thread::get_id()) - { - if (! m_batch_active) - { - delete m_write_txn; - m_write_txn = nullptr; - memset(&m_wcursors, 0, sizeof(m_wcursors)); - } - } - else if (m_tinfo->m_ti_rtxn) - { - mdb_txn_reset(m_tinfo->m_ti_rtxn); - memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - } - else + if (!m_write_txn) + throw0(DB_ERROR_TXN_START((std::string("Attempted to abort write txn when no such txn exists in ")+__FUNCTION__).c_str())); + if (m_writer != boost::this_thread::get_id()) + throw0(DB_ERROR_TXN_START((std::string("Attempted to abort write txn from the wrong thread in ")+__FUNCTION__).c_str())); + + if (! m_batch_active) { - // This would probably mean an earlier exception was caught, but then we - // proceeded further than we should have. - throw0(DB_ERROR((std::string("BlockchainLMDB::") + __func__ + - std::string(": block-level DB transaction abort called when write txn doesn't exist") - ).c_str())); + delete m_write_txn; + m_write_txn = nullptr; + memset(&m_wcursors, 0, sizeof(m_wcursors)); } } +void BlockchainLMDB::block_rtxn_abort() const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + mdb_txn_reset(m_tinfo->m_ti_rtxn); + memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); +} + uint64_t BlockchainLMDB::add_block(const std::pair<block, blobdata>& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, const std::vector<std::pair<transaction, blobdata>>& txs) { @@ -3728,11 +3770,6 @@ uint64_t BlockchainLMDB::add_block(const std::pair<block, blobdata>& blk, size_t { throw; } - catch (...) - { - block_txn_abort(); - throw; - } return ++m_height; } @@ -3742,16 +3779,16 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs) LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); - block_txn_start(false); + block_wtxn_start(); try { BlockchainDB::pop_block(blk, txs); - block_txn_stop(); + block_wtxn_stop(); } catch (...) { - block_txn_abort(); + block_wtxn_abort(); throw; } } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index f6b00817d..4b46f081e 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -310,11 +310,14 @@ public: virtual void batch_stop(); virtual void batch_abort(); - virtual void block_txn_start(bool readonly); - virtual void block_txn_stop(); - virtual void block_txn_abort(); - virtual bool block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) const; + virtual void block_wtxn_start(); + virtual void block_wtxn_stop(); + virtual void block_wtxn_abort(); + virtual bool block_rtxn_start() const; virtual void block_rtxn_stop() const; + virtual void block_rtxn_abort() const; + + bool block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) const; virtual void pop_block(block& blk, std::vector<transaction>& txs); diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 04fad26a4..34e635899 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -42,94 +42,98 @@ namespace cryptonote class BaseTestDB: public cryptonote::BlockchainDB { public: BaseTestDB() {} - virtual void open(const std::string& filename, const int db_flags = 0) { } - virtual void close() {} - virtual void sync() {} - virtual void safesyncmode(const bool onoff) {} - virtual void reset() {} - virtual std::vector<std::string> get_filenames() const { return std::vector<std::string>(); } - virtual bool remove_data_file(const std::string& folder) const { return true; } - virtual std::string get_db_name() const { return std::string(); } - virtual bool lock() { return true; } - virtual void unlock() { } - virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) { return true; } - virtual void batch_stop() {} - virtual void set_batch_transactions(bool) {} - virtual void block_txn_start(bool readonly=false) {} - virtual void block_txn_stop() {} - virtual void block_txn_abort() {} - virtual void drop_hard_fork_info() {} - virtual bool block_exists(const crypto::hash& h, uint64_t *height) const { return false; } - virtual cryptonote::blobdata get_block_blob_from_height(const uint64_t& height) const { return cryptonote::t_serializable_object_to_blob(get_block_from_height(height)); } - virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const { return cryptonote::blobdata(); } - virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const { return false; } - virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const { return false; } - virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const { return false; } - virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const { return false; } - virtual uint64_t get_block_height(const crypto::hash& h) const { return 0; } - virtual cryptonote::block_header get_block_header(const crypto::hash& h) const { return cryptonote::block_header(); } - virtual uint64_t get_block_timestamp(const uint64_t& height) const { return 0; } - virtual std::vector<uint64_t> get_block_cumulative_rct_outputs(const std::vector<uint64_t> &heights) const { return {}; } - virtual uint64_t get_top_block_timestamp() const { return 0; } - virtual size_t get_block_weight(const uint64_t& height) const { return 128; } - virtual std::vector<uint64_t> get_block_weights(uint64_t start_height, size_t count) const { return {}; } - virtual cryptonote::difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const { return 10; } - virtual cryptonote::difficulty_type get_block_difficulty(const uint64_t& height) const { return 0; } - virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const { return 10000000000; } - virtual uint64_t get_block_long_term_weight(const uint64_t& height) const { return 128; } - virtual std::vector<uint64_t> get_long_term_block_weights(uint64_t start_height, size_t count) const { return {}; } - virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const { return crypto::hash(); } - virtual std::vector<cryptonote::block> get_blocks_range(const uint64_t& h1, const uint64_t& h2) const { return std::vector<cryptonote::block>(); } - virtual std::vector<crypto::hash> get_hashes_range(const uint64_t& h1, const uint64_t& h2) const { return std::vector<crypto::hash>(); } - virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const { if (block_height) *block_height = 0; return crypto::hash(); } - virtual cryptonote::block get_top_block() const { return cryptonote::block(); } - virtual uint64_t height() const { return 1; } - virtual bool tx_exists(const crypto::hash& h) const { return false; } - virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const { return false; } - virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const { return 0; } - virtual cryptonote::transaction get_tx(const crypto::hash& h) const { return cryptonote::transaction(); } - virtual bool get_tx(const crypto::hash& h, cryptonote::transaction &tx) const { return false; } - virtual uint64_t get_tx_count() const { return 0; } - virtual std::vector<cryptonote::transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const { return std::vector<cryptonote::transaction>(); } - virtual uint64_t get_tx_block_height(const crypto::hash& h) const { return 0; } - virtual uint64_t get_num_outputs(const uint64_t& amount) const { return 1; } - virtual uint64_t get_indexing_base() const { return 0; } - virtual cryptonote::output_data_t get_output_key(const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const { return cryptonote::output_data_t(); } - virtual cryptonote::tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const { return cryptonote::tx_out_index(); } - virtual cryptonote::tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) const { return cryptonote::tx_out_index(); } - virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<cryptonote::tx_out_index> &indices) const {} - virtual void get_output_key(const epee::span<const uint64_t> &amounts, const std::vector<uint64_t> &offsets, std::vector<cryptonote::output_data_t> &outputs, bool allow_partial = false) const {} - virtual bool can_thread_bulk_indices() const { return false; } - virtual std::vector<uint64_t> get_tx_output_indices(const crypto::hash& h) const { return std::vector<uint64_t>(); } - virtual std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(const uint64_t tx_index, size_t n_txes) const { return std::vector<std::vector<uint64_t>>(); } - virtual bool has_key_image(const crypto::key_image& img) const { return false; } - virtual void remove_block() { } - virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const std::pair<cryptonote::transaction, cryptonote::blobdata>& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) {return 0;} - virtual void remove_transaction_data(const crypto::hash& tx_hash, const cryptonote::transaction& tx) {} - virtual uint64_t add_output(const crypto::hash& tx_hash, const cryptonote::tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) {return 0;} - virtual void add_tx_amount_output_indices(const uint64_t tx_index, const std::vector<uint64_t>& amount_output_indices) {} - virtual void add_spent_key(const crypto::key_image& k_image) {} - virtual void remove_spent_key(const crypto::key_image& k_image) {} + virtual void open(const std::string& filename, const int db_flags = 0) override { } + virtual void close() override {} + virtual void sync() override {} + virtual void safesyncmode(const bool onoff) override {} + virtual void reset() override {} + virtual std::vector<std::string> get_filenames() const override { return std::vector<std::string>(); } + virtual bool remove_data_file(const std::string& folder) const override { return true; } + virtual std::string get_db_name() const override { return std::string(); } + virtual bool lock() override { return true; } + virtual void unlock() override { } + virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override { return true; } + virtual void batch_stop() override {} + virtual void batch_abort() override {} + virtual void set_batch_transactions(bool) override {} + virtual void block_wtxn_start() override {} + virtual void block_wtxn_stop() override {} + virtual void block_wtxn_abort() override {} + virtual bool block_rtxn_start() const override { return true; } + virtual void block_rtxn_stop() const override {} + virtual void block_rtxn_abort() const override {} - virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const { return true; } - virtual bool for_blocks_range(const uint64_t&, const uint64_t&, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const { return true; } - virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const { return true; } - virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const { return true; } - virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const { return true; } - virtual bool is_read_only() const { return false; } - 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, uint64_t min_count) const { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); } - virtual bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const { return false; } + virtual void drop_hard_fork_info() override {} + virtual bool block_exists(const crypto::hash& h, uint64_t *height) const override { return false; } + virtual cryptonote::blobdata get_block_blob_from_height(const uint64_t& height) const override { return cryptonote::t_serializable_object_to_blob(get_block_from_height(height)); } + virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const override { return cryptonote::blobdata(); } + virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } + virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } + virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } + virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; } + virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; } + virtual cryptonote::block_header get_block_header(const crypto::hash& h) const override { return cryptonote::block_header(); } + virtual uint64_t get_block_timestamp(const uint64_t& height) const override { return 0; } + virtual std::vector<uint64_t> get_block_cumulative_rct_outputs(const std::vector<uint64_t> &heights) const override { return {}; } + virtual uint64_t get_top_block_timestamp() const override { return 0; } + virtual size_t get_block_weight(const uint64_t& height) const override { return 128; } + virtual std::vector<uint64_t> get_block_weights(uint64_t start_height, size_t count) const override { return {}; } + virtual cryptonote::difficulty_type get_block_cumulative_difficulty(const uint64_t& height) const override { return 10; } + virtual cryptonote::difficulty_type get_block_difficulty(const uint64_t& height) const override { return 0; } + virtual uint64_t get_block_already_generated_coins(const uint64_t& height) const override { return 10000000000; } + virtual uint64_t get_block_long_term_weight(const uint64_t& height) const override { return 128; } + virtual std::vector<uint64_t> get_long_term_block_weights(uint64_t start_height, size_t count) const override { return {}; } + virtual crypto::hash get_block_hash_from_height(const uint64_t& height) const override { return crypto::hash(); } + virtual std::vector<cryptonote::block> get_blocks_range(const uint64_t& h1, const uint64_t& h2) const override { return std::vector<cryptonote::block>(); } + virtual std::vector<crypto::hash> get_hashes_range(const uint64_t& h1, const uint64_t& h2) const override { return std::vector<crypto::hash>(); } + virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override { if (block_height) *block_height = 0; return crypto::hash(); } + virtual cryptonote::block get_top_block() const override { return cryptonote::block(); } + virtual uint64_t height() const override { return 1; } + virtual bool tx_exists(const crypto::hash& h) const override { return false; } + virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const override { return false; } + virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const override { return 0; } + virtual cryptonote::transaction get_tx(const crypto::hash& h) const override { return cryptonote::transaction(); } + virtual bool get_tx(const crypto::hash& h, cryptonote::transaction &tx) const override { return false; } + virtual uint64_t get_tx_count() const override { return 0; } + virtual std::vector<cryptonote::transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const override { return std::vector<cryptonote::transaction>(); } + virtual uint64_t get_tx_block_height(const crypto::hash& h) const override { return 0; } + virtual uint64_t get_num_outputs(const uint64_t& amount) const override { return 1; } + virtual uint64_t get_indexing_base() const override { return 0; } + virtual cryptonote::output_data_t get_output_key(const uint64_t& amount, const uint64_t& index, bool include_commitmemt) const override { return cryptonote::output_data_t(); } + virtual cryptonote::tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const override { return cryptonote::tx_out_index(); } + virtual cryptonote::tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) const override { return cryptonote::tx_out_index(); } + virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<cryptonote::tx_out_index> &indices) const override {} + virtual void get_output_key(const epee::span<const uint64_t> &amounts, const std::vector<uint64_t> &offsets, std::vector<cryptonote::output_data_t> &outputs, bool allow_partial = false) const override {} + virtual bool can_thread_bulk_indices() const override { return false; } + virtual std::vector<std::vector<uint64_t>> get_tx_amount_output_indices(const uint64_t tx_index, size_t n_txes) const override { return std::vector<std::vector<uint64_t>>(); } + virtual bool has_key_image(const crypto::key_image& img) const override { return false; } + virtual void remove_block() override { } + virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const std::pair<cryptonote::transaction, cryptonote::blobdata>& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) override {return 0;} + virtual void remove_transaction_data(const crypto::hash& tx_hash, const cryptonote::transaction& tx) override {} + virtual uint64_t add_output(const crypto::hash& tx_hash, const cryptonote::tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) override {return 0;} + virtual void add_tx_amount_output_indices(const uint64_t tx_index, const std::vector<uint64_t>& amount_output_indices) override {} + virtual void add_spent_key(const crypto::key_image& k_image) override {} + virtual void remove_spent_key(const crypto::key_image& k_image) override {} - virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const cryptonote::txpool_tx_meta_t& details) {} - virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) {} - virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const { return 0; } - virtual bool txpool_has_tx(const crypto::hash &txid) const { return false; } - virtual void remove_txpool_tx(const crypto::hash& txid) {} - virtual bool get_txpool_tx_meta(const crypto::hash& txid, cryptonote::txpool_tx_meta_t &meta) const { return false; } - virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const { return false; } - virtual uint64_t get_database_size() const { return 0; } - virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const { return ""; } - virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const { return false; } + virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const override { return true; } + virtual bool for_blocks_range(const uint64_t&, const uint64_t&, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const override { return true; } + virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const override { return true; } + virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const override { return true; } + virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const override { return true; } + virtual bool is_read_only() const override { return false; } + 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, uint64_t min_count) const override { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); } + virtual bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const override { return false; } + + virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const cryptonote::txpool_tx_meta_t& details) override {} + virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) override {} + virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override { return 0; } + virtual bool txpool_has_tx(const crypto::hash &txid) const override { return false; } + virtual void remove_txpool_tx(const crypto::hash& txid) override {} + virtual bool get_txpool_tx_meta(const crypto::hash& txid, cryptonote::txpool_tx_meta_t &meta) const override { return false; } + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const override { return false; } + virtual uint64_t get_database_size() const override { return 0; } + virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const override { return ""; } + virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const override { return false; } virtual void add_block( const cryptonote::block& blk , size_t block_weight @@ -138,20 +142,20 @@ public: , const uint64_t& coins_generated , uint64_t num_rct_outs , const crypto::hash& blk_hash - ) { } - virtual cryptonote::block get_block_from_height(const uint64_t& height) const { return cryptonote::block(); } - virtual void set_hard_fork_version(uint64_t height, uint8_t version) {} - virtual uint8_t get_hard_fork_version(uint64_t height) const { return 0; } - virtual void check_hard_fork_info() {} + ) override { } + virtual cryptonote::block get_block_from_height(const uint64_t& height) const override { return cryptonote::block(); } + virtual void set_hard_fork_version(uint64_t height, uint8_t version) override {} + virtual uint8_t get_hard_fork_version(uint64_t height) const override { return 0; } + virtual void check_hard_fork_info() override {} - virtual uint32_t get_blockchain_pruning_seed() const { return 0; } - virtual bool prune_blockchain(uint32_t pruning_seed = 0) { return true; } - virtual bool update_pruning() { return true; } - virtual bool check_pruning() { return true; } - virtual void prune_outputs(uint64_t amount) {} + virtual uint32_t get_blockchain_pruning_seed() const override { return 0; } + virtual bool prune_blockchain(uint32_t pruning_seed = 0) override { return true; } + virtual bool update_pruning() override { return true; } + virtual bool check_pruning() override { return true; } + virtual void prune_outputs(uint64_t amount) override {} - virtual uint64_t get_max_block_size() { return 100000000; } - virtual void add_max_block_size(uint64_t sz) { } + virtual uint64_t get_max_block_size() override { return 100000000; } + virtual void add_max_block_size(uint64_t sz) override { } }; } diff --git a/src/blockchain_utilities/README.md b/src/blockchain_utilities/README.md index ad5963f27..1462e3186 100644 --- a/src/blockchain_utilities/README.md +++ b/src/blockchain_utilities/README.md @@ -79,7 +79,7 @@ LMDB flags (more than one may be specified): ## Examples: -``` +```bash $ monero-blockchain-import --database lmdb#fastest $ monero-blockchain-import --database lmdb#nosync diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 8454595ac..cb9154f29 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -194,7 +194,11 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes); std::vector<block> pblocks; - core.prepare_handle_incoming_blocks(blocks, pblocks); + if (!core.prepare_handle_incoming_blocks(blocks, pblocks)) + { + MERROR("Failed to prepare to add blocks"); + return 1; + } if (!pblocks.empty() && pblocks.size() != blocks.size()) { MERROR("Unexpected parsed blocks size"); diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex adc433522..a7d309753 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index e31b96646..11bbe2e24 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -209,6 +209,7 @@ namespace cryptonote ADD_CHECKPOINT(1579000, "7d0d7a2346373afd41ed1e744a939fc5d474a7dbaa257be5c6fff4009e789241"); ADD_CHECKPOINT(1668900, "ac2dcaf3d2f58ffcf8391639f0f1ebafcb8eac43c49479c7c37f611868d07568"); ADD_CHECKPOINT(1775600, "1c6e01c661dc22cab939e79ec6a5272190624ce8356d2f7b958e4f9a57fdb05e"); + ADD_CHECKPOINT(1856000, "9b57f17f29c71a3acd8a7904b93c41fa6eb8d2b7c73936ce4f1702d14880ba29"); return true; } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 5e03bf897..dc1f335a7 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -48,7 +48,6 @@ static const char *DEFAULT_DNS_PUBLIC_ADDR[] = "80.67.169.40", // FDN (France) "89.233.43.71", // http://censurfridns.dk (Denmark) "109.69.8.51", // punCAT (Spain) - "77.109.148.137", // Xiala.net (Switzerland) "193.58.251.251", // SkyDNS (Russia) }; diff --git a/src/common/password.cpp b/src/common/password.cpp index 03d13db42..33e1f48fd 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -57,7 +57,7 @@ namespace DWORD mode_old; ::GetConsoleMode(h_cin, &mode_old); - DWORD mode_new = mode_old & ~(hide_input ? ENABLE_ECHO_INPUT : 0); + DWORD mode_new = mode_old & ~((hide_input ? ENABLE_ECHO_INPUT : 0) | ENABLE_LINE_INPUT); ::SetConsoleMode(h_cin, mode_new); bool r = true; @@ -77,10 +77,6 @@ namespace } else if (ucs2_ch == L'\r') { - continue; - } - else if (ucs2_ch == L'\n') - { std::cout << std::endl; break; } diff --git a/src/common/rpc_client.h b/src/common/rpc_client.h index cb5f79da8..dab3e562d 100644 --- a/src/common/rpc_client.h +++ b/src/common/rpc_client.h @@ -36,6 +36,7 @@ #include "storages/http_abstract_invoke.h" #include "net/http_auth.h" #include "net/http_client.h" +#include "net/net_ssl.h" #include "string_tools.h" namespace tools @@ -49,11 +50,12 @@ namespace tools uint32_t ip , uint16_t port , boost::optional<epee::net_utils::http::login> user + , epee::net_utils::ssl_options_t ssl_options ) : m_http_client{} { m_http_client.set_server( - epee::string_tools::get_ip_string_from_int32(ip), std::to_string(port), std::move(user) + epee::string_tools::get_ip_string_from_int32(ip), std::to_string(port), std::move(user), std::move(ssl_options) ); } diff --git a/src/common/util.cpp b/src/common/util.cpp index 3388974ce..db5aa3052 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -69,6 +69,9 @@ using namespace epee; #include "net/http_client.h" // epee::net_utils::... #ifdef WIN32 +#ifndef STRSAFE_NO_DEPRECATE +#define STRSAFE_NO_DEPRECATE +#endif #include <windows.h> #include <shlobj.h> #include <strsafe.h> diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 170911262..851c70a25 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -105,9 +105,12 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) memset(st, 0, sizeof(st)); for ( ; inlen >= rsiz; inlen -= rsiz, in += rsiz) { - for (i = 0; i < rsizw; i++) - st[i] ^= swap64le(((uint64_t *) in)[i]); - keccakf(st, KECCAK_ROUNDS); + for (i = 0; i < rsizw; i++) { + uint64_t ina; + memcpy(&ina, in + i * 8, 8); + st[i] ^= swap64le(ina); + } + keccakf(st, KECCAK_ROUNDS); } // last block and padding @@ -116,7 +119,8 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) local_abort("Bad keccak use"); } - memcpy(temp, in, inlen); + if (inlen > 0) + memcpy(temp, in, inlen); temp[inlen++] = 1; memset(temp + inlen, 0, rsiz - inlen); temp[rsiz - 1] |= 0x80; diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 7802fb67f..0a5860f3b 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -30,6 +30,7 @@ #include <assert.h> #include <stddef.h> +#include <stdlib.h> #include <string.h> #include "hash-ops.h" @@ -82,23 +83,24 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { size_t cnt = tree_hash_cnt( count ); - char ints[cnt][HASH_SIZE]; - memset(ints, 0 , sizeof(ints)); // zero out as extra protection for using uninitialized mem + char *ints = calloc(cnt, HASH_SIZE); // zero out as extra protection for using uninitialized mem + assert(ints); memcpy(ints, hashes, (2 * cnt - count) * HASH_SIZE); for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) { - cn_fast_hash(hashes[i], 64, ints[j]); + cn_fast_hash(hashes[i], 64, ints + j * HASH_SIZE); } assert(i == count); while (cnt > 2) { cnt >>= 1; for (i = 0, j = 0; j < cnt; i += 2, ++j) { - cn_fast_hash(ints[i], 64, ints[j]); + cn_fast_hash(ints + i * HASH_SIZE, 64, ints + j * HASH_SIZE); } } - cn_fast_hash(ints[0], 64, root_hash); + cn_fast_hash(ints, 64, root_hash); + free(ints); } } diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 20d92bdf1..055c4a22b 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -320,7 +320,7 @@ namespace cryptonote } if (!typename Archive<W>::is_saving()) pruned = true; - return true; + return ar.stream().good(); } private: diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 3dd98f0c6..79ce610a9 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -365,11 +365,11 @@ namespace boost else { // store high part - cryptonote::difficulty_type x_ = x >> 64; + cryptonote::difficulty_type x_ = (x >> 64) & 0xffffffffffffffff; uint64_t v = x_.convert_to<uint64_t>(); a & v; // store low part - x_ = x << 64 >> 64; + x_ = x & 0xffffffffffffffff; v = x_.convert_to<uint64_t>(); a & v; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 566622c1a..7d7de416d 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -221,8 +221,7 @@ namespace cryptonote tx.invalidate_hashes(); //TODO: validate tx - get_transaction_hash(tx, tx_hash); - return true; + return get_transaction_hash(tx, tx_hash); } //--------------------------------------------------------------- bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash) @@ -975,6 +974,7 @@ namespace cryptonote { crypto::hash h = null_hash; get_transaction_hash(t, h, NULL); + CHECK_AND_ASSERT_THROW_MES(get_transaction_hash(t, h, NULL), "Failed to calculate transaction hash"); return h; } //--------------------------------------------------------------- @@ -1327,7 +1327,7 @@ namespace cryptonote txs_ids.reserve(1 + b.tx_hashes.size()); crypto::hash h = null_hash; size_t bl_sz = 0; - get_transaction_hash(b.miner_tx, h, bl_sz); + CHECK_AND_ASSERT_THROW_MES(get_transaction_hash(b.miner_tx, h, bl_sz), "Failed to calculate transaction hash"); txs_ids.push_back(h); for(auto& th: b.tx_hashes) txs_ids.push_back(th); diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 89bca2f09..98158a513 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -185,26 +185,8 @@ void HardFork::init() else height = 1; - bool populate = false; - try - { - db.get_hard_fork_version(0); - } - catch (...) { populate = true; } - if (populate) { - MINFO("The DB has no hard fork info, reparsing from start"); - height = 1; - } - 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 - db.set_hard_fork_version(0, original_version); - } - else { - rescan_from_chain_height(height); - } - MDEBUG("reorganization done"); + rescan_from_chain_height(height); + MDEBUG("init done"); } uint8_t HardFork::get_block_version(uint64_t height) const @@ -266,11 +248,9 @@ bool HardFork::reorganize_from_chain_height(uint64_t height) bool HardFork::rescan_from_block_height(uint64_t height) { CRITICAL_REGION_LOCAL(lock); - db.block_txn_start(true); - if (height >= db.height()) { - db.block_txn_stop(); + db_rtxn_guard rtxn_guard(&db); + if (height >= db.height()) return false; - } versions.clear(); @@ -293,8 +273,6 @@ bool HardFork::rescan_from_block_height(uint64_t height) current_fork_index = voted; } - db.block_txn_stop(); - return true; } @@ -314,8 +292,7 @@ void HardFork::on_block_popped(uint64_t nblocks) const uint64_t new_chain_height = db.height(); const uint64_t old_chain_height = new_chain_height + nblocks; uint8_t version; - uint64_t height; - for (height = old_chain_height - 1; height >= new_chain_height; --height) + for (uint64_t height = old_chain_height - 1; height >= new_chain_height; --height) { version = versions.back(); last_versions[version]--; @@ -327,7 +304,7 @@ void HardFork::on_block_popped(uint64_t nblocks) // does not take voting into account for (current_fork_index = heights.size() - 1; current_fork_index > 0; --current_fork_index) - if (height >= heights[current_fork_index].height) + if (new_chain_height >= heights[current_fork_index].height) break; } diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index e6c6bddb6..e594eb049 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -123,7 +123,7 @@ namespace cryptonote m_miner_extra_sleep(BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS), m_block_reward(0) { - + m_attrs.set_stack_size(THREAD_STACK_SIZE); } //----------------------------------------------------------------------------------------------------- miner::~miner() @@ -360,7 +360,7 @@ namespace cryptonote 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) + bool miner::start(const account_public_address& adr, size_t threads_count, bool do_background, bool ignore_battery) { m_block_reward = 0; m_mine_address = adr; @@ -371,7 +371,6 @@ namespace cryptonote m_threads_autodetect.push_back({epee::misc_utils::get_ns_count(), m_total_hashes}); m_threads_total = 1; } - m_attrs = attrs; m_starter_nonce = crypto::rand<uint32_t>(); CRITICAL_REGION_LOCAL(m_threads_lock); if(is_mining()) @@ -395,7 +394,7 @@ namespace cryptonote for(size_t i = 0; i != m_threads_total; i++) { - m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this))); + m_threads.push_back(boost::thread(m_attrs, boost::bind(&miner::worker_thread, this))); } if (threads_count == 0) @@ -405,7 +404,7 @@ namespace cryptonote if( get_is_background_mining_enabled() ) { - m_background_mining_thread = boost::thread(attrs, boost::bind(&miner::background_worker_thread, this)); + m_background_mining_thread = boost::thread(m_attrs, boost::bind(&miner::background_worker_thread, this)); LOG_PRINT_L0("Background mining controller thread started" ); } @@ -487,10 +486,7 @@ namespace cryptonote { 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(), get_ignore_battery()); + start(m_mine_address, m_threads_total, get_is_background_mining_enabled(), get_ignore_battery()); } } //----------------------------------------------------------------------------------------------------- @@ -528,6 +524,7 @@ namespace cryptonote uint32_t local_template_ver = 0; block b; slow_hash_allocate_state(); + ++m_threads_active; while(!m_stop) { if(m_pausers_count)//anti split workaround diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 285075f51..ac7a0381c 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -64,7 +64,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, uint64_t block_reward); bool on_block_chain_update(); - bool start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs, bool do_background = false, bool ignore_battery = false); + bool start(const account_public_address& adr, size_t threads_count, bool do_background = false, bool ignore_battery = false); uint64_t get_speed() const; uint32_t get_threads_count() const; void send_stop_signal(); diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index fb96de226..2cbe89b01 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -30,6 +30,7 @@ set(cryptonote_core_sources blockchain.cpp cryptonote_core.cpp tx_pool.cpp + tx_sanity_check.cpp cryptonote_tx_utils.cpp) set(cryptonote_core_headers) @@ -39,6 +40,7 @@ set(cryptonote_core_private_headers blockchain.h cryptonote_core.h tx_pool.h + tx_sanity_check.h cryptonote_tx_utils.h) monero_private_headers(cryptonote_core diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 7ef8f8c45..ac2c07913 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -179,9 +179,11 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_long_term_effective_median_block_weight(0), m_long_term_block_weights_cache_tip_hash(crypto::null_hash), + m_long_term_block_weights_cache_rolling_median(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_difficulty_for_next_block_top_hash(crypto::null_hash), m_difficulty_for_next_block(1), - m_btc_valid(false) + m_btc_valid(false), + m_batch_success(true) { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -428,6 +430,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline block bl; block_verification_context bvc = boost::value_initialized<block_verification_context>(); generate_genesis_block(bl, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE); + db_wtxn_guard wtxn_guard(m_db); add_new_block(bl, bvc); CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "Failed to add genesis block to blockchain"); } @@ -443,7 +446,8 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline m_db->fixup(); } - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); + // check how far behind we are uint64_t top_block_timestamp = m_db->get_top_block_timestamp(); uint64_t timestamp_diff = time(NULL) - top_block_timestamp; @@ -464,7 +468,8 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline #endif 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(); + + rtxn_guard.stop(); uint64_t num_popped_blocks = 0; while (!m_db->is_read_only()) @@ -516,10 +521,16 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline } if (test_options && test_options->long_term_block_weight_window) + { m_long_term_block_weights_window = test_options->long_term_block_weight_window; + m_long_term_block_weights_cache_rolling_median = epee::misc_utils::rolling_median_t<uint64_t>(m_long_term_block_weights_window); + } - if (!update_next_cumulative_weight_limit()) - return false; + { + db_txn_guard txn_guard(m_db, m_db->is_read_only()); + if (!update_next_cumulative_weight_limit()) + return false; + } return true; } //------------------------------------------------------------------ @@ -609,17 +620,13 @@ void Blockchain::pop_blocks(uint64_t nblocks) CRITICAL_REGION_LOCAL(m_tx_pool); CRITICAL_REGION_LOCAL1(m_blockchain_lock); - while (!m_db->batch_start()) - { - m_blockchain_lock.unlock(); - m_tx_pool.unlock(); - epee::misc_utils::sleep_no_w(1000); - m_tx_pool.lock(); - m_blockchain_lock.lock(); - } + bool stop_batch = m_db->batch_start(); try { + const uint64_t blockchain_height = m_db->height(); + if (blockchain_height > 0) + nblocks = std::min(nblocks, blockchain_height - 1); for (i=0; i < nblocks; ++i) { pop_block_from_blockchain(); @@ -627,10 +634,14 @@ void Blockchain::pop_blocks(uint64_t nblocks) } catch (const std::exception& e) { - LOG_ERROR("Error when popping blocks, only " << i << " blocks are popped: " << e.what()); + LOG_ERROR("Error when popping blocks after processing " << i << " blocks: " << e.what()); + if (stop_batch) + m_db->batch_abort(); + return; } - m_db->batch_stop(); + if (stop_batch) + m_db->batch_stop(); } //------------------------------------------------------------------ // This function tells BlockchainDB to remove the top block from the @@ -725,6 +736,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b) m_db->reset(); m_hardfork->init(); + db_wtxn_guard wtxn_guard(m_db); block_verification_context bvc = boost::value_initialized<block_verification_context>(); add_new_block(b, bvc); if (!update_next_cumulative_weight_limit()) @@ -772,7 +784,7 @@ bool Blockchain::get_short_chain_history(std::list<crypto::hash>& ids) const if(!sz) return true; - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); bool genesis_included = false; uint64_t current_back_offset = 1; while(current_back_offset < sz) @@ -799,7 +811,6 @@ bool Blockchain::get_short_chain_history(std::list<crypto::hash>& ids) const { ids.push_back(m_db->get_block_hash_from_height(0)); } - m_db->block_txn_stop(); return true; } @@ -1277,21 +1288,20 @@ void Blockchain::get_last_n_blocks_weights(std::vector<uint64_t>& weights, size_ weights = m_db->get_block_weights(start_offset, count); } //------------------------------------------------------------------ -void Blockchain::get_long_term_block_weights(std::vector<uint64_t>& weights, uint64_t start_height, size_t count) const +uint64_t Blockchain::get_long_term_block_weight_median(uint64_t start_height, size_t count) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); PERF_TIMER(get_long_term_block_weights); - if (count == 0) - return; + CHECK_AND_ASSERT_THROW_MES(count > 0, "count == 0"); bool cached = false; uint64_t blockchain_height = m_db->height(); uint64_t tip_height = start_height + count - 1; crypto::hash tip_hash = crypto::null_hash; - if (tip_height < blockchain_height && count == m_long_term_block_weights_cache.size()) + if (tip_height < blockchain_height && count == (size_t)m_long_term_block_weights_cache_rolling_median.size()) { tip_hash = m_db->get_block_hash_from_height(tip_height); cached = tip_hash == m_long_term_block_weights_cache_tip_hash; @@ -1300,32 +1310,30 @@ void Blockchain::get_long_term_block_weights(std::vector<uint64_t>& weights, uin if (cached) { MTRACE("requesting " << count << " from " << start_height << ", cached"); - weights = m_long_term_block_weights_cache; - return; + return m_long_term_block_weights_cache_rolling_median.median(); } // in the vast majority of uncached cases, most is still cached, // as we just move the window one block up: - if (tip_height > 0 && count == m_long_term_block_weights_cache.size() && tip_height < blockchain_height) + if (tip_height > 0 && count == (size_t)m_long_term_block_weights_cache_rolling_median.size() && tip_height < blockchain_height) { crypto::hash old_tip_hash = m_db->get_block_hash_from_height(tip_height - 1); if (old_tip_hash == m_long_term_block_weights_cache_tip_hash) { - weights = m_long_term_block_weights_cache; - for (size_t i = 1; i < weights.size(); ++i) - weights[i - 1] = weights[i]; MTRACE("requesting " << count << " from " << start_height << ", incremental"); - weights.back() = m_db->get_block_long_term_weight(tip_height); - m_long_term_block_weights_cache = weights; m_long_term_block_weights_cache_tip_hash = tip_hash; - return; + m_long_term_block_weights_cache_rolling_median.insert(m_db->get_block_long_term_weight(tip_height)); + return m_long_term_block_weights_cache_rolling_median.median(); } } MTRACE("requesting " << count << " from " << start_height << ", uncached"); - weights = m_db->get_long_term_block_weights(start_height, count); - m_long_term_block_weights_cache = weights; + std::vector<uint64_t> weights = m_db->get_long_term_block_weights(start_height, count); m_long_term_block_weights_cache_tip_hash = tip_hash; + m_long_term_block_weights_cache_rolling_median.clear(); + for (uint64_t w: weights) + m_long_term_block_weights_cache_rolling_median.insert(w); + return m_long_term_block_weights_cache_rolling_median.median(); } //------------------------------------------------------------------ uint64_t Blockchain::get_current_cumulative_block_weight_limit() const @@ -1366,7 +1374,8 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, // just as we compare it, we'll just use a slightly old template, but // this would be the case anyway if we'd lock, and the change happened // just after the block template was created - if (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address)) && m_btc_nonce == ex_nonce && m_btc_pool_cookie == m_tx_pool.cookie()) { + if (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address)) && m_btc_nonce == ex_nonce + && m_btc_pool_cookie == m_tx_pool.cookie() && m_btc.prev_id == get_tail_id()) { MDEBUG("Using cached template"); m_btc.timestamp = time(NULL); // update timestamp unconditionally b = m_btc; @@ -1866,7 +1875,7 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard (m_db); rsp.current_blockchain_height = get_current_blockchain_height(); std::vector<std::pair<cryptonote::blobdata,block>> blocks; get_blocks(arg.blocks, blocks, rsp.missed_ids); @@ -1884,16 +1893,19 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO 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.second) - << std::endl - ); + // do not display an error if the peer asked for an unpruned block which we are not meant to have + if (tools::has_unpruned_block(get_block_height(bl.second), get_current_blockchain_height(), get_blockchain_pruning_seed())) + { + LOG_ERROR("Error retrieving blocks, missed " << missed_tx_ids.size() + << " transactions for block with hash: " << get_block_hash(bl.second) + << std::endl + ); + } // append missed transaction hashes to response missed_ids field, // as done below if any standalone transactions were requested // and missed. rsp.missed_ids.insert(rsp.missed_ids.end(), missed_tx_ids.begin(), missed_tx_ids.end()); - m_db->block_txn_stop(); return false; } @@ -1903,7 +1915,6 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO //get and pack other transactions, if needed get_transactions_blobs(arg.txs, rsp.txs, rsp.missed_ids); - m_db->block_txn_stop(); return true; } //------------------------------------------------------------------ @@ -2075,14 +2086,13 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc return false; } - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); // make sure that the last block in the request's block list matches // the genesis block auto gen_hash = m_db->get_block_hash_from_height(0); if(qblock_ids.back() != gen_hash) { 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; } @@ -2100,11 +2110,9 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc catch (const std::exception& e) { MWARNING("Non-critical error trying to find block by hash in BlockchainDB, hash: " << *bl_it); - m_db->block_txn_abort(); return false; } } - m_db->block_txn_stop(); // this should be impossible, as we checked that we share the genesis block, // but just in case... @@ -2284,7 +2292,7 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container // Find the split point between us and foreign blockchain and return // (by reference) the most recent common block hash along with up to // BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. -bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2295,11 +2303,15 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc return false; } - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); current_height = get_current_blockchain_height(); - const uint32_t pruning_seed = get_blockchain_pruning_seed(); - start_height = tools::get_next_unpruned_block_height(start_height, current_height, pruning_seed); - uint64_t stop_height = tools::get_next_pruned_block_height(start_height, current_height, pruning_seed); + uint64_t stop_height = current_height; + if (clip_pruned) + { + const uint32_t pruning_seed = get_blockchain_pruning_seed(); + start_height = tools::get_next_unpruned_block_height(start_height, current_height, pruning_seed); + stop_height = tools::get_next_pruned_block_height(start_height, current_height, pruning_seed); + } size_t count = 0; hashes.reserve(std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); for(size_t i = start_height; i < stop_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) @@ -2307,7 +2319,6 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc hashes.push_back(m_db->get_block_hash_from_height(i)); } - m_db->block_txn_stop(); return true; } @@ -2316,12 +2327,12 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height); + bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height, true); if (result) { cryptonote::difficulty_type wide_cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1); - resp.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); - resp.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>(); + resp.cumulative_difficulty = (wide_cumulative_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); + resp.cumulative_difficulty_top64 = ((wide_cumulative_difficulty >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); } return result; @@ -2354,7 +2365,7 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons } } - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); total_height = get_current_blockchain_height(); size_t count = 0, size = 0; blocks.reserve(std::min(std::min(max_count, (size_t)10000), (size_t)(total_height - start_height))); @@ -2380,7 +2391,6 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons blocks.back().second.push_back(std::make_pair(b.tx_hashes[i], std::move(txs[i]))); } } - m_db->block_txn_stop(); return true; } //------------------------------------------------------------------ @@ -3535,7 +3545,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& static bool seen_future_version = false; - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); uint64_t blockchain_height; const crypto::hash top_hash = get_tail_id(blockchain_height); ++blockchain_height; // block height to chain height @@ -3544,7 +3554,6 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& MERROR_VER("Block with id: " << id << std::endl << "has wrong prev_id: " << bl.prev_id << std::endl << "expected: " << top_hash); bvc.m_verifivation_failed = true; leave: - m_db->block_txn_stop(); return false; } @@ -3827,7 +3836,7 @@ leave: if(precomputed) block_processing_time += m_fake_pow_calc_time; - m_db->block_txn_stop(); + rtxn_guard.stop(); TIME_MEASURE_START(addblock); uint64_t new_height = 0; if (!bvc.m_verifivation_failed) @@ -3841,6 +3850,7 @@ leave: catch (const KEY_IMAGE_EXISTS& e) { LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what()); + m_batch_success = false; bvc.m_verifivation_failed = true; return_tx_to_pool(txs); return false; @@ -3849,6 +3859,7 @@ leave: { //TODO: figure out the best way to deal with this failure LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what()); + m_batch_success = false; bvc.m_verifivation_failed = true; return_tx_to_pool(txs); return false; @@ -3896,6 +3907,10 @@ leave: //------------------------------------------------------------------ bool Blockchain::prune_blockchain(uint32_t pruning_seed) { + m_tx_pool.lock(); + epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();}); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_db->prune_blockchain(pruning_seed); } //------------------------------------------------------------------ @@ -3928,9 +3943,7 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons if (hf_version < HF_VERSION_LONG_TERM_BLOCK_WEIGHT) return block_weight; - std::vector<uint64_t> weights; - get_long_term_block_weights(weights, db_height - nblocks, nblocks); - uint64_t long_term_median = epee::misc_utils::median(weights); + uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); uint64_t long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); uint64_t short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; @@ -3945,8 +3958,6 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti LOG_PRINT_L3("Blockchain::" << __func__); - m_db->block_txn_start(false); - // when we reach this, the last hf version is not yet written to the db const uint64_t db_height = m_db->height(); const uint8_t hf_version = get_current_hard_fork_version(); @@ -3964,7 +3975,6 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti { const uint64_t block_weight = m_db->get_block_weight(db_height - 1); - std::vector<uint64_t> weights, new_weights; uint64_t long_term_median; if (db_height == 1) { @@ -3975,9 +3985,7 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); if (nblocks == db_height) --nblocks; - get_long_term_block_weights(weights, db_height - nblocks - 1, nblocks); - new_weights = weights; - long_term_median = epee::misc_utils::median(weights); + long_term_median = get_long_term_block_weight_median(db_height - nblocks - 1, nblocks); } m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); @@ -3985,14 +3993,19 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti uint64_t short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); - if (new_weights.empty()) - new_weights.resize(1); - new_weights[0] = long_term_block_weight; - long_term_median = epee::misc_utils::median(new_weights); + if (db_height == 1) + { + long_term_median = long_term_block_weight; + } + else + { + m_long_term_block_weights_cache_tip_hash = m_db->get_block_hash_from_height(db_height - 1); + m_long_term_block_weights_cache_rolling_median.insert(long_term_block_weight); + long_term_median = m_long_term_block_weights_cache_rolling_median.median(); + } m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; - weights.clear(); + std::vector<uint64_t> weights; get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); uint64_t short_term_median = epee::misc_utils::median(weights); @@ -4009,9 +4022,8 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti if (long_term_effective_median_block_weight) *long_term_effective_median_block_weight = m_long_term_effective_median_block_weight; - m_db->add_max_block_size(m_current_block_cumul_weight_limit); - - m_db->block_txn_stop(); + if (!m_db->is_read_only()) + m_db->add_max_block_size(m_current_block_cumul_weight_limit); return true; } @@ -4022,12 +4034,11 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc) crypto::hash id = get_block_hash(bl); CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process CRITICAL_REGION_LOCAL1(m_blockchain_lock); - m_db->block_txn_start(true); + db_rtxn_guard rtxn_guard(m_db); if(have_block(id)) { LOG_PRINT_L3("block with id = " << id << " already exists"); bvc.m_already_exists = true; - m_db->block_txn_stop(); m_blocks_txs_check.clear(); return false; } @@ -4037,14 +4048,14 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc) { //chain switching or wrong block bvc.m_added_to_main_chain = false; - m_db->block_txn_stop(); + rtxn_guard.stop(); bool r = handle_alternative_block(bl, id, bvc); m_blocks_txs_check.clear(); return r; //never relay alternative blocks } - m_db->block_txn_stop(); + rtxn_guard.stop(); return handle_block_to_main_chain(bl, id, bvc); } //------------------------------------------------------------------ @@ -4158,7 +4169,10 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) try { - m_db->batch_stop(); + if (m_batch_success) + m_db->batch_stop(); + else + m_db->batch_abort(); success = true; } catch (const std::exception &e) @@ -4382,6 +4396,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete m_tx_pool.lock(); m_blockchain_lock.lock(); } + m_batch_success = true; const uint64_t height = m_db->height(); if ((height + blocks_entry.size()) < m_blocks_hash_check.size()) @@ -4834,7 +4849,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "570ce2357b08fadac6058e34f95c5e08323f9325de260d07b091a281a948a7b0"; +static const char expected_block_hashes_hash[] = "7dafb40b414a0e59bfced6682ef519f0b416bc914dd3d622b72e0dd1a47117c2"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 3588bbd1b..32ed96b5b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -37,7 +37,6 @@ #include <boost/multi_index/global_fun.hpp> #include <boost/multi_index/hashed_index.hpp> #include <boost/multi_index/member.hpp> -#include <boost/circular_buffer.hpp> #include <atomic> #include <functional> #include <unordered_map> @@ -46,6 +45,7 @@ #include "span.h" #include "syncobj.h" #include "string_tools.h" +#include "rolling_median.h" #include "cryptonote_basic/cryptonote_basic.h" #include "common/util.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" @@ -394,10 +394,11 @@ namespace cryptonote * @param hashes the hashes to be returned, return-by-reference * @param start_height the start height, return-by-reference * @param current_height the current blockchain height, return-by-reference + * @param clip_pruned whether to constrain results to unpruned data * * @return true if a block found in common, else false */ - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const; + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const; /** * @brief get recent block hashes for a foreign chain @@ -1063,7 +1064,7 @@ namespace cryptonote uint64_t m_long_term_block_weights_window; uint64_t m_long_term_effective_median_block_weight; mutable crypto::hash m_long_term_block_weights_cache_tip_hash; - mutable std::vector<uint64_t> m_long_term_block_weights_cache; + mutable epee::misc_utils::rolling_median_t<uint64_t> m_long_term_block_weights_cache_rolling_median; epee::critical_section m_difficulty_lock; crypto::hash m_difficulty_for_next_block_top_hash; @@ -1101,6 +1102,9 @@ namespace cryptonote uint64_t m_btc_expected_reward; bool m_btc_valid; + + bool m_batch_success; + std::shared_ptr<tools::Notify> m_block_notify; std::shared_ptr<tools::Notify> m_reorg_notify; @@ -1313,15 +1317,16 @@ namespace cryptonote void get_last_n_blocks_weights(std::vector<uint64_t>& weights, size_t count) const; /** - * @brief gets recent block long term weights for median calculation + * @brief gets block long term weight median * - * get the block long term weights of the last <count> blocks, and return by reference <weights>. + * get the block long term weight median of <count> blocks starting at <start_height> * - * @param weights return-by-reference the list of weights * @param start_height the block height of the first block to query * @param count the number of blocks to get weights for + * + * @return the long term median block weight */ - void get_long_term_block_weights(std::vector<uint64_t>& weights, uint64_t start_height, size_t count) const; + uint64_t get_long_term_block_weight_median(uint64_t start_height, size_t count) const; /** * @brief checks if a transaction is unlocked (its outputs spendable) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 91dea4982..13426230e 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -674,9 +674,15 @@ namespace cryptonote if (prune_blockchain) { // display a message if the blockchain is not pruned yet - if (m_blockchain_storage.get_current_blockchain_height() > 1 && !m_blockchain_storage.get_blockchain_pruning_seed()) + if (!m_blockchain_storage.get_blockchain_pruning_seed()) + { MGINFO("Pruning blockchain..."); - CHECK_AND_ASSERT_MES(m_blockchain_storage.prune_blockchain(), false, "Failed to prune blockchain"); + CHECK_AND_ASSERT_MES(m_blockchain_storage.prune_blockchain(), false, "Failed to prune blockchain"); + } + else + { + CHECK_AND_ASSERT_MES(m_blockchain_storage.update_blockchain_pruning(), false, "Failed to update blockchain pruning"); + } } return load_state_data(); @@ -1609,6 +1615,9 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_fork_time() { + if (m_nettype == FAKECHAIN) + return true; + HardFork::State state = m_blockchain_storage.get_hard_fork_state(); const el::Level level = el::Level::Warning; switch (state) { @@ -1824,7 +1833,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_block_rate() { - if (m_offline || m_target_blockchain_height > get_current_blockchain_height()) + if (m_offline || m_nettype == FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height()) { MDEBUG("Not checking block rate, offline or syncing"); return true; diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index c1cbe2acd..49d5a8ccc 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -95,13 +95,17 @@ namespace cryptonote // the whole prepare/handle/cleanup incoming block sequence. class LockedTXN { public: - LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false) { + LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false), m_active(false) { m_batch = m_blockchain.get_db().batch_start(); + m_active = true; } - ~LockedTXN() { try { if (m_batch) { m_blockchain.get_db().batch_stop(); } } catch (const std::exception &e) { MWARNING("LockedTXN dtor filtering exception: " << e.what()); } } + void commit() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_stop(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::commit filtering exception: " << e.what()); } } + void abort() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_abort(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::abort filtering exception: " << e.what()); } } + ~LockedTXN() { abort(); } private: Blockchain &m_blockchain; bool m_batch; + bool m_active; }; } //--------------------------------------------------------------------------------- @@ -255,6 +259,7 @@ namespace cryptonote if (!insert_key_images(tx, id, kept_by_block)) return false; m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id); + lock.commit(); } catch (const std::exception &e) { @@ -299,6 +304,7 @@ namespace cryptonote if (!insert_key_images(tx, id, kept_by_block)) return false; m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id); + lock.commit(); } catch (const std::exception &e) { @@ -398,6 +404,7 @@ namespace cryptonote return; } } + lock.commit(); if (changed) ++m_cookie; if (m_txpool_weight > bytes) @@ -494,6 +501,7 @@ namespace cryptonote m_blockchain.remove_txpool_tx(id); m_txpool_weight -= tx_weight; remove_transaction_keyimages(tx, id); + lock.commit(); } catch (const std::exception &e) { @@ -578,6 +586,7 @@ namespace cryptonote // ignore error } } + lock.commit(); ++m_cookie; } return true; @@ -641,6 +650,7 @@ namespace cryptonote // continue } } + lock.commit(); } //--------------------------------------------------------------------------------- size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const @@ -1119,6 +1129,7 @@ namespace cryptonote } } } + lock.commit(); if (changed) ++m_cookie; } @@ -1271,6 +1282,7 @@ namespace cryptonote append_key_images(k_images, tx); LOG_PRINT_L2(" added, new block weight " << total_weight << "/" << max_total_weight << ", coinbase " << print_money(best_coinbase)); } + lock.commit(); expected_reward = best_coinbase; LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, weight " @@ -1336,6 +1348,7 @@ namespace cryptonote // continue } } + lock.commit(); } if (n_removed > 0) ++m_cookie; @@ -1395,6 +1408,7 @@ namespace cryptonote // ignore error } } + lock.commit(); } m_cookie = 0; diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp new file mode 100644 index 000000000..36be91f2c --- /dev/null +++ b/src/cryptonote_core/tx_sanity_check.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2019, 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 <stdint.h> +#include <vector> +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "blockchain.h" +#include "tx_sanity_check.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "verify" + +namespace cryptonote +{ + +bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob) +{ + cryptonote::transaction tx; + + if (!cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)) + { + MERROR("Failed to parse transaction"); + return false; + } + + if (cryptonote::is_coinbase(tx)) + { + MERROR("Transaction is coinbase"); + return false; + } + std::set<uint64_t> rct_indices; + size_t n_indices = 0; + + for (const auto &txin : tx.vin) + { + if (txin.type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key &in_to_key = boost::get<cryptonote::txin_to_key>(txin); + if (in_to_key.amount != 0) + continue; + const std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(in_to_key.key_offsets); + for (uint64_t offset: absolute) + rct_indices.insert(offset); + n_indices += in_to_key.key_offsets.size(); + } + + if (n_indices <= 10) + { + MERROR("n_indices is only " << n_indices); + return true; + } + + uint64_t n_available = blockchain.get_num_mature_outputs(0); + if (n_available < 10000) + return true; + + if (rct_indices.size() < n_indices * 8 / 10) + { + MERROR("unique indices is only " << rct_indices.size() << "/" << n_indices); + return false; + } + + std::vector<uint64_t> offsets(rct_indices.begin(), rct_indices.end()); + uint64_t median = epee::misc_utils::median(offsets); + if (median < n_available * 6 / 10) + { + MERROR("median is " << median << "/" << n_available); + return false; + } + + return true; +} + +} diff --git a/src/cryptonote_core/tx_sanity_check.h b/src/cryptonote_core/tx_sanity_check.h new file mode 100644 index 000000000..c12d1b0b1 --- /dev/null +++ b/src/cryptonote_core/tx_sanity_check.h @@ -0,0 +1,36 @@ +// Copyright (c) 2019, 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/blobdatatype.h" + +namespace cryptonote +{ + class Blockchain; + + bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob); +} diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 0927b5d7f..dcc5ec6ed 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -52,6 +52,7 @@ PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) #define LOCALHOST_INT 2130706433 +#define CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT 500 namespace cryptonote { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index b7a50783a..03d04a074 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -341,6 +341,11 @@ namespace cryptonote if(m_core.have_block(hshd.top_id)) { + if (target > hshd.current_height) + { + MINFO(context << "peer is not ahead of us and we're syncing, disconnecting"); + return false; + } context.m_state = cryptonote_connection_context::state_normal; if(is_inital && target == m_core.get_current_blockchain_height()) on_connection_synchronized(); @@ -410,8 +415,8 @@ namespace cryptonote m_core.get_blockchain_top(hshd.current_height, hshd.top_id); hshd.top_version = m_core.get_ideal_hard_fork_version(hshd.current_height); difficulty_type wide_cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height); - hshd.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); - hshd.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>(); + hshd.cumulative_difficulty = (wide_cumulative_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); + hshd.cumulative_difficulty_top64 = ((wide_cumulative_difficulty >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); hshd.current_height +=1; hshd.pruning_seed = m_core.get_blockchain_pruning_seed(); return true; @@ -809,12 +814,27 @@ namespace cryptonote 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; + std::vector<bool> seen(b.tx_hashes.size(), false); for(auto& tx_idx: arg.missing_tx_indices) { if(tx_idx < b.tx_hashes.size()) { MDEBUG(" tx " << b.tx_hashes[tx_idx]); + if (seen[tx_idx]) + { + LOG_ERROR_CCONTEXT + ( + "Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX" + << ", request is asking for duplicate tx " + << ", tx index = " << tx_idx << ", block tx count " << b.tx_hashes.size() + << ", block_height = " << arg.current_blockchain_height + << ", dropping connection" + ); + drop_connection(context, true, false); + return 1; + } txids.push_back(b.tx_hashes[tx_idx]); + seen[tx_idx] = true; } else { @@ -914,6 +934,17 @@ namespace cryptonote int t_cryptonote_protocol_handler<t_core>::handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context) { MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)"); + + if (arg.blocks.size() + arg.txs.size() > CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT) + { + LOG_ERROR_CCONTEXT( + "Requested objects count is too big (" + << arg.blocks.size() + arg.txs.size() << ") expected not more then " + << CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT); + drop_connection(context, false, false); + return 1; + } + NOTIFY_RESPONSE_GET_OBJECTS::request rsp; if(!m_core.handle_get_objects(arg, rsp, context)) { @@ -2366,6 +2397,8 @@ skip: { MINFO("Target height decreasing from " << previous_target << " to " << target); m_core.set_target_blockchain_height(target); + if (target == 0 && context.m_state > cryptonote_connection_context::state_before_handshake && !m_stopping) + MCWARNING("global", "monerod is now disconnected from the network"); } m_block_queue.flush_spans(context.m_connection_id, false); diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 17b945c9a..ed799096b 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -40,10 +40,11 @@ t_command_parser_executor::t_command_parser_executor( uint32_t ip , uint16_t port , const boost::optional<tools::login>& login + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_executor(ip, port, login, is_rpc, rpc_server) + : m_executor(ip, port, login, ssl_options, is_rpc, rpc_server) {} bool t_command_parser_executor::print_peer_list(const std::vector<std::string>& args) @@ -595,6 +596,13 @@ bool t_command_parser_executor::unban(const std::vector<std::string>& args) return m_executor.unban(ip); } +bool t_command_parser_executor::banned(const std::vector<std::string>& args) +{ + if (args.size() != 1) return false; + std::string address = args[0]; + return m_executor.banned(address); +} + bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& args) { if (args.size() > 1) return false; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 098018642..aeaf4d254 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -40,6 +40,7 @@ #include "daemon/rpc_command_executor.h" #include "common/common_fwd.h" +#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" namespace daemonize { @@ -53,6 +54,7 @@ public: uint32_t ip , uint16_t port , const boost::optional<tools::login>& login + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc , cryptonote::core_rpc_server* rpc_server = NULL ); @@ -125,6 +127,8 @@ public: bool unban(const std::vector<std::string>& args); + bool banned(const std::vector<std::string>& args); + bool flush_txpool(const std::vector<std::string>& args); bool output_histogram(const std::vector<std::string>& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 69ad6ff10..f0b4b4ba0 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -43,10 +43,11 @@ t_command_server::t_command_server( uint32_t ip , uint16_t port , const boost::optional<tools::login>& login + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) - : m_parser(ip, port, login, is_rpc, rpc_server) + : m_parser(ip, port, login, ssl_options, is_rpc, rpc_server) , m_command_lookup() , m_is_rpc(is_rpc) { @@ -242,10 +243,16 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "unban" , std::bind(&t_command_parser_executor::unban, &m_parser, p::_1) - , "unban <IP>" + , "unban <address>" , "Unban a given <IP>." ); m_command_lookup.set_handler( + "banned" + , std::bind(&t_command_parser_executor::banned, &m_parser, p::_1) + , "banned <address>" + , "Check whether an <address> is banned." + ); + m_command_lookup.set_handler( "flush_txpool" , std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1) , "flush_txpool [<txid>]" diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index c8e77f551..da532223e 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -43,6 +43,7 @@ Passing RPC commands: #include "common/common_fwd.h" #include "console_handler.h" #include "daemon/command_parser_executor.h" +#include "net/net_fwd.h" namespace daemonize { @@ -57,6 +58,7 @@ public: uint32_t ip , uint16_t port , const boost::optional<tools::login>& login + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 531c080de..5084b6283 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -45,6 +45,7 @@ #include "daemon/command_server.h" #include "daemon/command_server.h" #include "daemon/command_line_args.h" +#include "net/net_ssl.h" #include "version.h" using namespace epee; @@ -163,7 +164,7 @@ bool t_daemon::run(bool interactive) if (interactive && mp_internals->rpcs.size()) { // 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->rpcs.front()->get_server())); + rpc_commands.reset(new daemonize::t_command_server(0, 0, boost::none, epee::net_utils::ssl_support_t::e_ssl_support_disabled, false, mp_internals->rpcs.front()->get_server())); rpc_commands->start_handling(std::bind(&daemonize::t_daemon::stop_p2p, this)); } diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index c3ac24b70..690d4d60e 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -257,7 +257,8 @@ int main(int argc, char const * argv[]) bf::path log_file_path {data_dir / std::string(CRYPTONOTE_NAME ".log")}; if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_file)) log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); - log_file_path = bf::absolute(log_file_path, relative_path_base); + if (!log_file_path.has_parent_path()) + log_file_path = bf::absolute(log_file_path, relative_path_base); mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size), command_line::get_arg(vm, daemon_args::arg_max_log_files)); // Set log level @@ -323,7 +324,11 @@ int main(int argc, char const * argv[]) } } - daemonize::t_command_server rpc_commands{rpc_ip, rpc_port, std::move(login)}; + auto ssl_options = cryptonote::rpc_args::process_ssl(vm, true); + if (!ssl_options) + return 1; + + daemonize::t_command_server rpc_commands{rpc_ip, rpc_port, std::move(login), std::move(*ssl_options)}; if (rpc_commands.process_command_vec(command)) { return 0; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 5901be662..bf4b3b09e 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -127,6 +127,7 @@ t_rpc_command_executor::t_rpc_command_executor( uint32_t ip , uint16_t port , const boost::optional<tools::login>& login + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc , cryptonote::core_rpc_server* rpc_server ) @@ -137,7 +138,7 @@ t_rpc_command_executor::t_rpc_command_executor( 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)); + m_rpc_client = new tools::t_rpc_client(ip, port, std::move(http_login), ssl_options); } else { @@ -553,7 +554,7 @@ bool t_rpc_command_executor::mining_status() { if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0) { - double ratio = mres.speed * mres.block_target / mres.difficulty; + double ratio = mres.speed * mres.block_target / (double)mres.difficulty; uint64_t daily = 86400ull / mres.block_target * mres.block_reward * ratio; uint64_t monthly = 86400ull / mres.block_target * 30.5 * mres.block_reward * ratio; uint64_t yearly = 86400ull / mres.block_target * 356 * mres.block_reward * ratio; @@ -722,7 +723,7 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u tools::msg_writer() << "" << std::endl; tools::msg_writer() << "height: " << header.height << ", timestamp: " << header.timestamp << " (" << tools::get_human_readable_timestamp(header.timestamp) << ")" - << ", size: " << header.block_size << ", weight: " << header.block_weight << ", transactions: " << header.num_txes << std::endl + << ", size: " << header.block_size << ", weight: " << header.block_weight << " (long term " << header.long_term_weight << "), transactions: " << header.num_txes << 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; @@ -1640,14 +1641,14 @@ bool t_rpc_command_executor::print_bans() for (auto i = res.bans.begin(); i != res.bans.end(); ++i) { - tools::msg_writer() << epee::string_tools::get_ip_string_from_int32(i->ip) << " banned for " << i->seconds << " seconds"; + tools::msg_writer() << i->host << " banned for " << i->seconds << " seconds"; } return true; } -bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) +bool t_rpc_command_executor::ban(const std::string &address, time_t seconds) { cryptonote::COMMAND_RPC_SETBANS::request req; cryptonote::COMMAND_RPC_SETBANS::response res; @@ -1655,11 +1656,8 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) epee::json_rpc::error error_resp; cryptonote::COMMAND_RPC_SETBANS::ban ban; - if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) - { - tools::fail_msg_writer() << "Invalid IP"; - return true; - } + ban.host = address; + ban.ip = 0; ban.ban = true; ban.seconds = seconds; req.bans.push_back(ban); @@ -1683,7 +1681,7 @@ bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) return true; } -bool t_rpc_command_executor::unban(const std::string &ip) +bool t_rpc_command_executor::unban(const std::string &address) { cryptonote::COMMAND_RPC_SETBANS::request req; cryptonote::COMMAND_RPC_SETBANS::response res; @@ -1691,11 +1689,8 @@ bool t_rpc_command_executor::unban(const std::string &ip) epee::json_rpc::error error_resp; cryptonote::COMMAND_RPC_SETBANS::ban ban; - if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) - { - tools::fail_msg_writer() << "Invalid IP"; - return true; - } + ban.host = address; + ban.ip = 0; ban.ban = false; ban.seconds = 0; req.bans.push_back(ban); @@ -1719,6 +1714,39 @@ bool t_rpc_command_executor::unban(const std::string &ip) return true; } +bool t_rpc_command_executor::banned(const std::string &address) +{ + cryptonote::COMMAND_RPC_BANNED::request req; + cryptonote::COMMAND_RPC_BANNED::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + req.address = address; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "banned", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_banned(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + if (res.banned) + tools::msg_writer() << address << " is banned for " << res.seconds << " seconds"; + else + tools::msg_writer() << address << " is not banned"; + + return true; +} + bool t_rpc_command_executor::flush_txpool(const std::string &txid) { cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::request req; @@ -2205,7 +2233,7 @@ bool t_rpc_command_executor::prune_blockchain() } } - tools::success_msg_writer() << "Blockchain pruned: seed " << epee::string_tools::to_string_hex(res.pruning_seed); + tools::success_msg_writer() << "Blockchain pruned"; return true; } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 3c2686b3f..61c25736d 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -43,6 +43,7 @@ #include "common/common_fwd.h" #include "common/rpc_client.h" #include "cryptonote_basic/cryptonote_basic.h" +#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -61,6 +62,7 @@ public: uint32_t ip , uint16_t port , const boost::optional<tools::login>& user + , const epee::net_utils::ssl_options_t& ssl_options , bool is_rpc = true , cryptonote::core_rpc_server* rpc_server = NULL ); @@ -135,9 +137,11 @@ public: bool print_bans(); - bool ban(const std::string &ip, time_t seconds); + bool ban(const std::string &address, time_t seconds); - bool unban(const std::string &ip); + bool unban(const std::string &address); + + bool banned(const std::string &address); bool flush_txpool(const std::string &txid); diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index 701c098f6..c6cd474fd 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -61,6 +61,10 @@ namespace daemonizer "run-as-service" , "Hidden -- true if running as windows service" }; + const command_line::arg_descriptor<bool> arg_non_interactive = { + "non-interactive" + , "Run non-interactive" + }; std::string get_argument_string(int argc, char const * argv[]) { @@ -83,6 +87,7 @@ namespace daemonizer command_line::add_arg(normal_options, arg_start_service); command_line::add_arg(normal_options, arg_stop_service); command_line::add_arg(hidden_options, arg_is_service); + command_line::add_arg(hidden_options, arg_non_interactive); } inline boost::filesystem::path get_default_data_dir() @@ -177,7 +182,10 @@ namespace daemonizer else // interactive { //LOG_PRINT_L0("Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL); - return executor.run_interactive(vm); + if (command_line::has_arg(vm, arg_non_interactive)) + return executor.run_non_interactive(vm); + else + return executor.run_interactive(vm); } return false; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 200370564..eba633da8 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -90,6 +90,20 @@ namespace hw { AKout = keys.AKout; } + ABPkeys &ABPkeys::operator=(const ABPkeys& keys) { + if (&keys == this) + return *this; + Aout = keys.Aout; + Bout = keys.Bout; + is_subaddress = keys.is_subaddress; + is_change_address = keys.is_change_address; + additional_key = keys.additional_key; + index = keys.index; + Pout = keys.Pout; + AKout = keys.AKout; + return *this; + } + bool Keymap::find(const rct::key& P, ABPkeys& keys) const { size_t sz = ABP.size(); for (size_t i=0; i<sz; i++) { diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index d4d98ce4a..fe9028733 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -77,6 +77,7 @@ namespace hw { ABPkeys(const rct::key& A, const rct::key& B, const bool is_subaddr, bool is_subaddress, bool is_change_address, size_t index, const rct::key& P,const rct::key& AK); ABPkeys(const ABPkeys& keys) ; ABPkeys() {index=0;is_subaddress=false;is_subaddress=false;is_change_address=false;} + ABPkeys &operator=(const ABPkeys &keys); }; class Keymap { diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index 58abde1d1..b7adf433d 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -115,10 +115,14 @@ namespace trezor { MDEBUG("Enumerating Trezor devices..."); enumerate(trans); + sort_transports_by_env(trans); - MDEBUG("Enumeration yielded " << trans.size() << " devices"); + MDEBUG("Enumeration yielded " << trans.size() << " Trezor devices"); for (auto &cur : trans) { MDEBUG(" device: " << *(cur.get())); + } + + for (auto &cur : trans) { std::string cur_path = cur->get_path(); if (boost::starts_with(cur_path, this->name)) { MDEBUG("Device Match: " << cur_path); @@ -412,10 +416,11 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); MDEBUG("on_button_request, code: " << msg->code()); + TREZOR_CALLBACK(on_button_request, msg->code()); + messages::common::ButtonAck ack; write_raw(&ack); - TREZOR_CALLBACK(on_button_request, msg->code()); resp = read_raw(); } diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index dd9b0b52f..59b281f13 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -31,11 +31,13 @@ #include <libusb.h> #endif +#include <algorithm> #include <boost/endian/conversion.hpp> #include <boost/asio/io_service.hpp> #include <boost/asio/ip/udp.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp> #include <boost/format.hpp> +#include "common/apply_permutation.h" #include "transport.hpp" #include "messages/messages-common.pb.h" @@ -95,6 +97,47 @@ namespace trezor{ return patch | (((uint64_t)minor) << bits_2) | (((uint64_t)major) << (bits_1 + bits_2)); } + typedef struct { + uint16_t trezor_type; + uint16_t id_vendor; + uint16_t id_product; + } trezor_usb_desc_t; + + static trezor_usb_desc_t TREZOR_DESC_T1 = {1, 0x534C, 0x0001}; + static trezor_usb_desc_t TREZOR_DESC_T2 = {2, 0x1209, 0x53C1}; + static trezor_usb_desc_t TREZOR_DESC_T2_BL = {3, 0x1209, 0x53C0}; + + static trezor_usb_desc_t TREZOR_DESCS[] = { + TREZOR_DESC_T1, + TREZOR_DESC_T2, + TREZOR_DESC_T2_BL, + }; + + static size_t TREZOR_DESCS_LEN = sizeof(TREZOR_DESCS)/sizeof(TREZOR_DESCS[0]); + + static ssize_t get_device_idx(uint16_t id_vendor, uint16_t id_product){ + for(size_t i = 0; i < TREZOR_DESCS_LEN; ++i){ + if (TREZOR_DESCS[i].id_vendor == id_vendor && TREZOR_DESCS[i].id_product == id_product){ + return i; + } + } + + return -1; + } + + static bool is_device_supported(ssize_t device_idx){ + CHECK_AND_ASSERT_THROW_MES(device_idx < (ssize_t)TREZOR_DESCS_LEN, "Device desc idx too big"); + if (device_idx < 0){ + return false; + } + +#ifdef TREZOR_1_SUPPORTED + return true; +#else + return TREZOR_DESCS[device_idx].trezor_type != 1; +#endif + } + // // Helpers // @@ -312,6 +355,24 @@ namespace trezor{ for(rapidjson::Value::ConstValueIterator itr = bridge_res.Begin(); itr != bridge_res.End(); ++itr){ auto element = itr->GetObject(); auto t = std::make_shared<BridgeTransport>(boost::make_optional(json_get_string(element["path"]))); + + auto itr_vendor = element.FindMember("vendor"); + auto itr_product = element.FindMember("product"); + if (itr_vendor != element.MemberEnd() && itr_product != element.MemberEnd() + && itr_vendor->value.IsNumber() && itr_product->value.IsNumber()){ + try { + const auto id_vendor = (uint16_t) itr_vendor->value.GetUint64(); + const auto id_product = (uint16_t) itr_product->value.GetUint64(); + const auto device_idx = get_device_idx(id_vendor, id_product); + if (!is_device_supported(device_idx)){ + MDEBUG("Device with idx " << device_idx << " is not supported. Vendor: " << id_vendor << ", product: " << id_product); + continue; + } + } catch(const std::exception &e){ + MERROR("Could not detect vendor & product: " << e.what()); + } + } + t->m_device_info.emplace(); t->m_device_info->CopyFrom(*itr, t->m_device_info->GetAllocator()); res.push_back(t); @@ -710,24 +771,20 @@ namespace trezor{ #ifdef WITH_DEVICE_TREZOR_WEBUSB static bool is_trezor1(libusb_device_descriptor * info){ - return info->idVendor == 0x534C && info->idProduct == 0x0001; + return info->idVendor == TREZOR_DESC_T1.id_vendor && info->idProduct == TREZOR_DESC_T1.id_product; } static bool is_trezor2(libusb_device_descriptor * info){ - return info->idVendor == 0x1209 && info->idProduct == 0x53C1; + return info->idVendor == TREZOR_DESC_T2.id_vendor && info->idProduct == TREZOR_DESC_T2.id_product; } static bool is_trezor2_bl(libusb_device_descriptor * info){ - return info->idVendor == 0x1209 && info->idProduct == 0x53C0; + return info->idVendor == TREZOR_DESC_T2_BL.id_vendor && info->idProduct == TREZOR_DESC_T2_BL.id_product; } - static uint8_t get_trezor_dev_mask(libusb_device_descriptor * info){ - uint8_t mask = 0; + static ssize_t get_trezor_dev_id(libusb_device_descriptor *info){ CHECK_AND_ASSERT_THROW_MES(info, "Empty device descriptor"); - mask |= is_trezor1(info) ? 1 : 0; - mask |= is_trezor2(info) ? 2 : 0; - mask |= is_trezor2_bl(info) ? 4 : 0; - return mask; + return get_device_idx(info->idVendor, info->idProduct); } static void set_libusb_log(libusb_context *ctx){ @@ -844,12 +901,12 @@ namespace trezor{ continue; } - const auto trezor_mask = get_trezor_dev_mask(&desc); - if (!trezor_mask){ + const auto trezor_dev_idx = get_trezor_dev_id(&desc); + if (!is_device_supported(trezor_dev_idx)){ continue; } - MTRACE("Found Trezor device: " << desc.idVendor << ":" << desc.idProduct << " mask " << (int)trezor_mask); + MTRACE("Found Trezor device: " << desc.idVendor << ":" << desc.idProduct << " dev_idx " << (int)trezor_dev_idx); auto t = std::make_shared<WebUsbTransport>(boost::make_optional(&desc)); t->m_bus_id = libusb_get_bus_number(devs[i]); @@ -909,8 +966,8 @@ namespace trezor{ continue; } - const auto trezor_mask = get_trezor_dev_mask(&desc); - if (!trezor_mask) { + const auto trezor_dev_idx = get_trezor_dev_id(&desc); + if (!is_device_supported(trezor_dev_idx)){ continue; } @@ -921,7 +978,7 @@ namespace trezor{ get_libusb_ports(devs[i], path); MTRACE("Found Trezor device: " << desc.idVendor << ":" << desc.idProduct - << ", mask: " << (int)trezor_mask + << ", dev_idx: " << (int)trezor_dev_idx << ". path: " << get_usb_path(bus_id, path)); if (bus_id == m_bus_id && path == m_port_numbers) { @@ -1110,6 +1167,39 @@ namespace trezor{ #endif } + void sort_transports_by_env(t_transport_vect & res){ + const char *env_trezor_path = getenv("TREZOR_PATH"); + if (!env_trezor_path){ + return; + } + + // Sort transports by the longest matching prefix with TREZOR_PATH + std::string trezor_path(env_trezor_path); + std::vector<size_t> match_idx(res.size()); + std::vector<size_t> path_permutation(res.size()); + + for(size_t i = 0; i < res.size(); ++i){ + auto cpath = res[i]->get_path(); + std::string * s1 = &trezor_path; + std::string * s2 = &cpath; + + // first has to be shorter in std::mismatch(). Returns first non-matching iterators. + if (s1->size() >= s2->size()){ + std::swap(s1, s2); + } + + const auto mism = std::mismatch(s1->begin(), s1->end(), s2->begin()); + match_idx[i] = mism.first - s1->begin(); + path_permutation[i] = i; + } + + std::sort(path_permutation.begin(), path_permutation.end(), [&](const size_t i0, const size_t i1) { + return match_idx[i0] > match_idx[i1]; + }); + + tools::apply_permutation(path_permutation, res); + } + std::shared_ptr<Transport> transport(const std::string & path){ if (boost::starts_with(path, BridgeTransport::PATH_PREFIX)){ return std::make_shared<BridgeTransport>(path.substr(strlen(BridgeTransport::PATH_PREFIX))); diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp index cde862547..affd91553 100644 --- a/src/device_trezor/trezor/transport.hpp +++ b/src/device_trezor/trezor/transport.hpp @@ -303,6 +303,11 @@ namespace trezor { void enumerate(t_transport_vect & res); /** + * Sorts found transports by TREZOR_PATH environment variable. + */ + void sort_transports_by_env(t_transport_vect & res); + + /** * Transforms path to the transport */ std::shared_ptr<Transport> transport(const std::string & path); diff --git a/src/lmdb/error.cpp b/src/lmdb/error.cpp index 359677064..91479521e 100644 --- a/src/lmdb/error.cpp +++ b/src/lmdb/error.cpp @@ -55,7 +55,7 @@ namespace { break; // map to nothing generic case MDB_PAGE_NOTFOUND: case MDB_CORRUPTED: - return std::errc::state_not_recoverable; + return std::errc::bad_address; case MDB_PANIC: case MDB_VERSION_MISMATCH: case MDB_INVALID: diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index 653314b04..7d2599e9a 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -129,7 +129,7 @@ namespace Language case 1: *wptr++ = cp; break;
case 2: *wptr++ = 0xc0 | (cp >> 6); *wptr++ = 0x80 | (cp & 0x3f); break;
case 3: *wptr++ = 0xe0 | (cp >> 12); *wptr++ = 0x80 | ((cp >> 6) & 0x3f); *wptr++ = 0x80 | (cp & 0x3f); break;
- case 4: *wptr++ = 0xf0 | (cp >> 18); *wptr += 0x80 | ((cp >> 12) & 0x3f); *wptr++ = 0x80 | ((cp >> 6) & 0x3f); *wptr++ = 0x80 | (cp & 0x3f); break;
+ case 4: *wptr++ = 0xf0 | (cp >> 18); *wptr++ = 0x80 | ((cp >> 12) & 0x3f); *wptr++ = 0x80 | ((cp >> 6) & 0x3f); *wptr++ = 0x80 | (cp & 0x3f); break;
default: throw std::runtime_error("Invalid UTF-8");
}
*wptr = 0;
diff --git a/src/net/error.h b/src/net/error.h index c8338f7e2..7c852dd20 100644 --- a/src/net/error.h +++ b/src/net/error.h @@ -42,7 +42,8 @@ namespace net invalid_i2p_address, invalid_port, //!< Outside of 0-65535 range invalid_tor_address,//!< Invalid base32 or length - unsupported_address //!< Type not supported by `get_network_address` + unsupported_address,//!< Type not supported by `get_network_address` + invalid_mask, //!< Outside of 0-32 range }; //! \return `std::error_category` for `net` namespace. diff --git a/src/net/parse.cpp b/src/net/parse.cpp index eaaadb67e..d93d7d352 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -58,4 +58,27 @@ namespace net return {epee::net_utils::ipv4_network_address{ip, port}}; return make_error_code(net::error::unsupported_address); } + + expect<epee::net_utils::ipv4_network_subnet> + get_ipv4_subnet_address(const boost::string_ref address, bool allow_implicit_32) + { + uint32_t mask = 32; + const boost::string_ref::size_type slash = address.find_first_of('/'); + if (slash != boost::string_ref::npos) + { + if (!epee::string_tools::get_xtype_from_string(mask, std::string{address.substr(slash + 1)})) + return make_error_code(net::error::invalid_mask); + if (mask > 32) + return make_error_code(net::error::invalid_mask); + } + else if (!allow_implicit_32) + return make_error_code(net::error::invalid_mask); + + std::uint32_t ip = 0; + boost::string_ref S(address.data(), slash != boost::string_ref::npos ? slash : address.size()); + if (!epee::string_tools::get_ip_int32_from_string(ip, std::string(S))) + return make_error_code(net::error::invalid_host); + + return {epee::net_utils::ipv4_network_subnet{ip, (uint8_t)mask}}; + } } diff --git a/src/net/parse.h b/src/net/parse.h index 5804c4128..9f0d66ea6 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -50,5 +50,18 @@ namespace net */ expect<epee::net_utils::network_address> get_network_address(boost::string_ref address, std::uint16_t default_port); + + /*! + Identifies an IPv4 subnet in CIDR notatioa and returns it as a generic + `network_address`. If the type is unsupported, it might be a hostname, + and `error() == net::error::kUnsupportedAddress` is returned. + + \param address An ipv4 address. + \param allow_implicit_32 whether to accept "raw" IPv4 addresses, with CIDR notation + + \return A tor or IPv4 address, else error. + */ + expect<epee::net_utils::ipv4_network_subnet> + get_ipv4_subnet_address(boost::string_ref address, bool allow_implicit_32 = false); } diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 42bb3b061..9ee5ce0de 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -248,7 +248,11 @@ namespace nodetool void change_max_in_public_peers(size_t count); virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME); virtual bool unblock_host(const epee::net_utils::network_address &address); - virtual std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; } + virtual bool block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds = P2P_IP_BLOCKTIME); + virtual bool unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet); + virtual bool is_host_blocked(const epee::net_utils::network_address &address, time_t *seconds) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return !is_remote_host_allowed(address, seconds); } + virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; } + virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_subnets; } virtual void add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context); virtual void remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context); @@ -319,7 +323,7 @@ namespace nodetool virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); virtual bool add_host_fail(const epee::net_utils::network_address &address); //----------------- i_connection_filter -------------------------------------------------------- - virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address); + virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL); //----------------------------------------------------------------------------------------------- bool parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port = 0); bool handle_command_line( @@ -461,8 +465,9 @@ namespace nodetool std::map<epee::net_utils::network_address, time_t> m_conn_fails_cache; epee::critical_section m_conn_fails_cache_lock; - epee::critical_section m_blocked_hosts_lock; - std::map<std::string, time_t> m_blocked_hosts; + epee::critical_section m_blocked_hosts_lock; // for both hosts and subnets + std::map<epee::net_utils::network_address, time_t> m_blocked_hosts; + std::map<epee::net_utils::ipv4_network_subnet, time_t> m_blocked_subnets; epee::critical_section m_host_fails_score_lock; std::map<std::string, uint64_t> m_host_fails_score; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 7d13b3216..ae51a63b2 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -155,19 +155,55 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address) + bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - auto it = m_blocked_hosts.find(address.host_str()); - if(it == m_blocked_hosts.end()) - return true; - if(time(nullptr) >= it->second) + + const time_t now = time(nullptr); + + // look in the hosts list + auto it = m_blocked_hosts.find(address); + if (it != m_blocked_hosts.end()) { - m_blocked_hosts.erase(it); - MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); - return true; + if (now >= it->second) + { + m_blocked_hosts.erase(it); + MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); + it = m_blocked_hosts.end(); + } + else + { + if (t) + *t = it->second - now; + return false; + } } - return false; + + // manually loop in subnets + if (address.get_type_id() == epee::net_utils::address_type::ipv4) + { + auto ipv4_address = address.template as<epee::net_utils::ipv4_network_address>(); + std::map<epee::net_utils::ipv4_network_subnet, time_t>::iterator it; + for (it = m_blocked_subnets.begin(); it != m_blocked_subnets.end(); ) + { + if (now >= it->second) + { + it = m_blocked_subnets.erase(it); + MCLOG_CYAN(el::Level::Info, "global", "Subnet " << it->first.host_str() << " unblocked."); + continue; + } + if (it->first.matches(ipv4_address)) + { + if (t) + *t = it->second - now; + return false; + } + ++it; + } + } + + // not found in hosts or subnets, allowed + return true; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> @@ -176,8 +212,15 @@ namespace nodetool if(!addr.is_blockable()) return false; + const time_t now = time(nullptr); + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - m_blocked_hosts[addr.host_str()] = time(nullptr) + seconds; + time_t limit; + if (now > std::numeric_limits<time_t>::max() - seconds) + limit = std::numeric_limits<time_t>::max(); + else + limit = now + seconds; + m_blocked_hosts[addr] = limit; // drop any connection to that address. This should only have to look into // the zone related to the connection, but really make sure everything is @@ -207,7 +250,7 @@ namespace nodetool bool node_server<t_payload_net_handler>::unblock_host(const epee::net_utils::network_address &address) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - auto i = m_blocked_hosts.find(address.host_str()); + auto i = m_blocked_hosts.find(address); if (i == m_blocked_hosts.end()) return false; m_blocked_hosts.erase(i); @@ -216,6 +259,58 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds) + { + const time_t now = time(nullptr); + + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); + time_t limit; + if (now > std::numeric_limits<time_t>::max() - seconds) + limit = std::numeric_limits<time_t>::max(); + else + limit = now + seconds; + m_blocked_subnets[subnet] = limit; + + // drop any connection to that subnet. This should only have to look into + // the zone related to the connection, but really make sure everything is + // swept ... + std::vector<boost::uuids::uuid> conns; + for(auto& zone : m_network_zones) + { + zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if (cntxt.m_remote_address.get_type_id() != epee::net_utils::ipv4_network_address::get_type_id()) + return true; + auto ipv4_address = cntxt.m_remote_address.template as<epee::net_utils::ipv4_network_address>(); + if (subnet.matches(ipv4_address)) + { + conns.push_back(cntxt.m_connection_id); + } + return true; + }); + for (const auto &c: conns) + zone.second.m_net_server.get_config_object().close(c); + + conns.clear(); + } + + MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked."); + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet) + { + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); + auto i = m_blocked_subnets.find(subnet); + if (i == m_blocked_subnets.end()) + return false; + m_blocked_subnets.erase(i); + MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " unblocked."); + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address) { if(!address.is_blockable()) @@ -937,7 +1032,10 @@ namespace nodetool } if(!context.m_is_income) m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port); - m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false); + if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false)) + { + m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id ); + } }); if(!r) @@ -1083,6 +1181,7 @@ namespace nodetool LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << na.str() /*<< ", try " << try_count*/); + zone.m_net_server.get_config_object().close(con->m_connection_id); return false; } @@ -1142,7 +1241,7 @@ namespace nodetool bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << na.str()); - + zone.m_net_server.get_config_object().close(con->m_connection_id); return false; } @@ -1219,19 +1318,53 @@ namespace nodetool size_t random_index; const uint32_t next_needed_pruning_stripe = m_payload_handler.get_next_needed_pruning_stripe().second; + // build a set of all the /16 we're connected to, and prefer a peer that's not in that set + std::set<uint32_t> classB; + if (&zone == &m_network_zones.at(epee::net_utils::zone::public_)) // at returns reference, not copy + { + zone.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if (cntxt.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) + { + + const epee::net_utils::network_address na = cntxt.m_remote_address; + const uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip(); + classB.insert(actual_ip & 0x0000ffff); + } + return true; + }); + } + std::deque<size_t> filtered; const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t>::max(); - size_t idx = 0; - zone.m_peerlist.foreach (use_white_list, [&filtered, &idx, limit, next_needed_pruning_stripe](const peerlist_entry &pe){ - if (filtered.size() >= limit) - return false; - if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0) - filtered.push_back(idx); - else if (next_needed_pruning_stripe == tools::get_pruning_stripe(pe.pruning_seed)) - filtered.push_front(idx); - ++idx; - return true; - }); + size_t idx = 0, skipped = 0; + for (int step = 0; step < 2; ++step) + { + bool skip_duplicate_class_B = step == 0; + zone.m_peerlist.foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe](const peerlist_entry &pe){ + if (filtered.size() >= limit) + return false; + bool skip = false; + if (skip_duplicate_class_B && pe.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) + { + const epee::net_utils::network_address na = pe.adr; + uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip(); + skip = classB.find(actual_ip & 0x0000ffff) != classB.end(); + } + if (skip) + ++skipped; + else if (next_needed_pruning_stripe == 0 || pe.pruning_seed == 0) + filtered.push_back(idx); + else if (next_needed_pruning_stripe == tools::get_pruning_stripe(pe.pruning_seed)) + filtered.push_front(idx); + ++idx; + return true; + }); + if (skipped == 0 || !filtered.empty()) + break; + if (skipped) + MGINFO("Skipping " << skipped << " possible peers as they share a class B with existing peers"); + } if (filtered.empty()) { MDEBUG("No available peer in " << (use_white_list ? "white" : "gray") << " list filtered by " << next_needed_pruning_stripe); @@ -1948,7 +2081,7 @@ namespace nodetool const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(zone_type); - zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new); + zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); m_payload_handler.get_payload_sync_data(rsp.payload_data); /* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon) @@ -2051,7 +2184,7 @@ namespace nodetool }); //fill response - zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new); + zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); get_local_node_data(rsp.node_data, zone); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 26451b333..34d151f5f 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -56,7 +56,8 @@ namespace nodetool virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0; virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0; - virtual std::map<std::string, time_t> get_blocked_hosts()=0; + virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts()=0; + virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets()=0; virtual bool add_host_fail(const epee::net_utils::network_address &address)=0; virtual void add_used_stripe_peer(const t_connection_context &context)=0; virtual void remove_used_stripe_peer(const t_connection_context &context)=0; @@ -112,9 +113,13 @@ namespace nodetool { return true; } - virtual std::map<std::string, time_t> get_blocked_hosts() + virtual std::map<epee::net_utils::network_address, time_t> get_blocked_hosts() { - return std::map<std::string, time_t>(); + return std::map<epee::net_utils::network_address, time_t>(); + } + virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets() + { + return std::map<epee::net_utils::ipv4_network_subnet, time_t>(); } virtual bool add_host_fail(const epee::net_utils::network_address &address) { diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 52814af94..883997fd6 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -102,7 +102,7 @@ namespace nodetool size_t get_white_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_white.size();} size_t get_gray_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_gray.size();} bool merge_peerlist(const std::vector<peerlist_entry>& outer_bs); - bool get_peerlist_head(std::vector<peerlist_entry>& bs_head, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); + bool get_peerlist_head(std::vector<peerlist_entry>& bs_head, bool anonymize, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); void get_peerlist(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white); void get_peerlist(peerlist_types& peers); bool get_white_peer_by_index(peerlist_entry& p, size_t i); @@ -263,23 +263,40 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::get_peerlist_head(std::vector<peerlist_entry>& bs_head, uint32_t depth) + bool peerlist_manager::get_peerlist_head(std::vector<peerlist_entry>& bs_head, bool anonymize, uint32_t depth) { - 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; - bs_head.reserve(depth); + + // picks a random set of peers within the first 120%, rather than a set of the first 100%. + // The intent is that if someone asks twice, they can't easily tell: + // - this address was not in the first list, but is in the second, so the only way this can be + // is if its last_seen was recently reset, so this means the target node recently had a new + // connection to that address + // - this address was in the first list, and not in the second, which means either the address + // was moved to the gray list (if it's not accessibe, which the attacker can check if + // the address accepts incoming connections) or it was the oldest to still fit in the 250 items, + // so its last_seen is old. + const uint32_t pick_depth = anonymize ? depth + depth / 5 : depth; + bs_head.reserve(pick_depth); for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index)) { - if(!vl.last_seen) - continue; - - if(cnt++ >= depth) + if(cnt++ >= pick_depth) break; bs_head.push_back(vl); } + + if (anonymize) + { + std::random_shuffle(bs_head.begin(), bs_head.end()); + if (bs_head.size() > depth) + bs_head.resize(depth); + for (auto &e: bs_head) + e.last_seen = 0; + } + return true; } //-------------------------------------------------------------------------------------------------- @@ -327,8 +344,14 @@ namespace nodetool trim_white_peerlist(); }else { - //update record in white list - m_peers_white.replace(by_addr_it_wt, ple); + //update record in white list + peerlist_entry new_ple = ple; + if (by_addr_it_wt->pruning_seed && ple.pruning_seed == 0) // guard against older nodes not passing pruning info around + new_ple.pruning_seed = by_addr_it_wt->pruning_seed; + if (by_addr_it_wt->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around + new_ple.rpc_port = by_addr_it_wt->rpc_port; + new_ple.last_seen = by_addr_it_wt->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted + m_peers_white.replace(by_addr_it_wt, new_ple); } //remove from gray list, if need auto by_addr_it_gr = m_peers_gray.get<by_addr>().find(ple.adr); @@ -362,8 +385,14 @@ namespace nodetool trim_gray_peerlist(); }else { - //update record in white list - m_peers_gray.replace(by_addr_it_gr, ple); + //update record in gray list + peerlist_entry new_ple = ple; + if (by_addr_it_gr->pruning_seed && ple.pruning_seed == 0) // guard against older nodes not passing pruning info around + new_ple.pruning_seed = by_addr_it_gr->pruning_seed; + if (by_addr_it_gr->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around + new_ple.rpc_port = by_addr_it_gr->rpc_port; + new_ple.last_seen = by_addr_it_gr->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted + m_peers_gray.replace(by_addr_it_gr, new_ple); } return true; CATCH_ENTRY_L0("peerlist_manager::append_with_peer_gray()", false); diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 40ef2ebcd..32f30adca 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -134,10 +134,11 @@ namespace boost a & port; a & length; - if (length > net::tor_address::buffer_size()) + const size_t buffer_size = net::tor_address::buffer_size(); + if (length > buffer_size) MONERO_THROW(net::error::invalid_tor_address, "Tor address too long"); - char host[net::tor_address::buffer_size()] = {0}; + char host[buffer_size] = {0}; a.load_binary(host, length); host[sizeof(host) - 1] = 0; @@ -155,10 +156,11 @@ namespace boost a & port; a & length; - if (length > net::i2p_address::buffer_size()) + const size_t buffer_size = net::i2p_address::buffer_size(); + if (length > buffer_size) MONERO_THROW(net::error::invalid_i2p_address, "i2p address too long"); - char host[net::i2p_address::buffer_size()] = {0}; + char host[buffer_size] = {0}; a.load_binary(host, length); host[sizeof(host) - 1] = 0; diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 59c6099d5..85774fcd5 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -81,7 +81,8 @@ namespace nodetool BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(adr) KV_SERIALIZE(id) - KV_SERIALIZE(last_seen) + if (!is_store || this_ref.last_seen != 0) + KV_SERIALIZE_OPT(last_seen, (int64_t)0) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) END_KV_SERIALIZE_MAP() @@ -132,7 +133,7 @@ namespace nodetool ss << pe.id << "\t" << pe.adr.str() << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") << " \tpruning seed " << pe.pruning_seed - << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) + << " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen)) << std::endl; } return ss.str(); diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index e394ef088..6270d4d14 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -48,12 +48,12 @@ extern "C" //#define DEBUG_BP -#if 1 +#if 0 #define PERF_TIMER_START_BP(x) PERF_TIMER_START_UNIT(x, 1000000) #define PERF_TIMER_STOP_BP(x) PERF_TIMER_STOP(x) #else -#define PERF_TIMER_START_BP(x) ((void*)0) -#define PERF_TIMER_STOP_BP(x) ((void*)0) +#define PERF_TIMER_START_BP(x) ((void)0) +#define PERF_TIMER_STOP_BP(x) ((void)0) #endif #define STRAUS_SIZE_LIMIT 232 @@ -439,35 +439,35 @@ static epee::span<const rct::key> slice(const rct::keyV &a, size_t start, size_t static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1) { - rct::keyV data; - data.reserve(3); - data.push_back(hash_cache); - data.push_back(mash0); - data.push_back(mash1); - return hash_cache = rct::hash_to_scalar(data); + rct::key data[3]; + data[0] = hash_cache; + data[1] = mash0; + data[2] = mash1; + rct::hash_to_scalar(hash_cache, data, sizeof(data)); + return hash_cache; } static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2) { - rct::keyV data; - data.reserve(4); - data.push_back(hash_cache); - data.push_back(mash0); - data.push_back(mash1); - data.push_back(mash2); - return hash_cache = rct::hash_to_scalar(data); + rct::key data[4]; + data[0] = hash_cache; + data[1] = mash0; + data[2] = mash1; + data[3] = mash2; + rct::hash_to_scalar(hash_cache, data, sizeof(data)); + return hash_cache; } static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2, const rct::key &mash3) { - rct::keyV data; - data.reserve(5); - data.push_back(hash_cache); - data.push_back(mash0); - data.push_back(mash1); - data.push_back(mash2); - data.push_back(mash3); - return hash_cache = rct::hash_to_scalar(data); + rct::key data[5]; + data[0] = hash_cache; + data[1] = mash0; + data[2] = mash1; + data[3] = mash2; + data[4] = mash3; + rct::hash_to_scalar(hash_cache, data, sizeof(data)); + return hash_cache; } /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ @@ -825,6 +825,7 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) size_t inv_offset = 0; std::vector<rct::key> to_invert; to_invert.reserve(11 * sizeof(proofs)); + size_t max_logM = 0; for (const Bulletproof *p: proofs) { const Bulletproof &proof = *p; @@ -861,6 +862,7 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) size_t M; for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < proof.V.size(); ++pd.logM); CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + max_logM = std::max(pd.logM, max_logM); const size_t rounds = pd.logM+logN; CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); @@ -888,7 +890,7 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) rct::key tmp; std::vector<MultiexpData> multiexp_data; - multiexp_data.reserve(nV + (2 * (10/*logM*/ + logN) + 4) * proofs.size() + 2 * maxMN); + multiexp_data.reserve(nV + (2 * (max_logM + logN) + 4) * proofs.size() + 2 * maxMN); multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); @@ -901,6 +903,8 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) rct::keyV m_z4(maxMN, rct::zero()), m_z5(maxMN, rct::zero()); rct::key m_y0 = rct::zero(), y1 = rct::zero(); int proof_data_index = 0; + rct::keyV w_cache; + rct::keyV proof8_V, proof8_L, proof8_R; for (const Bulletproof *p: proofs) { const Bulletproof &proof = *p; @@ -913,9 +917,9 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) const rct::key weight_z = rct::skGen(); // pre-multiply some points by 8 - rct::keyV proof8_V = proof.V; for (rct::key &k: proof8_V) k = rct::scalarmult8(k); - rct::keyV proof8_L = proof.L; for (rct::key &k: proof8_L) k = rct::scalarmult8(k); - rct::keyV proof8_R = proof.R; for (rct::key &k: proof8_R) k = rct::scalarmult8(k); + proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) proof8_V[i] = rct::scalarmult8(proof.V[i]); + proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) proof8_L[i] = rct::scalarmult8(proof.L[i]); + proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) proof8_R[i] = rct::scalarmult8(proof.R[i]); rct::key proof8_T1 = rct::scalarmult8(proof.T1); rct::key proof8_T2 = rct::scalarmult8(proof.T2); rct::key proof8_S = rct::scalarmult8(proof.S); @@ -976,7 +980,7 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) // precalc PERF_TIMER_START_BP(VERIFY_line_24_25_precalc); - rct::keyV w_cache(1<<rounds); + w_cache.resize(1<<rounds); w_cache[0] = winv[0]; w_cache[1] = pd.w[0]; for (size_t j = 1; j < rounds; ++j) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index e877c13ce..ff2a81d43 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -695,6 +695,7 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); } CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); + CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings"); rctSig rv; rv.type = RCTTypeFull; diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 50d0f4d91..f8729b872 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -191,6 +191,8 @@ namespace rct { Bulletproof(const rct::keyV &V, const rct::key &A, const rct::key &S, const rct::key &T1, const rct::key &T2, const rct::key &taux, const rct::key &mu, const rct::keyV &L, const rct::keyV &R, const rct::key &a, const rct::key &b, const rct::key &t): V(V), A(A), S(S), T1(T1), T2(T2), taux(taux), mu(mu), L(L), R(R), a(a), b(b), t(t) {} + bool operator==(const Bulletproof &other) const { return V == other.V && A == other.A && S == other.S && T1 == other.T1 && T2 == other.T2 && taux == other.taux && mu == other.mu && L == other.L && R == other.R && a == other.a && b == other.b && t == other.t; } + BEGIN_SERIALIZE_OBJECT() // Commitments aren't saved, they're restored via outPk // FIELD(V) @@ -250,7 +252,7 @@ namespace rct { { FIELD(type) if (type == RCTTypeNull) - return true; + return ar.stream().good(); if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) return false; VARINT_FIELD(txnFee) @@ -310,7 +312,7 @@ namespace rct { ar.delimit_array(); } ar.end_array(); - return true; + return ar.stream().good(); } }; struct rctSigPrunable { @@ -323,7 +325,7 @@ namespace rct { bool serialize_rctsig_prunable(Archive<W> &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin) { if (type == RCTTypeNull) - return true; + return ar.stream().good(); if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) return false; if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2) @@ -427,7 +429,7 @@ namespace rct { } ar.end_array(); } - return true; + return ar.stream().good(); } }; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 39a8b4745..cfa1c49d8 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <boost/preprocessor/stringize.hpp> #include "include_base_utils.h" #include "string_tools.h" using namespace epee; @@ -41,6 +42,7 @@ using namespace epee; #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_core/tx_sanity_check.h" #include "misc_language.h" #include "net/parse.h" #include "storages/http_abstract_invoke.h" @@ -57,6 +59,8 @@ using namespace epee; #define MAX_RESTRICTED_FAKE_OUTS_COUNT 40 #define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 5000 +#define OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION (3 * 86400) // 3 days max, the wallet requests 1.8 days + namespace { void add_reason(std::string &reasons, const char *reason) @@ -73,9 +77,9 @@ namespace void store_difficulty(cryptonote::difficulty_type difficulty, uint64_t &sdiff, std::string &swdiff, uint64_t &stop64) { - sdiff = (difficulty << 64 >> 64).convert_to<uint64_t>(); + sdiff = (difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); swdiff = cryptonote::hex(difficulty); - stop64 = (difficulty >> 64).convert_to<uint64_t>(); + stop64 = ((difficulty >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); } } @@ -88,15 +92,9 @@ namespace cryptonote command_line::add_arg(desc, arg_rpc_bind_port); command_line::add_arg(desc, arg_rpc_restricted_bind_port); command_line::add_arg(desc, arg_restricted_rpc); - command_line::add_arg(desc, arg_rpc_ssl); - command_line::add_arg(desc, arg_rpc_ssl_private_key); - command_line::add_arg(desc, arg_rpc_ssl_certificate); - command_line::add_arg(desc, arg_rpc_ssl_ca_certificates); - command_line::add_arg(desc, arg_rpc_ssl_allowed_fingerprints); - command_line::add_arg(desc, arg_rpc_ssl_allow_any_cert); command_line::add_arg(desc, arg_bootstrap_daemon_address); command_line::add_arg(desc, arg_bootstrap_daemon_login); - cryptonote::rpc_args::init_options(desc); + cryptonote::rpc_args::init_options(desc, true); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -116,7 +114,7 @@ namespace cryptonote m_restricted = restricted; m_net_server.set_threads_prefix("RPC"); - auto rpc_config = cryptonote::rpc_args::process(vm); + auto rpc_config = cryptonote::rpc_args::process(vm, true); if (!rpc_config) return false; @@ -149,38 +147,9 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); - epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect; - if (command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert)) - ssl_options.verification = epee::net_utils::ssl_verification_t::none; - else - { - std::string ssl_ca_path = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); - const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); - std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() }; - std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); - - if (!ssl_ca_path.empty() || !ssl_allowed_fingerprints.empty()) - ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(ssl_ca_path)}; - } - - ssl_options.auth = epee::net_utils::ssl_authentication_t{ - command_line::get_arg(vm, arg_rpc_ssl_private_key), command_line::get_arg(vm, arg_rpc_ssl_certificate) - }; - - // user specified CA file or fingeprints implies enabled SSL by default - if (ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) - { - const std::string ssl = command_line::get_arg(vm, arg_rpc_ssl); - if (!epee::net_utils::ssl_support_from_string(ssl_options.support, ssl)) - { - MFATAL("Invalid RPC SSL support: " << ssl); - return false; - } - } - auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( - rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(ssl_options) + rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -432,17 +401,12 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HASHES_FAST>(invoke_http_mode::BIN, "/gethashes.bin", req, res, r)) return r; - NOTIFY_RESPONSE_CHAIN_ENTRY::request resp; - - resp.start_height = req.start_height; - if(!m_core.find_blockchain_supplement(req.block_ids, resp)) + res.start_height = req.start_height; + if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false)) { res.status = "Failed"; return false; } - res.current_height = resp.total_height; - res.start_height = resp.start_height; - res.m_block_ids = std::move(resp.m_block_ids); res.status = CORE_RPC_STATUS_OK; return true; @@ -574,7 +538,7 @@ namespace cryptonote // try the pool for any missing txes size_t found_in_pool = 0; std::unordered_set<crypto::hash> pool_tx_hashes; - std::unordered_map<crypto::hash, bool> double_spend_seen; + std::unordered_map<crypto::hash, tx_info> per_tx_pool_tx_info; if (!missed_txs.empty()) { std::vector<tx_info> pool_tx_info; @@ -629,7 +593,7 @@ namespace cryptonote { if (ti.id_hash == hash_string) { - double_spend_seen.insert(std::make_pair(h, ti.double_spend_seen)); + per_tx_pool_tx_info.insert(std::make_pair(h, ti)); break; } } @@ -715,14 +679,17 @@ namespace cryptonote if (e.in_pool) { e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); - if (double_spend_seen.find(tx_hash) != double_spend_seen.end()) + auto it = per_tx_pool_tx_info.find(tx_hash); + if (it != per_tx_pool_tx_info.end()) { - e.double_spend_seen = double_spend_seen[tx_hash]; + e.double_spend_seen = it->second.double_spend_seen; + e.relayed = it->second.relayed; } else { - MERROR("Failed to determine double spend status for " << tx_hash); + MERROR("Failed to determine pool info for " << tx_hash); e.double_spend_seen = false; + e.relayed = false; } } else @@ -730,6 +697,7 @@ namespace cryptonote e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height); e.double_spend_seen = false; + e.relayed = false; } // fill up old style responses too, in case an old wallet asks @@ -845,6 +813,15 @@ namespace cryptonote return true; } + if (req.do_sanity_checks && !cryptonote::tx_sanity_check(m_core.get_blockchain_storage(), tx_blob)) + { + res.status = "Failed"; + res.reason = "Sanity check failed"; + res.sanity_check_failed = true; + return true; + } + res.sanity_check_failed = false; + 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, req.do_not_relay) || tvc.m_verifivation_failed) @@ -931,16 +908,13 @@ namespace cryptonote return true; } - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - cryptonote::miner &miner= m_core.get_miner(); if (miner.is_mining()) { res.status = "Already mining"; return true; } - if(!miner.start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + if(!miner.start(info.address, static_cast<size_t>(req.threads_count), req.do_background_mining, req.ignore_battery)) { res.status = "Failed, mining not started"; LOG_PRINT_L0(res.status); @@ -1296,7 +1270,10 @@ namespace cryptonote LOG_ERROR("Failed to find tx pub key in blockblob"); return false; } - res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + if (req.reserve_size) + res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + else + res.reserved_offset = 0; if(res.reserved_offset + req.reserve_size > block_blob.size()) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1394,11 +1371,9 @@ namespace cryptonote submit_req.push_back(boost::value_initialized<std::string>()); res.height = m_core.get_blockchain_storage().get_current_blockchain_height(); - bool r = CORE_RPC_STATUS_OK; - for(size_t i = 0; i < req.amount_of_blocks; i++) { - r = on_getblocktemplate(template_req, template_res, error_resp, ctx); + bool r = on_getblocktemplate(template_req, template_res, error_resp, ctx); res.status = template_res.status; template_req.prev_block.clear(); @@ -1797,20 +1772,60 @@ namespace cryptonote PERF_TIMER(on_get_bans); auto now = time(nullptr); - std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); - for (std::map<std::string, time_t>::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i) + std::map<epee::net_utils::network_address, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); + for (std::map<epee::net_utils::network_address, time_t>::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i) { if (i->second > now) { COMMAND_RPC_GETBANS::ban b; - b.host = i->first; + b.host = i->first.host_str(); b.ip = 0; uint32_t ip; - if (epee::string_tools::get_ip_int32_from_string(ip, i->first)) + if (epee::string_tools::get_ip_int32_from_string(ip, b.host)) b.ip = ip; b.seconds = i->second - now; res.bans.push_back(b); } } + std::map<epee::net_utils::ipv4_network_subnet, time_t> blocked_subnets = m_p2p.get_blocked_subnets(); + for (std::map<epee::net_utils::ipv4_network_subnet, time_t>::const_iterator i = blocked_subnets.begin(); i != blocked_subnets.end(); ++i) + { + if (i->second > now) { + COMMAND_RPC_GETBANS::ban b; + b.host = i->first.host_str(); + b.ip = 0; + b.seconds = i->second - now; + res.bans.push_back(b); + } + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_banned(const COMMAND_RPC_BANNED::request& req, COMMAND_RPC_BANNED::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + PERF_TIMER(on_banned); + + auto na_parsed = net::get_network_address(req.address, 0); + if (!na_parsed) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Unsupported host type"; + return false; + } + epee::net_utils::network_address na = std::move(*na_parsed); + + time_t seconds; + if (m_p2p.is_host_blocked(na, &seconds)) + { + res.banned = true; + res.seconds = seconds; + } + else + { + res.banned = false; + res.seconds = 0; + } res.status = CORE_RPC_STATUS_OK; return true; @@ -1823,13 +1838,29 @@ namespace cryptonote for (auto i = req.bans.begin(); i != req.bans.end(); ++i) { epee::net_utils::network_address na; + + // try subnet first + if (!i->host.empty()) + { + auto ns_parsed = net::get_ipv4_subnet_address(i->host); + if (ns_parsed) + { + if (i->ban) + m_p2p.block_subnet(*ns_parsed, i->seconds); + else + m_p2p.unblock_subnet(*ns_parsed); + continue; + } + } + + // then host if (!i->host.empty()) { auto na_parsed = net::get_network_address(i->host, 0); if (!na_parsed) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; - error_resp.message = "Unsupported host type"; + error_resp.message = "Unsupported host/subnet type"; return false; } na = std::move(*na_parsed); @@ -1910,6 +1941,13 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_HISTOGRAM>(invoke_http_mode::JON_RPC, "get_output_histogram", req, res, r)) return r; + const bool restricted = m_restricted && ctx; + if (restricted && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) + { + res.status = "Recent cutoff is too old"; + return true; + } + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; try { @@ -2089,6 +2127,13 @@ namespace cryptonote bool core_rpc_server::on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res, const connection_context *ctx) { PERF_TIMER(on_update); + + if (m_core.offline()) + { + res.status = "Daemon is running offline"; + return true; + } + static const char software[] = "monero"; #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); @@ -2299,7 +2344,7 @@ namespace cryptonote const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); for (uint64_t amount: req.amounts) { - auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, req.cumulative); + auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, [this](uint64_t height) { return m_core.get_blockchain_storage().get_db().get_block_hash_from_height(height); }, req.cumulative, m_core.get_current_blockchain_height()); if (!data) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -2342,7 +2387,7 @@ namespace cryptonote const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); for (uint64_t amount: req.amounts) { - auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, req.cumulative); + auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, [this](uint64_t height) { return m_core.get_blockchain_storage().get_db().get_block_hash_from_height(height); }, req.cumulative, m_core.get_current_blockchain_height()); if (!data) { res.status = "Failed to get output distribution"; @@ -2373,6 +2418,7 @@ namespace cryptonote return false; } res.pruning_seed = m_core.get_blockchain_pruning_seed(); + res.pruned = res.pruning_seed != 0; } catch (const std::exception &e) { @@ -2413,40 +2459,6 @@ namespace cryptonote , false }; - const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl = { - "rpc-ssl" - , "Enable SSL on RPC connections: enabled|disabled|autodetect" - , "autodetect" - }; - - const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl_private_key = { - "rpc-ssl-private-key" - , "Path to a PEM format private key" - , "" - }; - - const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl_certificate = { - "rpc-ssl-certificate" - , "Path to a PEM format certificate" - , "" - }; - - const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl_ca_certificates = { - "rpc-ssl-ca-certificates" - , "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." - }; - - const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_fingerprints = { - "rpc-ssl-allowed-fingerprints" - , "List of certificate fingerprints to allow" - }; - - const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_ssl_allow_any_cert = { - "rpc-ssl-allow-any-cert" - , "Allow any peer certificate" - , false - }; - const command_line::arg_descriptor<std::string> core_rpc_server::arg_bootstrap_daemon_address = { "bootstrap-daemon-address" , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced" diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index a42ca2494..266661fb0 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -40,6 +40,9 @@ #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc" + // yes, epee doesn't properly use its full namespace when calling its // functions from macros. *sigh* using namespace epee; @@ -151,6 +154,7 @@ namespace cryptonote MAP_JON_RPC_WE("hard_fork_info", on_hard_fork_info, COMMAND_RPC_HARD_FORK_INFO) MAP_JON_RPC_WE_IF("set_bans", on_set_bans, COMMAND_RPC_SETBANS, !m_restricted) MAP_JON_RPC_WE_IF("get_bans", on_get_bans, COMMAND_RPC_GETBANS, !m_restricted) + MAP_JON_RPC_WE_IF("banned", on_banned, COMMAND_RPC_BANNED, !m_restricted) 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) @@ -217,6 +221,7 @@ namespace cryptonote bool on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_banned(const COMMAND_RPC_BANNED::request& req, COMMAND_RPC_BANNED::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); 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, const connection_context *ctx = NULL); 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, const connection_context *ctx = NULL); bool on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d2aba8d67..a78faf5aa 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -84,7 +84,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 2 -#define CORE_RPC_VERSION_MINOR 5 +#define CORE_RPC_VERSION_MINOR 7 #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) @@ -364,6 +364,7 @@ namespace cryptonote uint64_t block_height; uint64_t block_timestamp; std::vector<uint64_t> output_indices; + bool relayed; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -374,9 +375,16 @@ namespace cryptonote KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) KV_SERIALIZE(double_spend_seen) - KV_SERIALIZE(block_height) - KV_SERIALIZE(block_timestamp) - KV_SERIALIZE(output_indices) + if (!this_ref.in_pool) + { + KV_SERIALIZE(block_height) + KV_SERIALIZE(block_timestamp) + KV_SERIALIZE(output_indices) + } + else + { + KV_SERIALIZE(relayed) + } END_KV_SERIALIZE_MAP() }; @@ -577,10 +585,12 @@ namespace cryptonote { std::string tx_as_hex; bool do_not_relay; + bool do_sanity_checks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_as_hex) KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(do_sanity_checks, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -599,6 +609,7 @@ namespace cryptonote bool overspend; bool fee_too_low; bool not_rct; + bool sanity_check_failed; bool untrusted; BEGIN_KV_SERIALIZE_MAP() @@ -613,6 +624,7 @@ namespace cryptonote KV_SERIALIZE(overspend) KV_SERIALIZE(fee_too_low) KV_SERIALIZE(not_rct) + KV_SERIALIZE(sanity_check_failed) KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; @@ -1864,6 +1876,33 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_BANNED + { + struct request_t + { + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string status; + bool banned; + uint32_t seconds; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(banned) + KV_SERIALIZE(seconds) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_FLUSH_TRANSACTION_POOL { struct request_t @@ -2308,11 +2347,13 @@ namespace cryptonote struct response_t { + bool pruned; uint32_t pruning_seed; std::string status; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) + KV_SERIALIZE(pruned) KV_SERIALIZE(pruning_seed) END_KV_SERIALIZE_MAP() }; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 540afe6b9..612b2cab6 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -141,7 +141,7 @@ namespace rpc auto& chain = m_core.get_blockchain_storage(); - if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, res.start_height, res.current_height)) + if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, res.start_height, res.current_height, false)) { res.status = Message::STATUS_FAILED; res.error_details = "Blockchain::find_blockchain_supplement() returned false"; @@ -408,10 +408,7 @@ namespace rpc return; } - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - - if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), req.do_background_mining, req.ignore_battery)) { res.error_details = "Failed, mining not started"; LOG_PRINT_L0(res.error_details); @@ -437,7 +434,7 @@ namespace rpc auto& chain = m_core.get_blockchain_storage(); res.info.wide_difficulty = chain.get_difficulty_for_next_block(); - res.info.difficulty = (res.info.wide_difficulty << 64 >> 64).convert_to<uint64_t>(); + res.info.difficulty = (res.info.wide_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); res.info.target = chain.get_difficulty_target(); @@ -459,7 +456,7 @@ namespace rpc res.info.testnet = m_core.get_nettype() == TESTNET; res.info.stagenet = m_core.get_nettype() == STAGENET; res.info.wide_cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1); - res.info.cumulative_difficulty = (res.info.wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); + res.info.cumulative_difficulty = (res.info.wide_cumulative_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); res.info.block_size_limit = res.info.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); res.info.block_size_median = res.info.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); res.info.start_time = (uint64_t)m_core.get_start_time(); @@ -778,7 +775,7 @@ namespace rpc const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); for (std::uint64_t amount : req.amounts) { - auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, req.cumulative); + auto data = rpc::RpcHandler::get_output_distribution([this](uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { return m_core.get_output_distribution(amount, from, to, start_height, distribution, base); }, amount, req.from_height, req_to_height, [this](uint64_t height) { return m_core.get_blockchain_storage().get_db().get_block_hash_from_height(height); }, req.cumulative, m_core.get_current_blockchain_height()); if (!data) { res.distributions.clear(); @@ -829,7 +826,7 @@ namespace rpc } header.wide_difficulty = m_core.get_blockchain_storage().block_difficulty(header.height); - header.difficulty = (header.wide_difficulty << 64 >> 64).convert_to<uint64_t>(); + header.difficulty = (header.wide_difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); return true; } diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index f2be94f51..4479bd1f1 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -33,28 +33,95 @@ #include <boost/bind.hpp> #include "common/command_line.h" #include "common/i18n.h" +#include "hex.h" namespace cryptonote { + namespace + { + boost::optional<epee::net_utils::ssl_options_t> do_process_ssl(const boost::program_options::variables_map& vm, const rpc_args::descriptors& arg, const bool any_cert_option) + { + bool ssl_required = false; + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (any_cert_option && command_line::get_arg(vm, arg.rpc_ssl_allow_any_cert)) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else + { + std::string ssl_ca_file = command_line::get_arg(vm, arg.rpc_ssl_ca_certificates); + const std::vector<std::string> ssl_allowed_fingerprints = command_line::get_arg(vm, arg.rpc_ssl_allowed_fingerprints); + + std::vector<std::vector<uint8_t>> allowed_fingerprints{ ssl_allowed_fingerprints.size() }; + std::transform(ssl_allowed_fingerprints.begin(), ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + for (const auto &fpr: allowed_fingerprints) + { + if (fpr.size() != SSL_FINGERPRINT_SIZE) + { + MERROR("SHA-256 fingerprint should be " BOOST_PP_STRINGIZE(SSL_FINGERPRINT_SIZE) " bytes long."); + return boost::none; + } + } + + if (!allowed_fingerprints.empty() || !ssl_ca_file.empty()) + { + ssl_required = true; + ssl_options = epee::net_utils::ssl_options_t{ + std::move(allowed_fingerprints), std::move(ssl_ca_file) + }; + + if (command_line::get_arg(vm, arg.rpc_ssl_allow_chained)) + ssl_options.verification = epee::net_utils::ssl_verification_t::user_ca; + } + } + + // user specified CA file or fingeprints implies enabled SSL by default + if (!ssl_required && !epee::net_utils::ssl_support_from_string(ssl_options.support, command_line::get_arg(vm, arg.rpc_ssl))) + { + MERROR("Invalid argument for " << std::string(arg.rpc_ssl.name)); + return boost::none; + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + command_line::get_arg(vm, arg.rpc_ssl_private_key), command_line::get_arg(vm, arg.rpc_ssl_certificate) + }; + + return {std::move(ssl_options)}; + } + } // anonymous + 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")}) , rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""}) + , rpc_ssl({"rpc-ssl", rpc_args::tr("Enable SSL on RPC connections: enabled|disabled|autodetect"), "autodetect"}) + , rpc_ssl_private_key({"rpc-ssl-private-key", rpc_args::tr("Path to a PEM format private key"), ""}) + , rpc_ssl_certificate({"rpc-ssl-certificate", rpc_args::tr("Path to a PEM format certificate"), ""}) + , rpc_ssl_ca_certificates({"rpc-ssl-ca-certificates", rpc_args::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s)."), ""}) + , rpc_ssl_allowed_fingerprints({"rpc-ssl-allowed-fingerprints", rpc_args::tr("List of certificate fingerprints to allow")}) + , rpc_ssl_allow_chained({"rpc-ssl-allow-chained", rpc_args::tr("Allow user (via --rpc-ssl-certificates) chain certificates"), false}) + , rpc_ssl_allow_any_cert({"rpc-ssl-allow-any-cert", rpc_args::tr("Allow any peer certificate"), false}) {} 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) + void rpc_args::init_options(boost::program_options::options_description& desc, const bool any_cert_option) { 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); command_line::add_arg(desc, arg.rpc_access_control_origins); + command_line::add_arg(desc, arg.rpc_ssl); + command_line::add_arg(desc, arg.rpc_ssl_private_key); + command_line::add_arg(desc, arg.rpc_ssl_certificate); + command_line::add_arg(desc, arg.rpc_ssl_ca_certificates); + command_line::add_arg(desc, arg.rpc_ssl_allowed_fingerprints); + command_line::add_arg(desc, arg.rpc_ssl_allow_chained); + if (any_cert_option) + command_line::add_arg(desc, arg.rpc_ssl_allow_any_cert); } - boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm) + boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm, const bool any_cert_option) { const descriptors arg{}; rpc_args config{}; @@ -118,6 +185,17 @@ namespace cryptonote config.access_control_origins = std::move(access_control_origins); } + auto ssl_options = do_process_ssl(vm, arg, any_cert_option); + if (!ssl_options) + return boost::none; + config.ssl_options = std::move(*ssl_options); + return {std::move(config)}; } + + boost::optional<epee::net_utils::ssl_options_t> rpc_args::process_ssl(const boost::program_options::variables_map& vm, const bool any_cert_option) + { + const descriptors arg{}; + return do_process_ssl(vm, arg, any_cert_option); + } } diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index 216ba3712..619f02b42 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -35,6 +35,7 @@ #include "common/command_line.h" #include "common/password.h" +#include "net/net_ssl.h" namespace cryptonote { @@ -54,16 +55,29 @@ namespace cryptonote const command_line::arg_descriptor<std::string> rpc_login; const command_line::arg_descriptor<bool> confirm_external_bind; const command_line::arg_descriptor<std::string> rpc_access_control_origins; + const command_line::arg_descriptor<std::string> rpc_ssl; + const command_line::arg_descriptor<std::string> rpc_ssl_private_key; + const command_line::arg_descriptor<std::string> rpc_ssl_certificate; + const command_line::arg_descriptor<std::string> rpc_ssl_ca_certificates; + const command_line::arg_descriptor<std::vector<std::string>> rpc_ssl_allowed_fingerprints; + const command_line::arg_descriptor<bool> rpc_ssl_allow_chained; + const command_line::arg_descriptor<bool> rpc_ssl_allow_any_cert; }; + // `allow_any_cert` bool toggles `--rpc-ssl-allow-any-cert` configuration + static const char* tr(const char* str); - static void init_options(boost::program_options::options_description& desc); + static void init_options(boost::program_options::options_description& desc, const bool any_cert_option = false); //! \return Arguments specified by user, or `boost::none` if error - static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm); + static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm, const bool any_cert_option = false); + + //! \return SSL arguments specified by user, or `boost::none` if error + static boost::optional<epee::net_utils::ssl_options_t> process_ssl(const boost::program_options::variables_map& vm, const bool any_cert_option = false); std::string bind_ip; std::vector<std::string> access_control_origins; boost::optional<tools::login> login; // currently `boost::none` if unspecified by user + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; }; } diff --git a/src/rpc/rpc_handler.cpp b/src/rpc/rpc_handler.cpp index e0a81c70f..af5cb98a3 100644 --- a/src/rpc/rpc_handler.cpp +++ b/src/rpc/rpc_handler.cpp @@ -26,26 +26,49 @@ namespace rpc } boost::optional<output_distribution_data> - RpcHandler::get_output_distribution(const std::function<bool(uint64_t, uint64_t, uint64_t, uint64_t&, std::vector<uint64_t>&, uint64_t&)> &f, uint64_t amount, uint64_t from_height, uint64_t to_height, bool cumulative) + RpcHandler::get_output_distribution(const std::function<bool(uint64_t, uint64_t, uint64_t, uint64_t&, std::vector<uint64_t>&, uint64_t&)> &f, uint64_t amount, uint64_t from_height, uint64_t to_height, const std::function<crypto::hash(uint64_t)> &get_hash, bool cumulative, uint64_t blockchain_height) { static struct D { boost::mutex mutex; std::vector<std::uint64_t> cached_distribution; std::uint64_t cached_from, cached_to, cached_start_height, cached_base; + crypto::hash cached_m10_hash; + crypto::hash cached_top_hash; bool cached; - D(): cached_from(0), cached_to(0), cached_start_height(0), cached_base(0), cached(false) {} + D(): cached_from(0), cached_to(0), cached_start_height(0), cached_base(0), cached_m10_hash(crypto::null_hash), cached_top_hash(crypto::null_hash), cached(false) {} } d; const boost::unique_lock<boost::mutex> lock(d.mutex); - if (d.cached && amount == 0 && d.cached_from == from_height && d.cached_to == to_height) + crypto::hash top_hash = crypto::null_hash; + if (d.cached_to < blockchain_height) + top_hash = get_hash(d.cached_to); + if (d.cached && amount == 0 && d.cached_from == from_height && d.cached_to == to_height && d.cached_top_hash == top_hash) return process_distribution(cumulative, d.cached_start_height, d.cached_distribution, d.cached_base); std::vector<std::uint64_t> distribution; std::uint64_t start_height, base; // see if we can extend the cache - a common case - if (d.cached && amount == 0 && d.cached_from == from_height && to_height > d.cached_to) + bool can_extend = d.cached && amount == 0 && d.cached_from == from_height && to_height > d.cached_to && top_hash == d.cached_top_hash; + if (!can_extend) + { + // we kept track of the hash 10 blocks below, if it exists, so if it matches, + // we can still pop the last 10 cached slots and try again + if (d.cached && amount == 0 && d.cached_from == from_height && d.cached_to - d.cached_from >= 10 && to_height > d.cached_to - 10) + { + crypto::hash hash10 = get_hash(d.cached_to - 10); + if (hash10 == d.cached_m10_hash) + { + d.cached_to -= 10; + d.cached_top_hash = hash10; + d.cached_m10_hash = crypto::null_hash; + d.cached_distribution.resize(d.cached_distribution.size() - 10); + can_extend = true; + } + } + } + if (can_extend) { std::vector<std::uint64_t> new_distribution; if (!f(amount, d.cached_to + 1, to_height, start_height, new_distribution, base)) @@ -74,6 +97,8 @@ namespace rpc { d.cached_from = from_height; d.cached_to = to_height; + d.cached_top_hash = get_hash(d.cached_to); + d.cached_m10_hash = d.cached_to >= 10 ? get_hash(d.cached_to - 10) : crypto::null_hash; d.cached_distribution = distribution; d.cached_start_height = start_height; d.cached_base = base; diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h index 2439eaa58..b81983d28 100644 --- a/src/rpc/rpc_handler.h +++ b/src/rpc/rpc_handler.h @@ -32,6 +32,7 @@ #include <cstdint> #include <string> #include <vector> +#include "crypto/hash.h" namespace cryptonote { @@ -56,7 +57,7 @@ class RpcHandler virtual std::string handle(const std::string& request) = 0; static boost::optional<output_distribution_data> - get_output_distribution(const std::function<bool(uint64_t, uint64_t, uint64_t, uint64_t&, std::vector<uint64_t>&, uint64_t&)> &f, uint64_t amount, uint64_t from_height, uint64_t to_height, bool cumulative); + get_output_distribution(const std::function<bool(uint64_t, uint64_t, uint64_t, uint64_t&, std::vector<uint64_t>&, uint64_t&)> &f, uint64_t amount, uint64_t from_height, uint64_t to_height, const std::function<crypto::hash(uint64_t)> &get_hash, bool cumulative, uint64_t blockchain_height); }; diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index a0e4eff9d..9f60cf311 100644 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -146,7 +146,8 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false> void serialize_uvarint(T &v) { typedef std::istreambuf_iterator<char> it; - tools::read_varint(it(stream_), it(), v); // XXX handle failure + if (tools::read_varint(it(stream_), it(), v) < 0) + stream_.setstate(std::ios_base::failbit); } void begin_array(size_t &s) diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h index e32e24b78..c551095f9 100644 --- a/src/serialization/difficulty_type.h +++ b/src/serialization/difficulty_type.h @@ -54,8 +54,8 @@ inline bool do_serialize(Archive<true>& ar, cryptonote::difficulty_type &diff) { if (!ar.stream().good()) return false; - const uint64_t hi = (diff >> 64).convert_to<uint64_t>(); - const uint64_t lo = (diff << 64 >> 64).convert_to<uint64_t>(); + const uint64_t hi = ((diff >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); + const uint64_t lo = (diff & 0xffffffffffffffff).convert_to<uint64_t>(); ar.serialize_varint(hi); ar.serialize_varint(lo); if (!ar.stream().good()) diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 007bf265f..553e9951f 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -212,7 +212,7 @@ inline bool do_serialize(Archive &ar, bool &v) * \brief self-explanatory */ #define END_SERIALIZE() \ - return true; \ + return ar.stream().good(); \ } /*! \macro VALUE(f) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 2e134931f..02a099811 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -118,7 +118,7 @@ typedef cryptonote::simple_wallet sw; if (!m_long_payment_id_support) { \ fail_msg_writer() << tr("Warning: Long payment IDs are obsolete."); \ fail_msg_writer() << tr("Long payment IDs are not encrypted on the blockchain, and will harm your privacy."); \ - fail_msg_writer() << tr("Use --long-payment-id-support if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \ + fail_msg_writer() << tr("Use --long-payment-id-support-bad-for-privacy if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \ return true; \ } \ } while(0) @@ -4092,6 +4092,14 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (welcome) message_writer(console_color_yellow, true) << tr("If you are new to Monero, type \"welcome\" for a brief overview."); + if (m_long_payment_id_support) + { + message_writer(console_color_red, false) << + tr("WARNING: obsolete long payment IDs are enabled. Sending transactions with those payment IDs are bad for your privacy."); + message_writer(console_color_red, false) << + tr("It is recommended that you do not use them, and ask recipients who ask for one to not endanger your privacy."); + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -4926,7 +4934,7 @@ 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 crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time) { message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << @@ -4948,6 +4956,8 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, (m_long_payment_id_support ? tr("WARNING: this transaction uses an unencrypted payment ID: consider using subaddresses instead.") : tr("WARNING: this transaction uses an unencrypted payment ID: these are obsolete. Support will be withdrawn in the future. Use subaddresses instead.")); } } + if (unlock_time) + message_writer() << tr("NOTE: This transaction is locked, see details with: show_transfer ") + epee::string_tools::pod_to_hex(txid); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else @@ -5571,7 +5581,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending if (j == source.real_output) highlight_height = heights[j]; } - std::pair<std::string, std::string> ring_str = show_outputs_line(heights, highlight_height); + std::pair<std::string, std::string> ring_str = show_outputs_line(heights, blockchain_height, highlight_height); ostr << ring_str.first << tr("\n|") << ring_str.second << tr("|\n"); } // warn if rings contain keys originating from the same tx or temporally very close block heights @@ -5906,12 +5916,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri uint64_t total_fee = 0; uint64_t dust_not_in_fee = 0; uint64_t dust_in_fee = 0; + uint64_t change = 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; + change += ptx_vector[n].change_dts.amount; if (ptx_vector[n].dust_added_to_fee) dust_in_fee += ptx_vector[n].dust; @@ -5949,7 +5961,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri 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; + prompt << boost::format(tr(".\nThis transaction (including %s change) will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % cryptonote::print_money(change) % ((unsigned long long)unlock_block) % days; } if (m_wallet->print_ring_members()) { @@ -7673,6 +7685,8 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec local_args.erase(local_args.begin()); } + const uint64_t last_block_height = m_wallet->get_blockchain_current_height(); + if (in || coinbase) { std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; m_wallet->get_payments(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices); @@ -7687,6 +7701,25 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec std::string destination = m_wallet->get_subaddress_as_str({m_current_subaddress_account, pd.m_subaddr_index.minor}); const std::string type = pd.m_coinbase ? tr("block") : tr("in"); const bool unlocked = m_wallet->is_transfer_unlocked(pd.m_unlock_time, pd.m_block_height); + std::string locked_msg = "unlocked"; + if (!unlocked) + { + locked_msg = "locked"; + const uint64_t unlock_time = pd.m_unlock_time; + if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + { + uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE); + if (bh >= last_block_height) + locked_msg = std::to_string(bh - last_block_height) + " blks"; + } + else + { + uint64_t current_time = static_cast<uint64_t>(time(NULL)); + uint64_t threshold = current_time + (m_wallet->use_fork_rules(2, 0) ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1); + if (threshold < pd.m_unlock_time) + locked_msg = get_human_readable_timespan(std::chrono::seconds(pd.m_unlock_time - threshold)); + } + } transfers.push_back({ type, pd.m_block_height, @@ -7700,7 +7733,7 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec {{destination, pd.m_amount}}, {pd.m_subaddr_index.minor}, note, - (unlocked) ? "unlocked" : "locked" + locked_msg }); } } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 76d446ba5..33b18612c 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -310,7 +310,7 @@ 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 crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time); virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); 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, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); diff --git a/src/version.cpp.in b/src/version.cpp.in index 8aaa41b19..28ce38df7 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.14.1.0" +#define DEF_MONERO_VERSION "0.14.1.2" #define DEF_MONERO_RELEASE_NAME "Boron Butterfly" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 032b873d6..1711db482 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -157,7 +157,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index f5f3c0e1b..1d5078a11 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -37,9 +37,10 @@ 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) +NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex) : m_http_client(http_client) , m_daemon_rpc_mutex(mutex) + , m_offline(false) { invalidate(); } @@ -61,6 +62,8 @@ void NodeRPCProxy::invalidate() boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const { + if (m_offline) + return boost::optional<std::string>("offline"); if (m_rpc_version == 0) { cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); @@ -84,6 +87,8 @@ void NodeRPCProxy::set_height(uint64_t h) boost::optional<std::string> NodeRPCProxy::get_info() const { + if (m_offline) + return boost::optional<std::string>("offline"); const time_t now = time(NULL); if (now >= m_get_info_time + 30) // re-cache every 30 seconds { @@ -134,6 +139,8 @@ boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &bloc boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) const { + if (m_offline) + return boost::optional<std::string>("offline"); if (m_earliest_height[version] == 0) { cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t); @@ -161,6 +168,8 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_ if (result) return result; + if (m_offline) + return boost::optional<std::string>("offline"); if (m_dynamic_base_fee_estimate_cached_height != height || m_dynamic_base_fee_estimate_grace_blocks != grace_blocks) { cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t); @@ -191,6 +200,8 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f if (result) return result; + if (m_offline) + return boost::optional<std::string>("offline"); if (m_dynamic_base_fee_estimate_cached_height != height) { cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t); diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 3630aec08..3b75c8b94 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -39,9 +39,10 @@ namespace tools class NodeRPCProxy { public: - NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::mutex &mutex); + NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex); void invalidate(); + void set_offline(bool offline) { m_offline = offline; } boost::optional<std::string> get_rpc_version(uint32_t &version) const; boost::optional<std::string> get_height(uint64_t &height) const; @@ -56,7 +57,8 @@ private: boost::optional<std::string> get_info() const; epee::net_utils::http::http_simple_client &m_http_client; - boost::mutex &m_daemon_rpc_mutex; + boost::recursive_mutex &m_daemon_rpc_mutex; + bool m_offline; mutable uint64_t m_height; mutable uint64_t m_earliest_height[256]; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b288994a5..8f3f30da1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -29,7 +29,6 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include <numeric> -#include <random> #include <tuple> #include <boost/format.hpp> #include <boost/optional/optional.hpp> @@ -40,6 +39,7 @@ #include <boost/algorithm/string/join.hpp> #include <boost/asio/ip/address.hpp> #include <boost/range/adaptor/transformed.hpp> +#include <boost/preprocessor/stringize.hpp> #include "include_base_utils.h" using namespace epee; @@ -129,7 +129,11 @@ using namespace cryptonote; #define FIRST_REFRESH_GRANULARITY 1024 -#define GAMMA_PICK_HALF_WINDOW 5 +#define GAMMA_SHAPE 19.28 +#define GAMMA_SCALE (1/1.61) + +#define DEFAULT_MIN_OUTPUT_COUNT 5 +#define DEFAULT_MIN_OUTPUT_VALUE (2*COIN) static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; @@ -221,6 +225,8 @@ namespace add_reason(reason, "fee too low"); if (res.not_rct) add_reason(reason, "tx is not ringct"); + if (res.sanity_check_failed) + add_reason(reason, "tx sanity check failed"); if (res.not_relayed) add_reason(reason, "tx was not relayed"); return reason; @@ -266,6 +272,7 @@ struct options { const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""}; const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false}; + const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) @@ -337,6 +344,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl { std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + for (const auto &fpr: ssl_allowed_fingerprints) + { + THROW_WALLET_EXCEPTION_IF(fpr.size() != SSL_FINGERPRINT_SIZE, tools::error::wallet_internal_error, + "SHA-256 fingerprint should be " BOOST_PP_STRINGIZE(SSL_FINGERPRINT_SIZE) " bytes long."); + } ssl_options = epee::net_utils::ssl_options_t{ std::move(ssl_allowed_fingerprints), std::move(daemon_ssl_ca_file) @@ -387,8 +399,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl { const boost::string_ref real_daemon = boost::string_ref{daemon_address}.substr(0, daemon_address.rfind(':')); + /* If SSL or proxy is enabled, then a specific cert, CA or fingerprint must + be specified. This is specific to the wallet. */ const bool verification_required = - ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || use_proxy; + ssl_options.verification != epee::net_utils::ssl_verification_t::none && + (ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || use_proxy); THROW_WALLET_EXCEPTION_IF( verification_required && !ssl_options.has_strong_verification(real_daemon), @@ -456,6 +471,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl if (command_line::get_arg(vm, opts.no_dns)) wallet->enable_dns(false); + if (command_line::get_arg(vm, opts.offline)) + wallet->set_offline(); + try { if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) @@ -792,7 +810,7 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra // pseudoOuts size += 32 * n_inputs; // ecdhInfo - size += 2 * 32 * n_outputs; + size += 8 * n_outputs; // outPk - only commitment is saved size += 32 * n_outputs; // txnFee @@ -954,6 +972,44 @@ 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"); } +gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shape, double scale): + rct_offsets(rct_offsets) +{ + gamma = std::gamma_distribution<double>(shape, scale); + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation"); + const size_t blocks_in_a_year = 86400 * 365 / DIFFICULTY_TARGET_V2; + const size_t blocks_to_consider = std::min<size_t>(rct_offsets.size(), blocks_in_a_year); + const size_t outputs_to_consider = rct_offsets.back() - (blocks_to_consider < rct_offsets.size() ? rct_offsets[rct_offsets.size() - blocks_to_consider - 1] : 0); + begin = rct_offsets.data(); + end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + num_rct_outputs = *(end - 1); + THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); + average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range +}; + +gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets): gamma_picker(rct_offsets, GAMMA_SHAPE, GAMMA_SCALE) {} + +uint64_t gamma_picker::pick() +{ + double x = gamma(engine); + x = exp(x); + uint64_t output_index = x / average_output_time; + if (output_index >= num_rct_outputs) + return std::numeric_limits<uint64_t>::max(); // bad pick + output_index = num_rct_outputs - 1 - output_index; + + const uint64_t *it = std::lower_bound(begin, end, output_index); + THROW_WALLET_EXCEPTION_IF(it == end, error::wallet_internal_error, "output_index not found"); + uint64_t index = std::distance(begin, it); + + const uint64_t first_rct = index == 0 ? 0 : rct_offsets[index - 1]; + const uint64_t n_rct = rct_offsets[index] - first_rct; + if (n_rct == 0) + return std::numeric_limits<uint64_t>::max(); // bad pick + MTRACE("Picking 1/" << n_rct << " in block " << index); + return first_rct + crypto::rand_idx(n_rct); +}; + wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password): w(w), locked(password != boost::none) @@ -1083,7 +1139,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_unattended(unattended), m_devices_registered(false), m_device_last_key_image_sync(0), - m_use_dns(true) + m_use_dns(true), + m_offline(false) { } @@ -1139,6 +1196,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.hw_device_derivation_path); command_line::add_arg(desc_params, opts.tx_notify); command_line::add_arg(desc_params, opts.no_dns); + command_line::add_arg(desc_params, opts.offline); } std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -1184,6 +1242,8 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia //---------------------------------------------------------------------------------------------------- bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) { + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); + if(m_http_client.is_connected()) m_http_client.disconnect(); m_daemon_address = std::move(daemon_address); @@ -1959,7 +2019,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); + m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, td.m_tx.unlock_time); } total_received_1 += amount; notify = true; @@ -2029,7 +2089,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); + m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, td.m_tx.unlock_time); } total_received_1 += extra_amount; notify = true; @@ -2392,7 +2452,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_bin("/getblocks.bin", req, res, 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"); @@ -2414,7 +2474,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("/gethashes.bin", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_bin("/gethashes.bin", req, res, 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"); @@ -2691,7 +2751,7 @@ void wallet2::update_pool_state(bool refreshed) cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req; cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_json("/get_transaction_pool_hashes.bin", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); 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"); @@ -2838,7 +2898,7 @@ void wallet2::update_pool_state(bool refreshed) req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); MDEBUG("Got " << r << " and " << res.status); if (r && res.status == CORE_RPC_STATUS_OK) @@ -2999,6 +3059,13 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create //---------------------------------------------------------------------------------------------------- void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool) { + if (m_offline) + { + blocks_fetched = 0; + received_money = 0; + return; + } + if(m_light_wallet) { // MyMonero get_address_info needs to be called occasionally to trigger wallet sync. @@ -3076,6 +3143,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo } }); + auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);}); bool first = true; while(m_run.load(std::memory_order_relaxed)) { @@ -3201,7 +3269,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo LOG_PRINT_L1("Failed to check pending transactions"); } - hwdev.computing_key_images(false); m_first_refresh_done = true; LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all())); @@ -3258,7 +3325,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> req.binary = true; req.compress = true; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_bin("/get_output_distribution.bin", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); if (!r) { @@ -5088,7 +5155,14 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) { THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); - boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + if (m_offline) + { + if (version) + *version = 0; + if (ssl) + *ssl = false; + return false; + } // TODO: Add light wallet version check. if(m_light_wallet) { @@ -5099,20 +5173,23 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) return m_light_wallet_connected; } - if(!m_http_client.is_connected(ssl)) { - m_node_rpc_proxy.invalidate(); - if (!m_http_client.connect(std::chrono::milliseconds(timeout))) - return false; + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); if(!m_http_client.is_connected(ssl)) - return false; + { + m_node_rpc_proxy.invalidate(); + if (!m_http_client.connect(std::chrono::milliseconds(timeout))) + return false; + if(!m_http_client.is_connected(ssl)) + return false; + } } if (version) { cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client); + bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t); if(!r) { *version = 0; return false; @@ -5126,6 +5203,18 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) return true; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_offline(bool offline) +{ + m_offline = offline; + m_http_client.set_auto_connect(!offline); + if (offline) + { + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); + if(m_http_client.is_connected()) + m_http_client.disconnect(); + } +} +//---------------------------------------------------------------------------------------------------- bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const { hw::device &hwdev = m_account.get_device(); @@ -5304,7 +5393,7 @@ void wallet2::trim_hashchain() cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res); m_daemon_rpc_mutex.lock(); req.height = m_blockchain.size() - 1; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); if (r && res.status == CORE_RPC_STATUS_OK) { @@ -5660,7 +5749,7 @@ void wallet2::rescan_spent() 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); + bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, 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"); @@ -5988,7 +6077,7 @@ void wallet2::commit_tx(pending_tx& ptx) oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/submit_raw_tx", oreq, ores, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx"); // MyMonero and OpenMonero use different status strings @@ -6000,9 +6089,10 @@ void wallet2::commit_tx(pending_tx& ptx) COMMAND_RPC_SEND_RAW_TX::request req; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); req.do_not_relay = false; + req.do_sanity_checks = true; COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout); + bool r = invoke_http_json("/sendrawtransaction", req, daemon_send_resp, 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"); @@ -6955,7 +7045,7 @@ uint32_t wallet2::adjust_priority(uint32_t priority) m_daemon_rpc_mutex.lock(); getbh_req.start_height = m_blockchain.size() - N; getbh_req.end_height = m_blockchain.size() - 1; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange"); THROW_WALLET_EXCEPTION_IF(getbh_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange"); @@ -7126,7 +7216,7 @@ bool wallet2::unset_ring(const crypto::hash &txid) req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to get transaction from daemon"); if (res.txs.empty()) @@ -7180,8 +7270,8 @@ bool wallet2::find_and_save_rings(bool force) req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txs_hashes[s])); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_json("/gettransactions", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); @@ -7322,7 +7412,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ oreq.count = light_wallet_requested_outputs_count; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/get_random_outs", oreq, ores, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); @@ -7462,7 +7552,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> req_t.unlocked = true; req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, 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.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); @@ -7485,7 +7575,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> req_t.cumulative = true; req_t.binary = true; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000); + bool r = invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, rpc_timeout * 1000); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution"); @@ -7524,61 +7614,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - struct gamma_engine - { - typedef uint64_t result_type; - static constexpr result_type min() { return 0; } - static constexpr result_type max() { return std::numeric_limits<result_type>::max(); } - result_type operator()() { return crypto::rand<result_type>(); } - } engine; - static const double shape = 19.28/*16.94*/; - //static const double shape = m_testnet ? 17.02 : 17.28; - static const double scale = 1/1.61; - std::gamma_distribution<double> gamma(shape, scale); - THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation"); - uint64_t last_usable_block = rct_offsets.size() - 1; - auto pick_gamma = [&]() - { - double x = gamma(engine); - x = exp(x); - uint64_t block_offset = x / DIFFICULTY_TARGET_V2; // this assumes constant target over the whole rct range - if (block_offset > last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) - return std::numeric_limits<uint64_t>::max(); // bad pick - block_offset = last_usable_block - block_offset; - THROW_WALLET_EXCEPTION_IF(block_offset > last_usable_block, error::wallet_internal_error, "Bad offset calculation"); - THROW_WALLET_EXCEPTION_IF(block_offset > 0 && rct_offsets[block_offset] < rct_offsets[block_offset - 1], - error::get_output_distribution, "Decreasing offsets in rct distribution: " + - std::to_string(block_offset - 1) + ": " + std::to_string(rct_offsets[block_offset - 1]) + ", " + - std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset])); - uint64_t first_block_offset = block_offset, last_block_offset = block_offset; - for (size_t half_window = 0; half_window <= GAMMA_PICK_HALF_WINDOW; ++half_window) - { - // end when we have a non empty block - uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0]; - if (cum0 > 1) - break; - uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0]; - if (cum1 > 1) - break; - if (first_block_offset == 0 && last_block_offset >= last_usable_block) - break; - // expand up to bounds - if (first_block_offset > 0) - --first_block_offset; - else - return std::numeric_limits<uint64_t>::max(); // bad pick - if (last_block_offset < last_usable_block - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) - ++last_block_offset; - else - return std::numeric_limits<uint64_t>::max(); // bad pick - } - const uint64_t first_rct = first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]; - const uint64_t n_rct = rct_offsets[last_block_offset] - first_rct; - if (n_rct == 0) - return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0; - MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height); - return first_rct + crypto::rand_idx(n_rct); - }; + std::unique_ptr<gamma_picker> gamma; + if (has_rct_distribution) + gamma.reset(new gamma_picker(rct_offsets)); size_t num_selected_transfers = 0; for(size_t idx: selected_transfers) @@ -7726,7 +7764,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } } - if (num_outs <= requested_outputs_count && !existing_ring_found) + if (num_outs <= requested_outputs_count) { for (uint64_t i = 0; i < num_outs; i++) req.outputs.push_back({amount, i}); @@ -7752,6 +7790,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // while we still need more mixins uint64_t num_usable_outs = num_outs; bool allow_blackballed = false; + MDEBUG("Starting gamma picking with " << num_outs << ", num_usable_outs " << num_usable_outs + << ", requested_outputs_count " << requested_outputs_count); while (num_found < requested_outputs_count) { // if we've gone through every possible output, we've gotten all we can @@ -7776,20 +7816,21 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> const char *type = ""; if (amount == 0 && has_rct_distribution) { + THROW_WALLET_EXCEPTION_IF(!gamma, error::wallet_internal_error, "No gamma picker"); // gamma distribution if (num_found -1 < recent_outputs_count + pre_fork_outputs_count) { - do i = pick_gamma(); while (i >= segregation_limit[amount].first); + do i = gamma->pick(); while (i >= segregation_limit[amount].first); type = "pre-fork gamma"; } else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count) { - do i = pick_gamma(); while (i < segregation_limit[amount].first || i >= num_outs); + do i = gamma->pick(); while (i < segregation_limit[amount].first || i >= num_outs); type = "post-fork gamma"; } else { - do i = pick_gamma(); while (i >= num_outs); + do i = gamma->pick(); while (i >= num_outs); type = "gamma"; } } @@ -7850,6 +7891,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> picks[type].insert(i); req.outputs.push_back({amount, i}); ++num_found; + MDEBUG("picked " << i << ", " << num_found << " now picked"); } for (const auto &pick: picks) @@ -7884,7 +7926,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // get the keys for those req.get_txid = false; m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); + bool r = invoke_http_bin("/get_outs.bin", req, daemon_resp, 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"); @@ -8596,7 +8638,7 @@ bool wallet2::light_wallet_login(bool &new_address) // Always create account if it doesn't exist. request.create_account = true; m_daemon_rpc_mutex.lock(); - bool connected = epee::net_utils::invoke_http_json("/login", request, response, m_http_client, rpc_timeout, "POST"); + bool connected = invoke_http_json("/login", request, response, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); // MyMonero doesn't send any status message. OpenMonero does. m_light_wallet_connected = connected && (response.status.empty() || response.status == "success"); @@ -8621,7 +8663,7 @@ bool wallet2::light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLE oreq.address = get_account().get_public_address_str(m_nettype); oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/import_wallet_request", oreq, response, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/import_wallet_request", oreq, response, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "import_wallet_request"); @@ -8647,7 +8689,7 @@ void wallet2::light_wallet_get_unspent_outs() m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/get_unspent_outs", oreq, ores, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/get_unspent_outs", oreq, ores, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_unspent_outs"); THROW_WALLET_EXCEPTION_IF(ores.status == "error", error::wallet_internal_error, ores.reason); @@ -8792,7 +8834,7 @@ bool wallet2::light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO: request.address = get_account().get_public_address_str(m_nettype); request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/get_address_info", request, response, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/get_address_info", request, response, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_info"); // TODO: Validate result @@ -8809,7 +8851,7 @@ void wallet2::light_wallet_get_address_txs() ireq.address = get_account().get_public_address_str(m_nettype); ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/get_address_txs", ireq, ires, m_http_client, rpc_timeout, "POST"); + bool r = invoke_http_json("/get_address_txs", ireq, ires, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_txs"); //OpenMonero sends status=success, Mymonero doesn't. @@ -9348,9 +9390,16 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp 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"); + uint64_t min_output_value = m_min_output_value; + uint32_t min_output_count = m_min_output_count; + if (min_output_value == 0 && min_output_count == 0) + { + min_output_value = DEFAULT_MIN_OUTPUT_VALUE; + min_output_count = DEFAULT_MIN_OUTPUT_COUNT; + } + if (m_transfers[idx].amount() >= min_output_value) { + if (get_count_above(m_transfers, *unused_transfers_indices, min_output_value) < min_output_count) { + LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(min_output_value) << ", not adding"); break; } } @@ -9509,7 +9558,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp tx.ptx = test_ptx; tx.weight = get_transaction_weight(test_tx, txBlob.size()); tx.outs = outs; - tx.needed_fee = needed_fee; + tx.needed_fee = test_ptx.fee; accumulated_fee += test_ptx.fee; accumulated_change += test_ptx.change_dts.amount; adding_fee = false; @@ -9907,7 +9956,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton tx.ptx = test_ptx; tx.weight = get_transaction_weight(test_tx, txBlob.size()); tx.outs = outs; - tx.needed_fee = needed_fee; + tx.needed_fee = test_ptx.fee; accumulated_fee += test_ptx.fee; accumulated_change += test_ptx.change_dts.amount; if (!unused_transfers_indices.empty() || !unused_dust_indices.empty()) @@ -10113,7 +10162,7 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co req_t.max_count = 0; req_t.unlocked = unlocked; req_t.recent_cutoff = 0; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram"); THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); @@ -10151,7 +10200,7 @@ uint64_t wallet2::get_num_rct_outputs() req_t.max_count = 0; req_t.unlocked = true; req_t.recent_cutoff = 0; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, 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.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); @@ -10276,7 +10325,7 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); @@ -10319,8 +10368,8 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_json("/gettransactions", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); @@ -10369,8 +10418,8 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_json("/gettransactions", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); @@ -10431,8 +10480,8 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); @@ -10487,8 +10536,8 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_json("/gettransactions", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); @@ -10560,8 +10609,8 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; { - const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout); } THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); @@ -10659,7 +10708,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); @@ -10708,7 +10757,7 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); @@ -10863,7 +10912,7 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account req.decode_as_json = false; req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); @@ -11155,7 +11204,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr gettx_req.decode_as_json = false; gettx_req.prune = true; m_daemon_rpc_mutex.lock(); - bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + bool ok = invoke_http_json("/gettransactions", gettx_req, gettx_res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); @@ -11166,7 +11215,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr for (size_t i = 0; i < proofs.size(); ++i) kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image)); m_daemon_rpc_mutex.lock(); - ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + ok = invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), error::wallet_internal_error, "Failed to get key image spent status from daemon"); @@ -11718,7 +11767,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag { PERF_TIMER(import_key_images_RPC); 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); + bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, 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"); @@ -11804,7 +11853,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag PERF_TIMER_START(import_key_images_E); m_daemon_rpc_mutex.lock(); - bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout); + bool r = invoke_http_json("/gettransactions", gettxs_req, gettxs_res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); @@ -12032,14 +12081,15 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs() const +std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const { PERF_TIMER(export_outputs); std::vector<tools::wallet2::transfer_details> outs; size_t offset = 0; - while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request)) - ++offset; + if (!all) + while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request)) + ++offset; outs.reserve(m_transfers.size() - offset); for (size_t n = offset; n < m_transfers.size(); ++n) @@ -12052,13 +12102,13 @@ std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export return std::make_pair(offset, outs); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::export_outputs_to_str() const +std::string wallet2::export_outputs_to_str(bool all) const { PERF_TIMER(export_outputs_to_str); std::stringstream oss; boost::archive::portable_binary_oarchive ar(oss); - const auto& outputs = export_outputs(); + const auto& outputs = export_outputs(all); ar << outputs; std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); @@ -12582,8 +12632,7 @@ std::string wallet2::make_uri(const std::string &address, const std::string &pay 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)) + if (!wallet2::parse_long_payment_id(payment_id, pid32)) { error = "Invalid payment id"; return std::string(); @@ -12677,8 +12726,7 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin 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)) + if (!wallet2::parse_long_payment_id(kv[1], hash)) { error = "Invalid payment id: " + kv[1]; return false; @@ -12739,7 +12787,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui height_mid, height_max }; - bool r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_bin("/getblocks_by_height.bin", req, res, rpc_timeout); if (!r || res.status != CORE_RPC_STATUS_OK) { std::ostringstream oss; @@ -12809,7 +12857,7 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response res = AUTO_VAL_INIT(res); m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, m_http_client, rpc_timeout); + bool r = invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "Failed to connect to daemon"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_txpool_backlog"); @@ -12879,7 +12927,7 @@ uint64_t wallet2::get_segregation_fork_height() const if (m_segregation_height > 0) return m_segregation_height; - if (m_use_dns) + if (m_use_dns && !m_offline) { // All four MoneroPulse domains have DNSSEC on and valid static const std::vector<std::string> dns_urls = { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 39380c9df..921c150cb 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -39,6 +39,7 @@ #include <boost/serialization/deque.hpp> #include <boost/thread/lock_guard.hpp> #include <atomic> +#include <random> #include "include_base_utils.h" #include "cryptonote_basic/account.h" @@ -76,6 +77,30 @@ namespace tools class wallet2; class Notify; + class gamma_picker + { + public: + uint64_t pick(); + gamma_picker(const std::vector<uint64_t> &rct_offsets); + gamma_picker(const std::vector<uint64_t> &rct_offsets, double shape, double scale); + + private: + struct gamma_engine + { + typedef uint64_t result_type; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return std::numeric_limits<result_type>::max(); } + result_type operator()() { return crypto::rand<result_type>(); } + } engine; + +private: + std::gamma_distribution<double> gamma; + const std::vector<uint64_t> &rct_offsets; + const uint64_t *begin, *end; + uint64_t num_rct_outputs; + double average_output_time; + }; + class wallet_keys_unlocker { public: @@ -93,7 +118,7 @@ namespace tools public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} 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, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} @@ -1139,8 +1164,8 @@ namespace tools bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<size_t, std::vector<tools::wallet2::transfer_details>> export_outputs() const; - std::string export_outputs_to_str() const; + std::pair<size_t, std::vector<tools::wallet2::transfer_details>> export_outputs(bool all = false) const; + std::string export_outputs_to_str(bool all = false) const; size_t import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; @@ -1232,19 +1257,22 @@ namespace tools template<class t_request, class t_response> inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") { - boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + if (m_offline) return false; + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); return epee::net_utils::invoke_http_json(uri, req, res, m_http_client, timeout, http_method); } template<class t_request, class t_response> inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") { - boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + if (m_offline) return false; + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); return epee::net_utils::invoke_http_bin(uri, req, res, m_http_client, timeout, http_method); } template<class t_request, class t_response> inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0") { - boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + if (m_offline) return false; + boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id); } @@ -1291,6 +1319,7 @@ namespace tools uint64_t hash_m_transfers(int64_t transfer_height, crypto::hash &hash) const; void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash); void enable_dns(bool enable) { m_use_dns = enable; } + void set_offline(bool offline = true); private: /*! @@ -1422,7 +1451,7 @@ namespace tools std::atomic<bool> m_run; - boost::mutex m_daemon_rpc_mutex; + boost::recursive_mutex m_daemon_rpc_mutex; bool m_trusted_daemon; i_wallet2_callback* m_callback; @@ -1474,6 +1503,7 @@ namespace tools std::string m_device_derivation_path; uint64_t m_device_last_key_image_sync; bool m_use_dns; + bool m_offline; // Aux transaction data from device std::unordered_map<crypto::hash, std::string> m_tx_device; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 71c64d3c1..0e2a34470 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -31,6 +31,7 @@ #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> #include <boost/algorithm/string.hpp> +#include <boost/preprocessor/stringize.hpp> #include <cstdint> #include "include_base_utils.h" using namespace epee; @@ -65,11 +66,6 @@ namespace const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; - const command_line::arg_descriptor<std::string> arg_rpc_ssl = {"rpc-ssl", tools::wallet2::tr("Enable SSL on wallet RPC connections: enabled|disabled|autodetect"), "autodetect"}; - const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; - const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; - const command_line::arg_descriptor<std::string> arg_rpc_ssl_ca_certificates = {"rpc-ssl-ca-certificates", tools::wallet2::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s).")}; - const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints = {"rpc-ssl-allowed-fingerprints", tools::wallet2::tr("List of certificate fingerprints to allow")}; constexpr const char default_rpc_username[] = "monero"; @@ -86,12 +82,10 @@ namespace void set_confirmations(tools::wallet_rpc::transfer_entry &entry, uint64_t blockchain_height, uint64_t block_reward) { if (entry.height >= blockchain_height || (entry.height == 0 && (!strcmp(entry.type.c_str(), "pending") || !strcmp(entry.type.c_str(), "pool")))) - { entry.confirmations = 0; - entry.suggested_confirmations_threshold = 0; - return; - } - entry.confirmations = blockchain_height - entry.height; + else + entry.confirmations = blockchain_height - entry.height; + if (block_reward == 0) entry.suggested_confirmations_threshold = 0; else @@ -245,37 +239,6 @@ namespace tools assert(bool(http_login)); } // end auth enabled - auto rpc_ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); - auto rpc_ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); - auto rpc_ssl_ca_file = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); - auto rpc_ssl_allowed_fingerprints = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); - auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl); - epee::net_utils::ssl_options_t rpc_ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; - - if (!rpc_ssl_ca_file.empty() || !rpc_ssl_allowed_fingerprints.empty()) - { - std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() }; - std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); - - rpc_ssl_options = epee::net_utils::ssl_options_t{ - std::move(allowed_fingerprints), std::move(rpc_ssl_ca_file) - }; - } - - // user specified CA file or fingeprints implies enabled SSL by default - if (rpc_ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) - { - if (!epee::net_utils::ssl_support_from_string(rpc_ssl_options.support, rpc_ssl)) - { - MERROR("Invalid argument for " << std::string(arg_rpc_ssl.name)); - return false; - } - } - - rpc_ssl_options.auth = epee::net_utils::ssl_authentication_t{ - std::move(rpc_ssl_private_key), std::move(rpc_ssl_certificate) - }; - m_auto_refresh_period = DEFAULT_AUTO_REFRESH_PERIOD; m_last_auto_refresh_time = boost::posix_time::min_date_time; @@ -285,7 +248,7 @@ namespace tools auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), - std::move(rpc_ssl_options) + std::move(rpc_config->ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -387,7 +350,7 @@ namespace tools 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->nettype(), d.is_subaddress, d.addr); + td.address = d.original.empty() ? get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) : d.original; } entry.type = "out"; @@ -412,6 +375,14 @@ namespace tools entry.amount = pd.m_amount_in - pd.m_change - entry.fee; entry.unlock_time = pd.m_tx.unlock_time; 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 = d.original.empty() ? get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) : d.original; + } + entry.type = is_failed ? "failed" : "pending"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; for (uint32_t i: pd.m_subaddr_indices) @@ -1774,6 +1745,11 @@ namespace tools else if (payment_id_str.size() == 2 * sizeof(payment_id8)) { r = epee::string_tools::hex_to_pod(payment_id_str, payment_id8); + if (r) + { + memcpy(payment_id.data, payment_id8.data, 8); + memset(payment_id.data + 8, 0, 24); + } } else { @@ -1842,11 +1818,7 @@ namespace tools { if (req.account_index != td.m_subaddr_index.major || (!req.subaddr_indices.empty() && req.subaddr_indices.count(td.m_subaddr_index.minor) == 0)) continue; - if (!transfers_found) - { - transfers_found = true; - } - auto txBlob = t_serializable_object_to_blob(td.m_tx); + transfers_found = true; wallet_rpc::transfer_details rpc_transfers; rpc_transfers.amount = td.amount(); rpc_transfers.spent = td.m_spent; @@ -1854,6 +1826,9 @@ namespace tools rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.subaddr_index = {td.m_subaddr_index.major, td.m_subaddr_index.minor}; rpc_transfers.key_image = td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : ""; + rpc_transfers.block_height = td.m_block_height; + rpc_transfers.frozen = td.m_frozen; + rpc_transfers.unlocked = m_wallet->is_transfer_unlocked(td); res.transfers.push_back(rpc_transfers); } } @@ -2581,7 +2556,7 @@ namespace tools try { - res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str()); + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all)); } catch (const std::exception &e) { @@ -2808,20 +2783,20 @@ namespace tools } 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, info.payment_id)) { 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"; + er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string"; return false; } else { - memcpy(payment_id.data, info.payment_id.data, 8); - memset(payment_id.data + 8, 0, 24); + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address"; + return false; } } } @@ -3111,6 +3086,18 @@ namespace tools er.message = "Invalid filename"; return false; } + if (m_wallet && req.autosave_current) + { + try + { + m_wallet->store(); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + } std::string wallet_file = m_wallet_dir + "/" + req.filename; { po::options_description desc("dummy"); @@ -3141,18 +3128,7 @@ namespace tools } if (m_wallet) - { - try - { - m_wallet->store(); - } - catch (const std::exception& e) - { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); - return false; - } delete m_wallet; - } m_wallet = wal.release(); return true; } @@ -3161,14 +3137,17 @@ namespace tools { if (!m_wallet) return not_open(er); - try + if (req.autosave_current) { - m_wallet->store(); - } - catch (const std::exception& e) - { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); - return false; + try + { + m_wallet->store(); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } } delete m_wallet; m_wallet = NULL; @@ -3385,6 +3364,20 @@ namespace tools return false; } + if (m_wallet && req.autosave_current) + { + try + { + if (!wallet_file.empty()) + m_wallet->store(); + } + catch (const std::exception &e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + } + try { if (!req.spendkey.empty()) @@ -3433,19 +3426,7 @@ namespace tools } if (m_wallet) - { - try - { - if (!wallet_file.empty()) - m_wallet->store(); - } - catch (const std::exception &e) - { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); - return false; - } delete m_wallet; - } m_wallet = wal.release(); res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); return true; @@ -3511,6 +3492,18 @@ namespace tools return false; } } + if (m_wallet && req.autosave_current) + { + try + { + m_wallet->store(); + } + catch (const std::exception &e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + } // process seed_offset if given { @@ -3621,18 +3614,7 @@ namespace tools } if (m_wallet) - { - try - { - m_wallet->store(); - } - catch (const std::exception &e) - { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); - return false; - } delete m_wallet; - } m_wallet = wal.release(); res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); res.info = "Wallet has been restored successfully."; @@ -4065,9 +4047,10 @@ namespace tools { cryptonote::TESTNET, "testnet" }, { cryptonote::STAGENET, "stagenet" }, }; + if (!req.any_net_type && !m_wallet) return not_open(er); for (const auto &net_type: net_types) { - if (!req.any_net_type && net_type.type != m_wallet->nettype()) + if (!req.any_net_type && (!m_wallet || net_type.type != m_wallet->nettype())) continue; if (req.allow_openalias) { @@ -4145,10 +4128,15 @@ namespace tools std::move(req.ssl_private_key_path), std::move(req.ssl_certificate_path) }; - if (ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled && !ssl_options.has_strong_verification(boost::string_ref{})) + const bool verification_required = + ssl_options.verification != epee::net_utils::ssl_verification_t::none && + ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled; + + if (verification_required && !ssl_options.has_strong_verification(boost::string_ref{})) { er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; er.message = "SSL is enabled but no user certificate or fingerprints were provided"; + return false; } if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) @@ -4160,6 +4148,39 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_set_log_level(const wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + if (req.level < 0 || req.level > 4) + { + er.code = WALLET_RPC_ERROR_CODE_INVALID_LOG_LEVEL; + er.message = "Error: log level not valid"; + return false; + } + mlog_set_log_level(req.level); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + mlog_set_log(req.categories.c_str()); + res.categories = mlog_get_categories(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx) { res.version = WALLET_RPC_VERSION; @@ -4356,11 +4377,6 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_from_json); command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - command_line::add_arg(desc_params, arg_rpc_ssl); - command_line::add_arg(desc_params, arg_rpc_ssl_private_key); - command_line::add_arg(desc_params, arg_rpc_ssl_certificate); - command_line::add_arg(desc_params, arg_rpc_ssl_ca_certificates); - command_line::add_arg(desc_params, arg_rpc_ssl_allowed_fingerprints); daemonizer::init_options(hidden_options, desc_params); desc_params.add(hidden_options); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 7d2272dd0..a327ed908 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -151,6 +151,8 @@ namespace tools MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) MAP_JON_RPC_WE("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS) MAP_JON_RPC_WE("set_daemon", on_set_daemon, wallet_rpc::COMMAND_RPC_SET_DAEMON) + MAP_JON_RPC_WE("set_log_level", on_set_log_level, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL) + MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES) MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -234,6 +236,8 @@ namespace tools bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_set_log_level(const wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); //json rpc v2 diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index bb360ae01..4504ac752 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 9 +#define WALLET_RPC_VERSION_MINOR 13 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -982,6 +982,9 @@ namespace wallet_rpc std::string tx_hash; cryptonote::subaddress_index subaddr_index; std::string key_image; + uint64_t block_height; + bool frozen; + bool unlocked; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount) @@ -990,6 +993,9 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(subaddr_index) KV_SERIALIZE(key_image) + KV_SERIALIZE(block_height) + KV_SERIALIZE(frozen) + KV_SERIALIZE(unlocked) END_KV_SERIALIZE_MAP() }; @@ -1626,7 +1632,10 @@ namespace wallet_rpc { struct request_t { + bool all; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(all) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2043,10 +2052,12 @@ namespace wallet_rpc { std::string filename; std::string password; + bool autosave_current; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(filename) KV_SERIALIZE(password) + KV_SERIALIZE_OPT(autosave_current, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2063,7 +2074,10 @@ namespace wallet_rpc { struct request_t { + bool autosave_current; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(autosave_current, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2108,6 +2122,7 @@ namespace wallet_rpc std::string spendkey; std::string viewkey; std::string password; + bool autosave_current; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_OPT(restore_height, (uint64_t)0) @@ -2116,6 +2131,7 @@ namespace wallet_rpc KV_SERIALIZE(spendkey) KV_SERIALIZE(viewkey) KV_SERIALIZE(password) + KV_SERIALIZE_OPT(autosave_current, true) END_KV_SERIALIZE_MAP() }; @@ -2141,6 +2157,7 @@ namespace wallet_rpc std::string seed_offset; std::string password; std::string language; + bool autosave_current; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_OPT(restore_height, (uint64_t)0) @@ -2149,6 +2166,7 @@ namespace wallet_rpc KV_SERIALIZE(seed_offset) KV_SERIALIZE(password) KV_SERIALIZE(language) + KV_SERIALIZE_OPT(autosave_current, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2477,5 +2495,48 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_SET_LOG_LEVEL + { + struct request_t + { + int8_t level; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(level) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_SET_LOG_CATEGORIES + { + struct request_t + { + std::string categories; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(categories) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string categories; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(categories) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 440a58a47..011d146d4 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -74,3 +74,4 @@ #define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41 #define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42 #define WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC -43 +#define WALLET_RPC_ERROR_CODE_INVALID_LOG_LEVEL -44 |