diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 60 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.h | 4 | ||||
-rw-r--r-- | src/rpc/core_rpc_server_commands_defs.h | 15 | ||||
-rw-r--r-- | src/rpc/daemon_handler.cpp | 22 | ||||
-rw-r--r-- | src/rpc/rpc_args.cpp | 3 | ||||
-rw-r--r-- | src/rpc/rpc_args.h | 2 | ||||
-rw-r--r-- | src/rpc/rpc_payment.cpp | 4 | ||||
-rw-r--r-- | src/rpc/rpc_version_str.cpp | 55 | ||||
-rw-r--r-- | src/rpc/rpc_version_str.h | 43 |
10 files changed, 169 insertions, 41 deletions
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index ebb1e767f..65d88b57e 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -37,6 +37,7 @@ set(rpc_sources bootstrap_daemon.cpp core_rpc_server.cpp rpc_payment.cpp + rpc_version_str.cpp instanciations) set(daemon_messages_sources @@ -54,6 +55,7 @@ set(rpc_base_headers rpc_handler.h) set(rpc_headers + rpc_version_str.h rpc_handler.h) set(daemon_rpc_server_headers) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9117b5b3a..e3415c65f 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include <boost/preprocessor/stringize.hpp> +#include <boost/uuid/nil_generator.hpp> #include "include_base_utils.h" #include "string_tools.h" using namespace epee; @@ -86,10 +87,14 @@ namespace RPCTracker(const char *rpc, tools::LoggingPerformanceTimer &timer): rpc(rpc), timer(timer) { } ~RPCTracker() { - boost::unique_lock<boost::mutex> lock(mutex); - auto &e = tracker[rpc]; - ++e.count; - e.time += timer.value(); + try + { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + ++e.count; + e.time += timer.value(); + } + catch (...) { /* ignore */ } } void pay(uint64_t amount) { boost::unique_lock<boost::mutex> lock(mutex); @@ -152,6 +157,7 @@ namespace cryptonote : m_core(cr) , m_p2p(p2p) , m_was_bootstrap_ever_used(false) + , disable_rpc_ban(false) {} //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const std::string &username_password) @@ -231,6 +237,7 @@ namespace cryptonote const boost::program_options::variables_map& vm , const bool restricted , const std::string& port + , bool allow_rpc_payment ) { m_restricted = restricted; @@ -241,8 +248,9 @@ namespace cryptonote if (!rpc_config) return false; + disable_rpc_ban = rpc_config->disable_rpc_ban; std::string address = command_line::get_arg(vm, arg_rpc_payment_address); - if (!address.empty()) + if (!address.empty() && allow_rpc_payment) { if (!m_restricted && nettype() != FAKECHAIN) { @@ -353,7 +361,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::add_host_fail(const connection_context *ctx, unsigned int score) { - if(!ctx || !ctx->m_remote_address.is_blockable()) + if(!ctx || !ctx->m_remote_address.is_blockable() || disable_rpc_ban) return false; CRITICAL_REGION_LOCAL(m_host_fails_score_lock); @@ -953,18 +961,21 @@ namespace cryptonote { e.double_spend_seen = it->second.double_spend_seen; e.relayed = it->second.relayed; + e.received_timestamp = it->second.receive_time; } else { MERROR("Failed to determine pool info for " << tx_hash); e.double_spend_seen = false; e.relayed = false; + e.received_timestamp = 0; } } else { 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.received_timestamp = 0; e.double_spend_seen = false; e.relayed = false; } @@ -1095,9 +1106,8 @@ namespace cryptonote } 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, crypto::null_hash}, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) + tx_verification_context tvc{}; + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (req.do_not_relay ? relay_method::none : relay_method::local), false) || tvc.m_verifivation_failed) { res.status = "Failed"; std::string reason = ""; @@ -1142,7 +1152,7 @@ namespace cryptonote NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = CORE_RPC_STATUS_OK; return true; @@ -1958,7 +1968,11 @@ namespace cryptonote m_was_bootstrap_ever_used = true; } - r = r && res.status == CORE_RPC_STATUS_OK; + if (r && res.status != CORE_RPC_STATUS_PAYMENT_REQUIRED && res.status != CORE_RPC_STATUS_OK) + { + MINFO("Failing RPC " << command_name << " due to peer return status " << res.status); + r = false; + } res.untrusted = true; return true; } @@ -2222,8 +2236,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - on_get_info(req, res, ctx); - if (res.status != CORE_RPC_STATUS_OK) + if (!on_get_info(req, res, ctx) || res.status != CORE_RPC_STATUS_OK) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = res.status; @@ -2370,7 +2383,7 @@ namespace cryptonote if (req.txids.empty()) { std::vector<transaction> pool_txs; - bool r = m_core.get_pool_transactions(pool_txs); + bool r = m_core.get_pool_transactions(pool_txs, true); if (!r) { res.status = "Failed to get txpool contents"; @@ -2617,6 +2630,7 @@ namespace cryptonote { RPC_TRACKER(update); + res.update = false; if (m_core.offline()) { res.status = "Daemon is running offline"; @@ -2747,13 +2761,11 @@ namespace cryptonote crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); cryptonote::blobdata txblob; - bool r = m_core.get_pool_transaction(txid, txblob); - if (r) + if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy)) { - cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(txblob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes } else @@ -2940,7 +2952,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_info); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON, "rpc_access_info", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON_RPC, "rpc_access_info", req, res, r)) return r; // if RPC payment is not enabled @@ -3004,6 +3016,8 @@ namespace cryptonote RPC_TRACKER(flush_cache); if (req.bad_txs) m_core.flush_bad_txs_cache(); + if (req.bad_blocks) + m_core.flush_invalid_blocks(); res.status = CORE_RPC_STATUS_OK; return true; } @@ -3012,7 +3026,7 @@ namespace cryptonote { RPC_TRACKER(rpc_access_submit_nonce); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON, "rpc_access_submit_nonce", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON_RPC, "rpc_access_submit_nonce", req, res, r)) return r; // if RPC payment is not enabled @@ -3071,7 +3085,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_pay); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON, "rpc_access_pay", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON_RPC, "rpc_access_pay", req, res, r)) return r; // if RPC payment is not enabled @@ -3130,7 +3144,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_data); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON, "rpc_access_data", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON_RPC, "rpc_access_data", req, res, r)) return r; if (!m_rpc_payment) @@ -3158,7 +3172,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_account); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON, "rpc_access_account", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON_RPC, "rpc_access_account", req, res, r)) return r; if (!m_rpc_payment) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 23c611470..3b8e9c20a 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -88,7 +88,8 @@ namespace cryptonote bool init( const boost::program_options::variables_map& vm, const bool restricted, - const std::string& port + const std::string& port, + bool allow_rpc_payment ); network_type nettype() const { return m_core.get_nettype(); } @@ -285,6 +286,7 @@ private: epee::critical_section m_host_fails_score_lock; std::map<std::string, uint64_t> m_host_fails_score; std::unique_ptr<rpc_payment> m_rpc_payment; + bool disable_rpc_ban; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 855ea854c..89e0a6726 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,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 3 -#define CORE_RPC_VERSION_MINOR 0 +#define CORE_RPC_VERSION_MINOR 1 #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) @@ -351,6 +351,7 @@ namespace cryptonote bool double_spend_seen; uint64_t block_height; uint64_t block_timestamp; + uint64_t received_timestamp; std::vector<uint64_t> output_indices; bool relayed; @@ -372,6 +373,7 @@ namespace cryptonote else { KV_SERIALIZE(relayed) + KV_SERIALIZE(received_timestamp) } END_KV_SERIALIZE_MAP() }; @@ -2556,22 +2558,23 @@ namespace cryptonote struct COMMAND_RPC_FLUSH_CACHE { - struct request_t + struct request_t: public rpc_request_base { bool bad_txs; + bool bad_blocks; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(bad_txs, false) + KV_SERIALIZE_OPT(bad_blocks, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index d7e081af3..24800ff20 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -28,6 +28,7 @@ #include "daemon_handler.h" +#include <boost/uuid/nil_generator.hpp> // likely included by daemon_handler.h's includes, // but including here for clarity #include "cryptonote_core/cryptonote_core.h" @@ -288,10 +289,9 @@ namespace rpc return; } - 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, crypto::null_hash}, tvc, false, false, !relay) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (relay ? relay_method::local : relay_method::none), false) || tvc.m_verifivation_failed) { if (tvc.m_verifivation_failed) { @@ -311,42 +311,42 @@ namespace rpc if (tvc.m_double_spend) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "double spend"; + res.error_details += "double spend"; } if (tvc.m_invalid_input) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "invalid input"; + res.error_details += "invalid input"; } if (tvc.m_invalid_output) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "invalid output"; + res.error_details += "invalid output"; } if (tvc.m_too_big) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "too big"; + res.error_details += "too big"; } if (tvc.m_overspend) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "overspend"; + res.error_details += "overspend"; } if (tvc.m_fee_too_low) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "fee too low"; + res.error_details += "fee too low"; } if (tvc.m_not_rct) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "tx is not ringct"; + res.error_details += "tx is not ringct"; } if (tvc.m_too_few_outputs) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "too few outputs"; + res.error_details += "too few outputs"; } if (res.error_details.empty()) { @@ -368,7 +368,7 @@ namespace rpc NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = Message::STATUS_OK; diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 0eaa0ef0e..dcb804d3e 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -103,6 +103,7 @@ namespace cryptonote , 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}) + , disable_rpc_ban({"disable-rpc-ban", rpc_args::tr("Do not ban hosts on RPC errors"), false, false}) {} const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); } @@ -123,6 +124,7 @@ namespace cryptonote 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); + command_line::add_arg(desc, arg.disable_rpc_ban); if (any_cert_option) command_line::add_arg(desc, arg.rpc_ssl_allow_any_cert); } @@ -136,6 +138,7 @@ namespace cryptonote config.bind_ipv6_address = command_line::get_arg(vm, arg.rpc_bind_ipv6_address); config.use_ipv6 = command_line::get_arg(vm, arg.rpc_use_ipv6); config.require_ipv4 = !command_line::get_arg(vm, arg.rpc_ignore_ipv4); + config.disable_rpc_ban = command_line::get_arg(vm, arg.disable_rpc_ban); if (!config.bind_ip.empty()) { // always parse IP here for error consistency diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index bdb9c70d5..ac6eb2744 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -65,6 +65,7 @@ namespace cryptonote 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; + const command_line::arg_descriptor<bool> disable_rpc_ban; }; // `allow_any_cert` bool toggles `--rpc-ssl-allow-any-cert` configuration @@ -85,5 +86,6 @@ namespace cryptonote 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; + bool disable_rpc_ban = false; }; } diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index 0637db728..b363c27b2 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -59,6 +59,10 @@ namespace cryptonote { rpc_payment::client_info::client_info(): + previous_seed_height(0), + seed_height(0), + previous_seed_hash(crypto::null_hash), + seed_hash(crypto::null_hash), cookie(0), top(crypto::null_hash), previous_top(crypto::null_hash), diff --git a/src/rpc/rpc_version_str.cpp b/src/rpc/rpc_version_str.cpp new file mode 100644 index 000000000..c60cf4891 --- /dev/null +++ b/src/rpc/rpc_version_str.cpp @@ -0,0 +1,55 @@ +// 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 "rpc_version_str.h" +#include "version.h" +#include <regex> + +namespace cryptonote +{ + +namespace rpc +{ + +// Expected format of Monero software version string: +// 1) Four numbers, one to two digits each, separated by periods +// 2) Optionally, one of the following suffixes: +// a) -release +// b) -<hash> where <hash> is exactly nine lowercase hex digits + +bool is_version_string_valid(const std::string& str) +{ + return std::regex_match(str, std::regex( + "^\\d{1,2}(\\.\\d{1,2}){3}(-(release|[0-9a-f]{9}))?$", + std::regex_constants::nosubs + )); +} + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/rpc_version_str.h b/src/rpc/rpc_version_str.h new file mode 100644 index 000000000..930c807d2 --- /dev/null +++ b/src/rpc/rpc_version_str.h @@ -0,0 +1,43 @@ +// 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 + +#include <string> + +namespace cryptonote +{ + +namespace rpc +{ + +bool is_version_string_valid(const std::string& str); + +} // namespace rpc + +} // namespace cryptonote |