diff options
-rw-r--r-- | contrib/epee/include/net/net_fwd.h | 38 | ||||
-rw-r--r-- | src/blockchain_db/blockchain_db.h | 15 | ||||
-rw-r--r-- | src/blockchain_db/testdb.h | 1 | ||||
-rw-r--r-- | src/common/rpc_client.h | 4 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 34 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 3 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 8 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 18 | ||||
-rw-r--r-- | src/daemon/command_parser_executor.cpp | 3 | ||||
-rw-r--r-- | src/daemon/command_parser_executor.h | 2 | ||||
-rw-r--r-- | src/daemon/command_server.cpp | 3 | ||||
-rw-r--r-- | src/daemon/command_server.h | 2 | ||||
-rw-r--r-- | src/daemon/daemon.cpp | 3 | ||||
-rw-r--r-- | src/daemon/main.cpp | 6 | ||||
-rw-r--r-- | src/daemon/rpc_command_executor.cpp | 3 | ||||
-rw-r--r-- | src/daemon/rpc_command_executor.h | 2 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 83 | ||||
-rw-r--r-- | src/rpc/rpc_args.cpp | 82 | ||||
-rw-r--r-- | src/rpc/rpc_args.h | 18 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 5 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 57 | ||||
-rwxr-xr-x | tests/functional_tests/get_output_distribution.py | 11 | ||||
-rw-r--r-- | tests/unit_tests/output_selection.cpp | 2 |
23 files changed, 243 insertions, 160 deletions
diff --git a/contrib/epee/include/net/net_fwd.h b/contrib/epee/include/net/net_fwd.h new file mode 100644 index 000000000..ba4fe6259 --- /dev/null +++ b/contrib/epee/include/net/net_fwd.h @@ -0,0 +1,38 @@ +// 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. + +#pragma once + +namespace epee +{ + namespace net_utils + { + struct ssl_authentication_t; + class ssl_options_t; + } +} diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 567be6a65..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 diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 6c97713d5..34e635899 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -54,6 +54,7 @@ public: 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 {} 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/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 39c9f8695..3841aa1cf 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -182,7 +182,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : 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__); } @@ -619,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(); @@ -637,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 @@ -1373,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; @@ -3844,6 +3846,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; @@ -3852,6 +3855,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; @@ -4161,7 +4165,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) @@ -4385,6 +4392,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()) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 6200ec87e..32ed96b5b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1102,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; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index be1ea5a17..55e1b287f 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -675,8 +675,14 @@ namespace cryptonote { // 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()) + { 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(); 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/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 17b945c9a..0b452800e 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) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 098018642..2efd78ec0 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 ); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 69ad6ff10..f665eec9c 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) { 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 dbbb2308c..690d4d60e 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -324,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 151baa33f..cca0f75f9 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 { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 3c2686b3f..df2894d09 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 ); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5777370fc..5ca5f3fe7 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -90,15 +90,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( @@ -118,7 +112,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; @@ -151,46 +145,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); - for (const auto &fpr: ssl_allowed_fingerprints) - { - if (fpr.size() != SSL_FINGERPRINT_SIZE) - { - MERROR("SHA-256 fingerprint should be " BOOST_PP_STRINGIZE(SSL_FINGERPRINT_SIZE) " bytes long."); - return false; - } - } - - 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) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -2439,40 +2396,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/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/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c87947e39..8f3f30da1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -399,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), diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 16a2b3808..8b8d832dc 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -66,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"; @@ -244,45 +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); - 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 false; - } - } - - 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; @@ -292,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) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -4167,7 +4123,11 @@ 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"; @@ -4412,11 +4372,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/tests/functional_tests/get_output_distribution.py b/tests/functional_tests/get_output_distribution.py index 81c606465..93822e90a 100755 --- a/tests/functional_tests/get_output_distribution.py +++ b/tests/functional_tests/get_output_distribution.py @@ -212,5 +212,14 @@ class GetOutputDistributionTest(): assert d.distribution[h] == 0 +class Guard: + def __enter__(self): + for i in range(4): + Wallet(idx = i).auto_refresh(False) + def __exit__(self, exc_type, exc_value, traceback): + for i in range(4): + Wallet(idx = i).auto_refresh(True) + if __name__ == '__main__': - GetOutputDistributionTest().run_test() + with Guard() as guard: + GetOutputDistributionTest().run_test() diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index 0094fc765..235b1c809 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -172,7 +172,7 @@ TEST(select_outputs, density) float chain_ratio = count_chain / (float)n_outs; MDEBUG(count_selected << "/" << NPICKS << " outputs selected in blocks of density " << d << ", " << 100.0f * selected_ratio << "%"); MDEBUG(count_chain << "/" << offsets.size() << " outputs in blocks of density " << d << ", " << 100.0f * chain_ratio << "%"); - ASSERT_LT(fabsf(selected_ratio - chain_ratio), 0.02f); + ASSERT_LT(fabsf(selected_ratio - chain_ratio), 0.025f); } } |