diff options
Diffstat (limited to 'src')
80 files changed, 6563 insertions, 1170 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e473c2984..3e0bfb59c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,9 @@ function (monero_add_library name) PRIVATE $<TARGET_PROPERTY:${name},INTERFACE_COMPILE_DEFINITIONS>) endfunction () +set_source_files_properties(${CMAKE_BINARY_DIR}/version.cpp PROPERTIES GENERATED ON) +monero_add_library(version ${CMAKE_BINARY_DIR}/version.cpp) + add_subdirectory(common) add_subdirectory(crypto) add_subdirectory(ringct) @@ -107,6 +110,7 @@ endif() add_subdirectory(mnemonics) if(NOT IOS) add_subdirectory(rpc) + add_subdirectory(serialization) endif() add_subdirectory(wallet) if(NOT IOS) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 4fd0f9780..985244f6b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2426,8 +2426,8 @@ bool BlockchainLMDB::for_blocks_range(const uint64_t& h1, const uint64_t& h2, st MDB_cursor_op op; if (h1) { - MDB_val_set(k, h1); - op = MDB_SET; + k = MDB_val{sizeof(h1), (void*)&h1}; + op = MDB_SET; } else { op = MDB_FIRST; diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index ffdaad4af..0eaf71084 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -77,6 +77,7 @@ target_link_libraries(blockchain_import cryptonote_core blockchain_db p2p + version epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} @@ -89,8 +90,6 @@ if(ARCH_WIDTH) PUBLIC -DARCH_WIDTH=${ARCH_WIDTH}) endif() -add_dependencies(blockchain_import - version) set_property(TARGET blockchain_import PROPERTY OUTPUT_NAME "monero-blockchain-import") @@ -104,6 +103,7 @@ target_link_libraries(blockchain_export cryptonote_core blockchain_db p2p + version epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} @@ -111,8 +111,6 @@ target_link_libraries(blockchain_export ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -add_dependencies(blockchain_export - version) set_property(TARGET blockchain_export PROPERTY OUTPUT_NAME "monero-blockchain-export") diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 55b8ad3e6..50887e35c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -37,8 +37,7 @@ set(common_sources i18n.cpp password.cpp perf_timer.cpp - task_region.cpp - thread_group.cpp + threadpool.cpp updates.cpp) if (STACK_TRACE) @@ -48,6 +47,7 @@ endif() set(common_headers) set(common_private_headers + apply_permutation.h base58.h boost_serialization_helper.h command_line.h @@ -66,8 +66,7 @@ set(common_private_headers password.h perf_timer.h stack_trace.h - task_region.h - thread_group.h + threadpool.h updates.h) monero_private_headers(common diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h new file mode 100644 index 000000000..4fd952686 --- /dev/null +++ b/src/common/apply_permutation.h @@ -0,0 +1,68 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Most of this file is originally copyright (c) 2017 Raymond Chen, Microsoft +// This algorithm is adapted from Raymond Chen's code: +// https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 + +#include <vector> +#include <functional> +#include "misc_log_ex.h" + +namespace tools +{ + +template<typename F> +void apply_permutation(std::vector<size_t> permutation, const F &swap) +{ + //sanity check + for (size_t n = 0; n < permutation.size(); ++n) + CHECK_AND_ASSERT_THROW_MES(std::find(permutation.begin(), permutation.end(), n) != permutation.end(), "Bad permutation"); + + for (size_t i = 0; i < permutation.size(); ++i) + { + size_t current = i; + while (i != permutation[current]) + { + size_t next = permutation[current]; + swap(current, next); + permutation[current] = current; + current = next; + } + permutation[current] = current; + } +} + +template<typename T> +void apply_permutation(const std::vector<size_t> &permutation, std::vector<T> &v) +{ + CHECK_AND_ASSERT_THROW_MES(permutation.size() == v.size(), "Mismatched vector sizes"); + apply_permutation(permutation, [&v](size_t i0, size_t i1){ std::swap(v[i0], v[i1]); }); +} + +} diff --git a/src/common/base58.cpp b/src/common/base58.cpp index 64cb7c0de..941373443 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -111,13 +111,13 @@ namespace tools uint64_t res = 0; switch (9 - size) { - case 1: res |= *data++; - case 2: res <<= 8; res |= *data++; - case 3: res <<= 8; res |= *data++; - case 4: res <<= 8; res |= *data++; - case 5: res <<= 8; res |= *data++; - case 6: res <<= 8; res |= *data++; - case 7: res <<= 8; res |= *data++; + case 1: res |= *data++; /* FALLTHRU */ + case 2: res <<= 8; res |= *data++; /* FALLTHRU */ + case 3: res <<= 8; res |= *data++; /* FALLTHRU */ + case 4: res <<= 8; res |= *data++; /* FALLTHRU */ + case 5: res <<= 8; res |= *data++; /* FALLTHRU */ + case 6: res <<= 8; res |= *data++; /* FALLTHRU */ + case 7: res <<= 8; res |= *data++; /* FALLTHRU */ case 8: res <<= 8; res |= *data; break; default: assert(false); } diff --git a/src/common/common_fwd.h b/src/common/common_fwd.h index 5d67251b1..f33e185b5 100644 --- a/src/common/common_fwd.h +++ b/src/common/common_fwd.h @@ -36,6 +36,5 @@ namespace tools struct login; class password_container; class t_http_connection; - class task_region; - class thread_group; + class threadpool; } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index e7ff11c5c..9c306505e 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -27,8 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "common/dns_utils.h" -#include "common/i18n.h" -#include "cryptonote_basic/cryptonote_basic_impl.h" // check local first (in the event of static or in-source compilation of libunbound) #include "unbound.h" @@ -326,8 +324,6 @@ bool DNSResolver::check_address_syntax(const char *addr) const namespace dns_utils { -const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); } - //----------------------------------------------------------------------- // TODO: parse the string in a less stupid way, probably with regex std::string address_from_txt_record(const std::string& s) diff --git a/src/common/sfinae_helpers.h b/src/common/sfinae_helpers.h new file mode 100644 index 000000000..ddd456dd2 --- /dev/null +++ b/src/common/sfinae_helpers.h @@ -0,0 +1,147 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// the loose definitions of types in this file are, well, loose. +// +// these helpers aren't here for absolute type certainty at compile-time, +// but rather to help with templated functions telling types apart. + +namespace sfinae +{ + + typedef char true_type; + + struct false_type { true_type a[2]; }; + + template <typename T> + struct is_not_container + { + private: + + // does not have const iterator + template <typename C> static false_type c_iter(typename C::const_iterator*); + template <typename C> static true_type c_iter(...); + + // does not have value_type + template <typename C> static false_type v_type(typename C::value_type*); + template <typename C> static true_type v_type(...); + + // does not have key_type + template <typename C> static false_type k_type(typename C::key_type*); + template <typename C> static true_type k_type(...); + + // does not have mapped_type + template <typename C> static false_type m_type(typename C::mapped_type*); + template <typename C> static true_type m_type(...); + + public: + + static const bool value = ( + ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) + ) + || std::is_same<T, std::string>::value + ); + + typedef T type; + }; + + template <typename T> + struct is_vector_like + { + private: + + // has const iterator + template <typename C> static true_type c_iter(typename C::const_iterator*); + template <typename C> static false_type c_iter(...); + + // has value_type + template <typename C> static true_type v_type(typename C::value_type*); + template <typename C> static false_type v_type(...); + + // does not have key_type + template <typename C> static false_type k_type(typename C::key_type*); + template <typename C> static true_type k_type(...); + + // does not have mapped_type + template <typename C> static false_type m_type(typename C::mapped_type*); + template <typename C> static true_type m_type(...); + + public: + + static const bool value = ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) && + !std::is_same<T, std::string>::value + ); + + typedef T type; + }; + + template <typename T> + struct is_map_like + { + private: + + // has const iterator + template <typename C> static true_type c_iter(typename C::const_iterator*); + template <typename C> static false_type c_iter(...); + + // has value_type + template <typename C> static true_type v_type(typename C::value_type*); + template <typename C> static false_type v_type(...); + + // has key_type + template <typename C> static true_type k_type(typename C::key_type*); + template <typename C> static false_type k_type(...); + + // has mapped_type + template <typename C> static true_type m_type(typename C::mapped_type*); + template <typename C> static false_type m_type(...); + + public: + + static const bool value = ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) && + !std::is_same<T, std::string>::value + ); + + typedef T type; + }; + +} // namespace sfinae diff --git a/src/common/task_region.cpp b/src/common/task_region.cpp deleted file mode 100644 index 9b4620c6e..000000000 --- a/src/common/task_region.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2014-2017, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "common/task_region.h" - -#include <boost/thread/locks.hpp> -#include <cassert> - -/* `mark_completed` and `wait` can throw in the lock call, but its difficult to -recover from either. An exception in `wait` means the post condition of joining -all threads cannot be achieved, and an exception in `mark_completed` means -certain deadlock. `noexcept` qualifier will force a call to `std::terminate` if -locking throws an exception, which should only happen if a recursive lock -attempt is made (which is not possible since no external function is called -while holding the lock). */ - -namespace tools -{ -void task_region_handle::state::mark_completed(id task_id) noexcept { - assert(task_id != 0 && (task_id & (task_id - 1)) == 0); // power of 2 check - if (pending.fetch_and(~task_id) == task_id) { - // synchronize with wait call, but do not need to hold - boost::unique_lock<boost::mutex>{sync_on_complete}; - all_complete.notify_all(); - } -} - -void task_region_handle::state::abort() noexcept { - state* current = this; - while (current) { - current->ready = 0; - current = current->next.get(); - } -} - -void task_region_handle::state::wait() noexcept { - state* current = this; - while (current) { - { - boost::unique_lock<boost::mutex> lock{current->sync_on_complete}; - current->all_complete.wait(lock, [current] { return current->pending == 0; }); - } - current = current->next.get(); - } -} - -void task_region_handle::state::wait(thread_group& threads) noexcept { - state* current = this; - while (current) { - while (current->pending != 0) { - if (!threads.try_run_one()) { - current->wait(); - return; - } - } - current = current->next.get(); - } -} - -void task_region_handle::create_state() { - st = std::make_shared<state>(std::move(st)); - next_id = 1; -} - -void task_region_handle::do_wait() noexcept { - assert(st); - const std::shared_ptr<state> temp = std::move(st); - temp->wait(threads); -} -} diff --git a/src/common/task_region.h b/src/common/task_region.h deleted file mode 100644 index 30972cce3..000000000 --- a/src/common/task_region.h +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2014-2017, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#pragma once - -#include <atomic> -#include <boost/thread/condition_variable.hpp> -#include <boost/thread/mutex.hpp> -#include <memory> -#include <type_traits> -#include <utility> - -#include "common/thread_group.h" - -namespace tools -{ - -/*! A model of the fork-join concept. `run(...)` "forks" (i.e. spawns new -tasks), and `~task_region_handle()` or `wait()` "joins" the spawned tasks. -`wait` will block until all tasks have completed, while `~task_region_handle()` -blocks until all tasks have completed or aborted. - -Do _NOT_ give this object to separate thread of execution (which includes -`task_region_handle::run(...)`) because joining on a different thread is -undesireable (potential deadlock). - -This class cannot be constructed directly, use the function -`task_region(...)` instead. -*/ -class task_region_handle -{ - struct state - { - using id = unsigned; - - explicit state(std::shared_ptr<state> next_src) noexcept - : next(std::move(next_src)) - , ready(0) - , pending(0) - , sync_on_complete() - , all_complete() { - } - - state(const state&) = default; - state(state&&) = default; - ~state() = default; - state& operator=(const state&) = default; - state& operator=(state&&) = default; - - void track_id(id task_id) noexcept { - pending |= task_id; - ready |= task_id; - } - - //! \return True only once whether a given id can execute - bool can_run(id task_id) noexcept { - return (ready.fetch_and(~task_id) & task_id); - } - - //! Mark id as completed, and synchronize with waiting threads - void mark_completed(id task_id) noexcept; - - //! Tell all unstarted functions in region to return immediately - void abort() noexcept; - - //! Blocks until all functions in region have aborted or completed. - void wait() noexcept; - - //! Same as `wait()`, except `this_thread` runs tasks while waiting. - void wait(thread_group& threads) noexcept; - - private: - /* This implementation is a bit pessimistic, it ensures that all copies - of a wrapped task can only be executed once. `thread_group` should never - do this, but some variable needs to track whether an abort should be done - anyway... */ - std::shared_ptr<state> next; - std::atomic<id> ready; //!< Tracks whether a task has been invoked - std::atomic<id> pending; //!< Tracks when a task has completed or aborted - boost::mutex sync_on_complete; - boost::condition_variable all_complete; - }; - - template<typename F> - struct wrapper - { - wrapper(state::id id_src, std::shared_ptr<state> st_src, F f_src) - : task_id(id_src), st(std::move(st_src)), f(std::move(f_src)) { - } - - wrapper(const wrapper&) = default; - wrapper(wrapper&&) = default; - wrapper& operator=(const wrapper&) = default; - wrapper& operator=(wrapper&&) = default; - - void operator()() { - if (st) { - if (st->can_run(task_id)) { - f(); - } - st->mark_completed(task_id); - } - } - - private: - const state::id task_id; - std::shared_ptr<state> st; - F f; - }; - -public: - friend struct task_region_; - - task_region_handle() = delete; - task_region_handle(const task_region_handle&) = delete; - task_region_handle(task_region_handle&&) = delete; - - //! Cancels unstarted pending tasks, and waits for them to respond. - ~task_region_handle() noexcept { - if (st) { - st->abort(); - st->wait(threads); - } - } - - task_region_handle& operator=(const task_region_handle&) = delete; - task_region_handle& operator=(task_region_handle&&) = delete; - - /*! If the group has no threads, `f` is immediately run before returning. - Otherwise, `f` is dispatched to the thread_group associated with `this` - region. If `f` is dispatched to another thread, and it throws, the process - will immediately terminate. See std::packaged_task for getting exceptions on - functions executed on other threads. */ - template<typename F> - void run(F&& f) { - if (threads.count() == 0) { - f(); - } else { - if (!st || next_id == 0) { - create_state(); - } - const state::id this_id = next_id; - next_id <<= 1; - - st->track_id(this_id); - threads.dispatch(wrapper<F>{this_id, st, std::move(f)}); - } - } - - //! Wait until all functions provided to `run` have completed. - void wait() noexcept { - if (st) { - do_wait(); - } - } - -private: - explicit task_region_handle(thread_group& threads_src) - : st(nullptr), threads(threads_src), next_id(0) { - } - - void create_state(); - void do_wait() noexcept; - - std::shared_ptr<state> st; - thread_group& threads; - state::id next_id; -}; - -/*! Function for creating a `task_region_handle`, which automatically calls -`task_region_handle::wait()` before returning. If a `thread_group` is not -provided, one is created with an optimal number of threads. The callback `f` -must have the signature `void(task_region_handle&)`. */ -struct task_region_ { - template<typename F> - void operator()(thread_group& threads, F&& f) const { - static_assert( - std::is_same<void, typename std::result_of<F(task_region_handle&)>::type>::value, - "f cannot have a return value" - ); - task_region_handle region{threads}; - f(region); - region.wait(); - } - - template<typename F> - void operator()(thread_group&& threads, F&& f) const { - (*this)(threads, std::forward<F>(f)); - } - - template<typename F> - void operator()(F&& f) const { - thread_group threads; - (*this)(threads, std::forward<F>(f)); - } -}; - -constexpr const task_region_ task_region{}; -} diff --git a/src/common/thread_group.cpp b/src/common/thread_group.cpp deleted file mode 100644 index 691a27a25..000000000 --- a/src/common/thread_group.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2014-2017, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "common/thread_group.h" - -#include <boost/thread/locks.hpp> -#include <cassert> -#include <limits> -#include <stdexcept> - -#include "cryptonote_config.h" -#include "common/util.h" - -namespace tools -{ -std::size_t thread_group::optimal() { - static_assert( - std::numeric_limits<unsigned>::max() <= std::numeric_limits<std::size_t>::max(), - "unexpected truncation" - ); - const std::size_t hardware = get_max_concurrency(); - return hardware ? (hardware - 1) : 0; -} - -std::size_t thread_group::optimal_with_max(std::size_t count) { - return count ? std::min(count - 1, optimal()) : 0; -} - -thread_group::thread_group(std::size_t count) : internal() { - if (count) { - internal.emplace(count); - } -} - -thread_group::data::data(std::size_t count) - : threads() - , head{nullptr} - , last(std::addressof(head)) - , mutex() - , has_work() - , stop(false) { - threads.reserve(count); - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - while (count--) { - threads.push_back(boost::thread(attrs, boost::bind(&thread_group::data::run, this))); - } -} - -thread_group::data::~data() noexcept { - { - const boost::unique_lock<boost::mutex> lock(mutex); - stop = true; - } - has_work.notify_all(); - for (auto& worker : threads) { - try { - worker.join(); - } - catch(...) {} - } -} - -std::unique_ptr<thread_group::data::work> thread_group::data::get_next() noexcept { - std::unique_ptr<work> rc = std::move(head.ptr); - if (rc != nullptr) { - head.ptr = std::move(rc->next.ptr); - if (head.ptr == nullptr) { - last = std::addressof(head); - } - } - return rc; -} - -bool thread_group::data::try_run_one() noexcept { - /* This function and `run()` can both throw when acquiring the lock, or in - dispatched function. It is tough to recover from either, particularly the - lock case. These functions are marked as noexcept so that if either call - throws, the entire process is terminated. Users of the `dispatch` call are - expected to make their functions noexcept, or use std::packaged_task to copy - exceptions so that the process will continue in all but the most pessimistic - cases (std::bad_alloc). This was the existing behavior; - `asio::io_service::run` propogates errors from dispatched calls, and uncaught - exceptions on threads result in process termination. */ - std::unique_ptr<work> next = nullptr; - { - const boost::unique_lock<boost::mutex> lock(mutex); - next = get_next(); - } - if (next) { - assert(next->f); - next->f(); - return true; - } - return false; -} - -void thread_group::data::run() noexcept { - // see `try_run_one()` source for additional information - while (true) { - std::unique_ptr<work> next = nullptr; - { - boost::unique_lock<boost::mutex> lock(mutex); - has_work.wait(lock, [this] { return head.ptr != nullptr || stop; }); - if (stop) { - return; - } - next = get_next(); - } - assert(next != nullptr); - assert(next->f); - next->f(); - } -} - -void thread_group::data::dispatch(std::function<void()> f) { - std::unique_ptr<work> latest(new work{std::move(f), node{nullptr}}); - node* const latest_node = std::addressof(latest->next); - { - const boost::unique_lock<boost::mutex> lock(mutex); - assert(last != nullptr); - assert(last->ptr == nullptr); - - last->ptr = std::move(latest); - last = latest_node; - } - has_work.notify_one(); -} -} diff --git a/src/common/thread_group.h b/src/common/thread_group.h deleted file mode 100644 index 48fd4cd56..000000000 --- a/src/common/thread_group.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2014-2017, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#pragma once - -#include <boost/optional/optional.hpp> -#include <boost/thread/condition_variable.hpp> -#include <boost/thread/mutex.hpp> -#include <boost/thread/thread.hpp> -#include <cstddef> -#include <functional> -#include <thread> -#include <utility> -#include <vector> - -namespace tools -{ -//! Manages zero or more threads for work dispatching. -class thread_group -{ -public: - - //! \return `get_max_concurrency() ? get_max_concurrency() - 1 : 0` - static std::size_t optimal(); - - //! \return `count ? min(count - 1, optimal()) : 0` - static std::size_t optimal_with_max(std::size_t count); - - //! Create an optimal number of threads. - explicit thread_group() : thread_group(optimal()) {} - - //! Create exactly `count` threads. - explicit thread_group(std::size_t count); - - thread_group(thread_group const&) = delete; - thread_group(thread_group&&) = delete; - - //! Joins threads, but does not necessarily run all dispatched functions. - ~thread_group() = default; - - thread_group& operator=(thread_group const&) = delete; - thread_group& operator=(thread_group&&) = delete; - - //! \return Number of threads owned by `this` group. - std::size_t count() const noexcept { - if (internal) { - return internal->count(); - } - return 0; - } - - //! \return True iff a function was available and executed (on `this_thread`). - bool try_run_one() noexcept { - if (internal) { - return internal->try_run_one(); - } - return false; - } - - /*! `f` is invoked immediately if `count() == 0`, otherwise execution of `f` - is queued for next available thread. If `f` is queued, any exception leaving - that function will result in process termination. Use std::packaged_task if - exceptions need to be handled. */ - template<typename F> - void dispatch(F&& f) { - if (internal) { - internal->dispatch(std::forward<F>(f)); - } - else { - f(); - } - } - -private: - class data { - public: - data(std::size_t count); - ~data() noexcept; - - std::size_t count() const noexcept { - return threads.size(); - } - - bool try_run_one() noexcept; - void dispatch(std::function<void()> f); - - private: - struct work; - - struct node { - std::unique_ptr<work> ptr; - }; - - struct work { - std::function<void()> f; - node next; - }; - - //! Requires lock on `mutex`. - std::unique_ptr<work> get_next() noexcept; - - //! Blocks until destructor is invoked, only call from thread. - void run() noexcept; - - private: - std::vector<boost::thread> threads; - node head; - node* last; - boost::condition_variable has_work; - boost::mutex mutex; - bool stop; - }; - -private: - // optionally construct elements, without separate heap allocation - boost::optional<data> internal; -}; - -} diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp new file mode 100644 index 000000000..41d0c25e0 --- /dev/null +++ b/src/common/threadpool.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/threadpool.h" + +#include <cassert> +#include <limits> +#include <stdexcept> + +#include "cryptonote_config.h" +#include "common/util.h" + +namespace tools +{ +threadpool::threadpool() : running(true), active(0) { + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + max = tools::get_max_concurrency() * 2; + size_t i = max; + while(i--) { + threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this))); + } +} + +threadpool::~threadpool() { + { + const boost::unique_lock<boost::mutex> lock(mutex); + running = false; + has_work.notify_all(); + } + for (size_t i = 0; i<threads.size(); i++) { + threads[i].join(); + } +} + +void threadpool::submit(waiter *obj, std::function<void()> f) { + entry e = {obj, f}; + boost::unique_lock<boost::mutex> lock(mutex); + if (active == max && !queue.empty()) { + // if all available threads are already running + // and there's work waiting, just run in current thread + lock.unlock(); + f(); + } else { + if (obj) + obj->inc(); + queue.push_back(e); + has_work.notify_one(); + } +} + +int threadpool::get_max_concurrency() { + return max / 2; +} + +void threadpool::waiter::wait() { + boost::unique_lock<boost::mutex> lock(mt); + while(num) cv.wait(lock); +} + +void threadpool::waiter::inc() { + const boost::unique_lock<boost::mutex> lock(mt); + num++; +} + +void threadpool::waiter::dec() { + const boost::unique_lock<boost::mutex> lock(mt); + num--; + if (!num) + cv.notify_one(); +} + +void threadpool::run() { + boost::unique_lock<boost::mutex> lock(mutex); + while (running) { + entry e; + while(queue.empty() && running) + has_work.wait(lock); + if (!running) break; + + active++; + e = queue.front(); + queue.pop_front(); + lock.unlock(); + e.f(); + + if (e.wo) + e.wo->dec(); + lock.lock(); + active--; + } +} +} diff --git a/src/common/threadpool.h b/src/common/threadpool.h new file mode 100644 index 000000000..1d56d7605 --- /dev/null +++ b/src/common/threadpool.h @@ -0,0 +1,87 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once + +#include <boost/thread/condition_variable.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <cstddef> +#include <functional> +#include <utility> +#include <vector> + +namespace tools +{ +//! A global thread pool +class threadpool +{ +public: + static threadpool& getInstance() { + static threadpool instance; + return instance; + } + + // The waiter lets the caller know when all of its + // tasks are completed. + class waiter { + boost::mutex mt; + boost::condition_variable cv; + int num; + public: + void inc(); + void dec(); + void wait(); //! Wait for a set of tasks to finish. + waiter() : num(0){} + ~waiter() { wait(); } + }; + + // Submit a task to the pool. The waiter pointer may be + // NULL if the caller doesn't care to wait for the + // task to finish. + void submit(waiter *waiter, std::function<void()> f); + + int get_max_concurrency(); + + private: + threadpool(); + ~threadpool(); + typedef struct entry { + waiter *wo; + std::function<void()> f; + } entry; + std::deque<entry> queue; + boost::condition_variable has_work; + boost::mutex mutex; + std::vector<boost::thread> threads; + int active; + int max; + bool running; + void run(); +}; + +} diff --git a/src/common/util.cpp b/src/common/util.cpp index 046961b06..74a6babf1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -39,11 +39,13 @@ using namespace epee; #include "net/http_client.h" // epee::net_utils::... #ifdef WIN32 -#include <windows.h> -#include <shlobj.h> -#include <strsafe.h> + #include <windows.h> + #include <shlobj.h> + #include <strsafe.h> #else -#include <sys/utsname.h> + #include <sys/file.h> + #include <sys/utsname.h> + #include <sys/stat.h> #endif #include <boost/filesystem.hpp> #include <boost/asio.hpp> @@ -53,7 +55,12 @@ namespace tools { std::function<void(int)> signal_handler::m_handler; - std::unique_ptr<std::FILE, tools::close_file> create_private_file(const std::string& name) + private_file::private_file() noexcept : m_handle(), m_filename() {} + + private_file::private_file(std::FILE* handle, std::string&& filename) noexcept + : m_handle(handle), m_filename(std::move(filename)) {} + + private_file private_file::create(std::string name) { #ifdef WIN32 struct close_handle @@ -70,17 +77,17 @@ namespace tools const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0; process.reset(temp); if (fail) - return nullptr; + return {}; } DWORD sid_size = 0; GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size)); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return nullptr; + return {}; std::unique_ptr<char[]> sid{new char[sid_size]}; if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size))) - return nullptr; + return {}; const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner; const DWORD daclSize = @@ -88,17 +95,17 @@ namespace tools const std::unique_ptr<char[]> dacl{new char[daclSize]}; if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION)) - return nullptr; + return {}; if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid)) - return nullptr; + return {}; SECURITY_DESCRIPTOR descriptor{}; if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION)) - return nullptr; + return {}; if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false)) - return nullptr; + return {}; SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false}; std::unique_ptr<void, close_handle> file{ @@ -106,7 +113,7 @@ namespace tools name.c_str(), GENERIC_WRITE, FILE_SHARE_READ, std::addressof(attributes), - CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, + CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE), nullptr ) }; @@ -121,22 +128,49 @@ namespace tools { _close(fd); } - return {real_file, tools::close_file{}}; + return {real_file, std::move(name)}; } } #else - const int fd = open(name.c_str(), (O_RDWR | O_EXCL | O_CREAT), S_IRUSR); - if (0 <= fd) + const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR); + if (0 <= fdr) { - std::FILE* file = fdopen(fd, "w"); - if (!file) + struct stat rstats = {}; + if (fstat(fdr, std::addressof(rstats)) != 0) { - close(fd); + close(fdr); + return {}; + } + fchmod(fdr, (S_IRUSR | S_IWUSR)); + const int fdw = open(name.c_str(), O_RDWR); + fchmod(fdr, rstats.st_mode); + close(fdr); + + if (0 <= fdw) + { + struct stat wstats = {}; + if (fstat(fdw, std::addressof(wstats)) == 0 && + rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino && + flock(fdw, (LOCK_EX | LOCK_NB)) == 0 && ftruncate(fdw, 0) == 0) + { + std::FILE* file = fdopen(fdw, "w"); + if (file) return {file, std::move(name)}; + } + close(fdw); } - return {file, tools::close_file{}}; } #endif - return nullptr; + return {}; + } + + private_file::~private_file() noexcept + { + try + { + boost::system::error_code ec{}; + boost::filesystem::remove(filename(), ec); + } + catch (...) {} } #ifdef WIN32 diff --git a/src/common/util.h b/src/common/util.h index 2452bc9d5..48bdbbc28 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -60,8 +60,30 @@ namespace tools } }; - //! \return File only readable by owner. nullptr if `filename` exists. - std::unique_ptr<std::FILE, close_file> create_private_file(const std::string& filename); + //! A file restricted to process owner AND process. Deletes file on destruction. + class private_file { + std::unique_ptr<std::FILE, close_file> m_handle; + std::string m_filename; + + private_file(std::FILE* handle, std::string&& filename) noexcept; + public: + + //! `handle() == nullptr && filename.empty()`. + private_file() noexcept; + + /*! \return File only readable by owner and only used by this process + OR `private_file{}` on error. */ + static private_file create(std::string filename); + + private_file(private_file&&) = default; + private_file& operator=(private_file&&) = default; + + //! Deletes `filename()` and closes `handle()`. + ~private_file() noexcept; + + std::FILE* handle() const noexcept { return m_handle.get(); } + const std::string& filename() const noexcept { return m_filename; } + }; /*! \brief Returns the default data directory. * diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 745dfb72e..e73f5d778 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -869,4 +869,21 @@ namespace cryptonote block_hashes_calculated = block_hashes_calculated_count; block_hashes_cached = block_hashes_cached_count; } + //--------------------------------------------------------------- + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_add((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + //--------------------------------------------------------------- + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_sub((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + } diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index d8ccf8eec..00080fb98 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -212,6 +212,8 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase); + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get<specific_type>(variant_var); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 3c5811d61..b620e3426 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -858,19 +858,6 @@ namespace cryptonote const boost::filesystem::path& power_supply_path = iter->path(); if (boost::filesystem::is_directory(power_supply_path)) { - std::ifstream power_supply_present_stream((power_supply_path / "present").string()); - if (power_supply_present_stream.fail()) - { - LOG_PRINT_L0("Unable to read from " << power_supply_path << " to check if power supply present"); - continue; - } - - if (power_supply_present_stream.get() != '1') - { - LOG_PRINT_L4("Power supply not present at " << power_supply_path); - continue; - } - boost::filesystem::path power_supply_type_path = power_supply_path / "type"; if (boost::filesystem::is_regular_file(power_supply_type_path)) { diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 8ff01cf8b..cdb46dda9 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -150,6 +150,7 @@ namespace config uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; uint16_t const P2P_DEFAULT_PORT = 18080; uint16_t const RPC_DEFAULT_PORT = 18081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 18082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 } }; // Bender's nightmare @@ -162,6 +163,7 @@ namespace config uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54; uint16_t const P2P_DEFAULT_PORT = 28080; uint16_t const RPC_DEFAULT_PORT = 28081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 28082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11 } }; // Bender's daydream diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 7c43323d4..7b73eebd1 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -55,6 +55,7 @@ monero_add_library(cryptonote_core ${cryptonote_core_private_headers}) target_link_libraries(cryptonote_core PUBLIC + version common cncrypto blockchain_db diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c7d81d4e8..e1cc7361b 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -45,6 +45,7 @@ #include "profile_tools.h" #include "file_io_utils.h" #include "common/int-util.h" +#include "common/threadpool.h" #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" @@ -751,7 +752,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block() m_timestamps = timestamps; m_difficulties = difficulties; } - size_t target = get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + size_t target = get_difficulty_target(); return next_difficulty(timestamps, difficulties, target); } //------------------------------------------------------------------ @@ -1571,6 +1572,98 @@ void Blockchain::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_A output_data_t data = m_db->get_output_key(amount, i); oen.out_key = data.pubkey; } + +uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const +{ + uint64_t num_outs = m_db->get_num_outputs(amount); + // ensure we don't include outputs that aren't yet eligible to be used + // outpouts are sorted by height + while (num_outs > 0) + { + const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); + const uint64_t height = m_db->get_tx_block_height(toi.first); + if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) + break; + --num_outs; + } + + return num_outs; +} + +std::vector<uint64_t> Blockchain::get_random_outputs(uint64_t amount, uint64_t count) const +{ + uint64_t num_outs = get_num_mature_outputs(amount); + + std::vector<uint64_t> indices; + + std::unordered_set<uint64_t> seen_indices; + + // if there aren't enough outputs to mix with (or just enough), + // use all of them. Eventually this should become impossible. + if (num_outs <= count) + { + for (uint64_t i = 0; i < num_outs; i++) + { + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + + // if tx is unlocked, add output to indices + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + indices.push_back(i); + } + } + } + else + { + // while we still need more mixins + while (indices.size() < count) + { + // if we've gone through every possible output, we've gotten all we can + if (seen_indices.size() == num_outs) + { + break; + } + + // get a random output index from the DB. If we've already seen it, + // return to the top of the loop and try again, otherwise add it to the + // list of output indices we've seen. + + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + uint64_t i = (uint64_t)(frac*num_outs); + // just in case rounding up to 1 occurs after sqrt + if (i == num_outs) + --i; + + if (seen_indices.count(i)) + { + continue; + } + seen_indices.emplace(i); + + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + + // if the output's transaction is unlocked, add the output's index to + // our list. + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + indices.push_back(i); + } + } + } + + return indices; +} + +crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_index) const +{ + output_data_t data = m_db->get_output_key(amount, global_index); + return data.pubkey; +} + //------------------------------------------------------------------ // This function takes an RPC request for mixins and creates an RPC response // with the requested mixins. @@ -1585,80 +1678,18 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT // from BlockchainDB where <n> is req.outs_count (number of mixins). for (uint64_t amount : req.amounts) { - auto num_outs = m_db->get_num_outputs(amount); - // ensure we don't include outputs that aren't yet eligible to be used - // outpouts are sorted by height - while (num_outs > 0) - { - const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); - const uint64_t height = m_db->get_tx_block_height(toi.first); - if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) - break; - --num_outs; - } - // create outs_for_amount struct and populate amount field COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount()); result_outs.amount = amount; - std::unordered_set<uint64_t> seen_indices; + std::vector<uint64_t> indices = get_random_outputs(amount, req.outs_count); - // if there aren't enough outputs to mix with (or just enough), - // use all of them. Eventually this should become impossible. - if (num_outs <= req.outs_count) + for (auto i : indices) { - for (uint64_t i = 0; i < num_outs; i++) - { - // get tx_hash, tx_out_index from DB - tx_out_index toi = m_db->get_output_tx_and_index(amount, i); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oe = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); - // if tx is unlocked, add output to result_outs - if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) - { - add_out_to_get_random_outs(result_outs, amount, i); - } - - } - } - else - { - // while we still need more mixins - while (result_outs.outs.size() < req.outs_count) - { - // if we've gone through every possible output, we've gotten all we can - if (seen_indices.size() == num_outs) - { - break; - } - - // get a random output index from the DB. If we've already seen it, - // return to the top of the loop and try again, otherwise add it to the - // list of output indices we've seen. - - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - uint64_t i = (uint64_t)(frac*num_outs); - // just in case rounding up to 1 occurs after sqrt - if (i == num_outs) - --i; - - if (seen_indices.count(i)) - { - continue; - } - seen_indices.emplace(i); - - // get tx_hash, tx_out_index from DB - tx_out_index toi = m_db->get_output_tx_and_index(amount, i); - - // if the output's transaction is unlocked, add the output's index to - // our list. - if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) - { - add_out_to_get_random_outs(result_outs, amount, i); - } - } + oe.global_amount_index = i; + oe.out_key = get_output_key(amount, i); } } return true; @@ -1816,6 +1847,15 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA return true; } //------------------------------------------------------------------ +void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const +{ + const auto o_data = m_db->get_output_key(amount, index); + key = o_data.pubkey; + mask = o_data.commitment; + tx_out_index toi = m_db->get_output_tx_and_index(amount, index); + unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); +} +//------------------------------------------------------------------ // This function takes a list of block hashes from another node // on the network to find where the split point is between us and them. // This is used to see what to send another node that needs to sync. @@ -2025,28 +2065,39 @@ void Blockchain::print_blockchain_outs(const std::string& file) const // 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, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); // if we can't find the split point, return false - if(!find_blockchain_supplement(qblock_ids, resp.start_height)) + if(!find_blockchain_supplement(qblock_ids, start_height)) { return false; } m_db->block_txn_start(true); - resp.total_height = get_current_blockchain_height(); + current_height = get_current_blockchain_height(); size_t count = 0; - for(size_t i = resp.start_height; i < resp.total_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) + for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { - resp.m_block_ids.push_back(m_db->get_block_hash_from_height(i)); + hashes.push_back(m_db->get_block_hash_from_height(i)); } - resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); + m_db->block_txn_stop(); return true; } + +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +{ + 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); + resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); + + return result; +} //------------------------------------------------------------------ //FIXME: change argument to std::vector, low priority // find split point between ours and foreign blockchain (or start at @@ -2333,6 +2384,26 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v7, sorted outs + if (m_hardfork->get_current_version() >= 7) { + const crypto::public_key *last_key = NULL; + for (size_t n = 0; n < tx.vout.size(); ++n) + { + const tx_out &o = tx.vout[n]; + if (o.target.type() == typeid(txout_to_key)) + { + const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target); + if (last_key && memcmp(&out_to_key.key, last_key, sizeof(*last_key)) >= 0) + { + MERROR_VER("transaction has unsorted outputs"); + tvc.m_invalid_output = true; + return false; + } + last_key = &out_to_key.key; + } + } + } + return true; } //------------------------------------------------------------------ @@ -2501,6 +2572,25 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } + // from v7, sorted ins + if (hf_version >= 7) { + const crypto::key_image *last_key_image = NULL; + for (size_t n = 0; n < tx.vin.size(); ++n) + { + const txin_v &txin = tx.vin[n]; + if (txin.type() == typeid(txin_to_key)) + { + const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); + if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0) + { + MERROR_VER("transaction has unsorted inputs"); + tvc.m_verifivation_failed = true; + return false; + } + last_key_image = &in_to_key.k_image; + } + } + } auto it = m_check_txin_table.find(tx_prefix_hash); if(it == m_check_txin_table.end()) { @@ -2513,33 +2603,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); - int threads = tools::get_max_concurrency(); - - boost::asio::io_service ioservice; - boost::thread_group threadpool; - bool ioservice_active = false; - - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - if(threads > 1) - { - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } - ioservice_active = true; - } - -#define KILL_IOSERVICE() \ - if(ioservice_active) \ - { \ - work.reset(); \ - while (!ioservice.stopped()) ioservice.poll(); \ - threadpool.join_all(); \ - ioservice.stop(); \ - ioservice_active = false; \ - } - - epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); }); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; + int threads = tpool.get_max_concurrency(); for (const auto& txin : tx.vin) { @@ -2600,7 +2666,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { // ND: Speedup // 1. Thread ring signature verification if possible. - ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); + tpool.submit(&waiter, boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); } else { @@ -2623,8 +2689,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, sig_index++; } - - KILL_IOSERVICE(); + if (tx.version == 1 && threads > 1) + waiter.wait(); if (tx.version == 1) { @@ -3249,7 +3315,7 @@ leave: // XXX old code adds miner tx here - int tx_index = 0; + size_t tx_index = 0; // Iterate over the block's transaction hashes, grabbing each // from the tx_pool and validating them. Each is then added // to txs. Keys spent in each are added to <keys> by the double spend check. @@ -3331,7 +3397,7 @@ leave: { // ND: if fast_check is enabled for blocks, there is no need to check // the transaction inputs, but do some sanity checks anyway. - if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) + if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) { MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); //TODO: why is this done? make sure that keeping invalid blocks makes sense. @@ -3708,7 +3774,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e return true; bool blocks_exist = false; - uint64_t threads = tools::get_max_concurrency(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + uint64_t threads = tpool.get_max_concurrency(); if (blocks_entry.size() > 1 && threads > 1 && m_max_prepare_blocks_threads > 1) { @@ -3717,15 +3784,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e threads = m_max_prepare_blocks_threads; uint64_t height = m_db->height(); - std::vector<boost::thread *> thread_list; int batches = blocks_entry.size() / threads; int extra = blocks_entry.size() % threads; MDEBUG("block_batches: " << batches); std::vector<std::unordered_map<crypto::hash, crypto::hash>> maps(threads); std::vector < std::vector < block >> blocks(threads); auto it = blocks_entry.begin(); - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); for (uint64_t i = 0; i < threads; i++) { @@ -3784,19 +3848,14 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e { m_blocks_longhash_table.clear(); uint64_t thread_height = height; + tools::threadpool::waiter waiter; for (uint64_t i = 0; i < threads; i++) { - thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])))); + tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i]))); thread_height += blocks[i].size(); } - for (size_t j = 0; j < thread_list.size(); j++) - { - thread_list[j]->join(); - delete thread_list[j]; - } - - thread_list.clear(); + waiter.wait(); if (m_cancel) return false; @@ -3920,30 +3979,20 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e // [output] stores all transactions for each tx_out_index::hash found std::vector<std::unordered_map<crypto::hash, cryptonote::transaction>> transactions(amounts.size()); - threads = tools::get_max_concurrency(); + threads = tpool.get_max_concurrency(); if (!m_db->can_thread_bulk_indices()) threads = 1; if (threads > 1) { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - - for (uint64_t i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool::waiter waiter; for (size_t i = 0; i < amounts.size(); i++) { uint64_t amount = amounts[i]; - ioservice.dispatch(boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i]))); + tpool.submit(&waiter, boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i]))); } - - work.reset(); - threadpool.join_all(); - ioservice.stop(); + waiter.wait(); } else { @@ -4095,6 +4144,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } +uint64_t Blockchain::get_difficulty_target() const +{ + return get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; +} + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { return m_db->get_output_histogram(amounts, unlocked, recent_cutoff); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index b8ea657b4..e2da535cd 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -374,6 +374,22 @@ namespace cryptonote * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. * * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @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 + * + * @return true if a block found in common, else false + */ + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const; + + /** + * @brief get recent block hashes for a foreign chain + * + * 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. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) * @param resp return-by-reference the split height and subsequent blocks' hashes * * @return true if a block found in common, else false @@ -427,6 +443,35 @@ namespace cryptonote bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); /** + * @brief get number of outputs of an amount past the minimum spendable age + * + * @param amount the output amount + * + * @return the number of mature outputs + */ + uint64_t get_num_mature_outputs(uint64_t amount) const; + + /** + * @brief get random outputs (indices) for an amount + * + * @param amount the amount + * @param count the number of random outputs to choose + * + * @return the outputs' amount-global indices + */ + std::vector<uint64_t> get_random_outputs(uint64_t amount, uint64_t count) const; + + /** + * @brief get the public key for an output + * + * @param amount the output amount + * @param global_index the output amount-global index + * + * @return the public key + */ + crypto::public_key get_output_key(uint64_t amount, uint64_t global_index) const; + + /** * @brief gets random outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -458,6 +503,17 @@ namespace cryptonote bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; /** + * @brief gets an output's key and unlocked state + * + * @param amount in - the output amount + * @param index in - the output global amount index + * @param mask out - the output's RingCT mask + * @param key out - the output's key + * @param unlocked out - the output's unlocked state + */ + void get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const; + + /** * @brief gets random ringct outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -775,6 +831,13 @@ namespace cryptonote bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; /** + * @brief get difficulty target based on chain and hardfork version + * + * @return difficulty target + */ + uint64_t get_difficulty_target() const; + + /** * @brief remove transactions from the transaction pool (if present) * * @param txids a list of hashes of transactions to be removed diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index c3aeb15f0..14e37d580 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -37,7 +37,7 @@ using namespace epee; #include "common/util.h" #include "common/updates.h" #include "common/download.h" -#include "common/task_region.h" +#include "common/threadpool.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" @@ -74,7 +74,7 @@ namespace cryptonote m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), m_disable_dns_checkpoints(false), - m_threadpool(tools::thread_group::optimal()), + m_threadpool(tools::threadpool::getInstance()), m_update_download(0) { m_checkpoints_updating.clear(); @@ -211,10 +211,9 @@ namespace cryptonote return m_blockchain_storage.get_current_blockchain_height(); } //----------------------------------------------------------------------------------------------- - bool core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const + void core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const { top_id = m_blockchain_storage.get_tail_id(height); - return true; } //----------------------------------------------------------------------------------------------- bool core::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const @@ -591,54 +590,53 @@ namespace cryptonote std::vector<result> results(tx_blobs.size()); tvc.resize(tx_blobs.size()); - tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { - std::list<blobdata>::const_iterator it = tx_blobs.begin(); - for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { - region.run([&, i, it] { + tools::threadpool::waiter waiter; + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + m_threadpool.submit(&waiter, [&, i, it] { + try + { + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + results[i].res = false; + } + }); + } + waiter.wait(); + it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + continue; + if(m_mempool.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + } + else if(m_blockchain_storage.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + } + else + { + m_threadpool.submit(&waiter, [&, i, it] { try { - results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); } catch (const std::exception &e) { - MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + MERROR_VER("Exception in handle_incoming_tx_post: " << e.what()); results[i].res = false; } }); } - }); - tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { - std::list<blobdata>::const_iterator it = tx_blobs.begin(); - for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { - if (!results[i].res) - continue; - if(m_mempool.have_tx(results[i].hash)) - { - LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); - } - else if(m_blockchain_storage.have_tx(results[i].hash)) - { - LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); - } - else - { - region.run([&, i, it] { - try - { - results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); - } - catch (const std::exception &e) - { - MERROR_VER("Exception in handle_incoming_tx_post: " << e.what()); - results[i].res = false; - } - }); - } - } - }); + } + waiter.wait(); bool ok = true; - std::list<blobdata>::const_iterator it = tx_blobs.begin(); + it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { if (!results[i].res) { @@ -810,6 +808,13 @@ namespace cryptonote return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4; } //----------------------------------------------------------------------------------------------- + bool core::are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const + { + spent.clear(); + + return m_mempool.check_for_key_images(key_im, spent); + } + //----------------------------------------------------------------------------------------------- std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) { uint64_t emission_amount = 0; @@ -1200,6 +1205,11 @@ namespace cryptonote return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos); } //----------------------------------------------------------------------------------------------- + bool core::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + return m_mempool.get_pool_for_rpc(tx_infos, key_image_infos); + } + //----------------------------------------------------------------------------------------------- bool core::get_short_chain_history(std::list<crypto::hash>& ids) const { return m_blockchain_storage.get_short_chain_history(ids); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 8e17569a9..1aed86b25 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -40,7 +40,7 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" -#include "common/thread_group.h" +#include "common/threadpool.h" #include "tx_pool.h" #include "blockchain.h" #include "cryptonote_basic/miner.h" @@ -299,10 +299,8 @@ namespace cryptonote * * @param height return-by-reference height of the block * @param top_id return-by-reference hash of the block - * - * @return true */ - bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + void get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; /** * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&, std::list<transaction>&) const @@ -463,6 +461,13 @@ namespace cryptonote bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @copydoc tx_memory_pool::get_pool_for_rpc + * + * @note see tx_memory_pool::get_pool_for_rpc + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** * @copydoc tx_memory_pool::get_transactions_count * * @note see tx_memory_pool::get_transactions_count @@ -708,6 +713,16 @@ namespace cryptonote bool are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; /** + * @brief check if multiple key images are spent in the transaction pool + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ + bool are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; + + /** * @brief get the number of blocks to sync in one go * * @return the number of blocks to sync in one go @@ -940,7 +955,7 @@ namespace cryptonote std::unordered_set<crypto::hash> bad_semantics_txes[2]; boost::mutex bad_semantics_txes_lock; - tools::thread_group m_threadpool; + tools::threadpool& m_threadpool; enum { UPDATES_DISABLED, diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 94f069827..d2a7eedf5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -31,6 +31,7 @@ #include "include_base_utils.h" using namespace epee; +#include "common/apply_permutation.h" #include "cryptonote_tx_utils.h" #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" @@ -156,7 +157,7 @@ namespace cryptonote return destinations[0].addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry> sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) { std::vector<rct::key> amount_keys; tx.set_null(); @@ -263,14 +264,25 @@ namespace cryptonote tx.vin.push_back(input_to_key); } - // "Shuffle" outs - std::vector<tx_destination_entry> shuffled_dsts(destinations); - std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](unsigned int i) { return crypto::rand<unsigned int>() % i; }); + // sort ins by their key image + std::vector<size_t> ins_order(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + ins_order[n] = n; + std::sort(ins_order.begin(), ins_order.end(), [&](const size_t i0, const size_t i1) { + const txin_to_key &tk0 = boost::get<txin_to_key>(tx.vin[i0]); + const txin_to_key &tk1 = boost::get<txin_to_key>(tx.vin[i1]); + return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) < 0; + }); + tools::apply_permutation(ins_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vin[i0], tx.vin[i1]); + std::swap(in_contexts[i0], in_contexts[i1]); + std::swap(sources[i0], sources[i1]); + }); uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; - for(const tx_destination_entry& dst_entr: shuffled_dsts) + for(const tx_destination_entry& dst_entr: destinations) { CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::key_derivation derivation; @@ -297,6 +309,20 @@ namespace cryptonote summary_outs_money += dst_entr.amount; } + // sort outs by their public key + std::vector<size_t> outs_order(tx.vout.size()); + for (size_t n = 0; n < tx.vout.size(); ++n) + outs_order[n] = n; + std::sort(outs_order.begin(), outs_order.end(), [&](size_t i0, size_t i1) { + const txout_to_key &tk0 = boost::get<txout_to_key>(tx.vout[i0].target); + const txout_to_key &tk1 = boost::get<txout_to_key>(tx.vout[i1].target); + return memcmp(&tk0.key, &tk1.key, sizeof(tk0.key)) < 0; + }); + tools::apply_permutation(outs_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vout[i0], tx.vout[i1]); + std::swap(amount_keys[i0], amount_keys[i1]); + }); + //check money if(summary_outs_money > summary_inputs_money ) { @@ -484,8 +510,9 @@ namespace cryptonote std::string genesis_coinbase_tx_hex = config::GENESIS_TX; blobdata tx_bl; - string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); - bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); + bool r = string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 7aa7c280d..69254fb5f 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -71,7 +71,7 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys); bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry> sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); bool generate_genesis_block( block& bl diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index e61d95ac3..9071c330c 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -80,7 +80,7 @@ namespace cryptonote uint64_t get_transaction_size_limit(uint8_t version) { - return get_min_block_size(version) * 125 / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + return get_min_block_size(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } // This class is meant to create a batch when none currently exists. @@ -202,6 +202,9 @@ namespace cryptonote return false; } + // assume failure during verification steps until success is certain + tvc.m_verifivation_failed = true; + time_t receive_time = time(nullptr); crypto::hash max_used_block_id = null_hash; @@ -246,6 +249,7 @@ namespace cryptonote { LOG_PRINT_L1("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; + tvc.m_invalid_input = true; return false; } }else @@ -285,9 +289,6 @@ namespace cryptonote tvc.m_should_be_relayed = true; } - // assume failure during verification steps until success is certain - tvc.m_verifivation_failed = true; - tvc.m_verifivation_failed = false; MINFO("Transaction added to pool: txid " << id << " bytes: " << blob_size << " fee/byte: " << (fee / (double)blob_size)); @@ -298,7 +299,8 @@ namespace cryptonote { crypto::hash h = null_hash; size_t blob_size = 0; - get_transaction_hash(tx, h, blob_size); + if (!get_transaction_hash(tx, h, blob_size) || blob_size == 0) + return false; return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version); } //--------------------------------------------------------------------------------- @@ -684,6 +686,65 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + cryptonote::rpc::tx_in_pool txi; + txi.tx_hash = txid; + transaction tx; + if (!parse_and_validate_tx_from_blob(*bd, tx)) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + txi.tx = tx; + txi.blob_size = meta.blob_size; + txi.fee = meta.fee; + txi.kept_by_block = meta.kept_by_block; + txi.max_used_block_height = meta.max_used_block_height; + txi.max_used_block_hash = meta.max_used_block_id; + txi.last_failed_block_height = meta.last_failed_height; + txi.last_failed_block_hash = meta.last_failed_id; + txi.receive_time = meta.receive_time; + txi.relayed = meta.relayed; + txi.last_relayed_time = meta.last_relayed_time; + txi.do_not_relay = meta.do_not_relay; + tx_infos.push_back(txi); + return true; + }, true); + + for (const key_images_container::value_type& kee : m_spent_key_images) { + std::vector<crypto::hash> tx_hashes; + const std::unordered_set<crypto::hash>& kei_image_set = kee.second; + for (const crypto::hash& tx_id_hash : kei_image_set) + { + tx_hashes.push_back(tx_id_hash); + } + + const crypto::key_image& k_image = kee.first; + key_image_infos[k_image] = tx_hashes; + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + spent.clear(); + + for (const auto& image : key_images) + { + spent.push_back(m_spent_key_images.find(image) == m_spent_key_images.end() ? false : true); + } + + return true; + } + //--------------------------------------------------------------------------------- bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -1031,12 +1092,13 @@ namespace cryptonote m_txs_by_fee_and_receive_time.clear(); m_spent_key_images.clear(); - return m_blockchain.for_all_txpool_txes([this](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { + std::vector<crypto::hash> remove; + bool r = m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { cryptonote::transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { - MERROR("Failed to parse tx from txpool"); - return false; + MWARNING("Failed to parse tx from txpool, removing"); + remove.push_back(txid); } if (!insert_key_images(tx, meta.kept_by_block)) { @@ -1046,6 +1108,25 @@ namespace cryptonote m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.blob_size, meta.receive_time), txid); return true; }, true); + if (!r) + return false; + if (!remove.empty()) + { + LockedTXN lock(m_blockchain); + for (const auto &txid: remove) + { + try + { + m_blockchain.remove_txpool_tx(txid); + } + catch (const std::exception &e) + { + MWARNING("Failed to remove corrupt transaction: " << txid); + // ignore error + } + } + } + return true; } //--------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 6414620c9..3e4ccb338 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -46,6 +46,7 @@ #include "blockchain_db/blockchain_db.h" #include "crypto/hash.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/message_data_structs.h" namespace cryptonote { @@ -269,6 +270,28 @@ namespace cryptonote bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @brief get information about all transactions and key images in the pool + * + * see documentation on tx_in_pool and key_images_with_tx_hashes for more details + * + * @param tx_infos [out] the transactions' information + * @param key_image_infos [out] the spent key images' information + * + * @return true + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** + * @brief check for presence of key images in the pool + * + * @param key_images [in] vector of key images to check + * @param spent [out] vector of bool to return + * + * @return true + */ + bool check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const; + + /** * @brief get a specific transaction from the pool * * @param h the hash of the transaction to get diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt index 4ce380a48..347e48eee 100644 --- a/src/cryptonote_protocol/CMakeLists.txt +++ b/src/cryptonote_protocol/CMakeLists.txt @@ -39,5 +39,3 @@ target_link_libraries(cryptonote_protocol p2p PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(cryptonote_protocol - version) diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 795442a2d..d0fc1d846 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -92,14 +92,17 @@ target_link_libraries(daemon p2p cryptonote_protocol daemonizer + serialization + daemon_rpc_server + version ${Boost_CHRONO_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${ZMQ_LIB} ${EXTRA_LIBRARIES}) -add_dependencies(daemon version) set_property(TARGET daemon PROPERTY OUTPUT_NAME "monerod") diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index 8eb3db195..7fa58f9d8 100644 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -46,6 +46,11 @@ namespace daemon_args , "Specify log file" , "" }; + const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = { + "max-log-file-size" + , "Specify maximum log file size [B]" + , MAX_LOG_FILE_SIZE + }; const command_line::arg_descriptor<std::string> arg_log_level = { "log-level" , "" @@ -64,6 +69,25 @@ namespace daemon_args , "Max number of threads to use for a parallel job" , 0 }; + + const command_line::arg_descriptor<std::string> arg_zmq_rpc_bind_ip = { + "zmq-rpc-bind-ip" + , "IP for ZMQ RPC server to listen on" + , "127.0.0.1" + }; + + const command_line::arg_descriptor<std::string> arg_zmq_rpc_bind_port = { + "zmq-rpc-bind-port" + , "Port for ZMQ RPC server to listen on" + , std::to_string(config::ZMQ_RPC_DEFAULT_PORT) + }; + + const command_line::arg_descriptor<std::string> arg_zmq_testnet_rpc_bind_port = { + "zmq-testnet-rpc-bind-port" + , "Port for testnet ZMQ RPC server to listen on" + , std::to_string(config::testnet::ZMQ_RPC_DEFAULT_PORT) + }; + } // namespace daemon_args #endif // DAEMON_COMMAND_LINE_ARGS_H diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 9df698547..b9f503c6b 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -241,7 +241,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "bc_dyn_stats" , std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1) - , "Print information about current blockchain dynamic state" + , "Print information about current blockchain dynamic state, bc_dyn_stats <last n blocks>" ); m_command_lookup.set_handler( "update" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 683eaf4ff..faa620c54 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -32,6 +32,8 @@ #include <stdexcept> #include "misc_log_ex.h" #include "daemon/daemon.h" +#include "rpc/daemon_handler.h" +#include "rpc/zmq_server.h" #include "common/password.h" #include "common/util.h" @@ -85,7 +87,18 @@ t_daemon::t_daemon( boost::program_options::variables_map const & vm ) : mp_internals{new t_internals{vm}} -{} +{ + bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + if (testnet) + { + zmq_rpc_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_testnet_rpc_bind_port); + } + else + { + zmq_rpc_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_port); + } + zmq_rpc_bind_address = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip); +} t_daemon::~t_daemon() = default; @@ -133,6 +146,30 @@ bool t_daemon::run(bool interactive) rpc_commands->start_handling(std::bind(&daemonize::t_daemon::stop_p2p, this)); } + cryptonote::rpc::DaemonHandler rpc_daemon_handler(mp_internals->core.get(), mp_internals->p2p.get()); + cryptonote::rpc::ZmqServer zmq_server(rpc_daemon_handler); + + if (!zmq_server.addTCPSocket(zmq_rpc_bind_address, zmq_rpc_bind_port)) + { + LOG_ERROR(std::string("Failed to add TCP Socket (") + zmq_rpc_bind_address + + ":" + zmq_rpc_bind_port + ") to ZMQ RPC Server"); + + if (interactive) + { + rpc_commands->stop_handling(); + } + + mp_internals->rpc.stop(); + + return false; + } + + MINFO("Starting ZMQ server..."); + zmq_server.run(); + + MINFO(std::string("ZMQ server started at ") + zmq_rpc_bind_address + + ":" + zmq_rpc_bind_port + "."); + mp_internals->p2p.run(); // blocks until p2p goes down if (rpc_commands) @@ -140,6 +177,8 @@ bool t_daemon::run(bool interactive) rpc_commands->stop_handling(); } + zmq_server.stop(); + mp_internals->rpc.stop(); mp_internals->core.get().get_miner().stop(); MGINFO("Node stopped."); diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 2b9f18669..8c7547f62 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -43,6 +43,8 @@ private: void stop_p2p(); private: std::unique_ptr<t_internals> mp_internals; + std::string zmq_rpc_bind_address; + std::string zmq_rpc_bind_port; public: t_daemon( boost::program_options::variables_map const & vm diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 44d2dae43..acc23b9f9 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -88,7 +88,11 @@ int main(int argc, char const * argv[]) bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log"); command_line::add_arg(core_settings, daemon_args::arg_log_file, default_log.string()); command_line::add_arg(core_settings, daemon_args::arg_log_level); + command_line::add_arg(core_settings, daemon_args::arg_max_log_file_size); command_line::add_arg(core_settings, daemon_args::arg_max_concurrency); + command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_ip); + command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port); + command_line::add_arg(core_settings, daemon_args::arg_zmq_testnet_rpc_bind_port); daemonizer::init_options(hidden_options, visible_options); daemonize::t_executor::init_options(core_settings); @@ -204,7 +208,7 @@ int main(int argc, char const * argv[]) if (! vm["log-file"].defaulted()) log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); log_file_path = bf::absolute(log_file_path, relative_path_base); - mlog_configure(log_file_path.string(), true); + mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size)); // Set log level if (!vm["log-level"].defaulted()) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index cda6f3f95..11e3a2252 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -700,6 +700,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { std::string fail_message = "Problem fetching transaction"; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash)); + req.decode_as_json = false; if (m_is_rpc) { if (!m_rpc_client->rpc_request(req, res, "/gettransactions", fail_message.c_str())) @@ -782,7 +783,7 @@ bool t_rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { if (1 == res.spent_status.size()) { // first as hex - tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent"); + tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent") << (res.spent_status.front() == cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL ? " (in pool)" : ""); } else { diff --git a/src/debug_utilities/CMakeLists.txt b/src/debug_utilities/CMakeLists.txt index 99198dc57..7a82c12d9 100644 --- a/src/debug_utilities/CMakeLists.txt +++ b/src/debug_utilities/CMakeLists.txt @@ -42,8 +42,6 @@ target_link_libraries(cn_deserialize epee ${CMAKE_THREAD_LIBS_INIT}) -add_dependencies(cn_deserialize - version) set_property(TARGET cn_deserialize PROPERTY OUTPUT_NAME "monero-utils-deserialize") @@ -65,8 +63,6 @@ target_link_libraries(object_sizes epee ${CMAKE_THREAD_LIBS_INIT}) -add_dependencies(object_sizes - version) set_property(TARGET object_sizes PROPERTY OUTPUT_NAME "monero-utils-object-sizes") diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index 0704940b5..2e34db5d1 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -47,5 +47,3 @@ target_link_libraries(p2p ${Boost_THREAD_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(p2p - version) diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 22ae5ec26..8bbaa9138 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -105,7 +105,7 @@ namespace nodetool bool init(const boost::program_options::variables_map& vm); bool deinit(); bool send_stop_signal(); - uint32_t get_this_peer_port(){return m_listenning_port;} + uint32_t get_this_peer_port(){return m_listening_port;} t_payload_net_handler& get_payload_object(); template <class Archive, class t_version_type> @@ -218,6 +218,8 @@ namespace nodetool bool is_peer_used(const peerlist_entry& peer); bool is_peer_used(const anchor_peerlist_entry& peer); bool is_addr_connected(const epee::net_utils::network_address& peer); + void add_upnp_port_mapping(uint32_t port); + void delete_upnp_port_mapping(uint32_t port); template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb); bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); @@ -287,7 +289,7 @@ namespace nodetool bool m_have_address; bool m_first_connection_maker_call; - uint32_t m_listenning_port; + uint32_t m_listening_port; uint32_t m_external_port; uint32_t m_ip_address; bool m_allow_local_ip; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 889cfaf9d..f1ca50f76 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -573,55 +573,15 @@ namespace nodetool res = m_net_server.init_server(m_port, m_bind_ip); CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); - m_listenning_port = m_net_server.get_binded_port(); - MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listenning_port); + m_listening_port = m_net_server.get_binded_port(); + MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listening_port); if(m_external_port) MDEBUG("External port defined as " << m_external_port); - // Add UPnP port mapping - if(m_no_igd == false) { - MDEBUG("Attempting to add IGD port mapping."); - int result; -#if MINIUPNPC_API_VERSION > 13 - // default according to miniupnpc.h - unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); -#else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); -#endif - UPNPUrls urls; - IGDdatas igdData; - char lanAddress[64]; - result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); - freeUPNPDevlist(deviceList); - if (result != 0) { - if (result == 1) { - std::ostringstream portString; - portString << m_listenning_port; - - // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly - UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); - - int portMappingResult; - portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); - if (portMappingResult != 0) { - LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); - } else { - MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); - } - } else if (result == 2) { - MWARNING("IGD was found but reported as not connected."); - } else if (result == 3) { - MWARNING("UPnP device was found but not recognized as IGD."); - } else { - MWARNING("UPNP_GetValidIGD returned an unknown result code."); - } + // add UPnP port mapping + if(!m_no_igd) + add_upnp_port_mapping(m_listening_port); - FreeUPNPUrls(&urls); - } else { - MINFO("No IGD was found."); - } - } return res; } //----------------------------------------------------------------------------------- @@ -688,6 +648,9 @@ namespace nodetool kill(); m_peerlist.deinit(); m_net_server.deinit_server(); + // remove UPnP port mapping + if(!m_no_igd) + delete_upnp_port_mapping(m_listening_port); return store_config(); } //----------------------------------------------------------------------------------- @@ -1127,6 +1090,8 @@ namespace nodetool if (use_white_list) { local_peers_count = m_peerlist.get_white_peers_count(); + if (!local_peers_count) + return false; max_random_index = std::min<uint64_t>(local_peers_count -1, 20); random_index = get_random_index_with_fixed_probability(max_random_index); } else { @@ -1389,7 +1354,7 @@ namespace nodetool node_data.local_time = local_time; node_data.peer_id = m_config.m_peer_id; if(!m_hide_my_port) - node_data.my_port = m_external_port ? m_external_port : m_listenning_port; + node_data.my_port = m_external_port ? m_external_port : m_listening_port; else node_data.my_port = 0; node_data.network_id = m_network_id; @@ -1951,8 +1916,13 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::gray_peerlist_housekeeping() { + if (!m_exclusive_peers.empty()) return true; + peerlist_entry pe = AUTO_VAL_INIT(pe); + if (m_net_server.is_stop_signal_sent()) + return false; + if (!m_peerlist.get_random_gray_peer(pe)) { return false; } @@ -1973,4 +1943,93 @@ namespace nodetool return true; } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port) + { + MDEBUG("Attempting to add IGD port mapping."); + int result; +#if MINIUPNPC_API_VERSION > 13 + // default according to miniupnpc.h + unsigned char ttl = 2; + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); +#else + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); +#endif + UPNPUrls urls; + IGDdatas igdData; + char lanAddress[64]; + result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); + freeUPNPDevlist(deviceList); + if (result != 0) { + if (result == 1) { + std::ostringstream portString; + portString << port; + + // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly + UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); + + int portMappingResult; + portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); + if (portMappingResult != 0) { + LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); + } else { + MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); + } + } else if (result == 2) { + MWARNING("IGD was found but reported as not connected."); + } else if (result == 3) { + MWARNING("UPnP device was found but not recognized as IGD."); + } else { + MWARNING("UPNP_GetValidIGD returned an unknown result code."); + } + + FreeUPNPUrls(&urls); + } else { + MINFO("No IGD was found."); + } + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping(uint32_t port) + { + MDEBUG("Attempting to delete IGD port mapping."); + int result; +#if MINIUPNPC_API_VERSION > 13 + // default according to miniupnpc.h + unsigned char ttl = 2; + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); +#else + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); +#endif + UPNPUrls urls; + IGDdatas igdData; + char lanAddress[64]; + result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); + freeUPNPDevlist(deviceList); + if (result != 0) { + if (result == 1) { + std::ostringstream portString; + portString << port; + + int portMappingResult; + portMappingResult = UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); + if (portMappingResult != 0) { + LOG_ERROR("UPNP_DeletePortMapping failed, error: " << strupnperror(portMappingResult)); + } else { + MLOG_GREEN(el::Level::Info, "Deleted IGD port mapping."); + } + } else if (result == 2) { + MWARNING("IGD was found but reported as not connected."); + } else if (result == 3) { + MWARNING("UPnP device was found but not recognized as IGD."); + } else { + MWARNING("UPNP_GetValidIGD returned an unknown result code."); + } + + FreeUPNPUrls(&urls); + } else { + MINFO("No IGD was found."); + } + } } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index a30a05422..8372445aa 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -190,35 +190,16 @@ namespace nodetool if (ver < 6) return; - if(ver < 3) - return; CRITICAL_REGION_LOCAL(m_peerlist_lock); - if(ver < 4) - { - //loading data from old storage - peers_indexed_old pio; - a & pio; - peers_indexed_from_old(pio, m_peers_white); - return; - } #if 0 // trouble loading more than one peer, can't find why a & m_peers_white; a & m_peers_gray; + a & m_peers_anchor; #else serialize_peers(a, m_peers_white, peerlist_entry(), ver); serialize_peers(a, m_peers_gray, peerlist_entry(), ver); -#endif - - if(ver < 5) { - return; - } - -#if 0 - // trouble loading more than one peer, can't find why - a & m_peers_anchor; -#else serialize_peers(a, m_peers_anchor, anchor_peerlist_entry(), ver); #endif } diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 8efd6a07c..946325367 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -30,8 +30,7 @@ #include "misc_log_ex.h" #include "common/perf_timer.h" -#include "common/task_region.h" -#include "common/thread_group.h" +#include "common/threadpool.h" #include "common/util.h" #include "rctSigs.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -731,17 +730,16 @@ namespace rct { try { if (semantics) { + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; std::deque<bool> results(rv.outPk.size(), false); - tools::thread_group threadpool(tools::thread_group::optimal_with_max(rv.outPk.size())); - - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - DP("range proofs verified?"); - for (size_t i = 0; i < rv.outPk.size(); i++) { - region.run([&, i] { - results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - }); - } - }); + DP("range proofs verified?"); + for (size_t i = 0; i < rv.outPk.size(); i++) { + tpool.submit(&waiter, [&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + waiter.wait(); for (size_t i = 0; i < rv.outPk.size(); ++i) { if (!results[i]) { @@ -794,7 +792,8 @@ namespace rct { const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); std::deque<bool> results(threads); - tools::thread_group threadpool(tools::thread_group::optimal_with_max(threads)); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; if (semantics) { key sumOutpks = identity(); @@ -819,13 +818,12 @@ namespace rct { results.clear(); results.resize(rv.outPk.size()); - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - for (size_t i = 0; i < rv.outPk.size(); i++) { - region.run([&, i] { - results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - }); - } - }); + for (size_t i = 0; i < rv.outPk.size(); i++) { + tpool.submit(&waiter, [&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + waiter.wait(); for (size_t i = 0; i < results.size(); ++i) { if (!results[i]) { @@ -839,13 +837,12 @@ namespace rct { results.clear(); results.resize(rv.mixRing.size()); - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { - region.run([&, i] { + for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { + tpool.submit(&waiter, [&, i] { results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); - }); - } - }); + }); + } + waiter.wait(); for (size_t i = 0; i < results.size(); ++i) { if (!results[i]) { diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index f6037d7e3..23bb6aaae 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -30,20 +30,61 @@ set(rpc_sources core_rpc_server.cpp rpc_args.cpp) +set(daemon_messages_sources + message.cpp + daemon_messages.cpp) + +set(daemon_rpc_server_sources + daemon_handler.cpp + zmq_server.cpp) + + set(rpc_headers rpc_args.h) -set(rpc_private_headers +set(daemon_rpc_server_headers) + + +set(rpc_daemon_private_headers core_rpc_server.h core_rpc_server_commands_defs.h core_rpc_server_error_codes.h) +set(daemon_messages_private_headers + message.h + daemon_messages.h) + +set(daemon_rpc_server_private_headers + message.h + daemon_messages.h + daemon_handler.h + rpc_handler.h + zmq_server.h) + + monero_private_headers(rpc ${rpc_private_headers}) + +monero_private_headers(daemon_rpc_server + ${daemon_rpc_server_private_headers}) + + monero_add_library(rpc ${rpc_sources} ${rpc_headers} ${rpc_private_headers}) + +monero_add_library(daemon_messages + ${daemon_messages_sources} + ${daemon_messages_headers} + ${daemon_messages_private_headers}) + +monero_add_library(daemon_rpc_server + ${daemon_rpc_server_sources} + ${daemon_rpc_server_headers} + ${daemon_rpc_server_private_headers}) + + target_link_libraries(rpc PUBLIC common @@ -54,5 +95,25 @@ target_link_libraries(rpc ${Boost_THREAD_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(rpc - version) + +target_link_libraries(daemon_messages + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + serialization + ${EXTRA_LIBRARIES}) + +target_link_libraries(daemon_rpc_server + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + daemon_messages + serialization + ${Boost_CHRONO_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${ZMQ_LIB} + ${EXTRA_LIBRARIES}) +target_include_directories(daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH}) +target_include_directories(obj_daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH}) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a6a61705b..e814dcc1e 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -50,6 +50,16 @@ using namespace epee; #define MAX_RESTRICTED_FAKE_OUTS_COUNT 40 #define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 500 +namespace +{ + void add_reason(std::string &reasons, const char *reason) + { + if (!reasons.empty()) + reasons += ", "; + reasons += reason; + } +} + namespace cryptonote { @@ -128,16 +138,12 @@ namespace cryptonote { CHECK_CORE_BUSY(); crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); - res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + res.target = m_core.get_blockchain_storage().get_difficulty_target(); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase res.tx_pool_size = m_core.get_pool_transactions_count(); res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count(); @@ -478,18 +484,30 @@ namespace cryptonote bool r = m_core.get_pool_transactions(pool_txs); if(r) { - for (std::list<transaction>::const_iterator i = pool_txs.begin(); i != pool_txs.end(); ++i) + // sort to match original request + std::list<transaction> sorted_txs; + std::list<cryptonote::transaction>::const_iterator i; + for (const crypto::hash &h: vh) { - crypto::hash tx_hash = get_transaction_hash(*i); - std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), tx_hash); - if (mi != missed_txs.end()) + if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end()) { - pool_tx_hashes.insert(tx_hash); - missed_txs.erase(mi); - txs.push_back(*i); + // core returns the ones it finds in the right order + if (get_transaction_hash(txs.front()) != h) + { + res.status = "Failed: tx hash mismatch"; + return true; + } + sorted_txs.push_back(std::move(txs.front())); + txs.pop_front(); + } + else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end()) + { + sorted_txs.push_back(*i); + missed_txs.remove(h); ++found_in_pool; } } + txs = sorted_txs; } LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); } @@ -510,11 +528,12 @@ namespace cryptonote e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); if (e.in_pool) { - e.block_height = std::numeric_limits<uint64_t>::max(); + e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); } 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); } // fill up old style responses too, in case an old wallet asks @@ -623,31 +642,33 @@ namespace cryptonote 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) { - if (tvc.m_verifivation_failed) - { - LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed"); - } - else - { - LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx"); - } res.status = "Failed"; + res.reason = ""; if ((res.low_mixin = tvc.m_low_mixin)) - res.reason = "ring size too small"; + add_reason(res.reason, "ring size too small"); if ((res.double_spend = tvc.m_double_spend)) - res.reason = "double spend"; + add_reason(res.reason, "double spend"); if ((res.invalid_input = tvc.m_invalid_input)) - res.reason = "invalid input"; + add_reason(res.reason, "invalid input"); if ((res.invalid_output = tvc.m_invalid_output)) - res.reason = "invalid output"; + add_reason(res.reason, "invalid output"); if ((res.too_big = tvc.m_too_big)) - res.reason = "too big"; + add_reason(res.reason, "too big"); if ((res.overspend = tvc.m_overspend)) - res.reason = "overspend"; + add_reason(res.reason, "overspend"); if ((res.fee_too_low = tvc.m_fee_too_low)) - res.reason = "fee too low"; + add_reason(res.reason, "fee too low"); if ((res.not_rct = tvc.m_not_rct)) - res.reason = "tx is not ringct"; + add_reason(res.reason, "tx is not ringct"); + const std::string punctuation = res.reason.empty() ? "" : ": "; + if (tvc.m_verifivation_failed) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed" << punctuation << res.reason); + } + else + { + LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx" << punctuation << res.reason); + } return true; } @@ -948,7 +969,7 @@ namespace cryptonote LOG_ERROR("Failed to find tx pub key in blockblob"); return false; } - res.reserved_offset += sizeof(tx_pub_key) + 3; //3 bytes: tag for TX_EXTRA_TAG_PUBKEY(1 byte), tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + 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(res.reserved_offset + req.reserve_size > block_blob.size()) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1048,13 +1069,7 @@ namespace cryptonote } uint64_t last_block_height; crypto::hash last_block_hash; - bool have_last_block_hash = m_core.get_blockchain_top(last_block_height, last_block_hash); - if (!have_last_block_hash) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: can't get last block hash."; - return false; - } + m_core.get_blockchain_top(last_block_height, last_block_hash); block last_block; bool have_last_block = m_core.get_block_by_hash(last_block_hash, last_block); if (!have_last_block) @@ -1287,11 +1302,7 @@ namespace cryptonote } crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); @@ -1703,11 +1714,7 @@ namespace cryptonote } crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.target_height = m_core.get_target_blockchain_height(); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 88327dd75..99430bf20 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,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 1 -#define CORE_RPC_VERSION_MINOR 13 +#define CORE_RPC_VERSION_MINOR 14 #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) @@ -215,6 +215,7 @@ namespace cryptonote std::string as_json; bool in_pool; uint64_t block_height; + uint64_t block_timestamp; std::vector<uint64_t> output_indices; BEGIN_KV_SERIALIZE_MAP() @@ -223,6 +224,7 @@ namespace cryptonote KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) KV_SERIALIZE(block_height) + KV_SERIALIZE(block_timestamp) KV_SERIALIZE(output_indices) END_KV_SERIALIZE_MAP() }; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp new file mode 100644 index 000000000..53eeb5e76 --- /dev/null +++ b/src/rpc/daemon_handler.cpp @@ -0,0 +1,887 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "daemon_handler.h" + +// likely included by daemon_handler.h's includes, +// but including here for clarity +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_protocol/blobdatatype.h" +#include "ringct/rctSigs.h" + +namespace cryptonote +{ + +namespace rpc +{ + + void DaemonHandler::handle(const GetHeight::Request& req, GetHeight::Response& res) + { + res.height = m_core.get_current_blockchain_height(); + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlocksFast::Request& req, GetBlocksFast::Response& res) + { + std::list<std::pair<blobdata, std::list<blobdata> > > blocks; + + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::find_blockchain_supplement() returned false"; + return; + } + + res.blocks.resize(blocks.size()); + res.output_indices.resize(blocks.size()); + + //TODO: really need to switch uses of std::list to std::vector unless + // it's a huge performance concern + + auto it = blocks.begin(); + + uint64_t block_count = 0; + while (it != blocks.end()) + { + cryptonote::rpc::block_with_transactions& bwt = res.blocks[block_count]; + + if (!parse_and_validate_block_from_blob(it->first, bwt.block)) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "failed retrieving a requested block"; + return; + } + + if (it->second.size() != bwt.block.tx_hashes.size()) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "incorrect number of transactions retrieved for block"; + return; + } + std::list<transaction> txs; + for (const auto& blob : it->second) + { + txs.resize(txs.size() + 1); + if (!parse_and_validate_tx_from_blob(blob, txs.back())) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "failed retrieving a requested transaction"; + return; + } + } + + cryptonote::rpc::block_output_indices& indices = res.output_indices[block_count]; + + // miner tx output indices + { + cryptonote::rpc::tx_output_indices tx_indices; + bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(bwt.block.miner_tx), tx_indices); + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + indices.push_back(tx_indices); + } + + // assume each block returned is returned with all its transactions + // in the correct order. + auto tx_it = txs.begin(); + for (const crypto::hash& h : bwt.block.tx_hashes) + { + bwt.transactions.emplace(h, *tx_it); + tx_it++; + + cryptonote::rpc::tx_output_indices tx_indices; + bool r = m_core.get_tx_outputs_gindexs(h, tx_indices); + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + + indices.push_back(tx_indices); + } + + it++; + block_count++; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetHashesFast::Request& req, GetHashesFast::Response& res) + { + res.start_height = req.start_height; + + auto& chain = m_core.get_blockchain_storage(); + + if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, res.start_height, res.current_height)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Blockchain::find_blockchain_supplement() returned false"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetTransactions::Request& req, GetTransactions::Response& res) + { + std::list<cryptonote::transaction> found_txs; + std::list<crypto::hash> missed_hashes; + + bool r = m_core.get_transactions(req.tx_hashes, found_txs, missed_hashes); + + // TODO: consider fixing core::get_transactions to not hide exceptions + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_transactions() returned false (exception caught there)"; + return; + } + + size_t num_found = found_txs.size(); + + // std::list is annoying + std::vector<cryptonote::transaction> found_txs_vec + { + std::make_move_iterator(std::begin(found_txs)), + std::make_move_iterator(std::end(found_txs)) + }; + + std::vector<crypto::hash> missed_vec + { + std::make_move_iterator(std::begin(missed_hashes)), + std::make_move_iterator(std::end(missed_hashes)) + }; + + std::vector<uint64_t> heights(num_found); + std::vector<bool> in_pool(num_found, false); + std::vector<crypto::hash> found_hashes(num_found); + + for (size_t i=0; i < num_found; i++) + { + found_hashes[i] = get_transaction_hash(found_txs_vec[i]); + heights[i] = m_core.get_blockchain_storage().get_db().get_tx_block_height(found_hashes[i]); + } + + // if any missing from blockchain, check in tx pool + if (!missed_vec.empty()) + { + std::list<cryptonote::transaction> pool_txs; + + m_core.get_pool_transactions(pool_txs); + + for (const auto& tx : pool_txs) + { + crypto::hash h = get_transaction_hash(tx); + + auto itr = std::find(missed_vec.begin(), missed_vec.end(), h); + + if (itr != missed_vec.end()) + { + found_hashes.push_back(h); + found_txs_vec.push_back(tx); + heights.push_back(std::numeric_limits<uint64_t>::max()); + in_pool.push_back(true); + missed_vec.erase(itr); + } + } + } + + for (size_t i=0; i < found_hashes.size(); i++) + { + cryptonote::rpc::transaction_info info; + info.height = heights[i]; + info.in_pool = in_pool[i]; + info.transaction = std::move(found_txs_vec[i]); + + res.txs.emplace(found_hashes[i], std::move(info)); + } + + res.missed_hashes = std::move(missed_vec); + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const KeyImagesSpent::Request& req, KeyImagesSpent::Response& res) + { + res.spent_status.resize(req.key_images.size(), KeyImagesSpent::STATUS::UNSPENT); + + std::vector<bool> chain_spent_status; + std::vector<bool> pool_spent_status; + + m_core.are_key_images_spent(req.key_images, chain_spent_status); + m_core.are_key_images_spent_in_pool(req.key_images, pool_spent_status); + + if ((chain_spent_status.size() != req.key_images.size()) || (pool_spent_status.size() != req.key_images.size())) + { + res.status = Message::STATUS_FAILED; + res.error_details = "tx_pool::have_key_images_as_spent() gave vectors of wrong size(s)."; + return; + } + + for(size_t i=0; i < req.key_images.size(); i++) + { + if ( chain_spent_status[i] ) + { + res.spent_status[i] = KeyImagesSpent::STATUS::SPENT_IN_BLOCKCHAIN; + } + else if ( pool_spent_status[i] ) + { + res.spent_status[i] = KeyImagesSpent::STATUS::SPENT_IN_POOL; + } + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetTxGlobalOutputIndices::Request& req, GetTxGlobalOutputIndices::Response& res) + { + if (!m_core.get_tx_outputs_gindexs(req.tx_hash, res.output_indices)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + + res.status = Message::STATUS_OK; + + } + + //TODO: handle "restricted" RPC + void DaemonHandler::handle(const GetRandomOutputsForAmounts::Request& req, GetRandomOutputsForAmounts::Response& res) + { + auto& chain = m_core.get_blockchain_storage(); + + try + { + for (const uint64_t& amount : req.amounts) + { + std::vector<uint64_t> indices = chain.get_random_outputs(amount, req.count); + + outputs_for_amount ofa; + + ofa.resize(indices.size()); + + for (size_t i = 0; i < indices.size(); i++) + { + crypto::public_key key = chain.get_output_key(amount, indices[i]); + ofa[i].amount_index = indices[i]; + ofa[i].key = key; + } + + amount_with_random_outputs amt; + amt.amount = amount; + amt.outputs = ofa; + + res.amounts_with_outputs.push_back(amt); + } + + res.status = Message::STATUS_OK; + } + catch (const std::exception& e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + } + } + + void DaemonHandler::handle(const SendRawTx::Request& req, SendRawTx::Response& res) + { + auto tx_blob = cryptonote::tx_to_blob(req.tx); + + 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.relay) || tvc.m_verifivation_failed) + { + if (tvc.m_verifivation_failed) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed"); + } + else + { + LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx"); + } + res.status = Message::STATUS_FAILED; + res.error_details = ""; + + if (tvc.m_low_mixin) + { + res.error_details = "mixin too low"; + } + if (tvc.m_double_spend) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "double spend"; + } + if (tvc.m_invalid_input) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "invalid input"; + } + if (tvc.m_invalid_output) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "invalid output"; + } + if (tvc.m_too_big) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "too big"; + } + if (tvc.m_overspend) + { + if (!res.error_details.empty()) res.error_details += " and "; + 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"; + } + if (tvc.m_not_rct) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "tx is not ringct"; + } + if (res.error_details.empty()) + { + res.error_details = "an unknown issue was found with the transaction"; + } + + return; + } + + if(!tvc.m_should_be_relayed || !req.relay) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); + res.error_details = "Not relayed"; + res.relayed = false; + res.status = Message::STATUS_OK; + + return; + } + + NOTIFY_NEW_TRANSACTIONS::request r; + r.txs.push_back(tx_blob); + m_core.get_protocol()->relay_transactions(r, fake_context); + + //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes + res.status = Message::STATUS_OK; + res.relayed = true; + + return; + } + + void DaemonHandler::handle(const StartMining::Request& req, StartMining::Response& res) + { + account_public_address adr; + if(!get_account_address_from_str(adr, m_core.get_testnet(), req.miner_address)) + { + res.error_details = "Failed, wrong address"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4; + + // if we couldn't detect threads, set it to a ridiculously high number + if(concurrency_count == 0) + { + concurrency_count = 257; + } + + // if there are more threads requested than the hardware supports + // then we fail and log that. + if(req.threads_count > concurrency_count) + { + res.error_details = "Failed, too many threads relative to CPU cores."; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + + if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + { + res.error_details = "Failed, mining not started"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + res.status = Message::STATUS_OK; + res.error_details = ""; + + } + + void DaemonHandler::handle(const GetInfo::Request& req, GetInfo::Response& res) + { + res.info.height = m_core.get_current_blockchain_height(); + + res.info.target_height = m_core.get_target_blockchain_height(); + + if (res.info.height > res.info.target_height) + { + res.info.target_height = res.info.height; + } + + auto& chain = m_core.get_blockchain_storage(); + + res.info.difficulty = chain.get_difficulty_for_next_block(); + + res.info.target = chain.get_difficulty_target(); + + res.info.tx_count = chain.get_total_transactions() - res.info.height; //without coinbase + + res.info.tx_pool_size = m_core.get_pool_transactions_count(); + + res.info.alt_blocks_count = chain.get_alternative_blocks_count(); + + uint64_t total_conn = m_p2p.get_connections_count(); + res.info.outgoing_connections_count = m_p2p.get_outgoing_connections_count(); + res.info.incoming_connections_count = total_conn - res.info.outgoing_connections_count; + + res.info.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); + + res.info.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); + + res.info.testnet = m_core.get_testnet(); + res.info.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1); + res.info.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); + res.info.start_time = (uint64_t)m_core.get_start_time(); + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const StopMining::Request& req, StopMining::Response& res) + { + if(!m_core.get_miner().stop()) + { + res.error_details = "Failed, mining not stopped"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const MiningStatus::Request& req, MiningStatus::Response& res) + { + const cryptonote::miner& lMiner = m_core.get_miner(); + res.active = lMiner.is_mining(); + res.is_background_mining_enabled = lMiner.get_is_background_mining_enabled(); + + if ( lMiner.is_mining() ) { + res.speed = lMiner.get_speed(); + res.threads_count = lMiner.get_threads_count(); + const account_public_address& lMiningAdr = lMiner.get_mining_address(); + res.address = get_account_address_as_str(m_core.get_testnet(), lMiningAdr); + } + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const SaveBC::Request& req, SaveBC::Response& res) + { + if (!m_core.get_blockchain_storage().store_blockchain()) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Error storing the blockchain"; + } + else + { + res.status = Message::STATUS_OK; + } + } + + void DaemonHandler::handle(const GetBlockHash::Request& req, GetBlockHash::Response& res) + { + if (m_core.get_current_blockchain_height() <= req.height) + { + res.hash = cryptonote::null_hash; + res.status = Message::STATUS_FAILED; + res.error_details = "height given is higher than current chain height"; + return; + } + + res.hash = m_core.get_block_id_by_height(req.height); + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockTemplate::Request& req, GetBlockTemplate::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SubmitBlock::Request& req, SubmitBlock::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetLastBlockHeader::Request& req, GetLastBlockHeader::Response& res) + { + const crypto::hash block_hash = m_core.get_tail_id(); + + if (!getBlockHeaderByHash(block_hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeaderByHash::Request& req, GetBlockHeaderByHash::Response& res) + { + if (!getBlockHeaderByHash(req.hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeaderByHeight::Request& req, GetBlockHeaderByHeight::Response& res) + { + const crypto::hash block_hash = m_core.get_block_id_by_height(req.height); + + if (!getBlockHeaderByHash(block_hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeadersByHeight::Request& req, GetBlockHeadersByHeight::Response& res) + { + res.headers.resize(req.heights.size()); + + for (size_t i=0; i < req.heights.size(); i++) + { + const crypto::hash block_hash = m_core.get_block_id_by_height(req.heights[i]); + + if (!getBlockHeaderByHash(block_hash, res.headers[i])) + { + res.status = Message::STATUS_FAILED; + res.error_details = "A requested block does not exist"; + return; + } + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlock::Request& req, GetBlock::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetPeerList::Request& req, GetPeerList::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetLogHashRate::Request& req, SetLogHashRate::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetLogLevel::Request& req, SetLogLevel::Response& res) + { + if (req.level < 0 || req.level > 4) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Error: log level not valid"; + } + else + { + res.status = Message::STATUS_OK; + mlog_set_log_level(req.level); + } + } + + void DaemonHandler::handle(const GetTransactionPool::Request& req, GetTransactionPool::Response& res) + { + bool r = m_core.get_pool_for_rpc(res.transactions, res.key_images); + + if (!r) res.status = Message::STATUS_FAILED; + else res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetConnections::Request& req, GetConnections::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetBlockHeadersRange::Request& req, GetBlockHeadersRange::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StopDaemon::Request& req, StopDaemon::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StartSaveGraph::Request& req, StartSaveGraph::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StopSaveGraph::Request& req, StopSaveGraph::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const HardForkInfo::Request& req, HardForkInfo::Response& res) + { + const Blockchain &blockchain = m_core.get_blockchain_storage(); + uint8_t version = req.version > 0 ? req.version : blockchain.get_ideal_hard_fork_version(); + res.info.version = blockchain.get_current_hard_fork_version(); + res.info.enabled = blockchain.get_hard_fork_voting_info(version, res.info.window, res.info.votes, res.info.threshold, res.info.earliest_height, res.info.voting); + res.info.state = blockchain.get_hard_fork_state(); + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBans::Request& req, GetBans::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetBans::Request& req, SetBans::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const FlushTransactionPool::Request& req, FlushTransactionPool::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetOutputHistogram::Request& req, GetOutputHistogram::Response& res) + { + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t> > histogram; + try + { + histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked, req.recent_cutoff); + } + catch (const std::exception &e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + return; + } + + res.histogram.clear(); + res.histogram.reserve(histogram.size()); + for (const auto &i: histogram) + { + if (std::get<0>(i.second) >= req.min_count && (std::get<0>(i.second) <= req.max_count || req.max_count == 0)) + res.histogram.emplace_back(output_amount_count{i.first, std::get<0>(i.second), std::get<1>(i.second), std::get<2>(i.second)}); + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetOutputKeys::Request& req, GetOutputKeys::Response& res) + { + try + { + for (const auto& i : req.outputs) + { + crypto::public_key key; + rct::key mask; + bool unlocked; + m_core.get_blockchain_storage().get_output_key_mask_unlocked(i.amount, i.index, key, mask, unlocked); + res.keys.emplace_back(output_key_mask_unlocked{key, mask, unlocked}); + } + } + catch (const std::exception& e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetRPCVersion::Request& req, GetRPCVersion::Response& res) + { + res.version = DAEMON_RPC_VERSION_ZMQ; + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetPerKBFeeEstimate::Request& req, GetPerKBFeeEstimate::Response& res) + { + res.estimated_fee_per_kb = m_core.get_blockchain_storage().get_dynamic_per_kb_fee_estimate(req.num_grace_blocks); + res.status = Message::STATUS_OK; + } + + bool DaemonHandler::getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& header) + { + block b; + + if (!m_core.get_block_by_hash(hash_in, b)) + { + return false; + } + + header.hash = hash_in; + header.height = boost::get<txin_gen>(b.miner_tx.vin.front()).height; + + header.major_version = b.major_version; + header.minor_version = b.minor_version; + header.timestamp = b.timestamp; + header.nonce = b.nonce; + header.prev_id = b.prev_id; + + header.depth = m_core.get_current_blockchain_height() - header.height - 1; + + header.reward = 0; + for (const auto& out : b.miner_tx.vout) + { + header.reward += out.amount; + } + + header.difficulty = m_core.get_blockchain_storage().block_difficulty(header.height); + + return true; + } + + std::string DaemonHandler::handle(const std::string& request) + { + MDEBUG("Handling RPC request: " << request); + + Message* resp_message = NULL; + + try + { + FullMessage req_full(request, true); + + rapidjson::Value& req_json = req_full.getMessage(); + + const std::string request_type = req_full.getRequestType(); + + // create correct Message subclass and call handle() on it + REQ_RESP_TYPES_MACRO(request_type, GetHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlocksFast, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetHashesFast, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTransactions, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, KeyImagesSpent, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTxGlobalOutputIndices, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetRandomOutputsForAmounts, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SendRawTx, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetInfo, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, StartMining, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, StopMining, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, MiningStatus, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SaveBC, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHash, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetLastBlockHeader, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHash, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeadersByHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetPeerList, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SetLogLevel, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTransactionPool, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, HardForkInfo, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetOutputHistogram, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetOutputKeys, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetRPCVersion, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetPerKBFeeEstimate, req_json, resp_message, handle); + + // if none of the request types matches + if (resp_message == NULL) + { + return BAD_REQUEST(request_type, req_full.getID()); + } + + FullMessage resp_full = FullMessage::responseMessage(resp_message, req_full.getID()); + + const std::string response = resp_full.getJson(); + delete resp_message; + resp_message = NULL; + + MDEBUG("Returning RPC response: " << response); + + return response; + } + catch (const std::exception& e) + { + if (resp_message) + { + delete resp_message; + } + + return BAD_JSON(e.what()); + } + } + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_handler.h b/src/rpc/daemon_handler.h new file mode 100644 index 000000000..0d356bad2 --- /dev/null +++ b/src/rpc/daemon_handler.h @@ -0,0 +1,145 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "daemon_messages.h" +#include "daemon_rpc_version.h" +#include "rpc_handler.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "p2p/net_node.h" + +namespace +{ + typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> > t_p2p; +} // anonymous namespace + +namespace cryptonote +{ + +namespace rpc +{ + +class DaemonHandler : public RpcHandler +{ + public: + + DaemonHandler(cryptonote::core& c, t_p2p& p2p) : m_core(c), m_p2p(p2p) { } + + ~DaemonHandler() { } + + void handle(const GetHeight::Request& req, GetHeight::Response& res); + + void handle(const GetBlocksFast::Request& req, GetBlocksFast::Response& res); + + void handle(const GetHashesFast::Request& req, GetHashesFast::Response& res); + + void handle(const GetTransactions::Request& req, GetTransactions::Response& res); + + void handle(const KeyImagesSpent::Request& req, KeyImagesSpent::Response& res); + + void handle(const GetTxGlobalOutputIndices::Request& req, GetTxGlobalOutputIndices::Response& res); + + void handle(const GetRandomOutputsForAmounts::Request& req, GetRandomOutputsForAmounts::Response& res); + + void handle(const SendRawTx::Request& req, SendRawTx::Response& res); + + void handle(const StartMining::Request& req, StartMining::Response& res); + + void handle(const GetInfo::Request& req, GetInfo::Response& res); + + void handle(const StopMining::Request& req, StopMining::Response& res); + + void handle(const MiningStatus::Request& req, MiningStatus::Response& res); + + void handle(const SaveBC::Request& req, SaveBC::Response& res); + + void handle(const GetBlockHash::Request& req, GetBlockHash::Response& res); + + void handle(const GetBlockTemplate::Request& req, GetBlockTemplate::Response& res); + + void handle(const SubmitBlock::Request& req, SubmitBlock::Response& res); + + void handle(const GetLastBlockHeader::Request& req, GetLastBlockHeader::Response& res); + + void handle(const GetBlockHeaderByHash::Request& req, GetBlockHeaderByHash::Response& res); + + void handle(const GetBlockHeaderByHeight::Request& req, GetBlockHeaderByHeight::Response& res); + + void handle(const GetBlockHeadersByHeight::Request& req, GetBlockHeadersByHeight::Response& res); + + void handle(const GetBlock::Request& req, GetBlock::Response& res); + + void handle(const GetPeerList::Request& req, GetPeerList::Response& res); + + void handle(const SetLogHashRate::Request& req, SetLogHashRate::Response& res); + + void handle(const SetLogLevel::Request& req, SetLogLevel::Response& res); + + void handle(const GetTransactionPool::Request& req, GetTransactionPool::Response& res); + + void handle(const GetConnections::Request& req, GetConnections::Response& res); + + void handle(const GetBlockHeadersRange::Request& req, GetBlockHeadersRange::Response& res); + + void handle(const StopDaemon::Request& req, StopDaemon::Response& res); + + void handle(const StartSaveGraph::Request& req, StartSaveGraph::Response& res); + + void handle(const StopSaveGraph::Request& req, StopSaveGraph::Response& res); + + void handle(const HardForkInfo::Request& req, HardForkInfo::Response& res); + + void handle(const GetBans::Request& req, GetBans::Response& res); + + void handle(const SetBans::Request& req, SetBans::Response& res); + + void handle(const FlushTransactionPool::Request& req, FlushTransactionPool::Response& res); + + void handle(const GetOutputHistogram::Request& req, GetOutputHistogram::Response& res); + + void handle(const GetOutputKeys::Request& req, GetOutputKeys::Response& res); + + void handle(const GetRPCVersion::Request& req, GetRPCVersion::Response& res); + + void handle(const GetPerKBFeeEstimate::Request& req, GetPerKBFeeEstimate::Response& res); + + std::string handle(const std::string& request); + + private: + + bool getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& response); + + cryptonote::core& m_core; + t_p2p& m_p2p; +}; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp new file mode 100644 index 000000000..640058df9 --- /dev/null +++ b/src/rpc/daemon_messages.cpp @@ -0,0 +1,900 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "daemon_messages.h" +#include "serialization/json_object.h" + +namespace cryptonote +{ + +namespace rpc +{ + +const char* const GetHeight::name = "get_height"; +const char* const GetBlocksFast::name = "get_blocks_fast"; +const char* const GetHashesFast::name = "get_hashes_fast"; +const char* const GetTransactions::name = "get_transactions"; +const char* const KeyImagesSpent::name = "key_images_spent"; +const char* const GetTxGlobalOutputIndices::name = "get_tx_global_output_indices"; +const char* const GetRandomOutputsForAmounts::name = "get_random_outputs_for_amounts"; +const char* const SendRawTx::name = "send_raw_tx"; +const char* const StartMining::name = "start_mining"; +const char* const StopMining::name = "stop_mining"; +const char* const MiningStatus::name = "mining_status"; +const char* const GetInfo::name = "get_info"; +const char* const SaveBC::name = "save_bc"; +const char* const GetBlockHash::name = "get_block_hash"; +const char* const GetLastBlockHeader::name = "get_last_block_header"; +const char* const GetBlockHeaderByHash::name = "get_block_header_by_hash"; +const char* const GetBlockHeaderByHeight::name = "get_block_header_by_height"; +const char* const GetBlockHeadersByHeight::name = "get_block_headers_by_height"; +const char* const GetPeerList::name = "get_peer_list"; +const char* const SetLogLevel::name = "set_log_level"; +const char* const GetTransactionPool::name = "get_transaction_pool"; +const char* const HardForkInfo::name = "hard_fork_info"; +const char* const GetOutputHistogram::name = "get_output_histogram"; +const char* const GetOutputKeys::name = "get_output_keys"; +const char* const GetRPCVersion::name = "get_rpc_version"; +const char* const GetPerKBFeeEstimate::name = "get_dynamic_per_kb_fee_estimate"; + + + + +rapidjson::Value GetHeight::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetHeight::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + val.AddMember("height", height, al); + + return val; +} + +void GetHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + + +rapidjson::Value GetBlocksFast::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, block_ids, block_ids); + val.AddMember("start_height", start_height, al); + val.AddMember("prune", prune, al); + + return val; +} + +void GetBlocksFast::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, block_ids, block_ids); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, prune, prune); +} + +rapidjson::Value GetBlocksFast::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, blocks, blocks); + val.AddMember("start_height", start_height, al); + val.AddMember("current_height", current_height, al); + INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); + + return val; +} + +void GetBlocksFast::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, blocks, blocks); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, current_height, current_height); + GET_FROM_JSON_OBJECT(val, output_indices, output_indices); +} + + +rapidjson::Value GetHashesFast::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, known_hashes, known_hashes); + val.AddMember("start_height", start_height, al); + + return val; +} + +void GetHashesFast::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, known_hashes, known_hashes); + GET_FROM_JSON_OBJECT(val, start_height, start_height); +} + +rapidjson::Value GetHashesFast::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hashes, hashes); + val.AddMember("start_height", start_height, al); + val.AddMember("current_height", current_height, al); + + return val; +} + +void GetHashesFast::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hashes, hashes); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, current_height, current_height); +} + + +rapidjson::Value GetTransactions::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, tx_hashes); + + return val; +} + +void GetTransactions::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx_hashes, tx_hashes); +} + +rapidjson::Value GetTransactions::Response::toJson(rapidjson::Document& doc) const +{ + rapidjson::Value val(rapidjson::kObjectType); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, txs, txs); + INSERT_INTO_JSON_OBJECT(val, doc, missed_hashes, missed_hashes); + + return val; +} + +void GetTransactions::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, txs, txs); + GET_FROM_JSON_OBJECT(val, missed_hashes, missed_hashes); +} + + +rapidjson::Value KeyImagesSpent::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); + + return val; +} + +void KeyImagesSpent::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, key_images, key_images); +} + +rapidjson::Value KeyImagesSpent::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, spent_status, spent_status); + + return val; +} + +void KeyImagesSpent::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, spent_status, spent_status); +} + + +rapidjson::Value GetTxGlobalOutputIndices::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx_hash); + + return val; +} + +void GetTxGlobalOutputIndices::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx_hash, tx_hash); +} + +rapidjson::Value GetTxGlobalOutputIndices::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); + + return val; +} + +void GetTxGlobalOutputIndices::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, output_indices, output_indices); +} + + +rapidjson::Value GetRandomOutputsForAmounts::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); + INSERT_INTO_JSON_OBJECT(val, doc, count, count); + + return val; +} + +void GetRandomOutputsForAmounts::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts, amounts); + GET_FROM_JSON_OBJECT(val, count, count); +} + +rapidjson::Value GetRandomOutputsForAmounts::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts_with_outputs, amounts_with_outputs); + + return val; +} + +void GetRandomOutputsForAmounts::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts_with_outputs, amounts_with_outputs); +} + + +rapidjson::Value SendRawTx::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx, tx); + INSERT_INTO_JSON_OBJECT(val, doc, relay, relay); + + return val; +} + +void SendRawTx::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx, tx); + GET_FROM_JSON_OBJECT(val, relay, relay); +} + +rapidjson::Value SendRawTx::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, relayed, relayed); + + return val; +} + + +void SendRawTx::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, relayed, relayed); +} + +rapidjson::Value StartMining::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, miner_address, miner_address); + INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(val, doc, do_background_mining, do_background_mining); + INSERT_INTO_JSON_OBJECT(val, doc, ignore_battery, ignore_battery); + + return val; +} + +void StartMining::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, miner_address, miner_address); + GET_FROM_JSON_OBJECT(val, threads_count, threads_count); + GET_FROM_JSON_OBJECT(val, do_background_mining, do_background_mining); + GET_FROM_JSON_OBJECT(val, ignore_battery, ignore_battery); +} + +rapidjson::Value StartMining::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StartMining::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value StopMining::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StopMining::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value StopMining::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StopMining::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value MiningStatus::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void MiningStatus::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value MiningStatus::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, active, active); + INSERT_INTO_JSON_OBJECT(val, doc, speed, speed); + INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(val, doc, address, address); + INSERT_INTO_JSON_OBJECT(val, doc, is_background_mining_enabled, is_background_mining_enabled); + + return val; +} + +void MiningStatus::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, active, active); + GET_FROM_JSON_OBJECT(val, speed, speed); + GET_FROM_JSON_OBJECT(val, threads_count, threads_count); + GET_FROM_JSON_OBJECT(val, address, address); + GET_FROM_JSON_OBJECT(val, is_background_mining_enabled, is_background_mining_enabled); +} + + +rapidjson::Value GetInfo::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetInfo::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetInfo::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, info, info); + + return val; +} + +void GetInfo::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, info, info); +} + + +rapidjson::Value SaveBC::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void SaveBC::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value SaveBC::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void SaveBC::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value GetBlockHash::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, height); + + return val; +} + +void GetBlockHash::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + +rapidjson::Value GetBlockHash::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); + + return val; +} + +void GetBlockHash::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hash, hash); +} + + +rapidjson::Value GetLastBlockHeader::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void GetLastBlockHeader::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetLastBlockHeader::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetLastBlockHeader::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeaderByHash::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); + + return val; +} + +void GetBlockHeaderByHash::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hash, hash); +} + +rapidjson::Value GetBlockHeaderByHash::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetBlockHeaderByHash::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeaderByHeight::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, height); + + return val; +} + +void GetBlockHeaderByHeight::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + +rapidjson::Value GetBlockHeaderByHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetBlockHeaderByHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeadersByHeight::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, heights, heights); + + return val; +} + +void GetBlockHeadersByHeight::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, heights, heights); +} + +rapidjson::Value GetBlockHeadersByHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, headers, headers); + + return val; +} + +void GetBlockHeadersByHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, headers, headers); +} + + +rapidjson::Value GetPeerList::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void GetPeerList::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetPeerList::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, white_list, white_list); + INSERT_INTO_JSON_OBJECT(val, doc, gray_list, gray_list); + + return val; +} + +void GetPeerList::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, white_list, white_list); + GET_FROM_JSON_OBJECT(val, gray_list, gray_list); +} + + +rapidjson::Value SetLogLevel::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + val.AddMember("level", level, al); + + return val; +} + +void SetLogLevel::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, level, level); +} + +rapidjson::Value SetLogLevel::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void SetLogLevel::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value GetTransactionPool::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetTransactionPool::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetTransactionPool::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, transactions, transactions); + INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); + + return val; +} + +void GetTransactionPool::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, transactions, transactions); + GET_FROM_JSON_OBJECT(val, key_images, key_images); +} + + +rapidjson::Value HardForkInfo::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, version); + + return val; +} + +void HardForkInfo::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, version, version); +} + +rapidjson::Value HardForkInfo::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, info, info); + + return val; +} + +void HardForkInfo::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, info, info); +} + + +rapidjson::Value GetOutputHistogram::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); + INSERT_INTO_JSON_OBJECT(val, doc, min_count, min_count); + INSERT_INTO_JSON_OBJECT(val, doc, max_count, max_count); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked, unlocked); + INSERT_INTO_JSON_OBJECT(val, doc, recent_cutoff, recent_cutoff); + + return val; +} + +void GetOutputHistogram::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts, amounts); + GET_FROM_JSON_OBJECT(val, min_count, min_count); + GET_FROM_JSON_OBJECT(val, max_count, max_count); + GET_FROM_JSON_OBJECT(val, unlocked, unlocked); + GET_FROM_JSON_OBJECT(val, recent_cutoff, recent_cutoff); +} + +rapidjson::Value GetOutputHistogram::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, histogram, histogram); + + return val; +} + +void GetOutputHistogram::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, histogram, histogram); +} + + +rapidjson::Value GetOutputKeys::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, outputs, outputs); + + return val; +} + +void GetOutputKeys::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, outputs, outputs); +} + +rapidjson::Value GetOutputKeys::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, keys, keys); + + return val; +} + +void GetOutputKeys::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, keys, keys); +} + + +rapidjson::Value GetRPCVersion::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetRPCVersion::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetRPCVersion::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, version); + + return val; +} + +void GetRPCVersion::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, version, version); +} + +rapidjson::Value GetPerKBFeeEstimate::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, num_grace_blocks, num_grace_blocks); + + return val; +} + +void GetPerKBFeeEstimate::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, num_grace_blocks, num_grace_blocks); +} + +rapidjson::Value GetPerKBFeeEstimate::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, estimated_fee_per_kb, estimated_fee_per_kb); + + return val; +} + +void GetPerKBFeeEstimate::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, estimated_fee_per_kb, estimated_fee_per_kb); +} + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h new file mode 100644 index 000000000..685f66843 --- /dev/null +++ b/src/rpc/daemon_messages.h @@ -0,0 +1,421 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "message.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "rpc/message_data_structs.h" +#include "rpc/daemon_rpc_version.h" +#include "cryptonote_basic/cryptonote_basic.h" + +#define BEGIN_RPC_MESSAGE_CLASS(classname) \ +class classname \ +{ \ + public: \ + static const char* const name; + +#define BEGIN_RPC_MESSAGE_REQUEST \ + class Request : public Message \ + { \ + public: \ + Request() { } \ + ~Request() { } \ + rapidjson::Value toJson(rapidjson::Document& doc) const; \ + void fromJson(rapidjson::Value& val); + +#define BEGIN_RPC_MESSAGE_RESPONSE \ + class Response : public Message \ + { \ + public: \ + Response() { } \ + ~Response() { } \ + rapidjson::Value toJson(rapidjson::Document& doc) const; \ + void fromJson(rapidjson::Value& val); + +#define END_RPC_MESSAGE_REQUEST }; +#define END_RPC_MESSAGE_RESPONSE }; +#define END_RPC_MESSAGE_CLASS }; + +#define COMMA() , + +// NOTE: when using a type with multiple template parameters, +// replace any comma in the template specifier with the macro +// above, or the preprocessor will eat the comma in a bad way. +#define RPC_MESSAGE_MEMBER(type, name) type name; + + +namespace cryptonote +{ + +namespace rpc +{ + +BEGIN_RPC_MESSAGE_CLASS(GetHeight); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetBlocksFast); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, block_ids); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(bool, prune); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::block_with_transactions>, blocks); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(uint64_t, current_height); + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::block_output_indices>, output_indices); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetHashesFast); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, known_hashes); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, hashes); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(uint64_t, current_height); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetTransactions); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<crypto::hash>, tx_hashes); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::unordered_map<crypto::hash COMMA() cryptonote::rpc::transaction_info>, txs); + RPC_MESSAGE_MEMBER(std::vector<crypto::hash>, missed_hashes); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(KeyImagesSpent); + enum STATUS { + UNSPENT = 0, + SPENT_IN_BLOCKCHAIN = 1, + SPENT_IN_POOL = 2, + }; + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<crypto::key_image>, key_images); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, spent_status); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetTxGlobalOutputIndices); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(crypto::hash, tx_hash); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, output_indices); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetRandomOutputsForAmounts); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, amounts); + RPC_MESSAGE_MEMBER(uint64_t, count); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<amount_with_random_outputs>, amounts_with_outputs); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SendRawTx); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(cryptonote::transaction, tx); + RPC_MESSAGE_MEMBER(bool, relay); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(bool, relayed); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StartMining); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::string, miner_address); + RPC_MESSAGE_MEMBER(uint64_t, threads_count); + RPC_MESSAGE_MEMBER(bool, do_background_mining); + RPC_MESSAGE_MEMBER(bool, ignore_battery); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetInfo); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(DaemonInfo, info); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopMining); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(MiningStatus); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(bool, active); + RPC_MESSAGE_MEMBER(uint64_t, speed); + RPC_MESSAGE_MEMBER(uint64_t, threads_count); + RPC_MESSAGE_MEMBER(std::string, address); + RPC_MESSAGE_MEMBER(bool, is_background_mining_enabled); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SaveBC); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHash); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(crypto::hash, hash); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockTemplate); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SubmitBlock); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetLastBlockHeader); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeaderByHash); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(crypto::hash, hash); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeaderByHeight); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeadersByHeight); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, heights); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::BlockHeaderResponse>, headers); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlock); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetPeerList); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<peer>, white_list); + RPC_MESSAGE_MEMBER(std::vector<peer>, gray_list); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetLogHashRate); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetLogLevel); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(int8_t, level); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetTransactionPool); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::tx_in_pool>, transactions); + RPC_MESSAGE_MEMBER(key_images_with_tx_hashes, key_images); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetConnections); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeadersRange); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopDaemon); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StartSaveGraph); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopSaveGraph); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(HardForkInfo); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint8_t, version); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(hard_fork_info, info); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBans); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetBans); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(FlushTransactionPool); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetOutputHistogram); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, amounts); + RPC_MESSAGE_MEMBER(uint64_t, min_count); + RPC_MESSAGE_MEMBER(uint64_t, max_count); + RPC_MESSAGE_MEMBER(bool, unlocked); + RPC_MESSAGE_MEMBER(uint64_t, recent_cutoff); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<output_amount_count>, histogram); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetOutputKeys); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<output_amount_and_index>, outputs); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<output_key_mask_unlocked>, keys); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetRPCVersion); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint32_t, version); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetPerKBFeeEstimate); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, num_grace_blocks); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint64_t, estimated_fee_per_kb); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_rpc_version.h b/src/rpc/daemon_rpc_version.h new file mode 100644 index 000000000..bb735fe7c --- /dev/null +++ b/src/rpc/daemon_rpc_version.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +namespace cryptonote +{ + +namespace rpc +{ + +static const uint32_t DAEMON_RPC_VERSION_ZMQ_MINOR = 0; +static const uint32_t DAEMON_RPC_VERSION_ZMQ_MAJOR = 1; + +static const uint32_t DAEMON_RPC_VERSION_ZMQ = DAEMON_RPC_VERSION_ZMQ_MINOR + (DAEMON_RPC_VERSION_ZMQ_MAJOR << 16); + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp new file mode 100644 index 000000000..086820180 --- /dev/null +++ b/src/rpc/message.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "message.h" +#include "daemon_rpc_version.h" +#include "serialization/json_object.h" + +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +namespace cryptonote +{ + +namespace rpc +{ + +const char* Message::STATUS_OK = "OK"; +const char* Message::STATUS_RETRY = "Retry"; +const char* Message::STATUS_FAILED = "Failed"; +const char* Message::STATUS_BAD_REQUEST = "Invalid request type"; +const char* Message::STATUS_BAD_JSON = "Malformed json"; + +rapidjson::Value Message::toJson(rapidjson::Document& doc) const +{ + rapidjson::Value val(rapidjson::kObjectType); + + auto& al = doc.GetAllocator(); + + val.AddMember("status", rapidjson::StringRef(status.c_str()), al); + val.AddMember("error_details", rapidjson::StringRef(error_details.c_str()), al); + INSERT_INTO_JSON_OBJECT(val, doc, rpc_version, DAEMON_RPC_VERSION_ZMQ); + + return val; +} + +void Message::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, status, status); + GET_FROM_JSON_OBJECT(val, error_details, error_details); + GET_FROM_JSON_OBJECT(val, rpc_version, rpc_version); +} + + +FullMessage::FullMessage(const std::string& request, Message* message) +{ + doc.SetObject(); + + doc.AddMember("method", rapidjson::StringRef(request.c_str()), doc.GetAllocator()); + doc.AddMember("params", message->toJson(doc), doc.GetAllocator()); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", rapidjson::Value("2.0"), doc.GetAllocator()); +} + +FullMessage::FullMessage(Message* message) +{ + doc.SetObject(); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", "2.0", doc.GetAllocator()); + + if (message->status == Message::STATUS_OK) + { + doc.AddMember("response", message->toJson(doc), doc.GetAllocator()); + } + else + { + cryptonote::rpc::error err; + + err.error_str = message->status; + err.message = message->error_details; + + INSERT_INTO_JSON_OBJECT(doc, doc, error, err); + } +} + +FullMessage::FullMessage(const std::string& json_string, bool request) +{ + doc.Parse(json_string.c_str()); + if (doc.HasParseError()) + { + throw cryptonote::json::PARSE_FAIL(); + } + + OBJECT_HAS_MEMBER_OR_THROW(doc, "jsonrpc") + + if (request) + { + OBJECT_HAS_MEMBER_OR_THROW(doc, "method") + OBJECT_HAS_MEMBER_OR_THROW(doc, "params") + } + else + { + if (!doc.HasMember("response") && !doc.HasMember("error")) + { + throw cryptonote::json::MISSING_KEY("error/response"); + } + } +} + +std::string FullMessage::getJson() +{ + + if (!doc.HasMember("id")) + { + doc.AddMember("id", rapidjson::Value("unused"), doc.GetAllocator()); + } + + rapidjson::StringBuffer buf; + + rapidjson::Writer<rapidjson::StringBuffer> writer(buf); + + doc.Accept(writer); + + return std::string(buf.GetString(), buf.GetSize()); +} + +std::string FullMessage::getRequestType() const +{ + OBJECT_HAS_MEMBER_OR_THROW(doc, "method") + return doc["method"].GetString(); +} + +rapidjson::Value& FullMessage::getMessage() +{ + if (doc.HasMember("params")) + { + return doc["params"]; + } + else if (doc.HasMember("response")) + { + return doc["response"]; + } + + //else + OBJECT_HAS_MEMBER_OR_THROW(doc, "error") + return doc["error"]; + +} + +rapidjson::Value FullMessage::getMessageCopy() +{ + rapidjson::Value& val = getMessage(); + + return rapidjson::Value(val, doc.GetAllocator()); +} + +rapidjson::Value& FullMessage::getID() +{ + OBJECT_HAS_MEMBER_OR_THROW(doc, "id") + return doc["id"]; +} + +void FullMessage::setID(rapidjson::Value& id) +{ + auto itr = doc.FindMember("id"); + if (itr != doc.MemberEnd()) + { + itr->value = id; + } + else + { + doc.AddMember("id", id, doc.GetAllocator()); + } +} + +cryptonote::rpc::error FullMessage::getError() +{ + cryptonote::rpc::error err; + err.use = false; + if (doc.HasMember("error")) + { + GET_FROM_JSON_OBJECT(doc, err, error); + err.use = true; + } + + return err; +} + +FullMessage FullMessage::requestMessage(const std::string& request, Message* message) +{ + return FullMessage(request, message); +} + +FullMessage FullMessage::requestMessage(const std::string& request, Message* message, rapidjson::Value& id) +{ + auto mes = requestMessage(request, message); + mes.setID(id); + return mes; +} + +FullMessage FullMessage::responseMessage(Message* message) +{ + return FullMessage(message); +} + +FullMessage FullMessage::responseMessage(Message* message, rapidjson::Value& id) +{ + auto mes = responseMessage(message); + mes.setID(id); + return mes; +} + +FullMessage* FullMessage::timeoutMessage() +{ + auto *full_message = new FullMessage(); + + auto& doc = full_message->doc; + auto& al = full_message->doc.GetAllocator(); + + doc.SetObject(); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", "2.0", al); + + cryptonote::rpc::error err; + + err.error_str = "RPC request timed out."; + INSERT_INTO_JSON_OBJECT(doc, doc, err, err); + + return full_message; +} + +// convenience functions for bad input +std::string BAD_REQUEST(const std::string& request) +{ + Message fail; + fail.status = Message::STATUS_BAD_REQUEST; + fail.error_details = std::string("\"") + request + "\" is not a valid request."; + + FullMessage fail_response = FullMessage::responseMessage(&fail); + + return fail_response.getJson(); +} + +std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id) +{ + Message fail; + fail.status = Message::STATUS_BAD_REQUEST; + fail.error_details = std::string("\"") + request + "\" is not a valid request."; + + FullMessage fail_response = FullMessage::responseMessage(&fail, id); + + return fail_response.getJson(); +} + +std::string BAD_JSON(const std::string& error_details) +{ + Message fail; + fail.status = Message::STATUS_BAD_JSON; + fail.error_details = error_details; + + FullMessage fail_response = FullMessage::responseMessage(&fail); + + return fail_response.getJson(); +} + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message.h b/src/rpc/message.h new file mode 100644 index 000000000..d1abe3fbe --- /dev/null +++ b/src/rpc/message.h @@ -0,0 +1,131 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "rapidjson/document.h" +#include "rpc/message_data_structs.h" +#include <string> + +/* I normally hate using macros, but in this case it would be untenably + * verbose to not use a macro. This macro saves the trouble of explicitly + * writing the below if block for every single RPC call. + */ +#define REQ_RESP_TYPES_MACRO( runtime_str, type, reqjson, resp_message_ptr, handler) \ + \ + if (runtime_str == type::name) \ + { \ + type::Request reqvar; \ + type::Response *respvar = new type::Response(); \ + \ + reqvar.fromJson(reqjson); \ + \ + handler(reqvar, *respvar); \ + \ + resp_message_ptr = respvar; \ + } + +namespace cryptonote +{ + +namespace rpc +{ + + class Message + { + public: + static const char* STATUS_OK; + static const char* STATUS_RETRY; + static const char* STATUS_FAILED; + static const char* STATUS_BAD_REQUEST; + static const char* STATUS_BAD_JSON; + + Message() : status(STATUS_OK) { } + + virtual ~Message() { } + + virtual rapidjson::Value toJson(rapidjson::Document& doc) const; + + virtual void fromJson(rapidjson::Value& val); + + std::string status; + std::string error_details; + uint32_t rpc_version; + }; + + class FullMessage + { + public: + ~FullMessage() { } + + FullMessage(FullMessage&& rhs) noexcept : doc(std::move(rhs.doc)) { } + + FullMessage(const std::string& json_string, bool request=false); + + std::string getJson(); + + std::string getRequestType() const; + + rapidjson::Value& getMessage(); + + rapidjson::Value getMessageCopy(); + + rapidjson::Value& getID(); + + void setID(rapidjson::Value& id); + + cryptonote::rpc::error getError(); + + static FullMessage requestMessage(const std::string& request, Message* message); + static FullMessage requestMessage(const std::string& request, Message* message, rapidjson::Value& id); + + static FullMessage responseMessage(Message* message); + static FullMessage responseMessage(Message* message, rapidjson::Value& id); + + static FullMessage* timeoutMessage(); + private: + + FullMessage() = default; + + FullMessage(const std::string& request, Message* message); + FullMessage(Message* message); + + rapidjson::Document doc; + }; + + + // convenience functions for bad input + std::string BAD_REQUEST(const std::string& request); + std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id); + + std::string BAD_JSON(const std::string& error_details); + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h new file mode 100644 index 000000000..00f1e0caa --- /dev/null +++ b/src/rpc/message_data_structs.h @@ -0,0 +1,189 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "crypto/hash.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "ringct/rctSigs.h" + +#include <unordered_map> +#include <vector> + +namespace cryptonote +{ + +namespace rpc +{ + + struct block_with_transactions + { + cryptonote::block block; + std::unordered_map<crypto::hash, cryptonote::transaction> transactions; + }; + + typedef std::vector<uint64_t> tx_output_indices; + + typedef std::vector<tx_output_indices> block_output_indices; + + struct transaction_info + { + cryptonote::transaction transaction; + bool in_pool; + uint64_t height; + }; + + struct output_key_and_amount_index + { + uint64_t amount_index; + crypto::public_key key; + }; + + typedef std::vector<output_key_and_amount_index> outputs_for_amount; + + struct amount_with_random_outputs + { + uint64_t amount; + outputs_for_amount outputs; + }; + + struct peer + { + uint64_t id; + uint32_t ip; + uint16_t port; + uint64_t last_seen; + }; + + struct tx_in_pool + { + cryptonote::transaction tx; + crypto::hash tx_hash; + uint64_t blob_size; + uint64_t fee; + crypto::hash max_used_block_hash; + uint64_t max_used_block_height; + bool kept_by_block; + crypto::hash last_failed_block_hash; + uint64_t last_failed_block_height; + uint64_t receive_time; + uint64_t last_relayed_time; + bool relayed; + bool do_not_relay; + }; + + typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes; + + struct output_amount_count + { + uint64_t amount; + uint64_t total_count; + uint64_t unlocked_count; + uint64_t recent_count; + }; + + struct output_amount_and_index + { + uint64_t amount; + uint64_t index; + }; + + struct output_key_mask_unlocked + { + crypto::public_key key; + rct::key mask; + bool unlocked; + }; + + struct hard_fork_info + { + uint8_t version; + bool enabled; + uint32_t window; + uint32_t votes; + uint32_t threshold; + uint8_t voting; + uint32_t state; + uint64_t earliest_height; + }; + + //required by JSON-RPC 2.0 spec + struct error + { + // not really using code, maybe later. + error() : use(false), code(1) { } + + bool use; // do not serialize + + int32_t code; + + // not required by spec, but int error codes aren't perfect + std::string error_str; + + std::string message; + + //TODO: data member? not required, may want later. + }; + + struct BlockHeaderResponse + { + uint64_t major_version; + uint64_t minor_version; + uint64_t timestamp; + crypto::hash prev_id; + uint32_t nonce; + uint64_t height; + uint64_t depth; + crypto::hash hash; + uint64_t difficulty; + uint64_t reward; + }; + + struct DaemonInfo + { + uint64_t height; + uint64_t target_height; + uint64_t difficulty; + uint64_t target; + uint64_t tx_count; + uint64_t tx_pool_size; + uint64_t alt_blocks_count; + uint64_t outgoing_connections_count; + uint64_t incoming_connections_count; + uint64_t white_peerlist_size; + uint64_t grey_peerlist_size; + bool testnet; + crypto::hash top_block_hash; + uint64_t cumulative_difficulty; + uint64_t block_size_limit; + uint64_t start_time; + }; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h new file mode 100644 index 000000000..d75180199 --- /dev/null +++ b/src/rpc/rpc_handler.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> + +namespace cryptonote +{ + +namespace rpc +{ + + +class RpcHandler +{ + public: + + virtual std::string handle(const std::string& request) = 0; + + RpcHandler() { } + + virtual ~RpcHandler() { } +}; + + +} // rpc + +} // cryptonote diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp new file mode 100644 index 000000000..afdff2328 --- /dev/null +++ b/src/rpc/zmq_server.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "zmq_server.h" +#include <boost/chrono/chrono.hpp> + +namespace cryptonote +{ + +namespace rpc +{ + +ZmqServer::ZmqServer(RpcHandler& h) : + handler(h), + stop_signal(false), + running(false), + context(DEFAULT_NUM_ZMQ_THREADS) // TODO: make this configurable +{ +} + +ZmqServer::~ZmqServer() +{ +} + +void ZmqServer::serve() +{ + + while (1) + { + try + { + zmq::message_t message; + + if (!rep_socket) + { + throw std::runtime_error("ZMQ RPC server reply socket is null"); + } + while (rep_socket->recv(&message)) + { + std::string message_string(reinterpret_cast<const char *>(message.data()), message.size()); + + MDEBUG(std::string("Received RPC request: \"") + message_string + "\""); + + std::string response = handler.handle(message_string); + + zmq::message_t reply(response.size()); + memcpy((void *) reply.data(), response.c_str(), response.size()); + + rep_socket->send(reply); + MDEBUG(std::string("Sent RPC reply: \"") + response + "\""); + + } + } + catch (const boost::thread_interrupted& e) + { + MDEBUG("ZMQ Server thread interrupted."); + } + catch (const zmq::error_t& e) + { + MERROR(std::string("ZMQ error: ") + e.what()); + } + boost::this_thread::interruption_point(); + } +} + +bool ZmqServer::addIPCSocket(std::string address, std::string port) +{ + MERROR("ZmqServer::addIPCSocket not yet implemented!"); + return false; +} + +bool ZmqServer::addTCPSocket(std::string address, std::string port) +{ + try + { + std::string addr_prefix("tcp://"); + + rep_socket.reset(new zmq::socket_t(context, ZMQ_REP)); + + rep_socket->setsockopt(ZMQ_RCVTIMEO, DEFAULT_RPC_RECV_TIMEOUT_MS); + + std::string bind_address = addr_prefix + address + std::string(":") + port; + rep_socket->bind(bind_address.c_str()); + } + catch (const std::exception& e) + { + MERROR(std::string("Error creating ZMQ Socket: ") + e.what()); + return false; + } + return true; +} + +void ZmqServer::run() +{ + running = true; + run_thread = boost::thread(boost::bind(&ZmqServer::serve, this)); +} + +void ZmqServer::stop() +{ + if (!running) return; + + stop_signal = true; + + run_thread.interrupt(); + run_thread.join(); + + running = false; + + return; +} + + +} // namespace cryptonote + +} // namespace rpc diff --git a/src/rpc/zmq_server.h b/src/rpc/zmq_server.h new file mode 100644 index 000000000..c278ff759 --- /dev/null +++ b/src/rpc/zmq_server.h @@ -0,0 +1,83 @@ +// Copyright (c) 2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <boost/thread/thread.hpp> +#include <zmq.hpp> +#include <string> +#include <memory> + +#include "common/command_line.h" + +#include "rpc_handler.h" + +namespace cryptonote +{ + +namespace rpc +{ + +static constexpr int DEFAULT_NUM_ZMQ_THREADS = 1; +static constexpr int DEFAULT_RPC_RECV_TIMEOUT_MS = 1000; + +class ZmqServer +{ + public: + + ZmqServer(RpcHandler& h); + + ~ZmqServer(); + + static void init_options(boost::program_options::options_description& desc); + + void serve(); + + bool addIPCSocket(std::string address, std::string port); + bool addTCPSocket(std::string address, std::string port); + + void run(); + void stop(); + + private: + RpcHandler& handler; + + volatile bool stop_signal; + volatile bool running; + + zmq::context_t context; + + boost::thread run_thread; + + std::unique_ptr<zmq::socket_t> rep_socket; +}; + + +} // namespace cryptonote + +} // namespace rpc diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt new file mode 100644 index 000000000..e4aa0b6a2 --- /dev/null +++ b/src/serialization/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (c) 2016, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(serialization_sources + json_object.cpp) + +set(serialization_headers) + +set(serialization_private_headers + json_object.h) + +monero_private_headers(serialization + ${serialization_private_headers}) +monero_add_library(serialization + ${serialization_sources} + ${serialization_headers} + ${serialization_private_headers}) +target_link_libraries(serialization + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + ${Boost_CHRONO_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${EXTRA_LIBRARIES}) +add_dependencies(serialization + version) + diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp new file mode 100644 index 000000000..e35389f9c --- /dev/null +++ b/src/serialization/json_object.cpp @@ -0,0 +1,1167 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "json_object.h" + +#include <limits> +#include <type_traits> +#include "string_tools.h" + +namespace cryptonote +{ + +namespace json +{ + +namespace +{ + template<typename Source, typename Destination> + constexpr bool precision_loss() + { + return + std::numeric_limits<Destination>::is_signed != std::numeric_limits<Source>::is_signed || + std::numeric_limits<Destination>::min() > std::numeric_limits<Source>::min() || + std::numeric_limits<Destination>::max() < std::numeric_limits<Source>::max(); + } + + template<typename Source, typename Type> + void convert_numeric(Source source, Type& i) + { + static_assert( + (std::is_same<Type, char>() && std::is_same<Source, int>()) || + std::numeric_limits<Source>::is_signed == std::numeric_limits<Type>::is_signed, + "comparisons below may have undefined behavior" + ); + if (source < std::numeric_limits<Type>::min()) + { + throw WRONG_TYPE{"numeric underflow"}; + } + if (std::numeric_limits<Type>::max() < source) + { + throw WRONG_TYPE{"numeric overflow"}; + } + i = Type(source); + } + + template<typename Type> + void to_int(const rapidjson::Value& val, Type& i) + { + if (!val.IsInt()) + { + throw WRONG_TYPE{"integer"}; + } + convert_numeric(val.GetInt(), i); + } + template<typename Type> + void to_int64(const rapidjson::Value& val, Type& i) + { + if (!val.IsInt64()) + { + throw WRONG_TYPE{"integer"}; + } + convert_numeric(val.GetInt64(), i); + } + + template<typename Type> + void to_uint(const rapidjson::Value& val, Type& i) + { + if (!val.IsUint()) + { + throw WRONG_TYPE{"unsigned integer"}; + } + convert_numeric(val.GetUint(), i); + } + template<typename Type> + void to_uint64(const rapidjson::Value& val, Type& i) + { + if (!val.IsUint64()) + { + throw WRONG_TYPE{"unsigned integer"}; + } + convert_numeric(val.GetUint64(), i); + } +} + +void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val) +{ + val = rapidjson::Value(i.c_str(), doc.GetAllocator()); +} + +void fromJsonValue(const rapidjson::Value& val, std::string& str) +{ + if (!val.IsString()) + { + throw WRONG_TYPE("string"); + } + + str = val.GetString(); +} + +void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val) +{ + val.SetBool(i); +} + +void fromJsonValue(const rapidjson::Value& val, bool& b) +{ + if (!val.IsBool()) + { + throw WRONG_TYPE("boolean"); + } + b = val.GetBool(); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned char& i) +{ + to_uint(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, char& i) +{ + to_int(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, signed char& i) +{ + to_int(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned short& i) +{ + to_uint(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, short& i) +{ + to_int(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const unsigned int i, rapidjson::Value& val) +{ + val = rapidjson::Value(i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned int& i) +{ + to_uint(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const int i, rapidjson::Value& val) +{ + val = rapidjson::Value(i); +} + +void fromJsonValue(const rapidjson::Value& val, int& i) +{ + to_int(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val) +{ + static_assert(!precision_loss<unsigned long long, std::uint64_t>(), "precision loss"); + val = rapidjson::Value(std::uint64_t(i)); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned long long& i) +{ + to_uint64(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val) +{ + static_assert(!precision_loss<long long, std::int64_t>(), "precision loss"); + val = rapidjson::Value(std::int64_t(i)); +} + +void fromJsonValue(const rapidjson::Value& val, long long& i) +{ + to_int64(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned long& i) +{ + to_uint64(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, long& i) +{ + to_int64(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, tx.version); + INSERT_INTO_JSON_OBJECT(val, doc, unlock_time, tx.unlock_time); + INSERT_INTO_JSON_OBJECT(val, doc, vin, tx.vin); + INSERT_INTO_JSON_OBJECT(val, doc, vout, tx.vout); + INSERT_INTO_JSON_OBJECT(val, doc, extra, tx.extra); + INSERT_INTO_JSON_OBJECT(val, doc, signatures, tx.signatures); + INSERT_INTO_JSON_OBJECT(val, doc, rct_signatures, tx.rct_signatures); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx.version, version); + GET_FROM_JSON_OBJECT(val, tx.unlock_time, unlock_time); + GET_FROM_JSON_OBJECT(val, tx.vin, vin); + GET_FROM_JSON_OBJECT(val, tx.vout, vout); + GET_FROM_JSON_OBJECT(val, tx.extra, extra); + GET_FROM_JSON_OBJECT(val, tx.signatures, signatures); + GET_FROM_JSON_OBJECT(val, tx.rct_signatures, rct_signatures); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, major_version, b.major_version); + INSERT_INTO_JSON_OBJECT(val, doc, minor_version, b.minor_version); + INSERT_INTO_JSON_OBJECT(val, doc, timestamp, b.timestamp); + INSERT_INTO_JSON_OBJECT(val, doc, prev_id, b.prev_id); + INSERT_INTO_JSON_OBJECT(val, doc, nonce, b.nonce); + INSERT_INTO_JSON_OBJECT(val, doc, miner_tx, b.miner_tx); + INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, b.tx_hashes); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, b.major_version, major_version); + GET_FROM_JSON_OBJECT(val, b.minor_version, minor_version); + GET_FROM_JSON_OBJECT(val, b.timestamp, timestamp); + GET_FROM_JSON_OBJECT(val, b.prev_id, prev_id); + GET_FROM_JSON_OBJECT(val, b.nonce, nonce); + GET_FROM_JSON_OBJECT(val, b.miner_tx, miner_tx); + GET_FROM_JSON_OBJECT(val, b.tx_hashes, tx_hashes); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val) +{ + val.SetObject(); + + if (txin.type() == typeid(cryptonote::txin_gen)) + { + val.AddMember("type", "txin_gen", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_gen>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_script)) + { + val.AddMember("type", "txin_to_script", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_script>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_scripthash)) + { + val.AddMember("type", "txin_to_scripthash", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_scripthash>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_key)) + { + val.AddMember("type", "txin_to_key", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_key>(txin)); + } +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "type") + OBJECT_HAS_MEMBER_OR_THROW(val, "value") + if (val["type"]== "txin_gen") + { + cryptonote::txin_gen tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"]== "txin_to_script") + { + cryptonote::txin_to_script tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"] == "txin_to_scripthash") + { + cryptonote::txin_to_scripthash tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"] == "txin_to_key") + { + cryptonote::txin_to_key tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, txin.height); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.height, height); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.prev, prev); + GET_FROM_JSON_OBJECT(val, txin.prevout, prevout); + GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(val, doc, script, txin.script); + INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.prev, prev); + GET_FROM_JSON_OBJECT(val, txin.prevout, prevout); + GET_FROM_JSON_OBJECT(val, txin.script, script); + GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, txin.amount); + INSERT_INTO_JSON_OBJECT(val, doc, key_offsets, txin.key_offsets); + INSERT_INTO_JSON_OBJECT(val, doc, k_image, txin.k_image); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.amount, amount); + GET_FROM_JSON_OBJECT(val, txin.key_offsets, key_offsets); + GET_FROM_JSON_OBJECT(val, txin.k_image, k_image); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val) +{ + val.SetObject(); + + if (txout.type() == typeid(cryptonote::txout_to_script)) + { + val.AddMember("type", "txout_to_script", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_script>(txout)); + } + else if (txout.type() == typeid(cryptonote::txout_to_scripthash)) + { + val.AddMember("type", "txout_to_scripthash", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_scripthash>(txout)); + } + else if (txout.type() == typeid(cryptonote::txout_to_key)) + { + val.AddMember("type", "txout_to_key", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_key>(txout)); + } +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "type") + OBJECT_HAS_MEMBER_OR_THROW(val, "value") + if (val["type"]== "txout_to_script") + { + cryptonote::txout_to_script tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } + else if (val["type"] == "txout_to_scripthash") + { + cryptonote::txout_to_scripthash tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } + else if (val["type"] == "txout_to_key") + { + cryptonote::txout_to_key tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, keys, txout.keys); + INSERT_INTO_JSON_OBJECT(val, doc, script, txout.script); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.keys, keys); + GET_FROM_JSON_OBJECT(val, txout.script, script); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, txout.hash); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.hash, hash); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, key, txout.key); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.key, key); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, txout.amount); + INSERT_INTO_JSON_OBJECT(val, doc, target, txout.target); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.amount, amount); + GET_FROM_JSON_OBJECT(val, txout.target, target); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val) +{ + val.SetObject(); + + auto& al = doc.GetAllocator(); + INSERT_INTO_JSON_OBJECT(val, doc, incoming, info.incoming); + INSERT_INTO_JSON_OBJECT(val, doc, localhost, info.localhost); + INSERT_INTO_JSON_OBJECT(val, doc, local_ip, info.local_ip); + + INSERT_INTO_JSON_OBJECT(val, doc, ip, info.ip); + INSERT_INTO_JSON_OBJECT(val, doc, port, info.port); + + INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id); + + INSERT_INTO_JSON_OBJECT(val, doc, recv_count, info.recv_count); + INSERT_INTO_JSON_OBJECT(val, doc, recv_idle_time, info.recv_idle_time); + + INSERT_INTO_JSON_OBJECT(val, doc, send_count, info.send_count); + INSERT_INTO_JSON_OBJECT(val, doc, send_idle_time, info.send_idle_time); + + INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); + + INSERT_INTO_JSON_OBJECT(val, doc, live_time, info.live_time); + + INSERT_INTO_JSON_OBJECT(val, doc, avg_download, info.avg_download); + INSERT_INTO_JSON_OBJECT(val, doc, current_download, info.current_download); + + INSERT_INTO_JSON_OBJECT(val, doc, avg_upload, info.avg_upload); + INSERT_INTO_JSON_OBJECT(val, doc, current_upload, info.current_upload); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.incoming, incoming); + GET_FROM_JSON_OBJECT(val, info.localhost, localhost); + GET_FROM_JSON_OBJECT(val, info.local_ip, local_ip); + + GET_FROM_JSON_OBJECT(val, info.ip, ip); + GET_FROM_JSON_OBJECT(val, info.port, port); + + GET_FROM_JSON_OBJECT(val, info.peer_id, peer_id); + + GET_FROM_JSON_OBJECT(val, info.recv_count, recv_count); + GET_FROM_JSON_OBJECT(val, info.recv_idle_time, recv_idle_time); + + GET_FROM_JSON_OBJECT(val, info.send_count, send_count); + GET_FROM_JSON_OBJECT(val, info.send_idle_time, send_idle_time); + + GET_FROM_JSON_OBJECT(val, info.state, state); + + GET_FROM_JSON_OBJECT(val, info.live_time, live_time); + + GET_FROM_JSON_OBJECT(val, info.avg_download, avg_download); + GET_FROM_JSON_OBJECT(val, info.current_download, current_download); + + GET_FROM_JSON_OBJECT(val, info.avg_upload, avg_upload); + GET_FROM_JSON_OBJECT(val, info.current_upload, current_upload); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); + INSERT_INTO_JSON_OBJECT(val, doc, txs, blk.txs); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, blk.block, block); + GET_FROM_JSON_OBJECT(val, blk.txs, txs); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); + INSERT_INTO_JSON_OBJECT(val, doc, transactions, blk.transactions); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_transactions& blk) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, blk.block, block); + GET_FROM_JSON_OBJECT(val, blk.transactions, transactions); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, tx_info.height); + INSERT_INTO_JSON_OBJECT(val, doc, in_pool, tx_info.in_pool); + INSERT_INTO_JSON_OBJECT(val, doc, transaction, tx_info.transaction); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_info& tx_info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx_info.height, height); + GET_FROM_JSON_OBJECT(val, tx_info.in_pool, in_pool); + GET_FROM_JSON_OBJECT(val, tx_info.transaction, transaction); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount_index, out.amount_index); + INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_amount_index& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount_index, amount_index); + GET_FROM_JSON_OBJECT(val, out.key, key); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, outputs, out.outputs); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_random_outputs& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.outputs, outputs); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, id, peer.id); + INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip); + INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port); + INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, peer.id, id); + GET_FROM_JSON_OBJECT(val, peer.ip, ip); + GET_FROM_JSON_OBJECT(val, peer.port, port); + GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx, tx.tx); + INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx.tx_hash); + INSERT_INTO_JSON_OBJECT(val, doc, blob_size, tx.blob_size); + INSERT_INTO_JSON_OBJECT(val, doc, fee, tx.fee); + INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_hash, tx.max_used_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_height, tx.max_used_block_height); + INSERT_INTO_JSON_OBJECT(val, doc, kept_by_block, tx.kept_by_block); + INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_hash, tx.last_failed_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_height, tx.last_failed_block_height); + INSERT_INTO_JSON_OBJECT(val, doc, receive_time, tx.receive_time); + INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time); + INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed); + INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx.tx, tx); + GET_FROM_JSON_OBJECT(val, tx.blob_size, blob_size); + GET_FROM_JSON_OBJECT(val, tx.fee, fee); + GET_FROM_JSON_OBJECT(val, tx.max_used_block_hash, max_used_block_hash); + GET_FROM_JSON_OBJECT(val, tx.max_used_block_height, max_used_block_height); + GET_FROM_JSON_OBJECT(val, tx.kept_by_block, kept_by_block); + GET_FROM_JSON_OBJECT(val, tx.last_failed_block_hash, last_failed_block_hash); + GET_FROM_JSON_OBJECT(val, tx.last_failed_block_height, last_failed_block_height); + GET_FROM_JSON_OBJECT(val, tx.receive_time, receive_time); + GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time); + GET_FROM_JSON_OBJECT(val, tx.relayed, relayed); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, info.version); + INSERT_INTO_JSON_OBJECT(val, doc, enabled, info.enabled); + INSERT_INTO_JSON_OBJECT(val, doc, window, info.window); + INSERT_INTO_JSON_OBJECT(val, doc, votes, info.votes); + INSERT_INTO_JSON_OBJECT(val, doc, threshold, info.threshold); + INSERT_INTO_JSON_OBJECT(val, doc, voting, info.voting); + INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); + INSERT_INTO_JSON_OBJECT(val, doc, earliest_height, info.earliest_height); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.version, version); + GET_FROM_JSON_OBJECT(val, info.enabled, enabled); + GET_FROM_JSON_OBJECT(val, info.window, window); + GET_FROM_JSON_OBJECT(val, info.votes, votes); + GET_FROM_JSON_OBJECT(val, info.threshold, threshold); + GET_FROM_JSON_OBJECT(val, info.voting, voting); + GET_FROM_JSON_OBJECT(val, info.state, state); + GET_FROM_JSON_OBJECT(val, info.earliest_height, earliest_height); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, total_count, out.total_count); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked_count, out.unlocked_count); + INSERT_INTO_JSON_OBJECT(val, doc, recent_count, out.recent_count); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_count& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.total_count, total_count); + GET_FROM_JSON_OBJECT(val, out.unlocked_count, unlocked_count); + GET_FROM_JSON_OBJECT(val, out.recent_count, recent_count); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, index, out.index); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_and_index& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.index, index); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); + INSERT_INTO_JSON_OBJECT(val, doc, mask, out.mask); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked, out.unlocked); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.key, key); + GET_FROM_JSON_OBJECT(val, out.mask, mask); + GET_FROM_JSON_OBJECT(val, out.unlocked, unlocked); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, code, err.code); + INSERT_INTO_JSON_OBJECT(val, doc, error_str, err.error_str); + INSERT_INTO_JSON_OBJECT(val, doc, message, err.message); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, error.code, code); + GET_FROM_JSON_OBJECT(val, error.error_str, error_str); + GET_FROM_JSON_OBJECT(val, error.message, message); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, major_version, response.major_version); + INSERT_INTO_JSON_OBJECT(val, doc, minor_version, response.minor_version); + INSERT_INTO_JSON_OBJECT(val, doc, timestamp, response.timestamp); + INSERT_INTO_JSON_OBJECT(val, doc, prev_id, response.prev_id); + INSERT_INTO_JSON_OBJECT(val, doc, nonce, response.nonce); + INSERT_INTO_JSON_OBJECT(val, doc, height, response.height); + INSERT_INTO_JSON_OBJECT(val, doc, depth, response.depth); + INSERT_INTO_JSON_OBJECT(val, doc, hash, response.hash); + INSERT_INTO_JSON_OBJECT(val, doc, difficulty, response.difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, reward, response.reward); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, response.major_version, major_version); + GET_FROM_JSON_OBJECT(val, response.minor_version, minor_version); + GET_FROM_JSON_OBJECT(val, response.timestamp, timestamp); + GET_FROM_JSON_OBJECT(val, response.prev_id, prev_id); + GET_FROM_JSON_OBJECT(val, response.nonce, nonce); + GET_FROM_JSON_OBJECT(val, response.height, height); + GET_FROM_JSON_OBJECT(val, response.depth, depth); + GET_FROM_JSON_OBJECT(val, response.hash, hash); + GET_FROM_JSON_OBJECT(val, response.difficulty, difficulty); + GET_FROM_JSON_OBJECT(val, response.reward, reward); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rctSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, type, sig.type); + INSERT_INTO_JSON_OBJECT(val, doc, message, sig.message); + INSERT_INTO_JSON_OBJECT(val, doc, mixRing, sig.mixRing); + INSERT_INTO_JSON_OBJECT(val, doc, pseudoOuts, sig.pseudoOuts); + INSERT_INTO_JSON_OBJECT(val, doc, ecdhInfo, sig.ecdhInfo); + INSERT_INTO_JSON_OBJECT(val, doc, outPk, sig.outPk); + INSERT_INTO_JSON_OBJECT(val, doc, txnFee, sig.txnFee); + INSERT_INTO_JSON_OBJECT(val, doc, p, sig.p); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.type, type); + GET_FROM_JSON_OBJECT(val, sig.message, message); + GET_FROM_JSON_OBJECT(val, sig.mixRing, mixRing); + GET_FROM_JSON_OBJECT(val, sig.pseudoOuts, pseudoOuts); + GET_FROM_JSON_OBJECT(val, sig.ecdhInfo, ecdhInfo); + GET_FROM_JSON_OBJECT(val, sig.outPk, outPk); + GET_FROM_JSON_OBJECT(val, sig.txnFee, txnFee); + GET_FROM_JSON_OBJECT(val, sig.p, p); +} + +void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, dest, key.dest); + INSERT_INTO_JSON_OBJECT(val, doc, mask, key.mask); +} + +void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, key.dest, dest); + GET_FROM_JSON_OBJECT(val, key.mask, mask); +} + +void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, mask, tuple.mask); + INSERT_INTO_JSON_OBJECT(val, doc, amount, tuple.amount); +} + +void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tuple.mask, mask); + GET_FROM_JSON_OBJECT(val, tuple.amount, amount); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, rangeSigs, sig.rangeSigs); + INSERT_INTO_JSON_OBJECT(val, doc, MGs, sig.MGs); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.rangeSigs, rangeSigs); + GET_FROM_JSON_OBJECT(val, sig.MGs, MGs); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, asig, sig.asig); + + std::vector<rct::key> keyVector(sig.Ci, std::end(sig.Ci)); + INSERT_INTO_JSON_OBJECT(val, doc, Ci, keyVector); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.asig, asig); + + std::vector<rct::key> keyVector; + cryptonote::json::fromJsonValue(val["Ci"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.Ci[i] = keyVector[i]; + } +} + +void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + std::vector<rct::key> keyVector(sig.s0, std::end(sig.s0)); + INSERT_INTO_JSON_OBJECT(val, doc, s0, sig.s0); + + keyVector.assign(sig.s1, std::end(sig.s1)); + INSERT_INTO_JSON_OBJECT(val, doc, s1, sig.s1); + + INSERT_INTO_JSON_OBJECT(val, doc, ee, sig.ee); +} + +void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "s0") + std::vector<rct::key> keyVector; + cryptonote::json::fromJsonValue(val["s0"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.s0[i] = keyVector[i]; + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "s1") + keyVector.clear(); + cryptonote::json::fromJsonValue(val["s1"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.s1[i] = keyVector[i]; + } + + GET_FROM_JSON_OBJECT(val, sig.ee, ee); +} + +void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, ss, sig.ss); + INSERT_INTO_JSON_OBJECT(val, doc, cc, sig.cc); +} + +void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + + GET_FROM_JSON_OBJECT(val, sig.ss, ss); + GET_FROM_JSON_OBJECT(val, sig.cc, cc); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, info.height); + INSERT_INTO_JSON_OBJECT(val, doc, target_height, info.target_height); + INSERT_INTO_JSON_OBJECT(val, doc, difficulty, info.difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, target, info.target); + INSERT_INTO_JSON_OBJECT(val, doc, tx_count, info.tx_count); + INSERT_INTO_JSON_OBJECT(val, doc, tx_pool_size, info.tx_pool_size); + INSERT_INTO_JSON_OBJECT(val, doc, alt_blocks_count, info.alt_blocks_count); + INSERT_INTO_JSON_OBJECT(val, doc, outgoing_connections_count, info.outgoing_connections_count); + INSERT_INTO_JSON_OBJECT(val, doc, incoming_connections_count, info.incoming_connections_count); + INSERT_INTO_JSON_OBJECT(val, doc, white_peerlist_size, info.white_peerlist_size); + INSERT_INTO_JSON_OBJECT(val, doc, grey_peerlist_size, info.grey_peerlist_size); + INSERT_INTO_JSON_OBJECT(val, doc, testnet, info.testnet); + INSERT_INTO_JSON_OBJECT(val, doc, top_block_hash, info.top_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, cumulative_difficulty, info.cumulative_difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, block_size_limit, info.block_size_limit); + INSERT_INTO_JSON_OBJECT(val, doc, start_time, info.start_time); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.height, height); + GET_FROM_JSON_OBJECT(val, info.target_height, target_height); + GET_FROM_JSON_OBJECT(val, info.difficulty, difficulty); + GET_FROM_JSON_OBJECT(val, info.target, target); + GET_FROM_JSON_OBJECT(val, info.tx_count, tx_count); + GET_FROM_JSON_OBJECT(val, info.tx_pool_size, tx_pool_size); + GET_FROM_JSON_OBJECT(val, info.alt_blocks_count, alt_blocks_count); + GET_FROM_JSON_OBJECT(val, info.outgoing_connections_count, outgoing_connections_count); + GET_FROM_JSON_OBJECT(val, info.incoming_connections_count, incoming_connections_count); + GET_FROM_JSON_OBJECT(val, info.white_peerlist_size, white_peerlist_size); + GET_FROM_JSON_OBJECT(val, info.grey_peerlist_size, grey_peerlist_size); + GET_FROM_JSON_OBJECT(val, info.testnet, testnet); + GET_FROM_JSON_OBJECT(val, info.top_block_hash, top_block_hash); + GET_FROM_JSON_OBJECT(val, info.cumulative_difficulty, cumulative_difficulty); + GET_FROM_JSON_OBJECT(val, info.block_size_limit, block_size_limit); + GET_FROM_JSON_OBJECT(val, info.start_time, start_time); +} + +} // namespace json + +} // namespace cryptonote diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h new file mode 100644 index 000000000..7b9519c48 --- /dev/null +++ b/src/serialization/json_object.h @@ -0,0 +1,371 @@ +// Copyright (c) 2016-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "rapidjson/document.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "rpc/message_data_structs.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "common/sfinae_helpers.h" + +#define OBJECT_HAS_MEMBER_OR_THROW(val, key) \ + do \ + { \ + if (!val.HasMember(key)) \ + { \ + throw cryptonote::json::MISSING_KEY(key); \ + } \ + } while (0); + +#define INSERT_INTO_JSON_OBJECT(jsonVal, doc, key, source) \ + rapidjson::Value key##Val; \ + cryptonote::json::toJsonValue(doc, source, key##Val); \ + jsonVal.AddMember(#key, key##Val, doc.GetAllocator()); + +#define GET_FROM_JSON_OBJECT(source, dst, key) \ + OBJECT_HAS_MEMBER_OR_THROW(source, #key) \ + decltype(dst) dstVal##key; \ + cryptonote::json::fromJsonValue(source[#key], dstVal##key); \ + dst = dstVal##key; + +namespace cryptonote +{ + +namespace json +{ + +struct JSON_ERROR : public std::exception +{ + protected: + JSON_ERROR() { } + std::string m; + + public: + virtual ~JSON_ERROR() { } + + const char* what() const throw() + { + return m.c_str(); + } +}; + +struct MISSING_KEY : public JSON_ERROR +{ + MISSING_KEY(const char* key) + { + m = std::string("Key \"") + key + "\" missing from object."; + } +}; + +struct WRONG_TYPE : public JSON_ERROR +{ + WRONG_TYPE(const char* type) + { + m = std::string("Json value has incorrect type, expected: ") + type; + } +}; + +struct BAD_INPUT : public JSON_ERROR +{ + BAD_INPUT() + { + m = "An item failed to convert from json object to native object"; + } +}; + +struct PARSE_FAIL : public JSON_ERROR +{ + PARSE_FAIL() + { + m = "Failed to parse the json request"; + } +}; + +template<typename Type> +inline constexpr bool is_to_hex() +{ + return std::is_pod<Type>() && !std::is_integral<Type>(); +} + + +// POD to json value +template <class Type> +typename std::enable_if<is_to_hex<Type>()>::type toJsonValue(rapidjson::Document& doc, const Type& pod, rapidjson::Value& value) +{ + value = rapidjson::Value(epee::string_tools::pod_to_hex(pod).c_str(), doc.GetAllocator()); +} + +template <class Type> +typename std::enable_if<is_to_hex<Type>()>::type fromJsonValue(const rapidjson::Value& val, Type& t) +{ + if (!val.IsString()) + { + throw WRONG_TYPE("string"); + } + + //TODO: handle failure to convert hex string to POD type + bool success = epee::string_tools::hex_to_pod(val.GetString(), t); + + if (!success) + { + throw BAD_INPUT(); + } +} + +void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, std::string& str); + +void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, bool& b); + +// integers overloads for toJsonValue are not needed for standard promotions + +void fromJsonValue(const rapidjson::Value& val, unsigned char& i); + +void fromJsonValue(const rapidjson::Value& val, signed char& i); + +void fromJsonValue(const rapidjson::Value& val, char& i); + +void fromJsonValue(const rapidjson::Value& val, unsigned short& i); + +void fromJsonValue(const rapidjson::Value& val, short& i); + +void toJsonValue(rapidjson::Document& doc, const unsigned i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, unsigned& i); + +void toJsonValue(rapidjson::Document& doc, const int, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, int& i); + + +void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, unsigned long long& i); + +void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, long long& i); + +inline void toJsonValue(rapidjson::Document& doc, const unsigned long i, rapidjson::Value& val) { + toJsonValue(doc, static_cast<unsigned long long>(i), val); +} +void fromJsonValue(const rapidjson::Value& val, unsigned long& i); + +inline void toJsonValue(rapidjson::Document& doc, const long i, rapidjson::Value& val) { + toJsonValue(doc, static_cast<long long>(i), val); +} +void fromJsonValue(const rapidjson::Value& val, long& i); + +// end integers + +void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_transactions& blk); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_info& tx_info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_amount_index& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_random_outputs& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_count& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_and_index& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response); + +void toJsonValue(rapidjson::Document& doc, const rct::rctSig& i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& i, rct::rctSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key); + +void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple); + +void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info); + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val); + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map); + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val); + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec); + + +// ideally would like to have the below functions in the .cpp file, but +// unfortunately because of how templates work they have to be here. + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val) +{ + val.SetObject(); + + auto& al = doc.GetAllocator(); + + for (const auto& i : map) + { + rapidjson::Value k; + rapidjson::Value m; + toJsonValue(doc, i.first, k); + toJsonValue(doc, i.second, m); + val.AddMember(k, m, al); + } +} + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + auto itr = val.MemberBegin(); + + while (itr != val.MemberEnd()) + { + typename Map::key_type k; + typename Map::mapped_type m; + fromJsonValue(itr->name, k); + fromJsonValue(itr->value, m); + map.emplace(k, m); + ++itr; + } +} + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val) +{ + val.SetArray(); + + for (const auto& t : vec) + { + rapidjson::Value v; + toJsonValue(doc, t, v); + val.PushBack(v, doc.GetAllocator()); + } +} + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec) +{ + if (!val.IsArray()) + { + throw WRONG_TYPE("json array"); + } + + for (rapidjson::SizeType i=0; i < val.Size(); i++) + { + typename Vec::value_type v; + fromJsonValue(val[i], v); + vec.push_back(v); + } +} + +} // namespace json + +} // namespace cryptonote diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index 443e9b87e..b56085b8f 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -49,14 +49,13 @@ target_link_libraries(simplewallet common mnemonics p2p + version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -add_dependencies(simplewallet - version) set_property(TARGET simplewallet PROPERTY OUTPUT_NAME "monero-wallet-cli") diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 857e2af6e..d625634c8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -290,7 +290,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto return true; } -bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +bool simple_wallet::print_seed(bool encrypted) { bool success = false; std::string electrum_words; @@ -311,7 +311,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st m_wallet->set_seed_language(mnemonic_language); } - success = m_wallet->get_seed(electrum_words); + std::string seed_pass; + if (encrypted) + { + auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); + if (std::cin.eof() || !pwd_container) + return true; + seed_pass = pwd_container->password(); + } + + success = m_wallet->get_seed(electrum_words, seed_pass); } if (success) @@ -325,6 +334,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st return true; } +bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(false); +} + +bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(true); +} + bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if (m_wallet->watch_only()) @@ -757,6 +776,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog")); + m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed")); m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); @@ -768,6 +788,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information")); + m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information")); m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file")); m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); @@ -1026,6 +1047,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("Electrum-style word list failed verification"); return false; } + + auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none")); + if (std::cin.eof() || !pwd_container) + return false; + std::string seed_pass = pwd_container->password(); + if (!seed_pass.empty()) + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } if (!m_generate_from_view_key.empty()) { @@ -1680,9 +1708,18 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) catch (const std::exception& e) { fail_msg_writer() << tr("failed to load wallet: ") << e.what(); - // only suggest removing cache if the password was actually correct - if (m_wallet && m_wallet->verify_password(password)) - fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; + if (m_wallet) + { + // only suggest removing cache if the password was actually correct + bool password_is_correct = false; + try + { + password_is_correct = m_wallet->verify_password(password); + } + catch (...) { } // guard against I/O errors + if (password_is_correct) + fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; + } return false; } success_msg_writer() << @@ -2329,7 +2366,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } } - size_t fake_outs_count; + size_t fake_outs_count = 0; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -2906,7 +2943,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a std::vector<std::string> local_args = args_; - size_t fake_outs_count; + size_t fake_outs_count = 0; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -3474,6 +3511,9 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) catch (const tools::error::tx_rejected& e) { fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; } catch (const tools::error::tx_sum_overflow& e) { @@ -4507,6 +4547,15 @@ bool simple_wallet::status(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::wallet_info(const std::vector<std::string> &args) +{ + message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); + message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::sign(const std::vector<std::string> &args) { if (args.size() != 1) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 079fae9f5..3b29e3ca2 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -97,6 +97,7 @@ namespace cryptonote bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); bool spendkey(const std::vector<std::string> &args = std::vector<std::string>()); bool seed(const std::vector<std::string> &args = std::vector<std::string>()); + bool encrypted_seed(const std::vector<std::string> &args = std::vector<std::string>()); /*! * \brief Sets seed language. @@ -165,6 +166,7 @@ namespace cryptonote bool set_tx_note(const std::vector<std::string> &args); bool get_tx_note(const std::vector<std::string> &args); bool status(const std::vector<std::string> &args); + bool wallet_info(const std::vector<std::string> &args); bool set_default_priority(const std::vector<std::string> &args); bool sign(const std::vector<std::string> &args); bool verify(const std::vector<std::string> &args); @@ -185,6 +187,7 @@ namespace cryptonote bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); std::string get_prompt() const; + bool print_seed(bool encrypted); /*! * \brief Prints the seed with a nice message diff --git a/src/version.cmake b/src/version.cmake index 623d3cf1f..45a97cd20 100644 --- a/src/version.cmake +++ b/src/version.cmake @@ -36,7 +36,7 @@ if(RET) message(WARNING "Cannot determine current commit. Make sure that you are building either from a Git working tree or from a source archive.") set(VERSIONTAG "unknown") - configure_file("src/version.h.in" "${TO}") + configure_file("src/version.cpp.in" "${TO}") else() message(STATUS "You are currently on commit ${COMMIT}") @@ -59,5 +59,5 @@ else() endif() endif() - configure_file("src/version.h.in" "${TO}") + configure_file("src/version.cpp.in" "${TO}") endif() diff --git a/src/version.cpp.in b/src/version.cpp.in new file mode 100644 index 000000000..320a8d58a --- /dev/null +++ b/src/version.cpp.in @@ -0,0 +1,9 @@ +#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" +#define DEF_MONERO_VERSION "0.11.0.0" +#define DEF_MONERO_RELEASE_NAME "Helium Hydra" +#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG + +const char* const MONERO_VERSION_TAG = DEF_MONERO_VERSION_TAG; +const char* const MONERO_VERSION = DEF_MONERO_VERSION; +const char* const MONERO_RELEASE_NAME = DEF_MONERO_RELEASE_NAME; +const char* const MONERO_VERSION_FULL = DEF_MONERO_VERSION_FULL; diff --git a/src/version.h b/src/version.h new file mode 100644 index 000000000..d1d06c790 --- /dev/null +++ b/src/version.h @@ -0,0 +1,6 @@ +#pragma once + +extern const char* const MONERO_VERSION_TAG; +extern const char* const MONERO_VERSION; +extern const char* const MONERO_RELEASE_NAME; +extern const char* const MONERO_VERSION_FULL; diff --git a/src/version.h.in b/src/version.h.in deleted file mode 100644 index 281b52db4..000000000 --- a/src/version.h.in +++ /dev/null @@ -1,4 +0,0 @@ -#define MONERO_VERSION_TAG "@VERSIONTAG@" -#define MONERO_VERSION "0.11.0.0" -#define MONERO_RELEASE_NAME "Helium Hydra" -#define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 639080051..fe87d0de1 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -84,7 +84,6 @@ target_link_libraries(wallet ${Boost_REGEX_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(wallet version) if (NOT BUILD_GUI_DEPS) set(wallet_rpc_sources @@ -110,13 +109,13 @@ if (NOT BUILD_GUI_DEPS) cryptonote_core cncrypto common + version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) - add_dependencies(wallet_rpc_server version) set_property(TARGET wallet_rpc_server PROPERTY OUTPUT_NAME "monero-wallet-rpc") diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index 28f835ebd..a955cb166 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -42,7 +42,7 @@ namespace Monero { AddressBook::~AddressBook() {} AddressBookImpl::AddressBookImpl(WalletImpl *wallet) - : m_wallet(wallet) {} + : m_wallet(wallet), m_errorCode(Status_Ok) {} bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description) { diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7afc1f449..9cd72b543 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -303,7 +303,7 @@ WalletImpl::~WalletImpl() // Pause refresh thread - prevents refresh from starting again pauseRefresh(); // Close wallet - stores cache and stops ongoing refresh operation - close(); + close(false); // do not store wallet as part of the closing activities // Stop refresh thread stopRefresh(); delete m_wallet2Callback; @@ -566,19 +566,21 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return m_status == Status_Ok; } -bool WalletImpl::close() +bool WalletImpl::close(bool store) { bool result = false; LOG_PRINT_L1("closing wallet..."); try { - // Do not store wallet with invalid status - // Status Critical refers to errors on opening or creating wallets. - if (status() != Status_Critical) - m_wallet->store(); - else - LOG_ERROR("Status_Critical - not storing wallet"); - LOG_PRINT_L1("wallet::store done"); + if (store) { + // Do not store wallet with invalid status + // Status Critical refers to errors on opening or creating wallets. + if (status() != Status_Critical) + m_wallet->store(); + else + LOG_ERROR("Status_Critical - not storing wallet"); + LOG_PRINT_L1("wallet::store done"); + } LOG_PRINT_L1("Calling wallet::stop..."); m_wallet->stop(); LOG_PRINT_L1("wallet::stop done"); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 8190c7873..1e3d1e600 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -63,7 +63,7 @@ public: const std::string &address_string, const std::string &viewkey_string, const std::string &spendkey_string = ""); - bool close(); + bool close(bool store = true); std::string seed() const; std::string getSeedLanguage() const; void setSeedLanguage(const std::string &arg); @@ -153,7 +153,6 @@ private: std::string m_password; TransactionHistoryImpl * m_history; bool m_trustedDaemon; - WalletListener * m_walletListener; Wallet2CallbackImpl * m_wallet2Callback; AddressBookImpl * m_addressBook; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a23533530..897137d35 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -102,10 +102,12 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, return wallet; } -bool WalletManagerImpl::closeWallet(Wallet *wallet) +bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) { WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); - bool result = wallet_->close(); + if (!wallet_) + return false; + bool result = wallet_->close(store); if (!result) { m_errorString = wallet_->errorString(); } else { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index aa6ea439e..8455f0f16 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -48,7 +48,7 @@ public: const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString = ""); - virtual bool closeWallet(Wallet *wallet); + virtual bool closeWallet(Wallet *wallet, bool store = true); bool walletExists(const std::string &path); bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; std::vector<std::string> findWallets(const std::string &path); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 323a3a7fe..986f33073 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -45,6 +45,7 @@ using namespace epee; #include "cryptonote_basic/cryptonote_basic_impl.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" +#include "common/threadpool.h" #include "profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" @@ -89,14 +90,6 @@ using namespace cryptonote; #define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f -#define KILL_IOSERVICE() \ - do { \ - work.reset(); \ - while (!ioservice.stopped()) ioservice.poll(); \ - threadpool.join_all(); \ - ioservice.stop(); \ - } while(0) - #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" namespace @@ -296,6 +289,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, return false; } restore_deterministic_wallet = true; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_passphrase, std::string, String, false, std::string()); + if (field_seed_passphrase_found) + { + if (!field_seed_passphrase.empty()) + recovery_key = cryptonote::decrypt_key(recovery_key, field_seed_passphrase); + } } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); @@ -383,7 +383,11 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::account_public_address address2; bool has_payment_id; crypto::hash8 new_payment_id; - get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address); + if (!get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address)) + { + tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address; + return false; + } address.m_spend_public_key = address2.m_spend_public_key; } wallet->generate(field_filename, field_password, address, viewkey); @@ -527,7 +531,7 @@ bool wallet2::is_deterministic() const return keys_deterministic; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_seed(std::string& electrum_words) const +bool wallet2::get_seed(std::string& electrum_words, const std::string &passphrase) const { bool keys_deterministic = is_deterministic(); if (!keys_deterministic) @@ -541,7 +545,10 @@ bool wallet2::get_seed(std::string& electrum_words) const return false; } - crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_spend_secret_key, electrum_words, seed_language); + crypto::secret_key key = get_account().get_keys().m_spend_secret_key; + if (!passphrase.empty()) + key = cryptonote::encrypt_key(key, passphrase); + crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language); return true; } @@ -684,7 +691,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote std::deque<crypto::key_image> ki(tx.vout.size()); std::deque<uint64_t> amount(tx.vout.size()); std::deque<rct::key> mask(tx.vout.size()); - int threads = tools::get_max_concurrency(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; + int threads = tpool.get_max_concurrency(); const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); @@ -720,13 +729,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote ++num_vouts_received; // process the other outs from that tx - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); @@ -734,10 +736,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } - KILL_IOSERVICE(); + waiter.wait(); for (size_t i = 1; i < tx.vout.size(); ++i) { if (error[i]) @@ -766,23 +768,18 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } else if (tx.vout.size() > 1 && threads > 1) { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } - KILL_IOSERVICE(); + waiter.wait(); tx_money_got_in_outs = 0; for (size_t i = 0; i < tx.vout.size(); ++i) { @@ -1251,7 +1248,8 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "size mismatch"); - int threads = tools::get_max_concurrency(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + int threads = tpool.get_max_concurrency(); if (threads > 1) { std::vector<crypto::hash> round_block_hashes(threads); @@ -1262,23 +1260,16 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: for (size_t b = 0; b < blocks_size; b += threads) { size_t round_size = std::min((size_t)threads, blocks_size - b); - - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (size_t i = 0; i < round_size; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool::waiter waiter; std::list<block_complete_entry>::const_iterator tmpblocki = blocki; for (size_t i = 0; i < round_size; ++i) { - ioservice.dispatch(boost::bind(&wallet2::parse_block_round, this, std::cref(tmpblocki->block), + tpool.submit(&waiter, boost::bind(&wallet2::parse_block_round, this, std::cref(tmpblocki->block), std::ref(round_blocks[i]), std::ref(round_block_hashes[i]), std::ref(error[i]))); ++tmpblocki; } - KILL_IOSERVICE(); + waiter.wait(); tmpblocki = blocki; for (size_t i = 0; i < round_size; ++i) { @@ -1552,23 +1543,22 @@ void wallet2::update_pool_state(bool refreshed) { if (res.txs.size() == txids.size()) { - size_t n = 0; - for (const auto &txid: txids) + for (const auto &tx_entry: res.txs) { - // might have just been put in a block - if (res.txs[n].in_pool) + if (tx_entry.in_pool) { cryptonote::transaction tx; cryptonote::blobdata bd; crypto::hash tx_hash, tx_prefix_hash; - if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[n].as_hex, bd)) + if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd)) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - if (tx_hash == txid) + const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + if (i != txids.end()) { - process_new_transaction(txid, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); - m_scanned_pool_txs[0].insert(txid); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]); @@ -1577,7 +1567,7 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); + MERROR("Got txid " << tx_hash << " which we did not ask for"); } } else @@ -1587,14 +1577,13 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Failed to parse tx " << txid); + LOG_PRINT_L0("Failed to parse transaction from daemon"); } } else { - LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); } - ++n; } } else @@ -1698,7 +1687,8 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash; std::list<crypto::hash> short_chain_history; - boost::thread pull_thread; + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; uint64_t blocks_start_height; std::list<cryptonote::block_complete_entry> blocks; std::vector<COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; @@ -1736,11 +1726,11 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re std::list<cryptonote::block_complete_entry> next_blocks; std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> next_o_indices; bool error = false; - pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, next_o_indices, error);}); + tpool.submit(&waiter, [&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, next_o_indices, error);}); process_blocks(blocks_start_height, blocks, o_indices, added_blocks); blocks_fetched += added_blocks; - pull_thread.join(); + waiter.wait(); if(blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); @@ -1762,8 +1752,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re catch (const std::exception&) { blocks_fetched += added_blocks; - if (pull_thread.joinable()) - pull_thread.join(); + waiter.wait(); if(try_count < 3) { LOG_PRINT_L1("Another try pull_blocks (try_count=" << try_count << ")..."); @@ -2668,10 +2657,11 @@ void wallet2::store_to(const std::string &path, const std::string &password) // if we here, main wallet file is saved and we only need to save keys and address files if (!same_file) { prepare_file_names(path); - store_keys(m_keys_file, password, false); + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); // save address to the new file const std::string address_file = m_wallet_file + ".address.txt"; - bool r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet)); + r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet)); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file); // remove old wallet file r = boost::filesystem::remove(old_file); @@ -5404,6 +5394,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } spent = 0; unspent = 0; + std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. + std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx + // was created by sweep_all, so we can't know the spent height and other detailed info. for(size_t i = 0; i < m_transfers.size(); ++i) { transfer_details &td = m_transfers[i]; @@ -5414,8 +5407,146 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag unspent += amount; LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): " << (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")"); + + if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN) + { + bool is_spent_tx_found = false; + for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it) + { + bool is_spent_tx = false; + for(const cryptonote::txin_v& in : it->m_tx.vin) + { + if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image) + { + is_spent_tx = true; + break; + } + } + if (is_spent_tx) + { + is_spent_tx_found = true; + spent_txids.insert(it->m_txid); + break; + } + } + + if (!is_spent_tx_found) + swept_transfers.push_back(i); + } } MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + + if (check_spent) + { + // query outgoing txes + COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res; + gettxs_req.decode_as_json = false; + for (const crypto::hash& spent_txid : spent_txids) + gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid)); + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, 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"); + THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); + + // process each outgoing tx + auto spent_txid = spent_txids.begin(); + for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs) + { + THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool"); + + // parse tx + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed"); + cryptonote::transaction spent_tx; + crypto::hash spnet_txid_parsed, spent_txid_prefix; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed"); + THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch"); + + // get received (change) amount + uint64_t tx_money_got_in_outs = 0; + const cryptonote::account_keys& keys = m_account.get_keys(); + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + size_t output_index = 0; + for (const cryptonote::tx_out& out : spent_tx.vout) + { + uint64_t money_transfered = 0; + bool error = false, received = false; + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, out, derivation, output_index, received, money_transfered, error); + THROW_WALLET_EXCEPTION_IF(error, error::wallet_internal_error, "check_acc_out_precomp failed"); + if (received) + { + if (money_transfered == 0) + { + rct::key mask; + money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_pub_key, keys.m_view_secret_key, output_index, mask); + } + tx_money_got_in_outs += money_transfered; + } + ++output_index; + } + + // get spent amount + uint64_t tx_money_spent_in_ins = 0; + for (const cryptonote::txin_v& in : spent_tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); + if (it != m_key_images.end()) + { + const transfer_details& td = m_transfers[it->second]; + uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount; + if (amount > 0) + { + THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error, + std::string("Inconsistent amount in tx input: got ") + print_money(amount) + + std::string(", expected ") + print_money(td.amount())); + } + amount = td.amount(); + tx_money_spent_in_ins += amount; + + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid); + set_spent(it->second, e.block_height); + if (m_callback) + m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx); + } + } + + // create outgoing payment + process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs); + + // erase corresponding incoming payment + for (auto j = m_payments.begin(); j != m_payments.end(); ++j) + { + if (j->second.m_tx_hash == *spent_txid) + { + m_payments.erase(j); + break; + } + } + + ++spent_txid; + } + + for (size_t n : swept_transfers) + { + const transfer_details& td = m_transfers[n]; + confirmed_transfer_details pd; + pd.m_change = (uint64_t)-1; // cahnge is unknown + pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown + std::string err; + pd.m_block_height = get_daemon_blockchain_height(err); // spent block height is unknown, so hypothetically set to the highest + crypto::hash spent_txid = crypto::rand<crypto::hash>(); // spent txid is unknown, so hypothetically set to random + m_confirmed_txs.insert(std::make_pair(spent_txid, pd)); + } + } + return m_transfers[signed_key_images.size() - 1].m_block_height; } wallet2::payment_container wallet2::export_payments() const @@ -5559,7 +5690,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret crypto::secret_key_to_public_key(skey, pkey); const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), - error::wallet_internal_error, "Failed to authenticate criphertext"); + error::wallet_internal_error, "Failed to authenticate ciphertext"); } crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); return plaintext; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index adf03abcc..971e98351 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -363,7 +363,7 @@ namespace tools * \brief Checks if deterministic wallet */ bool is_deterministic() const; - bool get_seed(std::string& electrum_words) const; + bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const; /*! * \brief Gets the seed language */ diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8da8c62eb..7a5e01af7 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -663,7 +663,7 @@ struct WalletManager * \param wallet previously opened / created wallet instance * \return None */ - virtual bool closeWallet(Wallet *wallet) = 0; + virtual bool closeWallet(Wallet *wallet, bool store = true) = 0; /* * ! checks if wallet with the given name already exists diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 34c5a2a5d..22cfcc269 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -84,6 +84,7 @@ namespace wallet_args #endif const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; + const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = {"max-log-file-size", "Specify maximum log file size [B]", MAX_LOG_FILE_SIZE}; const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", wallet_args::tr("Max number of threads to use for a parallel job"), DEFAULT_MAX_CONCURRENCY}; const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", wallet_args::tr("Specify log file"), ""}; const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", wallet_args::tr("Config file"), "", true}; @@ -99,8 +100,9 @@ namespace wallet_args command_line::add_arg(desc_general, command_line::arg_help); command_line::add_arg(desc_general, command_line::arg_version); - command_line::add_arg(desc_params, arg_log_file, ""); + command_line::add_arg(desc_params, arg_log_file); command_line::add_arg(desc_params, arg_log_level); + command_line::add_arg(desc_params, arg_max_log_file_size); command_line::add_arg(desc_params, arg_max_concurrency); command_line::add_arg(desc_params, arg_config_file); @@ -114,6 +116,21 @@ namespace wallet_args auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); po::store(parser.run(), vm); + if (command_line::get_arg(vm, command_line::arg_help)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" + "daemon to work correctly.") << ENDL; + tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; + tools::msg_writer() << desc_all; + return false; + } + else if (command_line::get_arg(vm, command_line::arg_version)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + return false; + } + if(command_line::has_arg(vm, arg_config_file)) { std::string config = command_line::get_arg(vm, arg_config_file); @@ -141,27 +158,12 @@ namespace wallet_args log_path = command_line::get_arg(vm, arg_log_file); else log_path = mlog_get_default_log_path(default_log_name); - mlog_configure(log_path, log_to_console); + mlog_configure(log_path, log_to_console, command_line::get_arg(vm, arg_max_log_file_size)); if (!vm["log-level"].defaulted()) { mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); } - if (command_line::get_arg(vm, command_line::arg_help)) - { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; - tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" - "daemon to work correctly.") << ENDL; - tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; - tools::msg_writer() << desc_all; - return boost::none; - } - else if (command_line::get_arg(vm, command_line::arg_version)) - { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return boost::none; - } - if(command_line::has_arg(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 773d12775..cb35482bd 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <boost/format.hpp> #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> #include <cstdint> @@ -37,7 +38,6 @@ using namespace epee; #include "wallet/wallet_args.h" #include "common/command_line.h" #include "common/i18n.h" -#include "common/util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "wallet_rpc_server_commands_defs.h" @@ -70,18 +70,12 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_filename(), m_stop(false), m_trusted_daemon(false) + wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_trusted_daemon(false), m_vm(NULL) { } //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::~wallet_rpc_server() { - try - { - boost::system::error_code ec{}; - boost::filesystem::remove(rpc_login_filename, ec); - } - catch (...) {} } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::set_wallet(wallet2 *cr) @@ -160,7 +154,15 @@ namespace tools #else #define MKDIR(path, mode) mkdir(path, mode) #endif - MKDIR(m_wallet_dir.c_str(), 0700); + if (MKDIR(m_wallet_dir.c_str(), 0700) < 0) + { +#ifdef _WIN32 + LOG_ERROR(tr("Failed to create directory ") + m_wallet_dir); +#else + LOG_ERROR((boost::format(tr("Failed to create directory %s: %s")) % m_wallet_dir % strerror(errno)).str()); +#endif + return false; + } } if (disable_auth) @@ -182,34 +184,32 @@ namespace tools default_rpc_username, string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size()) ); + + std::string temp = "monero-wallet-rpc." + bind_port + ".login"; + rpc_login_file = tools::private_file::create(temp); + if (!rpc_login_file.handle()) + { + LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); + return false; + } + std::fputs(http_login->username.c_str(), rpc_login_file.handle()); + std::fputc(':', rpc_login_file.handle()); + std::fputs(http_login->password.c_str(), rpc_login_file.handle()); + std::fflush(rpc_login_file.handle()); + if (std::ferror(rpc_login_file.handle())) + { + LOG_ERROR(tr("Error writing to file ") << temp); + return false; + } + LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } - else + else // chosen user/pass { http_login.emplace( std::move(rpc_config->login->username), std::move(rpc_config->login->password).password() ); } assert(bool(http_login)); - - std::string temp = "monero-wallet-rpc." + bind_port + ".login"; - const auto cookie = tools::create_private_file(temp); - if (!cookie) - { - LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); - return false; - } - rpc_login_filename.swap(temp); // nothrow guarantee destructor cleanup - temp = rpc_login_filename; - std::fputs(http_login->username.c_str(), cookie.get()); - std::fputc(':', cookie.get()); - std::fputs(http_login->password.c_str(), cookie.get()); - std::fflush(cookie.get()); - if (std::ferror(cookie.get())) - { - LOG_ERROR(tr("Error writing to file ") << temp); - return false; - } - LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } // end auth enabled m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); @@ -1545,7 +1545,7 @@ namespace tools er.message = "Failed to add address book entry"; return false; } - res.index = m_wallet->get_address_book().size(); + res.index = m_wallet->get_address_book().size() - 1; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1896,7 +1896,15 @@ just_dir: wrpc.send_stop_signal(); }); LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet rpc server")); - wrpc.run(); + try + { + wrpc.run(); + } + catch (const std::exception &e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Failed to run wallet: ") << e.what()); + return 1; + } LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet rpc server")); try { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index dd54222b0..e5ed0a846 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -33,6 +33,7 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> #include <string> +#include "common/util.h" #include "net/http_server_impl_base.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" @@ -154,7 +155,7 @@ namespace tools wallet2 *m_wallet; std::string m_wallet_dir; - std::string rpc_login_filename; + tools::private_file rpc_login_file; std::atomic<bool> m_stop; bool m_trusted_daemon; epee::net_utils::http::http_simple_client m_http_client; |