diff --git a/CMakeLists.txt b/CMakeLists.txt index a9577f28..616b86d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,40 @@ cmake_minimum_required(VERSION 3.10) +if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo) +endif(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + +if(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) + message("==> The configuration is ${CMAKE_BUILD_TYPE}. Debug info will be extracted into separate files.") + + function (add_executable _name) + _add_executable(${ARGV}) + + if (TARGET ${_name}) + add_custom_command(TARGET ${_name} POST_BUILD + COMMAND echo "$: extracting debug info" + COMMAND ${CMAKE_COMMAND} -E chdir $ objcopy --only-keep-debug "$" "$.debug" + COMMAND ${CMAKE_COMMAND} -E chdir $ strip --strip-debug --strip-unneeded "$" + COMMAND ${CMAKE_COMMAND} -E chdir $ objcopy --add-gnu-debuglink="$.debug" "$" + ) + endif() + endfunction() + + function (add_library _name _type) + _add_library(${ARGV}) + + if (TARGET ${_name} AND ${_type} STREQUAL SHARED) + add_custom_command(TARGET ${_name} POST_BUILD + COMMAND echo "$: extracting debug info" + COMMAND ${CMAKE_COMMAND} -E chdir $ objcopy --only-keep-debug "$" "$.debug" + COMMAND ${CMAKE_COMMAND} -E chdir $ strip --strip-debug --strip-unneeded "$" + COMMAND ${CMAKE_COMMAND} -E chdir $ objcopy --add-gnu-debuglink="$.debug" "$" + ) + endif() + endfunction() + +endif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) + project(graft_server) option(OPT_BUILD_TESTS "Build tests." OFF) @@ -190,7 +225,7 @@ endif() set_target_properties(graft PROPERTIES LINK_FLAGS "-Wl,-E -rdynamic") -add_dependencies(graft libr3 cryptonode graftlet_lib) +add_dependencies(graft version libr3 cryptonode graftlet_lib) ### requests_common library add_library(requests_common STATIC @@ -245,7 +280,9 @@ add_library(supernode_common STATIC ${PROJECT_SOURCE_DIR}/src/supernode/requests/sale_status.cpp ${PROJECT_SOURCE_DIR}/src/supernode/requests/send_raw_tx.cpp ${PROJECT_SOURCE_DIR}/src/supernode/requests/send_supernode_announce.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/requests/send_supernode_stakes.cpp ${PROJECT_SOURCE_DIR}/src/supernode/requests/send_transfer.cpp + ${PROJECT_SOURCE_DIR}/src/supernode/requests/blockchain_based_list.cpp ${PROJECT_SOURCE_DIR}/src/rta/DaemonRpcClient.cpp ${PROJECT_SOURCE_DIR}/src/rta/fullsupernodelist.cpp ${PROJECT_SOURCE_DIR}/src/rta/supernode.cpp @@ -286,6 +323,8 @@ add_executable(supernode ${PROJECT_SOURCE_DIR}/src/supernode/main.cpp ) +set_target_properties(supernode PROPERTIES LINK_FLAGS "-Wl,-E") + target_include_directories(supernode PRIVATE ${GRAFT_INCLUDE_DIRS} ) diff --git a/data/config.ini b/data/config.ini index 948d4978..dbc86429 100644 --- a/data/config.ini +++ b/data/config.ini @@ -29,7 +29,9 @@ lru-timeout-ms=60000 data-dir= stake-wallet-name=stake-wallet testnet=true -stake-wallet-refresh-interval-ms=50000 +stake-wallet-refresh-interval-ms=90000 +stake-wallet-refresh-interval-random-factor=0 +wallet-public-address= [ipfilter] ;; path to ipfilter rules file @@ -55,4 +57,4 @@ walletnode=http://127.0.0.1:28694 [graftlets] ;;dirs parameter, a list of directories to search graftlets separated by colons. If a directory is set relative it will be interpreted both relative to the current directory and relative to the executable location. By default, 'graftlets' directory will be used relative to the executable location. ;;e.g. dirs=/var/opt/graftlets:graftlets -dirs=graftlets:/var/opt +dirs=graftlets:graftlets/supernode:/var/opt diff --git a/graftlets/CMakeLists.txt b/graftlets/CMakeLists.txt index 91f76f85..c44a564f 100644 --- a/graftlets/CMakeLists.txt +++ b/graftlets/CMakeLists.txt @@ -25,6 +25,14 @@ include_directories( ${PROJECT_SOURCE_DIR}/../modules/libr3/include ) + +add_library(graftlet_walletAddress SHARED + WalletAddress.cpp +) +set_target_properties(graftlet_walletAddress PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/supernode +) + if (OPT_BUILD_TESTS) message("==> Test graftlets included") @@ -35,5 +43,5 @@ if (OPT_BUILD_TESTS) add_library(graftlet_plugin1 SHARED TestGraftlet1.cpp ) -endif() +endif() diff --git a/graftlets/TestGraftlet.cpp b/graftlets/TestGraftlet.cpp index e2edaf70..afd30fe4 100644 --- a/graftlets/TestGraftlet.cpp +++ b/graftlets/TestGraftlet.cpp @@ -56,7 +56,7 @@ class TestGraftlet: public IGraftlet return graft::Status::Ok; } - virtual void initOnce() + virtual void initOnce(const graft::CommonOpts& opts) override { // REGISTER_ACTION(TestGraftlet, testUndefined); REGISTER_ACTION(TestGraftlet, testInt1); diff --git a/graftlets/TestGraftlet1.cpp b/graftlets/TestGraftlet1.cpp index d15dcd9b..db700bf7 100644 --- a/graftlets/TestGraftlet1.cpp +++ b/graftlets/TestGraftlet1.cpp @@ -56,7 +56,7 @@ class TestGraftlet1: public IGraftlet return graft::Status::Ok; } - virtual void initOnce() + virtual void initOnce(const graft::CommonOpts& opts) override { // REGISTER_ACTION(TestGraftlet1, testUndefined); REGISTER_ACTION(TestGraftlet1, testInt1); diff --git a/graftlets/WalletAddress.cpp b/graftlets/WalletAddress.cpp new file mode 100644 index 00000000..fa32d8bf --- /dev/null +++ b/graftlets/WalletAddress.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2018, The Graft 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. + +#define __GRAFTLET__ +#include "lib/graft/GraftletRegistry.h" +#include "lib/graft/IGraftlet.h" + +#include "WalletAddress.h" +#include "supernode/requestdefines.h" +#include "rta/supernode.h" +#include "lib/graft/graft_exception.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_protocol/blobdatatype.h" +#include "file_io_utils.h" + +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "graftlet.WalletAddress" + +class WalletAddress: public IGraftlet +{ +public: + WalletAddress(const char* name) : IGraftlet(name) { } + + virtual void initOnce(const graft::CommonOpts& /*opts*/) override + { + REGISTER_ENDPOINT("/dapi/v2.0/cryptonode/getwalletaddress", METHOD_GET | METHOD_POST, WalletAddress, getWalletAddressHandler); + } +private: + graft::Status getWalletAddressHandler(const graft::Router::vars_t& vars, const graft::Input& input, graft::Context& ctx, graft::Output& output); + +}; + +GRAFTLET_EXPORTS_BEGIN("walletAddress", GRAFTLET_MKVER(1,1)); +GRAFTLET_PLUGIN(WalletAddress, IGraftlet, "walletAddressGL"); +GRAFTLET_EXPORTS_END + +GRAFTLET_PLUGIN_DEFAULT_CHECK_FW_VERSION(GRAFTLET_MKVER(0,3)) + +graft::Status WalletAddress::getWalletAddressHandler(const graft::Router::vars_t& vars, const graft::Input& input, graft::Context& ctx, + graft::Output& output) +{ + LOG_PRINT_L2(__FUNCTION__); + + if (ctx.local.getLastStatus() != graft::Status::None) { + graft::supernode::request::GetWalletAddressErrorResponse err; + err.error = string("internal error: wrong status: " + to_string((int)ctx.local.getLastStatus())); + return graft::Status::Error; + } + + graft::SupernodePtr supernode = ctx.global.get(CONTEXT_KEY_SUPERNODE, graft::SupernodePtr()); + if (!supernode) { + graft::supernode::request::GetWalletAddressErrorResponse err; + err.error = string("supernode was not setup correctly"); + return graft::Status::Error; + } + + graft::supernode::request::GetWalletAddressResponse response; + response.testnet = supernode->testnet(); + response.id_key = supernode->idKeyAsString(); + response.wallet_public_address = supernode->walletAddress(); + + crypto::signature sign; + std::string data = supernode->walletAddress() + ":" + supernode->idKeyAsString(); + supernode->signMessage(data, sign); + response.signature = epee::string_tools::pod_to_hex(sign); + output.load(response); + return graft::Status::Ok; +} + +namespace +{ + +struct Informer +{ + Informer() + { + LOG_PRINT_L2("graftlet " << getGraftletName() << " loading"); + } + ~Informer() + { + LOG_PRINT_L2("graftlet " << getGraftletName() << " unloading"); + } +}; + +Informer informer; + +} //namespace + + diff --git a/graftlets/WalletAddress.h b/graftlets/WalletAddress.h new file mode 100644 index 00000000..17b7be9d --- /dev/null +++ b/graftlets/WalletAddress.h @@ -0,0 +1,48 @@ +// Copyright (c) 2018, The Graft 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 "lib/graft/router.h" +#include "lib/graft/jsonrpc.h" + +namespace graft::supernode::request { + +GRAFT_DEFINE_IO_STRUCT_INITED(GetWalletAddressResponse, + (bool, testnet, false), + (std::string, wallet_public_address, ""), + (std::string, id_key, ""), + (std::string, signature, "") //signature of wallet_public_address + ":" + id_key + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(GetWalletAddressErrorResponse, + (bool, testnet, false), + (std::string, error, "The wallet public address is not set") + ); +} //namespace graft::supernode::request + diff --git a/include/lib/graft/GraftletLoader.h b/include/lib/graft/GraftletLoader.h index 24d8236d..df41330b 100644 --- a/include/lib/graft/GraftletLoader.h +++ b/include/lib/graft/GraftletLoader.h @@ -106,6 +106,8 @@ class GraftletLoader using Version = int; using GraftletExceptionList = std::vector< std::pair< DllName, std::vector< std::pair >>>; + GraftletLoader(const graft::CommonOpts& opts) : m_opts(opts) { } + static Version getFwVersion() { return m_fwVersion; } static void setFwVersion(Version fwVersion) { m_fwVersion = fwVersion; } @@ -188,7 +190,7 @@ class GraftletLoader GraftletRegistry* gr = it1->second; std::shared_ptr concreteGraftlet = gr->resolveGraftlet(); if(!concreteGraftlet.get()) throw std::runtime_error("Cannot resolve dll name:" + dllName + " type:" + typeid(BaseT).name()); - concreteGraftlet->init(); + concreteGraftlet->init(m_opts); ClsName name = concreteGraftlet->getClsName(); std::any any(concreteGraftlet); @@ -258,6 +260,8 @@ class GraftletLoader static Version m_fwVersion; static ExceptionMap m_exceptionMap; + const graft::CommonOpts& m_opts; + //we can use functions in a dll until we release object of boost::dll::shared_library //dll name -> (lib, version, path) std::map> m_name2lib; diff --git a/include/lib/graft/IGraftlet.h b/include/lib/graft/IGraftlet.h index efadb8b0..3724b4ff 100644 --- a/include/lib/graft/IGraftlet.h +++ b/include/lib/graft/IGraftlet.h @@ -12,6 +12,7 @@ #include #include #include "lib/graft/router.h" +#include "lib/graft/serveropts.h" #define REGISTER_ACTION(T, f) \ register_handler_memf(#f, this, &T::f) @@ -33,11 +34,11 @@ class IGraftlet IGraftlet(const IGraftlet&) = delete; IGraftlet& operator = (const IGraftlet&) = delete; - void init() + void init(const graft::CommonOpts& opts) { if(m_inited) return; m_inited = true; - initOnce(); + initOnce(opts); } const ClsName& getClsName() const { return m_clsName; } @@ -130,7 +131,7 @@ class IGraftlet } protected: IGraftlet(const ClsName& name = ClsName() ) : m_clsName(name) { } - virtual void initOnce() = 0; + virtual void initOnce(const graft::CommonOpts& opts) = 0; private: using TypeIndex2any = std::map >; using Map = std::map; diff --git a/include/lib/graft/common/utils.h b/include/lib/graft/common/utils.h index 5c1e3f25..6fe439cc 100644 --- a/include/lib/graft/common/utils.h +++ b/include/lib/graft/common/utils.h @@ -12,8 +12,7 @@ std::string base64_encode(const std::string &data); template T random_number(T startRange, T endRange) { - std::random_device rd; - std::mt19937 mt(rd()); + static std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution dist(startRange, endRange); return dist(mt); } diff --git a/include/lib/graft/handler_api.h b/include/lib/graft/handler_api.h index 6c9b0092..18cee9fe 100644 --- a/include/lib/graft/handler_api.h +++ b/include/lib/graft/handler_api.h @@ -13,8 +13,9 @@ class HandlerAPI public: virtual void sendUpstreamBlocking(Output& output, Input& input, std::string& err) = 0; virtual bool addPeriodicTask(const Router::Handler& h_worker, - std::chrono::milliseconds interval_ms, - std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max()) = 0; + std::chrono::milliseconds interval_ms, + std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max(), + double random_factor = 0) = 0; virtual request::system_info::Counter& runtimeSysInfo() = 0; virtual const ConfigOpts& configOpts() const = 0; }; diff --git a/include/lib/graft/serveropts.h b/include/lib/graft/serveropts.h index 490d49c9..312886df 100644 --- a/include/lib/graft/serveropts.h +++ b/include/lib/graft/serveropts.h @@ -6,6 +6,15 @@ namespace graft { +struct CommonOpts +{ + // testnet flag + bool testnet; + // data directory - base directory where supernode stake wallet and other supernodes wallets are located + std::string data_dir; + std::string wallet_public_address; +}; + struct IPFilterOpts { int requests_per_sec = 0; @@ -30,6 +39,7 @@ struct ConfigOpts std::vector graftlet_dirs; int lru_timeout_ms; IPFilterOpts ipfilter; + CommonOpts common; void check_asserts() const { diff --git a/include/lib/graft/task.h b/include/lib/graft/task.h index 6fc8f1ad..65f438bb 100644 --- a/include/lib/graft/task.h +++ b/include/lib/graft/task.h @@ -153,19 +153,23 @@ class PeriodicTask : public BaseTask PeriodicTask( TaskManager& manager, const Router::Handler3& h3, std::chrono::milliseconds timeout_ms, - std::chrono::milliseconds initial_timeout_ms + std::chrono::milliseconds initial_timeout_ms, + double random_factor = 0 ) : BaseTask(manager, Router::JobParams({Input(), Router::vars_t(), h3})) , m_timeout_ms(timeout_ms), m_initial_timeout_ms(initial_timeout_ms) + , m_random_factor(random_factor) { } - PeriodicTask(TaskManager& manager, const Router::Handler3& h3, std::chrono::milliseconds timeout_ms) - : PeriodicTask(manager, h3, timeout_ms, timeout_ms) + PeriodicTask(TaskManager& manager, const Router::Handler3& h3, + std::chrono::milliseconds timeout_ms, double random_factor = 0) + : PeriodicTask(manager, h3, timeout_ms, timeout_ms, random_factor) { } std::chrono::milliseconds m_timeout_ms; std::chrono::milliseconds m_initial_timeout_ms; + double m_random_factor; bool m_initial_run {true}; public: @@ -199,7 +203,9 @@ class TaskManager : private HandlerAPI void sendUpstream(BaseTaskPtr bt); void addPeriodicTask(const Router::Handler3& h3, - std::chrono::milliseconds interval_ms, std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max()); + std::chrono::milliseconds interval_ms, + std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max(), + double random_factor = 0); ////getters virtual mg_mgr* getMgMgr() = 0; @@ -220,7 +226,8 @@ class TaskManager : private HandlerAPI virtual void sendUpstreamBlocking(Output& output, Input& input, std::string& err) override; virtual bool addPeriodicTask(const Router::Handler& h_worker, std::chrono::milliseconds interval_ms, - std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max()) override; + std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max(), + double random_factor = 0 ) override; virtual request::system_info::Counter& runtimeSysInfo() override; virtual const ConfigOpts& configOpts() const override; @@ -282,7 +289,7 @@ class TaskManager : private HandlerAPI using PromiseItem = UpstreamTask::PromiseItem; using PromiseQueue = tp::MPMCBoundedQueue; - using PeridicTaskItem = std::tuple; + using PeridicTaskItem = std::tuple; using PeriodicTaskQueue = tp::MPMCBoundedQueue; std::unique_ptr m_promiseQueue; diff --git a/include/lib/graft/thread_pool/thread_pool_options.hpp b/include/lib/graft/thread_pool/thread_pool_options.hpp index 057e9e96..4e454834 100644 --- a/include/lib/graft/thread_pool/thread_pool_options.hpp +++ b/include/lib/graft/thread_pool/thread_pool_options.hpp @@ -60,7 +60,7 @@ class ThreadPoolOptions /// Implementation inline ThreadPoolOptions::ThreadPoolOptions() - : m_thread_count(std::max(1u, std::thread::hardware_concurrency())) + : m_thread_count(std::max(2u, std::thread::hardware_concurrency())) , m_queue_size(1024u) , m_workers_expelling_interval_ms(1000u) { @@ -68,7 +68,7 @@ inline ThreadPoolOptions::ThreadPoolOptions() inline void ThreadPoolOptions::setThreadCount(size_t count) { - m_thread_count = std::max(1u, count); + m_thread_count = std::max(2u, count); } inline void ThreadPoolOptions::setQueueSize(size_t size) diff --git a/include/rta/DaemonRpcClient.h b/include/rta/DaemonRpcClient.h index c2fdfc03..a9c92f61 100644 --- a/include/rta/DaemonRpcClient.h +++ b/include/rta/DaemonRpcClient.h @@ -51,15 +51,15 @@ class DaemonRpcClient bool get_tx(const std::string &hash_str, cryptonote::transaction &out_tx, uint64_t &block_num, bool &mined); bool get_height(uint64_t &height); bool get_block_hash(uint64_t height, std::string &hash); + bool send_supernode_stakes(const char* network_address, const char* address); + bool send_supernode_blockchain_based_list(const char* network_address, const char* address, uint64_t last_received_block_height); protected: bool init(const std::string &daemon_address, boost::optional daemon_login); - private: epee::net_utils::http::http_simple_client m_http_client; std::chrono::seconds m_rpc_timeout; - }; } diff --git a/include/rta/fullsupernodelist.h b/include/rta/fullsupernodelist.h index 2210745e..68f398fd 100644 --- a/include/rta/fullsupernodelist.h +++ b/include/rta/fullsupernodelist.h @@ -1,6 +1,8 @@ #ifndef FULLSUPERNODELIST_H #define FULLSUPERNODELIST_H +#include + #include "rta/supernode.h" #include "rta/DaemonRpcClient.h" @@ -25,7 +27,7 @@ class FullSupernodeList { public: static constexpr int32_t TIERS = 4; - static constexpr int32_t ITEMS_PER_TIER = 1; + static constexpr int32_t ITEMS_PER_TIER = 2; static constexpr int32_t AUTH_SAMPLE_SIZE = TIERS * ITEMS_PER_TIER; static constexpr int64_t AUTH_SAMPLE_HASH_HEIGHT = 20; // block number for calculating auth sample should be calculated as current block height - AUTH_SAMPLE_HASH_HEIGHT; static constexpr int64_t ANNOUNCE_TTL_SECONDS = 60 * 60; // if more than ANNOUNCE_TTL_SECONDS passed from last annouce - supernode excluded from auth sample selection @@ -57,7 +59,7 @@ class FullSupernodeList /*! * \brief remove - removes Supernode from list. closes it's wallet and frees memory - * \param address - supernode address + * \param id - supernode id * \return - true if supernode removed */ bool remove(const std::string &address); @@ -70,33 +72,31 @@ class FullSupernodeList /*! * \brief exists - checks if supernode with given address exists in list - * \param address - supernode address + * \param id - supernode id * \return - true if exists */ - bool exists(const std::string &address) const; - - /*! - * \brief update - updates supernode's key images. this will probably cause stake amount change - * \param address - supernode's address - * \param key_images - list of key images - * \return - true of successfully updated - */ - bool update(const std::string &address, const std::vector &key_images); + bool exists(const std::string &id) const; /*! * \brief get - returns supernode instance (pointer) - * \param address - supernode's address + * \param id - supernode's public id * \return - shared pointer to supernode or empty pointer (nullptr) is no such address */ - SupernodePtr get(const std::string &address) const; + SupernodePtr get(const std::string &id) const; + + typedef std::vector supernode_array; /*! - * \brief buildAuthSample - builds auth sample (8 supernodes) for given block height - * \param height - block height used to perform selection - * \param out - vector of supernode pointers - * \return - true on success + * \brief buildAuthSample - builds auth sample (8 supernodes) for given block height + * \param height - block height used to perform selection + * \param payment_id - payment id which is used for building auth sample + * \param out - vector of supernode pointers + * \param out_auth_block_number - block number which was used for auth sample + * \return - true on success */ - bool buildAuthSample(uint64_t height, std::vector &out); + bool buildAuthSample(uint64_t height, const std::string& payment_id, supernode_array &out, uint64_t &out_auth_block_number); + + bool buildAuthSample(const std::string& payment_id, supernode_array &out, uint64_t &out_auth_block_number); /*! * \brief items - returns address list of known supernodes @@ -126,18 +126,74 @@ class FullSupernodeList */ size_t refreshedItems() const; + typedef std::vector supernode_stake_array; + + /*! + * \brief updateStakes - update stakes + * \param - array of stakes + * \return + */ + void updateStakes(uint64_t block_number, const supernode_stake_array& stakes, const std::string& cryptonode_rpc_address, bool testnet); + + struct blockchain_based_list_entry + { + std::string supernode_public_id; + std::string supernode_public_address; + uint64_t amount; + }; + + typedef std::vector blockchain_based_list_tier; + typedef std::vector blockchain_based_list; + typedef std::shared_ptr blockchain_based_list_ptr; + + /*! + * \brief setBlockchainBasedList - updates full list of supernodes + * \return + */ + void setBlockchainBasedList(uint64_t block_number, const blockchain_based_list_ptr& list); + + /*! + * \brief blockchainBasedListMaxBlockNumber - number of latest block which blockchain list is built for + * \return + */ + uint64_t getBlockchainBasedListMaxBlockNumber() const; + + /*! + * \brief synchronizeWithCryptonode - synchronize with cryptonode + * \return + */ + void synchronizeWithCryptonode(const char* supernode_network_address, const char* supernode_address); + + /*! + * \brief getBlockchainHeight - returns current daemon block height + * \return + */ + uint64_t getBlockchainHeight() const; private: - bool loadWallet(const std::string &wallet_path); + // bool loadWallet(const std::string &wallet_path); + void addImpl(SupernodePtr item); + bool selectSupernodes(size_t items_count, const std::string& payment_id, const blockchain_based_list_tier& src_array, supernode_array& dst_array); + + typedef std::unordered_map blockchain_based_list_map; + + blockchain_based_list_ptr findBlockchainBasedList(uint64_t block_number) const; private: + // key is public id as a string std::unordered_map m_list; std::string m_daemon_address; bool m_testnet; - DaemonRpcClient m_rpc_client; + mutable DaemonRpcClient m_rpc_client; mutable boost::shared_mutex m_access; std::unique_ptr m_tp; std::atomic_size_t m_refresh_counter; + uint64_t m_blockchain_based_list_max_block_number; + uint64_t m_stakes_max_block_number; + blockchain_based_list_map m_blockchain_based_lists; + std::mt19937_64 m_rng; + boost::posix_time::ptime m_next_recv_stakes; + boost::posix_time::ptime m_next_recv_blockchain_based_list; }; using FullSupernodeListPtr = boost::shared_ptr; diff --git a/include/rta/supernode.h b/include/rta/supernode.h index 52985959..396949bb 100644 --- a/include/rta/supernode.h +++ b/include/rta/supernode.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,19 @@ namespace cryptonote { namespace graft::supernode::request { struct SupernodeAnnounce; } namespace graft { + +/*! + * \brief Supernode stake description + */ +struct supernode_stake +{ + uint64_t amount = 0; + uint64_t block_height = 0; + uint64_t unlock_time = 0; + std::string supernode_public_id; + std::string supernode_public_address; +}; + /*! * \brief The Supernode class - Representing supernode instance */ @@ -28,33 +42,14 @@ class Supernode public: using SignedKeyImage = std::pair; - // 50,000 GRFT – tier 1 - // 90,000 GRFT – tier 2 - // 150,000 GRFT – tier 3 - // 250,000 GRFT – tier 4 - static constexpr uint64_t TIER1_STAKE_AMOUNT = COIN * 50000; - static constexpr uint64_t TIER2_STAKE_AMOUNT = COIN * 90000; - static constexpr uint64_t TIER3_STAKE_AMOUNT = COIN * 150000; - static constexpr uint64_t TIER4_STAKE_AMOUNT = COIN * 250000; + static constexpr uint64_t TIER1_STAKE_AMOUNT = config::graft::TIER1_STAKE_AMOUNT; + static constexpr uint64_t TIER2_STAKE_AMOUNT = config::graft::TIER2_STAKE_AMOUNT; + static constexpr uint64_t TIER3_STAKE_AMOUNT = config::graft::TIER3_STAKE_AMOUNT; + static constexpr uint64_t TIER4_STAKE_AMOUNT = config::graft::TIER4_STAKE_AMOUNT; - /*! - * \brief Supernode - constructs supernode - * \param wallet_path - filename of the existing wallet or new wallet. in case filename doesn't exists, new wallet will be created - * \param wallet_password - wallet's password. wallet_path doesn't exists - this password will be used to protect new wallet; - * \param daemon_address - address of the cryptonode daemon - * \param testnet - testnet flag - * \param seed_language - seed language - */ - Supernode(const std::string &wallet_path, const std::string &wallet_password, const std::string &daemon_address, bool testnet = false, - const std::string &seed_language = std::string()); - ~Supernode(); + Supernode(const std::string &wallet_address, const crypto::public_key &id_key, const std::string &daemon_address, bool testnet = false); - /*! - * \brief setDaemonAddress - setup connection with the cryptonode daemon - * \param address - address in "hostname:port" form - * \return - true on success - */ - bool setDaemonAddress(const std::string &address); + ~Supernode(); /*! * \brief refresh - get latest blocks from the daemon @@ -74,18 +69,12 @@ class Supernode * \return - stake amount in atomic units */ uint64_t stakeAmount() const; + /*! * \brief tier - returns the tier of this supernode based on its stake amount * \return - the tier (1-4) of the supernode or 0 if the verified stake amount is below tier 1 */ uint32_t tier() const; - /*! - * \brief walletBalance - returns wallet balance as seen by the internal wallet; note that this - * can be wrong for a view-only wallet with unverified transactions: you - * typically want to use stakeAmount() instead. - * \return - wallet balance in atomic units - */ - uint64_t walletBalance() const; /*! * \brief walletAddress - returns wallet address as string * \return @@ -93,50 +82,9 @@ class Supernode std::string walletAddress() const; /*! - * \brief daemonHeight - returns cryptonode's blockchain height - * \return - */ - uint64_t daemonHeight() const; - - /*! - * \brief exportKeyImages - exports key images - * \param key_images - destination vector - * \return - true on success + * \brief setWalletAddress - sets wallet public address */ - bool exportKeyImages(std::vector &key_images) const; - - - /*! - * \brief importKeyImages - imports key images - * \param key_images - source vector - * \param height - output height - * \return - true on success - */ - bool importKeyImages(const std::vector &key_images, uint64_t &height); - - /*! - * \brief createFromViewOnlyWallet - creates new Supernode object, creates underlying read-only stake wallet - * \param path - path to wallet files to be created - * \param account_public_address - public address of read-only wallet - * \param viewkey - private view key - * \param testnet - testnet flag - * \return - pointer to Supernode object on success - */ - static Supernode * createFromViewOnlyWallet(const std::string &path, - const std::string &address, - const crypto::secret_key& viewkey = crypto::secret_key(), bool testnet = false); - - /*! - * \brief load - creates new Supernode object from existing wallet - * \param wallet_path - path to existing wallet file - * \param wallet_password - wallet password - * \param daemon_address - daemon address connection for supernode - * \param testnet - testnet flag - * \param seed_language - seed language - * \return - Supernode pointer on success - */ - static Supernode * load(const std::string &wallet_path, const std::string &wallet_password, const std::string &daemon_address, bool testnet = false, - const std::string &seed_language = std::string()); + void setWalletAddress(const std::string &address); /*! * \brief updateFromAnnounce - updates supernode from announce (helper to extract signed key images from graft::supernode::request::SupernodeAnnounce) @@ -147,23 +95,25 @@ class Supernode /*! * \brief createFromAnnounce - creates new Supernode instance from announce - * \param path - wallet file path * \param announce - announce object * \param testnet - testnet flag * \return - Supernode pointer on success */ - static Supernode * createFromAnnounce(const std::string &path, - const graft::supernode::request::SupernodeAnnounce& announce, + static Supernode * createFromAnnounce(const graft::supernode::request::SupernodeAnnounce& announce, const std::string &daemon_address, bool testnet); - bool prepareAnnounce(graft::supernode::request::SupernodeAnnounce& announce); - /*! - * \brief exportViewkey - exports stake wallet private viewkey - * \return private viewkey + * \brief createFromStake - creates new Supernode instance from a stake + * \param stake - stake of the supernode + * \param testnet - testnet flag + * \return - Supernode pointer on success */ - crypto::secret_key exportViewkey() const; + static Supernode * createFromStake(const supernode_stake& stake, + const std::string &daemon_address, + bool testnet); + + bool prepareAnnounce(graft::supernode::request::SupernodeAnnounce& announce); /*! * \brief signMessage - signs message. internally hashes the message and signs the hash @@ -181,7 +131,7 @@ class Supernode * \param signature - signer's signature * \return - true if signature valid */ - bool verifySignature(const std::string &msg, const std::string &address, const crypto::signature &signature) const; + static bool verifySignature(const std::string &msg, const crypto::public_key &pkey, const crypto::signature &signature); /*! * \brief getScoreHash - calculates supernode score (TODO: as 265-bit integer) @@ -190,7 +140,7 @@ class Supernode * \return - true on success */ - bool verifyHash(const crypto::hash &hash, const std::string &address, const crypto::signature &signature) const; + static bool verifyHash(const crypto::hash &hash, const crypto::public_key &pkey, const crypto::signature &signature); void getScoreHash(const crypto::hash &block_hash, crypto::hash &result) const; @@ -198,6 +148,7 @@ class Supernode std::string networkAddress() const; void setNetworkAddress(const std::string &networkAddress); + /*! * \brief getAmountFromTx - scans given tx for outputs destined to this address * \param tx - transaction object @@ -242,19 +193,65 @@ class Supernode */ bool busy() const; + /*! + * \brief stakeBlockHeight - height of block for stake + * \return - height of block + */ + uint64_t stakeBlockHeight() const; + + /*! + * \brief stakeUnlockTime - number of blocks for unlocking stake + * \return + */ + uint64_t stakeUnlockTime() const; + + /*! + * \brief setStake - set stake details + * \param - stake amount + * \param - height of block + * \param - unlock time + */ + void setStake(uint64_t amount, uint64_t blockHeight, uint64_t unlockTime); + + /*! + * \brief loadKeys + * \param filename + * \return + */ + bool loadKeys(const std::string &filename); + + /*! + * \brief initKeys + * \param force + * \return + */ + void initKeys(); + + bool saveKeys(const std::string &filename, bool force = false); + + const crypto::public_key &idKey() const; + const crypto::secret_key &secretKey() const; + std::string idKeyAsString() const; -private: - Supernode(bool testnet = false); private: - using wallet2_ptr = boost::scoped_ptr; - mutable wallet2_ptr m_wallet; - static boost::shared_ptr m_ioservice; - std::string m_network_address; + Supernode(bool testnet = false); + static bool validateAnnounce(const graft::supernode::request::SupernodeAnnounce& announce, crypto::public_key &id_key); - std::atomic m_last_update_time; - mutable boost::shared_mutex m_wallet_guard; +private: + // wallet's address. empty in case 'their' supernode + mutable boost::shared_mutex m_access; + std::string m_wallet_address; + crypto::public_key m_id_key; + crypto::secret_key m_secret_key; + bool m_has_secret_key = false; + std::atomic m_last_update_time; + uint64_t m_stake_amount; + uint64_t m_stake_block_height; + uint64_t m_stake_unlock_time; + bool m_testnet = false; + std::string m_network_address; }; using SupernodePtr = boost::shared_ptr; diff --git a/include/supernode/requests/blockchain_based_list.h b/include/supernode/requests/blockchain_based_list.h new file mode 100644 index 00000000..73f79170 --- /dev/null +++ b/include/supernode/requests/blockchain_based_list.h @@ -0,0 +1,33 @@ +#pragma once + +#include "lib/graft/router.h" +#include "lib/graft/inout.h" +#include "lib/graft/jsonrpc.h" + +namespace graft::supernode::request { + +GRAFT_DEFINE_IO_STRUCT_INITED(BlockchainBasedListTierEntry, + (std::string, supernode_public_id, std::string()), + (std::string, supernode_public_address, std::string()), + (uint64_t, amount, 0) + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(BlockchainBasedListTier, + (std::vector, supernodes, std::vector()) + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(BlockchainBasedList, + (uint64_t, block_height, uint64_t()), + (std::vector, tiers, std::vector()) + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(BlockchainBasedListResponse, + (int, Status, 0) + ); + +GRAFT_DEFINE_JSON_RPC_REQUEST(BlockchainBasedListJsonRpcRequest, BlockchainBasedList); +GRAFT_DEFINE_JSON_RPC_RESPONSE_RESULT(BlockchainBasedListJsonRpcResponse, BlockchainBasedListResponse); + +void registerBlockchainBasedListRequest(graft::Router &router); + +} diff --git a/include/supernode/requests/sale.h b/include/supernode/requests/sale.h index 0c6cb1cb..afabf628 100644 --- a/include/supernode/requests/sale.h +++ b/include/supernode/requests/sale.h @@ -9,7 +9,7 @@ namespace graft::supernode::request { // Sale request payload GRAFT_DEFINE_IO_STRUCT_INITED(SaleRequest, - (std::string, Address, std::string()), + (std::string, IdKey, std::string()), (std::string, SaleDetails, std::string()), (std::string, PaymentID, std::string()), (uint64_t, Amount, 0) diff --git a/include/supernode/requests/sale_details.h b/include/supernode/requests/sale_details.h index d12b0381..856d4b34 100644 --- a/include/supernode/requests/sale_details.h +++ b/include/supernode/requests/sale_details.h @@ -11,6 +11,7 @@ namespace graft::supernode::request { // fee per supernode GRAFT_DEFINE_IO_STRUCT_INITED(SupernodeFee, (std::string, Address, std::string()), + (std::string, IdKey, std::string()), (std::string, Fee, std::string()) ); diff --git a/include/supernode/requests/sale_status.h b/include/supernode/requests/sale_status.h index 1ff49c6a..08ba84eb 100644 --- a/include/supernode/requests/sale_status.h +++ b/include/supernode/requests/sale_status.h @@ -16,7 +16,7 @@ GRAFT_DEFINE_IO_STRUCT(SaleStatusRequest, GRAFT_DEFINE_IO_STRUCT_INITED(UpdateSaleStatusBroadcast, (std::string, PaymentID, std::string()), (int, Status, 0), - (std::string, address, std::string()), // address who updates the status + (std::string, id_key, std::string()), // address who updates the status (std::string, signature, std::string()) // signature who updates the status ); diff --git a/include/supernode/requests/send_supernode_announce.h b/include/supernode/requests/send_supernode_announce.h index fe1f4351..465edc0c 100644 --- a/include/supernode/requests/send_supernode_announce.h +++ b/include/supernode/requests/send_supernode_announce.h @@ -7,18 +7,10 @@ namespace graft::supernode::request { -GRAFT_DEFINE_IO_STRUCT(SignedKeyImageStr, - (std::string, key_image), - (std::string, signature) - ); - GRAFT_DEFINE_IO_STRUCT_INITED(SupernodeAnnounce, - (std::vector, signed_key_images, std::vector()), - (uint64_t, timestamp, 0), - (std::string, address, std::string()), - (uint64_t, stake_amount, 0), + (std::string, supernode_public_id, std::string()), (uint64_t, height, 0), - (std::string, secret_viewkey, std::string()), + (std::string, signature, std::string()), (std::string, network_address, std::string()) ); diff --git a/include/supernode/requests/send_supernode_stakes.h b/include/supernode/requests/send_supernode_stakes.h new file mode 100644 index 00000000..b098deab --- /dev/null +++ b/include/supernode/requests/send_supernode_stakes.h @@ -0,0 +1,32 @@ +#pragma once + +#include "lib/graft/router.h" +#include "lib/graft/inout.h" +#include "lib/graft/jsonrpc.h" + +namespace graft::supernode::request { + +GRAFT_DEFINE_IO_STRUCT_INITED(SupernodeStake, + (uint64_t, amount, 0), + (uint32_t, tier, 0), + (uint64_t, block_height, 0), + (uint64_t, unlock_time, 0), + (std::string, supernode_public_id, std::string()), + (std::string, supernode_public_address, std::string()) + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(SupernodeStakes, + (uint64_t, block_height, 0), + (std::vector, stakes, std::vector()) + ); + +GRAFT_DEFINE_IO_STRUCT_INITED(SendSupernodeStakesResponse, + (int, status, 0) + ); + +GRAFT_DEFINE_JSON_RPC_REQUEST(SendSupernodeStakesJsonRpcRequest, SupernodeStakes); +GRAFT_DEFINE_JSON_RPC_RESPONSE_RESULT(SendSupernodeStakesJsonRpcResponse, SendSupernodeStakesResponse); + +void registerSendSupernodeStakesRequest(graft::Router &router); + +} diff --git a/include/supernode/server.h b/include/supernode/server.h index 11e4880a..d9594201 100644 --- a/include/supernode/server.h +++ b/include/supernode/server.h @@ -49,6 +49,7 @@ class GraftServer private: void initLog(int log_level); void initGlobalContext(); + void prepareDataDir(ConfigOpts& configOpts); void addGenericCallbackRoute(); void serve(); static void initSignals(); diff --git a/include/supernode/supernode.h b/include/supernode/supernode.h index 09e2003a..a99af49b 100644 --- a/include/supernode/supernode.h +++ b/include/supernode/supernode.h @@ -9,18 +9,15 @@ class Supernode : public GraftServer { struct ConfigOptsEx : public ConfigOpts { - // data directory - base directory where supernode stake wallet and other supernodes wallets are located - std::string data_dir; std::string stake_wallet_name; size_t stake_wallet_refresh_interval_ms; + double stake_wallet_refresh_interval_random_factor; // runtime parameters. // path to watch-only wallets (supernodes) std::string watchonly_wallets_path; - // testnet flag - bool testnet; }; - void prepareDataDir(); + void prepareSupernode(); void startSupernodePeriodicTasks(); void setHttpRouters(ConnectionManager& httpcm); void setCoapRouters(ConnectionManager& coapcm); diff --git a/include/walletnode/server.h b/include/walletnode/server.h index 85030825..e4adad44 100644 --- a/include/walletnode/server.h +++ b/include/walletnode/server.h @@ -25,19 +25,13 @@ class WalletServer: public GraftServer virtual void initRouters() override; private: - struct ConfigOptsEx : public ConfigOpts - { - // testnet flag - bool testnet; - }; - void initWalletManager(); void startPeriodicTasks(); void setHttpRouters(ConnectionManager& httpcm); void registerWalletRequests(ConnectionManager& httpcm); void flushWalletDiskCaches(); - ConfigOptsEx m_configEx; + ConfigOpts m_configOpts; std::unique_ptr m_walletManager; }; diff --git a/modules/cryptonode b/modules/cryptonode index d365c8e2..4637baeb 160000 --- a/modules/cryptonode +++ b/modules/cryptonode @@ -1 +1 @@ -Subproject commit d365c8e28a0449d1f3b2a19909788fc95833bb1b +Subproject commit 4637baebbd87ad46b25ffaac4339060f06aa374b diff --git a/src/lib/graft/task.cpp b/src/lib/graft/task.cpp index 768c45b9..7197eb1b 100644 --- a/src/lib/graft/task.cpp +++ b/src/lib/graft/task.cpp @@ -6,6 +6,7 @@ #include "lib/graft/handler_api.h" #include "lib/graft/expiring_list.h" #include "lib/graft/sys_info.h" +#include "lib/graft/common/utils.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "supernode.task" @@ -419,17 +420,18 @@ inline size_t TaskManager::next_pow2(size_t val) } bool TaskManager::addPeriodicTask(const Router::Handler& h_worker, - std::chrono::milliseconds interval_ms, - std::chrono::milliseconds initial_interval_ms) + std::chrono::milliseconds interval_ms, + std::chrono::milliseconds initial_interval_ms, + double random_factor) { if(io_thread) {//it is called from pre_action or post_action, and we can call requestAddPeriodicTask directly - addPeriodicTask({nullptr, h_worker, nullptr}, interval_ms, initial_interval_ms); + addPeriodicTask({nullptr, h_worker, nullptr}, interval_ms, initial_interval_ms, random_factor); return true; } else { - PeridicTaskItem item = std::make_tuple(Router::Handler3(nullptr, h_worker, nullptr), interval_ms, initial_interval_ms); + PeridicTaskItem item = std::make_tuple(Router::Handler3(nullptr, h_worker, nullptr), interval_ms, initial_interval_ms, random_factor); bool ok = m_periodicTaskQueue->push( std::move(item) ); if(!ok) return false; notifyJobReady(); @@ -457,7 +459,8 @@ void TaskManager::checkPeriodicTaskIO() Router::Handler3& h3 = std::get<0>(pti); std::chrono::milliseconds& interval_ms = std::get<1>(pti); std::chrono::milliseconds& initial_interval_ms = std::get<2>(pti); - addPeriodicTask(h3, interval_ms, initial_interval_ms); + double& random_factor = std::get<3>(pti); + addPeriodicTask(h3, interval_ms, initial_interval_ms, random_factor); } } @@ -816,11 +819,11 @@ void TaskManager::processOk(BaseTaskPtr bt) } void TaskManager::addPeriodicTask( - const Router::Handler3& h3, std::chrono::milliseconds interval_ms, std::chrono::milliseconds initial_interval_ms) + const Router::Handler3& h3, std::chrono::milliseconds interval_ms, std::chrono::milliseconds initial_interval_ms, double random_factor) { if(initial_interval_ms == std::chrono::milliseconds::max()) initial_interval_ms = interval_ms; - BaseTask* bt = BaseTask::Create(*this, h3, interval_ms, initial_interval_ms).get(); + BaseTask* bt = BaseTask::Create(*this, h3, interval_ms, initial_interval_ms, random_factor).get(); PeriodicTask* pt = dynamic_cast(bt); assert(pt); schedule(pt); @@ -840,7 +843,7 @@ void TaskManager::onClientDone(BaseTaskPtr bt) void TaskManager::initThreadPool(int threadCount, int workersQueueSize, int expellingIntervalMs) { if(threadCount <= 0) threadCount = std::thread::hardware_concurrency(); - threadCount = next_pow2(threadCount); + threadCount = std::max(size_t(2), next_pow2(threadCount)); if(workersQueueSize <= 0) workersQueueSize = 32; tp::ThreadPoolOptions th_op; @@ -976,9 +979,15 @@ void PeriodicTask::finalize() std::chrono::milliseconds PeriodicTask::getTimeout() { - auto ret = (m_initial_run) ? m_initial_timeout_ms : m_timeout_ms; - m_initial_run = false; - return ret; + if(m_initial_run) + { + m_initial_run = false; + return m_initial_timeout_ms; + } + if(m_random_factor < 0.0001) return m_timeout_ms; + using i_type = decltype(m_timeout_ms.count()); + i_type v = graft::utils::random_number(m_timeout_ms.count(), (i_type)(m_timeout_ms.count()*(1.0 + m_random_factor))); + return std::chrono::milliseconds(v); } ClientTask::ClientTask(ConnectionManager* connectionManager, mg_connection *client, Router::JobParams& prms) diff --git a/src/rta/DaemonRpcClient.cpp b/src/rta/DaemonRpcClient.cpp index ce7d1ba2..d6e2d7cc 100644 --- a/src/rta/DaemonRpcClient.cpp +++ b/src/rta/DaemonRpcClient.cpp @@ -172,6 +172,43 @@ bool DaemonRpcClient::get_block_hash(uint64_t height, string &hash) return true; } +bool DaemonRpcClient::send_supernode_stakes(const char* network_address, const char* id) +{ + epee::json_rpc::request req = AUTO_VAL_INIT(req); + epee::json_rpc::response res = AUTO_VAL_INIT(res); + req.jsonrpc = "2.0"; + req.id = epee::serialization::storage_entry(0); + req.method = "send_supernode_stakes"; + req.params.network_address = network_address; + req.params.supernode_public_id = id; + bool r = epee::net_utils::invoke_http_json("/json_rpc/rta", req, res, m_http_client, m_rpc_timeout); + if (!r) { + LOG_ERROR("/json_rpc/rta/send_supernode_stakes error"); + return false; + } + + return true; +} + +bool DaemonRpcClient::send_supernode_blockchain_based_list(const char* network_address, const char* id, uint64_t last_received_block_height) +{ + epee::json_rpc::request req = AUTO_VAL_INIT(req); + epee::json_rpc::response res = AUTO_VAL_INIT(res); + req.jsonrpc = "2.0"; + req.id = epee::serialization::storage_entry(0); + req.method = "send_supernode_blockchain_based_list"; + req.params.network_address = network_address; + req.params.supernode_public_id = id; + req.params.last_received_block_height = last_received_block_height; + bool r = epee::net_utils::invoke_http_json("/json_rpc/rta", req, res, m_http_client, m_rpc_timeout); + if (!r) { + LOG_ERROR("/json_rpc/rta/send_supernode_blockchain_based_list error"); + return false; + } + + return true; +} + bool DaemonRpcClient::init(const string &daemon_address, boost::optional daemon_login) { return m_http_client.set_server(daemon_address, daemon_login); diff --git a/src/rta/fullsupernodelist.cpp b/src/rta/fullsupernodelist.cpp index dc07624d..2a92fb0c 100644 --- a/src/rta/fullsupernodelist.cpp +++ b/src/rta/fullsupernodelist.cpp @@ -15,6 +15,11 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "supernode.fullsupernodelist" +constexpr size_t STAKES_RECV_TIMEOUT_SECONDS = 600; +constexpr size_t BLOCKCHAIN_BASED_LIST_RECV_TIMEOUT_SECONDS = 180; +constexpr size_t BLOCKCHAIN_BASED_LIST_DELAY_BLOCK_COUNT = 10; +constexpr size_t REPEATED_REQUEST_DELAY_SECONDS = 10; + namespace fs = boost::filesystem; using namespace boost::multiprecision; @@ -128,6 +133,10 @@ FullSupernodeList::FullSupernodeList(const string &daemon_address, bool testnet) , m_testnet(testnet) , m_rpc_client(daemon_address, "", "") , m_tp(new utils::ThreadPool()) + , m_blockchain_based_list_max_block_number() + , m_stakes_max_block_number() + , m_next_recv_stakes(boost::date_time::not_a_date_time) + , m_next_recv_blockchain_based_list(boost::date_time::not_a_date_time) { m_refresh_counter = 0; } @@ -145,26 +154,31 @@ bool FullSupernodeList::add(Supernode *item) bool FullSupernodeList::add(SupernodePtr item) { - if (exists(item->walletAddress())) { - LOG_ERROR("item already exists: " << item->walletAddress()); + if (exists(item->idKeyAsString())) { + LOG_ERROR("item already exists: " << item->idKeyAsString()); return false; } boost::unique_lock writerLock(m_access); - m_list.insert(std::make_pair(item->walletAddress(), item)); - LOG_PRINT_L1("added supernode: " << item->walletAddress()); - LOG_PRINT_L1("list size: " << m_list.size()); + addImpl(item); return true; } +void FullSupernodeList::addImpl(SupernodePtr item) +{ + m_list.insert(std::make_pair(item->idKeyAsString(), item)); + LOG_PRINT_L1("added supernode: " << item->idKeyAsString()); + LOG_PRINT_L1("list size: " << m_list.size()); +} + size_t FullSupernodeList::loadFromDir(const string &base_dir) { - vector wallets = findWallets(base_dir); - size_t result = 0; - LOG_PRINT_L1("found wallets: " << wallets.size()); - for (const auto &wallet_path : wallets) { - loadWallet(wallet_path); - } +// vector wallets = findWallets(base_dir); +// size_t result = 0; +// LOG_PRINT_L1("found wallets: " << wallets.size()); +// for (const auto &wallet_path : wallets) { +// loadWallet(wallet_path); +// } return this->size(); } @@ -172,24 +186,24 @@ size_t FullSupernodeList::loadFromDir(const string &base_dir) size_t FullSupernodeList::loadFromDirThreaded(const string &base_dir, size_t &found_wallets) { - vector wallets = findWallets(base_dir); - LOG_PRINT_L1("found wallets: " << wallets.size()); - found_wallets = wallets.size(); +// vector wallets = findWallets(base_dir); +// LOG_PRINT_L1("found wallets: " << wallets.size()); +// found_wallets = wallets.size(); - utils::ThreadPool tp; +// utils::ThreadPool tp; - for (const auto &wallet_path : wallets) { - tp.enqueue(boost::bind(&FullSupernodeList::loadWallet, this, wallet_path)); - } +// for (const auto &wallet_path : wallets) { +// tp.enqueue(boost::bind(&FullSupernodeList::loadWallet, this, wallet_path)); +// } - tp.run(); +// tp.run(); return this->size(); } -bool FullSupernodeList::remove(const string &address) +bool FullSupernodeList::remove(const string &id) { - boost::unique_lock readerLock(m_access); - return m_list.erase(address) > 0; + boost::unique_lock writerLock(m_access); + return m_list.erase(id) > 0; } size_t FullSupernodeList::size() const @@ -198,24 +212,24 @@ size_t FullSupernodeList::size() const return m_list.size(); } -bool FullSupernodeList::exists(const string &address) const +bool FullSupernodeList::exists(const string &id) const { boost::shared_lock readerLock(m_access); - return m_list.find(address) != m_list.end(); + return m_list.find(id) != m_list.end(); } -bool FullSupernodeList::update(const string &address, const vector &key_images) -{ +//bool FullSupernodeList::update(const string &address, const vector &key_images) +//{ - boost::unique_lock writerLock(m_access); - auto it = m_list.find(address); - if (it != m_list.end()) { - uint64_t height = 0; - return it->second->importKeyImages(key_images, height); - } - return false; -} +// boost::unique_lock writerLock(m_access); +// auto it = m_list.find(address); +// if (it != m_list.end()) { +// uint64_t height = 0; +// return it->second->importKeyImages(key_images, height); +// } +// return false; +//} SupernodePtr FullSupernodeList::get(const string &address) const { @@ -226,32 +240,118 @@ SupernodePtr FullSupernodeList::get(const string &address) const return SupernodePtr(nullptr); } -bool FullSupernodeList::buildAuthSample(uint64_t height, vector &out) +bool FullSupernodeList::selectSupernodes(size_t items_count, const std::string& payment_id, const blockchain_based_list_tier& src_array, supernode_array& dst_array) { - crypto::hash block_hash; - string block_hash_str; + size_t src_array_size = src_array.size(); - MDEBUG("building auth sample for height: " << height); + if (items_count > src_array_size) + items_count = src_array_size; - if (!getBlockHash(height - AUTH_SAMPLE_HASH_HEIGHT, block_hash_str)) { - LOG_ERROR("getBlockHash error"); - return false; + for (size_t i=0; isecond; + + size_t random_value = m_rng(); + + MDEBUG(".....select random value " << random_value << " items count is " << items_count << " with clamp to " << (src_array_size - i) << " items; result is " << (random_value % (src_array_size - i))); + + random_value %= src_array_size - i; + + if (random_value >= items_count) + continue; + + MDEBUG(".....supernode " << src_array[i].supernode_public_id << " has been selected"); + + dst_array.push_back(supernode); + + items_count--; } - epee::string_tools::hex_to_pod(block_hash_str, block_hash); + return true; +} + +bool FullSupernodeList::buildAuthSample(uint64_t height, const std::string& payment_id, supernode_array &out, uint64_t &out_auth_block_number) +{ + uint64_t blockchain_based_list_height = height - BLOCKCHAIN_BASED_LIST_DELAY_BLOCK_COUNT; + + out_auth_block_number = blockchain_based_list_height; + + MDEBUG("building auth sample for height " << height << "(blockchain_based_list_height=" << blockchain_based_list_height << ") and PaymentID '" << payment_id << "'"); - std::array, TIERS> tier_supernodes; + std::array tier_supernodes; { - boost::shared_lock readerLock(m_access); - int64_t now = static_cast(std::time(nullptr)); - int64_t cutoff_time = now - ANNOUNCE_TTL_SECONDS; - for (const auto &sn_pair : m_list) { - const auto &sn = sn_pair.second; - const auto tier = sn->tier(); - MTRACE("checking supernode " << sn_pair.first << ", updated: " << (now - sn->lastUpdateTime()) << "s ago" - << ", tier: " << tier); - if (tier > 0 && sn->lastUpdateTime() >= cutoff_time) - tier_supernodes[tier - 1].push_back(sn); + blockchain_based_list_ptr bbl = std::make_shared(); + { + blockchain_based_list_ptr tmp_bbl = findBlockchainBasedList(blockchain_based_list_height); + for(blockchain_based_list_tier& src : *tmp_bbl) + { + blockchain_based_list_tier dst; + std::copy_if(src.begin(), src.end(), std::back_inserter(dst), [this](const auto& entry)->bool + { + auto it = m_list.find(entry.supernode_public_id); + if(it == m_list.end()) return false; + SupernodePtr& sn = it->second; + uint64_t lastUpdateAge = static_cast(std::time(nullptr)) - sn->lastUpdateTime(); + if(FullSupernodeList::ANNOUNCE_TTL_SECONDS < lastUpdateAge) return false; + return true; + }); + bbl->emplace_back(std::move(dst)); + } + } + + + + if (!bbl) + { + LOG_ERROR("unable to build auth sample for block height " << height << " (blockchain_based_list_height=" << blockchain_based_list_height << ") and PaymentID " + << payment_id << ". Blockchain based list for this block is absent, latest block is " << getBlockchainBasedListMaxBlockNumber()); + return false; + } + + boost::unique_lock writerLock(m_access); + + //seed RNG + + std::seed_seq seed(reinterpret_cast(payment_id.c_str()), + reinterpret_cast(payment_id.c_str() + payment_id.size())); + + m_rng.seed(seed); + + //select supernodes for a full supernode list + + MDEBUG("use blockchain based list for height " << blockchain_based_list_height << " (" << bbl.get() << ")"); + int t = 0; + for (const blockchain_based_list_tier& l : *bbl) + { + MDEBUG("...tier #" << t); + int j=0; + for (const blockchain_based_list_entry& e : l) + MDEBUG(".....[" << j++ << "]=" << e.supernode_public_id); + t++; + } + + for (size_t i=0, tiers_count=bbl->size(); i &o out.reserve(ITEMS_PER_TIER * TIERS); auto out_it = back_inserter(out); for (int i = 0; i < TIERS; i++) { - std::partial_sort( - tier_supernodes[i].begin(), tier_supernodes[i].begin() + select[i], tier_supernodes[i].end(), - [&](const SupernodePtr a, const SupernodePtr b) { - crypto::hash hash_a, hash_b; - a->getScoreHash(block_hash, hash_a); - b->getScoreHash(block_hash, hash_b); - return hash_to_int256(hash_a) < hash_to_int256(hash_b); - }); std::copy(tier_supernodes[i].begin(), tier_supernodes[i].begin() + select[i], out_it); } if (VLOG_IS_ON(2)) { std::string auth_sample_str, tier_sample_str; for (const auto &a : out) { - auth_sample_str += a->walletAddress() + "\n"; + auth_sample_str += a->idKeyAsString() + "\n"; } for (size_t i = 0; i < select.size(); i++) { if (i > 0) tier_sample_str += ", "; @@ -308,14 +400,24 @@ bool FullSupernodeList::buildAuthSample(uint64_t height, vector &o MTRACE("auth sample: \n" << auth_sample_str); } + if (out.size() > AUTH_SAMPLE_SIZE) + out.resize(AUTH_SAMPLE_SIZE); + + MDEBUG("..." << out.size() << " supernodes has been selected"); + return out.size() == AUTH_SAMPLE_SIZE; } +bool FullSupernodeList::buildAuthSample(const string &payment_id, FullSupernodeList::supernode_array &out, uint64_t &out_auth_block_number) +{ + return buildAuthSample(getBlockchainBasedListMaxBlockNumber(), payment_id, out, out_auth_block_number); +} + vector FullSupernodeList::items() const { + boost::shared_lock readerLock(m_access); vector result; result.reserve(m_list.size()); - boost::shared_lock readerLock(m_access); for (auto const& it: m_list) result.push_back(it.first); @@ -352,29 +454,164 @@ size_t FullSupernodeList::refreshedItems() const return m_refresh_counter; } -bool FullSupernodeList::loadWallet(const std::string &wallet_path) +void FullSupernodeList::updateStakes(uint64_t block_number, const supernode_stake_array& stakes, const std::string& cryptonode_rpc_address, bool testnet) { - bool result = false; + MDEBUG("update stakes"); + + boost::unique_lock writerLock(m_access); + + if (block_number <= m_stakes_max_block_number) + { + MDEBUG("stakes for block #" << block_number << " have already been received (last stakes have been received for block #" << m_stakes_max_block_number << ")"); + return; + } + + //clear supernode data - MDEBUG("loading wallet from: " << wallet_path); - Supernode * sn = Supernode::load(wallet_path, "", m_daemon_address, m_testnet); - if (sn) { - if (!this->add(sn)) { - LOG_ERROR("Can't add supernode " << sn->walletAddress() << ", already exists"); - delete sn; - } else { - MINFO("Added supernode: " << sn->walletAddress() << ", stake: " << sn->stakeAmount()); - result = true; + for (const std::unordered_map::value_type& sn_desc : m_list) + { + SupernodePtr sn = sn_desc.second; + + if (!sn) + continue; + + sn->setStake(0, 0, 0); + } + + //update supernodes + + for (const supernode_stake& stake : stakes) + { + auto it = m_list.find(stake.supernode_public_id); + + if (it == m_list.end()) + { + SupernodePtr sn (Supernode::createFromStake(stake, cryptonode_rpc_address, testnet)); + + if (!sn) + { + LOG_ERROR("Cant create watch-only supernode wallet for id: " << stake.supernode_public_id); + continue; + } + + MINFO("About to add supernode to list [" << sn << "]: " << sn->idKeyAsString()); + + addImpl(sn); + + continue; } + + //update stake + + SupernodePtr sn = it->second; + + sn->setStake(stake.amount, stake.block_height, stake.unlock_time); + sn->setWalletAddress(stake.supernode_public_address); + } + + m_stakes_max_block_number = block_number; + m_next_recv_stakes = boost::posix_time::second_clock::local_time() + boost::posix_time::seconds(STAKES_RECV_TIMEOUT_SECONDS); +} + +namespace +{ + +bool check_timeout_expired(boost::posix_time::ptime& next_recv_time) +{ + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + + if (!next_recv_time.is_special() && next_recv_time > now) + return false; + + next_recv_time = now + boost::posix_time::seconds(REPEATED_REQUEST_DELAY_SECONDS); + + return true; +} + +} + +void FullSupernodeList::synchronizeWithCryptonode(const char* network_address, const char* address) +{ + if (check_timeout_expired(m_next_recv_stakes)) + { + m_rpc_client.send_supernode_stakes(network_address, address); + } + + if (check_timeout_expired(m_next_recv_blockchain_based_list)) + { + m_rpc_client.send_supernode_blockchain_based_list(network_address, address, m_blockchain_based_list_max_block_number); } - return result; } +uint64_t FullSupernodeList::getBlockchainHeight() const +{ + uint64_t result = 0; + bool ret = m_rpc_client.get_height(result); + return ret ? result : 0; +} + +void FullSupernodeList::setBlockchainBasedList(uint64_t block_number, const blockchain_based_list_ptr& list) +{ + boost::unique_lock writerLock(m_access); + + MDEBUG("update blockchain based list for height " << block_number << " (" << list.get() << ")"); + int t = 0; + for (const blockchain_based_list_tier& l : *list) + { + MDEBUG("...tier #" << t); + int j=0; + for (const blockchain_based_list_entry& e : l) + MDEBUG(".....[" << j++ << "]=" << e.supernode_public_id); + t++; + } + + blockchain_based_list_map::iterator it = m_blockchain_based_lists.find(block_number); + + if (it != m_blockchain_based_lists.end()) + { + MWARNING("Overriding blockchain based list for block " << block_number); + it->second = list; + return; + } + + m_next_recv_blockchain_based_list = boost::posix_time::second_clock::local_time() + boost::posix_time::seconds(BLOCKCHAIN_BASED_LIST_RECV_TIMEOUT_SECONDS); + + m_blockchain_based_lists[block_number] = list; + + if (block_number > m_blockchain_based_list_max_block_number) + m_blockchain_based_list_max_block_number = block_number; + + //flush cache - remove old blockchain based lists + + uint64_t oldest_block_number = m_blockchain_based_list_max_block_number - config::graft::SUPERNODE_HISTORY_SIZE; + + for (blockchain_based_list_map::iterator it=m_blockchain_based_lists.begin(); it!=m_blockchain_based_lists.end();) + if (it->first < oldest_block_number) it = m_blockchain_based_lists.erase(it); + else ++it; +} + +FullSupernodeList::blockchain_based_list_ptr FullSupernodeList::findBlockchainBasedList(uint64_t block_number) const +{ + boost::shared_lock readerLock(m_access); + + blockchain_based_list_map::const_iterator it = m_blockchain_based_lists.find(block_number); + + if (it == m_blockchain_based_lists.end()) + return blockchain_based_list_ptr(); + + return it->second; +} + +uint64_t FullSupernodeList::getBlockchainBasedListMaxBlockNumber() const +{ + boost::shared_lock readerLock(m_access); + return m_blockchain_based_list_max_block_number; +} std::ostream& operator<<(std::ostream& os, const std::vector supernodes) { for (size_t i = 0; i < supernodes.size(); ++i) { - os << supernodes[i]->walletAddress(); + os << supernodes[i]->idKeyAsString(); if (i < supernodes.size() - 1) os << ", "; } diff --git a/src/rta/supernode.cpp b/src/rta/supernode.cpp index 122c0c60..d9ccdaa2 100644 --- a/src/rta/supernode.cpp +++ b/src/rta/supernode.cpp @@ -17,71 +17,36 @@ using namespace std; namespace graft { - -using graft::supernode::request::SignedKeyImageStr; using graft::supernode::request::SupernodeAnnounce; -boost::shared_ptr Supernode::m_ioservice { new boost::asio::io_service() }; #ifndef __cpp_inline_variables constexpr uint64_t Supernode::TIER1_STAKE_AMOUNT, Supernode::TIER2_STAKE_AMOUNT, Supernode::TIER3_STAKE_AMOUNT, Supernode::TIER4_STAKE_AMOUNT; #endif -Supernode::Supernode(const string &wallet_path, const string &wallet_password, const string &daemon_address, bool testnet, - const string &seed_language) - : m_wallet{new tools::wallet2(testnet, false, Supernode::m_ioservice)} +Supernode::Supernode(const string &wallet_address, const crypto::public_key &id_key, const string &daemon_address, bool testnet) + : m_wallet_address(wallet_address) + , m_id_key(id_key) + , m_has_secret_key(false) , m_last_update_time {0} + , m_stake_amount() + , m_stake_block_height() + , m_stake_unlock_time() + , m_testnet(testnet) { - bool keys_file_exists; - bool wallet_file_exists; - - tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists); - - MDEBUG("daemon address: " << daemon_address << ", keys_file_exists: " << boolalpha << keys_file_exists << noboolalpha - << " wallet_file_exists: " << boolalpha << wallet_file_exists << noboolalpha); - - // wallet needs to be initialized with the daemon address first. - // Otherwize, wallet will get wrong "refresh_from_blockheight" value - m_wallet->init(daemon_address); - - // existing wallet, open it - if (keys_file_exists) { - m_wallet->load(wallet_path, wallet_password); - // new wallet, generating it - } else { - if(!seed_language.empty()) - m_wallet->set_seed_language(seed_language); - crypto::secret_key recovery_val, secret_key; - recovery_val = m_wallet->generate(wallet_path, wallet_password, secret_key, false, false); - } - - // new wallet. in case we don't have a connection to a daemon at the moment wallet created - // it may happen wallet get "refresh from block height" parameter in the future - if (!keys_file_exists) { - std::string error; - uint64 daemon_height = m_wallet->get_daemon_blockchain_height(error); - // daemon_height will be 0 in case no daemon - if (daemon_height == 0) - m_wallet->set_refresh_from_block_height(0); - } - m_wallet->store(); - MDEBUG("refresh from block height: " << m_wallet->get_refresh_from_block_height()); - MINFO("supernode created: " << "[" << this << "] " << this->walletAddress()); + MINFO("supernode created: " << "[" << this << "] " << this->walletAddress() << ", " << this->idKeyAsString()); } + Supernode::~Supernode() { - LOG_PRINT_L0("destroying supernode: " << "[" << this << "] " << this->walletAddress()); - { - boost::unique_lock writeLock(m_wallet_guard); - m_wallet->store(); - } + LOG_PRINT_L0("destroying supernode: " << "[" << this << "] " << this->walletAddress() << ", " << this->idKeyAsString()); } uint64_t Supernode::stakeAmount() const { - boost::shared_lock readLock(m_wallet_guard); - return m_wallet->unspent_balance(); + boost::shared_lock readerLock(m_access); + return m_stake_amount; } uint32_t Supernode::tier() const @@ -94,164 +59,28 @@ uint32_t Supernode::tier() const (stake >= TIER4_STAKE_AMOUNT); } -uint64_t Supernode::walletBalance() const -{ - boost::shared_lock readLock(m_wallet_guard); - return m_wallet->balance(); -} - string Supernode::walletAddress() const { - return m_wallet->get_account().get_public_address_str(m_wallet->testnet()); -} - -uint64_t Supernode::daemonHeight() const -{ - uint64_t result = 0; - std::string err; - boost::shared_lock readLock(m_wallet_guard); - result = m_wallet->get_daemon_blockchain_height(err); - if (!result) { - LOG_ERROR(err); - } - return result; -} - -bool Supernode::exportKeyImages(vector &key_images) const -{ - boost::shared_lock readLock(m_wallet_guard); - try { - key_images = m_wallet->export_key_images(); - return !key_images.empty(); - } catch (const std::exception &e) { - LOG_ERROR("wallet exception: " << e.what()); - return false; - } catch (...) { - LOG_ERROR("unknown exception"); - return false; - } + boost::shared_lock readerLock(m_access); + return m_wallet_address; } -bool Supernode::importKeyImages(const vector &key_images, uint64_t &height) +void Supernode::setWalletAddress(const std::string &address) { - uint64_t spent = 0; - uint64_t unspent = 0; + boost::unique_lock writerLock(m_access); - try - { - // shared_mutex isn't recursive, we need to unlock it before daemonHeight() called - boost::unique_lock writeLock(m_wallet_guard); - m_wallet->import_key_images(key_images, spent, unspent); - m_last_update_time = static_cast(std::time(nullptr)); - } - catch(const std::exception& e) - { - LOG_ERROR("wallet exception: " << e.what()); - return false; - } - catch (...) - { - LOG_ERROR("unknown exception"); - return false; - } - - height = this->daemonHeight(); - return true; -} - -Supernode *Supernode::createFromViewOnlyWallet(const string &path, const string &address, const secret_key &viewkey, bool testnet) -{ - Supernode * result = nullptr; - bool keys_file_exists; - bool wallet_file_exists; - // TODO: password - string password = ""; - tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); - - if (keys_file_exists) { - LOG_ERROR("attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."); - return result; - } - - cryptonote::account_public_address wallet_addr; - if (!cryptonote::get_account_address_from_str(wallet_addr, testnet, address)) { - LOG_ERROR("Error parsing address"); - return result; - } - - result = new Supernode(testnet); - result->m_wallet->generate(path, password, wallet_addr, viewkey); - return result; -} - -Supernode *Supernode::load(const string &wallet_path, const string &wallet_password, const string &daemon_address, bool testnet, const string &seed_language) -{ - Supernode * sn = nullptr; - try { - sn = new Supernode(wallet_path, wallet_password, daemon_address, testnet); - sn->refresh(); - - if (false/*sn->stakeAmount() < Supernode::TIER1_STAKE_AMOUNT*/) { - LOG_ERROR("wallet " << sn->walletAddress() << " doesn't have enough stake to be supernode: " << sn->stakeAmount()); - delete sn; - return nullptr; - } else { - LOG_PRINT_L1("Loaded supernode: " << sn->walletAddress() << ", stake: " << sn->stakeAmount()); - } - } catch (...) { // wallet exception; TODO: catch specific exception if possible - LOG_ERROR("libwallet exception"); - } - - return sn; + m_wallet_address = address; } bool Supernode::updateFromAnnounce(const SupernodeAnnounce &announce) { - // check if address match - MDEBUG("updating supernode from announce: " << announce.address); - if (this->walletAddress() != announce.address) { - LOG_ERROR("wrong address. this address: " << this->walletAddress() << ", announce address: " << announce.address); + //setNetworkAddress(announce.network_address); + crypto::public_key id_key; + if (!Supernode::validateAnnounce(announce, id_key)) return false; - } - // update wallet's blockchain first - // refresh is protected by mutex - if (!this->refresh()) - return false; - - vector signed_key_images; - - for (const SignedKeyImageStr &skis : announce.signed_key_images) { - crypto::key_image ki; - crypto::signature s; - - if (!epee::string_tools::hex_to_pod(skis.key_image, ki)) { - LOG_ERROR("failed to parse key image: " << skis.key_image); - return false; - } - - if (!epee::string_tools::hex_to_pod(skis.signature, s)) { - LOG_ERROR("failed to parse key signature: " << skis.signature); - return false; - } - signed_key_images.push_back(std::make_pair(ki, s)); - } - - uint64_t height = 0; - if (!importKeyImages(signed_key_images, height)) { - LOG_ERROR("failed to import key images"); - return false; - } - - if (!signed_key_images.empty() && height == 0) { - LOG_ERROR("key images imported but height is 0"); - return false; - } - - // TODO: check self amount vs announced amount - setNetworkAddress(announce.network_address); - m_last_update_time = static_cast(std::time(nullptr)); + setLastUpdateTime(std::time(nullptr)); uint64 stake_amount = stakeAmount(); MDEBUG("update from announce done for: " << walletAddress() << "; last update time updated to: " << m_last_update_time << @@ -259,143 +88,96 @@ bool Supernode::updateFromAnnounce(const SupernodeAnnounce &announce) return true; } -Supernode *Supernode::createFromAnnounce(const string &path, const SupernodeAnnounce &announce, const std::string &daemon_address, +Supernode *Supernode::createFromAnnounce(const SupernodeAnnounce &announce, const std::string &daemon_address, bool testnet) { - Supernode * result = nullptr; - crypto::secret_key viewkey; - if (!epee::string_tools::hex_to_pod(announce.secret_viewkey, viewkey)) { - LOG_ERROR("Failed to parse secret viewkey from string: " << announce.secret_viewkey); + crypto::public_key id_key; + if (!Supernode::validateAnnounce(announce, id_key)) return nullptr; - } - - try { - result = Supernode::createFromViewOnlyWallet(path, announce.address, viewkey, testnet); - // before importing key images, wallet needs to be connected to daemon and syncrhonized - if (result) { - result->setDaemonAddress(daemon_address); - result->updateFromAnnounce(announce); - } - } catch (...) { - LOG_ERROR("wallet exception"); - delete result; - result = nullptr; - } + Supernode * result = new Supernode("", id_key, daemon_address, testnet); + result->setLastUpdateTime(time(nullptr)); + // TODO: get stake amount here? return result; } bool Supernode::prepareAnnounce(SupernodeAnnounce &announce) { - { - boost::shared_lock lock(m_wallet_guard); - if (!m_wallet->is_synced()) { - MWARNING("Wallet is not synced"); - return false; - } - - announce.timestamp = time(nullptr); - announce.secret_viewkey = epee::string_tools::pod_to_hex(this->exportViewkey()); - announce.height = m_wallet->get_blockchain_current_height(); - - vector signed_key_images; - if (!exportKeyImages(signed_key_images)) { - LOG_ERROR("Failed to export key images"); - return false; - } - - for (const SignedKeyImage &ski : signed_key_images) { - SignedKeyImageStr skis; - skis.key_image = epee::string_tools::pod_to_hex(ski.first); - skis.signature = epee::string_tools::pod_to_hex(ski.second); - announce.signed_key_images.push_back(std::move(skis)); - } - } + announce.supernode_public_id = this->idKeyAsString(); + announce.height = m_stake_block_height; - announce.stake_amount = this->stakeAmount(); - announce.address = this->walletAddress(); + crypto::signature sign; + if (!signMessage(announce.supernode_public_id + to_string(announce.height), sign)) + return false; + announce.signature = epee::string_tools::pod_to_hex(sign); announce.network_address = this->networkAddress(); return true; } -crypto::secret_key Supernode::exportViewkey() const +Supernode* Supernode::createFromStake(const supernode_stake& stake, const std::string &daemon_address, bool testnet) { - boost::shared_lock readLock(m_wallet_guard); - return m_wallet->get_account().get_keys().m_view_secret_key; -} + crypto::public_key id_key; + if (!epee::string_tools::hex_to_pod(stake.supernode_public_id, id_key)) { + MERROR("Failed to parse id key from stake: " << stake.supernode_public_id); + return nullptr; + } + std::unique_ptr result (new Supernode(stake.supernode_public_address, id_key, daemon_address, testnet)); + + result->setLastUpdateTime(time(nullptr)); + result->setStake(stake.amount, stake.block_height, stake.unlock_time); + + return result.release(); +} bool Supernode::signMessage(const string &msg, crypto::signature &signature) const { - if (m_wallet->watch_only()) { - LOG_ERROR("Attempting to sign with watch-only wallet"); - return false; - } - crypto::hash hash; + crypto::cn_fast_hash(msg.data(), msg.size(), hash); + MDEBUG("signing message: " << msg << ", hash: " << hash); return this->signHash(hash, signature); } bool Supernode::signHash(const crypto::hash &hash, crypto::signature &signature) const { - const cryptonote::account_keys &keys = m_wallet->get_account().get_keys(); - crypto::generate_signature(hash, keys.m_account_address.m_spend_public_key, keys.m_spend_secret_key, signature); + if (!m_has_secret_key) { + LOG_ERROR("Attempting to sign with without private key"); + return false; + } + + crypto::generate_signature(hash, m_id_key, m_secret_key, signature); return true; } -bool Supernode::verifySignature(const string &msg, const string &address, const crypto::signature &signature) const +bool Supernode::verifySignature(const string &msg, const crypto::public_key &pkey, const crypto::signature &signature) { crypto::hash hash; crypto::cn_fast_hash(msg.data(), msg.size(), hash); - return verifyHash(hash, address, signature); + return verifyHash(hash, pkey, signature); } -bool Supernode::verifyHash(const crypto::hash &hash, const string &address, const crypto::signature &signature) const +bool Supernode::verifyHash(const crypto::hash &hash, const crypto::public_key &pkey, const crypto::signature &signature) { - cryptonote::account_public_address wallet_addr; - if (!cryptonote::get_account_address_from_str(wallet_addr, m_wallet->testnet(), address)) { - LOG_ERROR("Error parsing address"); - return false; - } - return crypto::check_signature(hash, wallet_addr.m_spend_public_key, signature); -} - - - -bool Supernode::setDaemonAddress(const string &address) -{ - // boost::unique_lock writeLock(m_wallet_guard); - return m_wallet->init(address); + return crypto::check_signature(hash, pkey, signature); } bool Supernode::refresh() { - MDEBUG("about to refresh account: " << this->walletAddress()); - boost::unique_lock writeLock(m_wallet_guard); - try { - m_wallet->refresh(); - m_wallet->rescan_unspent(); - m_wallet->store(); - } catch (...) { - LOG_ERROR("Failed to refresh supernode wallet: " << this->walletAddress()); - return false; - } MDEBUG("account refreshed: " << this->walletAddress()); return true; } bool Supernode::testnet() const { - return m_wallet->testnet(); + return m_testnet; } void Supernode::getScoreHash(const crypto::hash &block_hash, crypto::hash &result) const { - // address wont be changed during object lifetime, not need to lock - cryptonote::blobdata data = m_wallet->get_account().get_public_address_str(testnet()); + cryptonote::blobdata data = epee::string_tools::pod_to_hex(m_id_key); data += epee::string_tools::pod_to_hex(block_hash); crypto::cn_fast_hash(data.c_str(), data.size(), result); } @@ -414,12 +196,13 @@ void Supernode::setNetworkAddress(const string &networkAddress) bool Supernode::getAmountFromTx(const cryptonote::transaction &tx, uint64_t &amount) { - return m_wallet->get_amount_from_tx(tx, amount); + // return m_wallet->get_amount_from_tx(tx, amount); + return false; } bool Supernode::getPaymentIdFromTx(const cryptonote::transaction &tx, string &paymentId) { - return true; + return false; } bool Supernode::validateAddress(const string &address, bool testnet) @@ -430,7 +213,6 @@ bool Supernode::validateAddress(const string &address, bool testnet) int64_t Supernode::lastUpdateTime() const { - return m_last_update_time; } @@ -441,19 +223,131 @@ void Supernode::setLastUpdateTime(int64_t time) bool Supernode::busy() const { - if (m_wallet_guard.try_lock_shared()) { - m_wallet_guard.unlock_shared(); + return false; +} + +uint64_t Supernode::stakeBlockHeight() const +{ + boost::shared_lock readerLock(m_access); + return m_stake_block_height; +} + +void Supernode::setStake(uint64_t stakeAmount, uint64_t blockHeight, uint64_t unlockTime) +{ + boost::unique_lock writerLock(m_access); + + m_stake_amount = stakeAmount; + m_stake_block_height = blockHeight; + m_stake_unlock_time = unlockTime; +} + +uint64_t Supernode::stakeUnlockTime() const +{ + boost::shared_lock readerLock(m_access); + return m_stake_unlock_time; +} + +bool Supernode::loadKeys(const string &filename) +{ + if (!boost::filesystem::exists(filename)) { + MWARNING("failed to load keys from file: " << filename << ", file doesn't exists"); + return false; + } + + MTRACE(" Reading wallet keys file '" << filename << "'"); + + std::string key_data; + if (!epee::file_io_utils::load_file_to_string(filename, key_data)) { + MERROR("failed to load keys from file: " << filename << ", read error"); + return false; + } + + if (!epee::string_tools::hex_to_pod(key_data, m_secret_key)) { + MERROR("failed to load keys from file: " << filename << ", parse error"); + return false; + } + + if (!crypto::secret_key_to_public_key(m_secret_key, m_id_key)) { + MERROR("failed to load keys from file: " << filename << ", can't generate public key"); return false; - } else { - return true; } + m_has_secret_key = true; + return true; +} + + +void graft::Supernode::initKeys() +{ + crypto::generate_keys(m_id_key, m_secret_key); + m_has_secret_key = true; } -Supernode::Supernode(bool testnet) -: m_wallet{ new tools::wallet2(testnet) } + +bool Supernode::saveKeys(const string &filename, bool force) { + if (boost::filesystem::exists(filename) && !force) { + MWARNING("key file already exists: " << filename << " but overwrite is not forced"); + return false; + } + //save secret key + boost::filesystem::path wallet_keys_file_tmp = filename; + wallet_keys_file_tmp += ".tmp"; + std::string data = epee::string_tools::pod_to_hex(m_secret_key); + if (!epee::file_io_utils::save_string_to_file(wallet_keys_file_tmp.string(), data)) { + MERROR("Cannot write to file '" << wallet_keys_file_tmp << "'"); + return false; + } + + boost::system::error_code errcode; + boost::filesystem::rename(wallet_keys_file_tmp, filename, errcode); + if (errcode) { + MERROR("Cannot rename '" << wallet_keys_file_tmp.string() << " to '" << filename << "', :" << errcode.message()); + return false; + } + return true; } +const public_key &Supernode::idKey() const +{ + return m_id_key; } +const secret_key &Supernode::secretKey() const +{ + return m_secret_key; +} + +string Supernode::idKeyAsString() const +{ + return epee::string_tools::pod_to_hex(m_id_key); +} + +bool Supernode::validateAnnounce(const SupernodeAnnounce& announce, crypto::public_key &id_key) +{ + if (announce.supernode_public_id.empty()) { + MERROR("Empty public id"); + return false; + } + + if (!epee::string_tools::hex_to_pod(announce.supernode_public_id, id_key)) { + MERROR("Failed to parse id key from announce: " << announce.supernode_public_id); + return false; + } + + crypto::signature sign; + if (!epee::string_tools::hex_to_pod(announce.signature, sign)) { + MERROR("Failed to parse signature from announce: " << announce.signature); + return false; + } + + string msg = announce.supernode_public_id + to_string(announce.height); + if (!Supernode::verifySignature(msg, id_key, sign)) { + MERROR("Signature check failed "); + return false; + } + return true; +} + +} // namespace graft + diff --git a/src/supernode/requestdefines.cpp b/src/supernode/requestdefines.cpp index 5fb57b87..bd1dde33 100644 --- a/src/supernode/requestdefines.cpp +++ b/src/supernode/requestdefines.cpp @@ -128,7 +128,7 @@ void cleanPaySaleData(const std::string& payment_id, Context& ctx) void buildBroadcastSaleStatusOutput(const std::string& payment_id, int status, const SupernodePtr& supernode, Output& output) { UpdateSaleStatusBroadcast ussb; - ussb.address = supernode->walletAddress(); + ussb.id_key = supernode->idKeyAsString(); ussb.Status = status; ussb.PaymentID = payment_id; diff --git a/src/supernode/requests.cpp b/src/supernode/requests.cpp index b52f8788..412707ab 100644 --- a/src/supernode/requests.cpp +++ b/src/supernode/requests.cpp @@ -15,6 +15,8 @@ #include "supernode/requests/send_raw_tx.h" #include "supernode/requests/authorize_rta_tx.h" #include "supernode/requests/send_supernode_announce.h" +#include "supernode/requests/send_supernode_stakes.h" +#include "supernode/requests/blockchain_based_list.h" namespace graft::supernode::request::debug { void __registerDebugRequests(Router& router); } namespace graft::request::system_info { void register_request(Router& router); } @@ -33,6 +35,8 @@ void registerRTARequests(graft::Router &router) registerRejectPayRequest(router); registerAuthorizeRtaTxRequests(router); registerSendSupernodeAnnounceRequest(router); + registerSendSupernodeStakesRequest(router); + registerBlockchainBasedListRequest(router); } void registerWalletApiRequests(graft::Router &router) diff --git a/src/supernode/requests/authorize_rta_tx.cpp b/src/supernode/requests/authorize_rta_tx.cpp index 8e29006e..e38eebbd 100644 --- a/src/supernode/requests/authorize_rta_tx.cpp +++ b/src/supernode/requests/authorize_rta_tx.cpp @@ -50,7 +50,7 @@ namespace { namespace graft::supernode::request { GRAFT_DEFINE_IO_STRUCT_INITED(SupernodeSignature, - (std::string, address, std::string()), + (std::string, id_key, std::string()), (std::string, result_signature, std::string()), // signarure for tx_id + result (std::string, tx_signature, std::string()) // signature for tx_id only ); @@ -92,21 +92,21 @@ struct RtaAuthResult { std::vector approved; std::vector rejected; - bool alreadyApproved(const std::string &address) + bool alreadyApproved(const std::string &id) { - return contains(approved, address); + return contains(approved, id); } - bool alreadyRejected(const std::string &address) + bool alreadyRejected(const std::string &id) { - return contains(rejected, address); + return contains(rejected, id); } private: - bool contains(const std::vector &v, const std::string &address) + bool contains(const std::vector &v, const std::string &id) { return std::find_if(v.begin(), v.end(), [&](const SupernodeSignature &item) { - return item.address == address; + return item.id_key == id; }) != v.end(); } }; @@ -114,17 +114,19 @@ struct RtaAuthResult // TODO: this function duplicates PendingTransaction::putRtaSignatures void putRtaSignaturesToTx(cryptonote::transaction &tx, const std::vector &signatures, bool testnet) { +#if 0 std::vector bin_signs; for (const auto &sign : signatures) { cryptonote::rta_signature bin_sign; - if (!cryptonote::get_account_address_from_str(bin_sign.address, testnet, sign.address)) { - LOG_ERROR("error parsing address from string: " << sign.address); + if (!cryptonote::get_account_address_from_str(bin_sign.address, testnet, sign.id_key)) { + LOG_ERROR("error parsing address from string: " << sign.id_key); continue; } epee::string_tools::hex_to_pod(sign.tx_signature, bin_sign.signature); bin_signs.push_back(bin_sign); } tx.put_rta_signatures(bin_signs); +#endif } @@ -143,7 +145,7 @@ bool signAuthResponse(AuthorizeRtaTxResponse &arg, const SupernodePtr &supernode epee::string_tools::hex_to_pod(arg.tx_id, tx_id); supernode->signHash(tx_id, sign); arg.signature.tx_signature = epee::string_tools::pod_to_hex(sign); - arg.signature.address = supernode->walletAddress(); + arg.signature.id_key = supernode->idKeyAsString(); return true; } @@ -176,8 +178,10 @@ bool validateAuthResponse(const AuthorizeRtaTxResponse &arg, const SupernodePtr std::string msg = arg.tx_id + ":" + to_string(arg.result); - bool r1 = supernode->verifySignature(msg, arg.signature.address, sign_result); - bool r2 = supernode->verifyHash(tx_id, arg.signature.address, sign_tx_id); + crypto::public_key id_key; + epee::string_tools::hex_to_pod(arg.signature.id_key, id_key); + bool r1 = supernode->verifySignature(msg, id_key, sign_result); + bool r2 = supernode->verifyHash(tx_id, id_key, sign_tx_id); return r1 && r2; } @@ -292,7 +296,7 @@ Status handleTxAuthRequest(const Router::vars_t& vars, const graft::Input& /*inp // TODO: read payment id from transaction, map tx_id to payment_id MulticastRequestJsonRpc authResponseMulticast; authResponseMulticast.method = "multicast"; - authResponseMulticast.params.sender_address = supernode->walletAddress(); + authResponseMulticast.params.sender_address = supernode->idKeyAsString(); authResponseMulticast.params.receiver_addresses = req.params.receiver_addresses; authResponseMulticast.params.callback_uri = PATH_RESPONSE; AuthorizeRtaTxResponse authResponse; @@ -301,7 +305,7 @@ Status handleTxAuthRequest(const Router::vars_t& vars, const graft::Input& /*inp RTAAuthResult::Approved : RTAAuthResult::Rejected); signAuthResponse(authResponse, supernode); - authResponse.signature.address = supernode->walletAddress(); + authResponse.signature.id_key = supernode->idKeyAsString(); // store tx ctx.global.set(authResponse.tx_id + CONTEXT_KEY_TX_BY_TXID, tx, RTA_TX_TTL); @@ -408,7 +412,7 @@ Status handleRtaAuthResponseMulticast(const Router::vars_t& vars, const graft::I string payment_id = ctx.global.get(ctx_payment_id_key, std::string()); MDEBUG("incoming tx auth response payment: " << payment_id << ", tx_id: " << rtaAuthResp.tx_id - << ", from: " << rtaAuthResp.signature.address + << ", from: " << rtaAuthResp.signature.id_key << ", result: " << int(result)); // store payment id for a logging purposes @@ -430,9 +434,9 @@ Status handleRtaAuthResponseMulticast(const Router::vars_t& vars, const graft::I authResult = ctx.global.get(ctx_tx_to_auth_resp, authResult); } - if (authResult.alreadyApproved(rtaAuthResp.signature.address) - || authResult.alreadyRejected(rtaAuthResp.signature.address)) { - return errorCustomError(string("supernode: ") + rtaAuthResp.signature.address + " already processed", + if (authResult.alreadyApproved(rtaAuthResp.signature.id_key) + || authResult.alreadyRejected(rtaAuthResp.signature.id_key)) { + return errorCustomError(string("supernode: ") + rtaAuthResp.signature.id_key + " already processed", ERROR_ADDRESS_INVALID, output); } @@ -442,7 +446,7 @@ Status handleRtaAuthResponseMulticast(const Router::vars_t& vars, const graft::I authResult.rejected.push_back(rtaAuthResp.signature); } - MDEBUG("rta result accepted from " << rtaAuthResp.signature.address + MDEBUG("rta result accepted from " << rtaAuthResp.signature.id_key << ", payment: " << payment_id); // store result in context diff --git a/src/supernode/requests/blockchain_based_list.cpp b/src/supernode/requests/blockchain_based_list.cpp new file mode 100644 index 00000000..8cc82696 --- /dev/null +++ b/src/supernode/requests/blockchain_based_list.cpp @@ -0,0 +1,122 @@ +// Copyright (c) 2018, The Graft 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 "supernode/requests/blockchain_based_list.h" +#include "supernode/requestdefines.h" +#include "rta/fullsupernodelist.h" +#include "rta/supernode.h" + +#include +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "supernode.blockchainbasedlistrequest" + +namespace { + static const char* PATH = "/blockchain_based_list"; +} + +namespace graft::supernode::request { + +namespace +{ + +Status blockchainBasedListHandler + (const Router::vars_t& vars, + const graft::Input& input, + graft::Context& ctx, + graft::Output& output) +{ + LOG_PRINT_L1(PATH << " called with payload: " << input.data()); + + boost::shared_ptr fsl = ctx.global.get("fsl", boost::shared_ptr()); + SupernodePtr supernode = ctx.global.get("supernode", SupernodePtr()); + + if (!fsl.get()) { + LOG_ERROR("Internal error. Supernode list object missing"); + return Status::Error; + } + + if (!supernode.get()) { + LOG_ERROR("Internal error. Supernode object missing"); + return Status::Error; + } + + BlockchainBasedListJsonRpcRequest req; + + if (!input.get(req)) + { + // can't parse request + LOG_ERROR("Failed to parse request"); + return Status::Error; + } + + //handle tiers + + FullSupernodeList::blockchain_based_list tiers; + + for (const BlockchainBasedListTier& tier : req.params.tiers) + { + const std::vector& supernode_descs = tier.supernodes; + + FullSupernodeList::blockchain_based_list_tier supernodes; + + supernodes.reserve(supernode_descs.size()); + + for (const BlockchainBasedListTierEntry& supernode_desc : supernode_descs) + { + FullSupernodeList::blockchain_based_list_entry entry; + + entry.supernode_public_id = supernode_desc.supernode_public_id; + entry.supernode_public_address = supernode_desc.supernode_public_address; + entry.amount = supernode_desc.amount; + + supernodes.emplace_back(std::move(entry)); + } + + tiers.emplace_back(std::move(supernodes)); + } + + fsl->setBlockchainBasedList(req.params.block_height, FullSupernodeList::blockchain_based_list_ptr( + new FullSupernodeList::blockchain_based_list(std::move(tiers)))); + + return Status::Ok; +} + +} + +void registerBlockchainBasedListRequest(graft::Router &router) +{ + Router::Handler3 h3(nullptr, blockchainBasedListHandler, nullptr); + + router.addRoute(PATH, METHOD_POST, h3); + + LOG_PRINT_L0("route " << PATH << " registered"); +} + +} diff --git a/src/supernode/requests/debug.cpp b/src/supernode/requests/debug.cpp index 9402c1a0..b71e826d 100644 --- a/src/supernode/requests/debug.cpp +++ b/src/supernode/requests/debug.cpp @@ -17,18 +17,21 @@ #include #undef MONERO_DEFAULT_LOG_CATEGORY -#define MONERO_DEFAULT_LOG_CATEGORY "supernode.payrequest" +#define MONERO_DEFAULT_LOG_CATEGORY "supernode.debugrequest" namespace graft::supernode::request::debug { GRAFT_DEFINE_IO_STRUCT(DbSupernode, (std::string, Address), + (std::string, PublicId), (uint64, StakeAmount), + (uint64, ExpiringBlock), (uint64, LastUpdateAge) ); GRAFT_DEFINE_IO_STRUCT(SupernodeListResponse, - (std::vector, items) + (std::vector, items), + (uint64_t, height) ); GRAFT_DEFINE_JSON_RPC_RESPONSE_RESULT(SupernodeListJsonRpcResult, SupernodeListResponse); @@ -49,6 +52,7 @@ Status getSupernodeList(const Router::vars_t& vars, const graft::Input& input, auto supernodes = fsl->items(); SupernodeListJsonRpcResult resp; + resp.result.height = fsl->getBlockchainBasedListMaxBlockNumber(); for (auto& sa : supernodes) { auto sPtr = fsl->get(sa); @@ -67,7 +71,9 @@ Status getSupernodeList(const Router::vars_t& vars, const graft::Input& input, DbSupernode dbSupernode; dbSupernode.LastUpdateAge = lastUpdateAge; dbSupernode.Address = sPtr->walletAddress(); + dbSupernode.PublicId = sPtr->idKeyAsString(); dbSupernode.StakeAmount = sPtr->stakeAmount(); + dbSupernode.ExpiringBlock = sPtr->stakeBlockHeight() + sPtr->stakeUnlockTime(); resp.result.items.push_back(dbSupernode); } output.load(resp); @@ -81,28 +87,33 @@ Status getAuthSample(const Router::vars_t& vars, const graft::Input& input, FullSupernodeListPtr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, FullSupernodeListPtr()); std::vector sample; + uint64_t sample_block_number = 0; - uint64_t height; + std::string payment_id; try { - height = stoull(vars.find("height")->second); + payment_id = vars.find("payment_id")->second; } catch(...) { return errorInternalError("invalid input", output); } - const bool ok = fsl->buildAuthSample(height, sample); + SupernodeListJsonRpcResult resp; + + const bool ok = fsl->buildAuthSample(fsl->getBlockchainBasedListMaxBlockNumber(), payment_id, sample, sample_block_number); if(!ok) { return errorInternalError("failed to build auth sample", output); } - SupernodeListJsonRpcResult resp; + resp.result.height = sample_block_number; + for(auto& sPtr : sample) { DbSupernode sn; sn.Address = sPtr->walletAddress(); + sn.PublicId = sPtr->idKeyAsString(); sn.StakeAmount = sPtr->stakeAmount(); sn.LastUpdateAge = static_cast(std::time(nullptr)) - sPtr->lastUpdateTime(); resp.result.items.push_back(sn); @@ -139,7 +150,7 @@ void __registerDebugRequests(Router &router) router.addRoute("/debug/supernode_list/{all:[0-1]}", METHOD_GET, _HANDLER(getSupernodeList)); router.addRoute("/debug/announce", METHOD_POST, _HANDLER(doAnnounce)); router.addRoute("/debug/close_wallets/", METHOD_POST, _HANDLER(closeStakeWallets)); - router.addRoute("/debug/auth_sample/{height:[0-9]+}", METHOD_GET, _HANDLER(getAuthSample)); + router.addRoute("/debug/auth_sample/{payment_id:[0-9a-zA-Z]+}", METHOD_GET, _HANDLER(getAuthSample)); } } diff --git a/src/supernode/requests/pay.cpp b/src/supernode/requests/pay.cpp index eae4f2c1..27d882de 100644 --- a/src/supernode/requests/pay.cpp +++ b/src/supernode/requests/pay.cpp @@ -65,7 +65,7 @@ Status processAuthorizationRequest(const std::string &tx_hex, const PayRequest & // send multicast to /cryptonode/authorize_rta_tx_request MulticastRequestJsonRpc cryptonode_req; for (const auto & sn : authSample) { - cryptonode_req.params.receiver_addresses.push_back(sn->walletAddress()); + cryptonode_req.params.receiver_addresses.push_back(sn->idKeyAsString()); } Output innerOut; @@ -79,7 +79,7 @@ Status processAuthorizationRequest(const std::string &tx_hex, const PayRequest & cryptonode_req.method = "multicast"; cryptonode_req.params.callback_uri = "/cryptonode/authorize_rta_tx_request"; cryptonode_req.params.data = innerOut.data(); - cryptonode_req.params.sender_address = supernode->walletAddress(); + cryptonode_req.params.sender_address = supernode->idKeyAsString(); // store payment id as we need it to change the sale/pay state in next call ctx.local["payment_id"] = pay_request.PaymentID; // TODO: what is the purpose of PayData? @@ -99,6 +99,9 @@ Status handleClientPayRequest(const Router::vars_t& vars, const graft::Input& in graft::Context& ctx, graft::Output& output) { MDEBUG(__FUNCTION__ << " begin"); + // Disable pay request for RTA mining + return errorInternalError("Not implemented", output); + PayRequestJsonRpc req; if (!input.get(req)) { @@ -119,7 +122,8 @@ Status handleClientPayRequest(const Router::vars_t& vars, const graft::Input& in } std::vector authSample; - if (!fsl->buildAuthSample(in.BlockNumber, authSample) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { + uint64_t auth_sample_block_number = 0; + if (!fsl->buildAuthSample(in.BlockNumber, in.PaymentID, authSample, auth_sample_block_number) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { return errorBuildAuthSample(output); } @@ -209,7 +213,8 @@ Status handleWaitingTxReply(const Router::vars_t& vars, const graft::Input& inpu } std::vector authSample; - if (!fsl->buildAuthSample(payData.BlockNumber, authSample) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { + uint64_t auth_sample_block_number = 0; + if (!fsl->buildAuthSample(payData.BlockNumber, payData.PaymentID, authSample, auth_sample_block_number) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { return errorBuildAuthSample(output); } diff --git a/src/supernode/requests/sale.cpp b/src/supernode/requests/sale.cpp index ba29689b..2a4dd871 100644 --- a/src/supernode/requests/sale.cpp +++ b/src/supernode/requests/sale.cpp @@ -58,10 +58,7 @@ Status handleClientSaleRequest(const Router::vars_t& vars, const graft::Input& i } bool testnet = ctx.global["testnet"]; - if (!Supernode::validateAddress(in.Address, testnet)) - { - return errorInvalidAddress(output); - } + std::string payment_id = in.PaymentID; if (payment_id.empty()) // request comes from POS. @@ -74,7 +71,7 @@ Status handleClientSaleRequest(const Router::vars_t& vars, const graft::Input& i FullSupernodeListPtr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, FullSupernodeListPtr()); // reply to caller (POS) - SaleData data(in.Address, supernode->daemonHeight(), in.Amount); + SaleData data(in.IdKey, fsl->getBlockchainBasedListMaxBlockNumber(), in.Amount); // what needs to be multicasted to auth sample ? // 1. payment_id @@ -86,7 +83,8 @@ Status handleClientSaleRequest(const Router::vars_t& vars, const graft::Input& i // generate auth sample std::vector authSample; - if (!fsl->buildAuthSample(data.BlockNumber, authSample) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { + uint64_t auth_sample_block_number = 0; + if (!fsl->buildAuthSample(data.BlockNumber, payment_id, authSample, auth_sample_block_number) || authSample.size() != FullSupernodeList::AUTH_SAMPLE_SIZE) { return errorCustomError(MESSAGE_RTA_CANT_BUILD_AUTH_SAMPLE, ERROR_INVALID_PARAMS, output); } @@ -113,7 +111,7 @@ Status handleClientSaleRequest(const Router::vars_t& vars, const graft::Input& i MulticastRequestJsonRpc cryptonode_req; for (const auto & sn : authSample) { - cryptonode_req.params.receiver_addresses.push_back(sn->walletAddress()); + cryptonode_req.params.receiver_addresses.push_back(sn->idKeyAsString()); } MINFO("processed, payment_id: " << payment_id diff --git a/src/supernode/requests/sale_details.cpp b/src/supernode/requests/sale_details.cpp index 45ded0b7..c4bd1aaa 100644 --- a/src/supernode/requests/sale_details.cpp +++ b/src/supernode/requests/sale_details.cpp @@ -46,6 +46,7 @@ bool prepareSaleDetailsResponse(const SaleDetailsRequest &req, graft::Context &c for (const auto &member : authSample) { SupernodeFee snf; snf.Address = member->walletAddress(); + snf.IdKey = member->idKeyAsString(); snf.Fee = std::to_string(total_fee / authSample.size()); resp.AuthSample.push_back(snf); } @@ -97,10 +98,11 @@ Status handleClientRequest(const Router::vars_t& vars, const graft::Input& input } vector authSample; + uint64_t auth_sample_block_number = 0; FullSupernodeListPtr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, FullSupernodeListPtr()); SupernodePtr supernode = ctx.global.get(CONTEXT_KEY_SUPERNODE, SupernodePtr()); - if (!fsl->buildAuthSample(in.BlockNumber, authSample)) { + if (!fsl->buildAuthSample(in.BlockNumber, in.PaymentID, authSample, auth_sample_block_number)) { return errorBuildAuthSample(output); } // we have sale details locally, easy way @@ -126,7 +128,7 @@ Status handleClientRequest(const Router::vars_t& vars, const graft::Input& input // in this case, we MUST have sale details received from multicast if (std::find_if(authSample.begin(), authSample.end(), [&](const SupernodePtr &sn) { - return sn->walletAddress() == supernode->walletAddress(); + return sn->idKeyAsString() == supernode->idKeyAsString(); }) != authSample.end()) { ostringstream oss; oss << authSample; @@ -143,10 +145,10 @@ Status handleClientRequest(const Router::vars_t& vars, const graft::Input& input in.callback_uri = "/cryptonode/callback/sale_details/" + boost::uuids::to_string(ctx.getId()); innerOut.loadT(in); UnicastRequestJsonRpc unicastReq; - unicastReq.params.sender_address = supernode->walletAddress(); + unicastReq.params.sender_address = supernode->idKeyAsString(); size_t maxIndex = authSample.size() - 1; size_t randomIndex = utils::random_number(0, maxIndex); - unicastReq.params.receiver_address = authSample.at(randomIndex)->walletAddress(); + unicastReq.params.receiver_address = authSample.at(randomIndex)->idKeyAsString(); MDEBUG("requesting sale details from remote supernode: " << unicastReq.params.receiver_address << ", for payment: " << in.PaymentID); @@ -204,8 +206,8 @@ Status handleSaleDetailsResponse(const Router::vars_t& vars, const graft::Input& MDEBUG("received sale details from remote supernode: " << unicastReq.sender_address << ", payment: " << payment_id); - if (unicastReq.receiver_address != supernode->walletAddress()) { - string msg = string("wrong receiver address: " + unicastReq.receiver_address + ", expected address: " + supernode->walletAddress()); + if (unicastReq.receiver_address != supernode->idKeyAsString()) { + string msg = string("wrong receiver id: " + unicastReq.receiver_address + ", expected id: " + supernode->idKeyAsString()); LOG_ERROR(msg); return errorInternalError(msg, output); } @@ -266,8 +268,8 @@ Status handleSaleDetailsUnicastRequest(const Router::vars_t& vars, const graft:: UnicastRequest unicastReq = in.params; SupernodePtr supernode = ctx.global.get(CONTEXT_KEY_SUPERNODE, SupernodePtr()); - if (unicastReq.receiver_address != supernode->walletAddress()) { - string msg = string("wrong receiver address: " + supernode->walletAddress()); + if (unicastReq.receiver_address != supernode->idKeyAsString()) { + string msg = string("wrong receiver id: " + supernode->idKeyAsString()); LOG_ERROR(msg); return sendOkResponseToCryptonode(output); // cryptonode doesn't care about any errors, it's job is only deliver request } @@ -284,13 +286,14 @@ Status handleSaleDetailsUnicastRequest(const Router::vars_t& vars, const graft:: } vector authSample; + uint64_t auth_sample_block_number = 0; FullSupernodeListPtr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, FullSupernodeListPtr()); MDEBUG("sale_details request from remote supernode: " << unicastReq.sender_address << ", payment: " << sdr.PaymentID << ", block: " << sdr.BlockNumber); - if (!fsl->buildAuthSample(sdr.BlockNumber, authSample)) { + if (!fsl->buildAuthSample(sdr.BlockNumber, sdr.PaymentID, authSample, auth_sample_block_number)) { LOG_ERROR("failed to build auth sample for block: " << sdr.BlockNumber << ", payment: " << sdr.PaymentID); return sendOkResponseToCryptonode(output); // cryptonode doesn't care about any errors, it's job is only deliver request @@ -313,7 +316,7 @@ Status handleSaleDetailsUnicastRequest(const Router::vars_t& vars, const graft:: callbackReq.params.data = innerOut.data(); callbackReq.params.callback_uri = sdr.callback_uri; - callbackReq.params.sender_address = supernode->walletAddress(); + callbackReq.params.sender_address = supernode->idKeyAsString(); callbackReq.params.receiver_address = unicastReq.sender_address; callbackReq.method = "unicast"; output.load(callbackReq); diff --git a/src/supernode/requests/sale_status.cpp b/src/supernode/requests/sale_status.cpp index c0f067d4..e9d4513a 100644 --- a/src/supernode/requests/sale_status.cpp +++ b/src/supernode/requests/sale_status.cpp @@ -72,7 +72,7 @@ Status updateSaleStatusHandler(const Router::vars_t& vars, const graft::Input& i MDEBUG("sale status update received for payment: " << ussb.PaymentID); - if (!checkSaleStatusUpdateSignature(ussb.PaymentID, ussb.Status, ussb.address, ussb.signature, supernode)) { + if (!checkSaleStatusUpdateSignature(ussb.PaymentID, ussb.Status, ussb.id_key, ussb.signature, supernode)) { error.code = ERROR_RTA_SIGNATURE_FAILED; error.message = "status update: failed to validate signature for payment: " + ussb.PaymentID; LOG_ERROR(error.message); @@ -119,7 +119,7 @@ string signSaleStatusUpdate(const string &payment_id, int status, const Supernod } -bool checkSaleStatusUpdateSignature(const string &payment_id, int status, const string &address, const string &signature, +bool checkSaleStatusUpdateSignature(const string &payment_id, int status, const string &id_key_, const string &signature, const SupernodePtr &supernode) { crypto::signature sign; @@ -129,7 +129,15 @@ bool checkSaleStatusUpdateSignature(const string &payment_id, int status, const } std::string msg = payment_id + ":" + to_string(status); - return supernode->verifySignature(msg, address, sign); + // TODO: address -> id_key + crypto::public_key id_key; + if (!epee::string_tools::hex_to_pod(id_key_, id_key)) { + LOG_ERROR("Error parsing id_key: " << id_key_); + return false; + } + + + return supernode->verifySignature(msg, id_key, sign); } } diff --git a/src/supernode/requests/send_supernode_announce.cpp b/src/supernode/requests/send_supernode_announce.cpp index 02e96d77..b692f8a8 100644 --- a/src/supernode/requests/send_supernode_announce.cpp +++ b/src/supernode/requests/send_supernode_announce.cpp @@ -60,16 +60,17 @@ Status handleSupernodeAnnounce(const Router::vars_t& vars, const graft::Input& i LOG_PRINT_L1(PATH << " called with payload: " << input.data()); // TODO: implement DOS protection, ignore too frequent requests - boost::shared_ptr fsl = ctx.global.get("fsl", boost::shared_ptr()); - SupernodePtr supernode = ctx.global.get("supernode", SupernodePtr()); + boost::shared_ptr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, + boost::shared_ptr()); + SupernodePtr supernode = ctx.global.get(CONTEXT_KEY_SUPERNODE, SupernodePtr()); - if (!fsl.get()) { + if (!fsl) { LOG_ERROR("Internal error. Supernode list object missing"); return Status::Error; } - if (!supernode.get()) { + if (!supernode) { LOG_ERROR("Internal error. Supernode object missing"); return Status::Error; } @@ -83,42 +84,36 @@ Status handleSupernodeAnnounce(const Router::vars_t& vars, const graft::Input& i // handle announce const SupernodeAnnounce & announce = req.params; - MINFO("received announce for address: " << announce.address); + MINFO("received announce for id: " << announce.supernode_public_id); - if (fsl->exists(announce.address)) { + if (fsl->exists(announce.supernode_public_id)) { // check if supernode currently busy - SupernodePtr sn = fsl->get(announce.address); + SupernodePtr sn = fsl->get(announce.supernode_public_id); if (sn->busy()) { - MWARNING("Unable to update supernode with announce: " << announce.address << ", BUSY"); + MWARNING("Unable to update supernode with announce: " << announce.supernode_public_id << ", BUSY"); return Status::Error; // we don't care about reply here, already replied to the client } - if (!fsl->get(announce.address)->updateFromAnnounce(announce)) { - LOG_ERROR("Failed to update supernode with announce: " << announce.address); + if (!fsl->get(announce.supernode_public_id)->updateFromAnnounce(announce)) { + LOG_ERROR("Failed to update supernode with announce: " << announce.supernode_public_id); return Status::Error; // we don't care about reply here, already replied to the client } } else { - std::string watchonly_wallets_path = ctx.global["watchonly_wallets_path"]; - assert(!watchonly_wallets_path.empty()); - boost::filesystem::path p(watchonly_wallets_path); - p /= announce.address; - std::string wallet_path = p.string(); std::string cryptonode_rpc_address = ctx.global["cryptonode_rpc_address"]; bool testnet = ctx.global["testnet"]; - MINFO("creating wallet in: " << p.string()); - Supernode * s = Supernode::createFromAnnounce(wallet_path, announce, + Supernode * s = Supernode::createFromAnnounce(announce, cryptonode_rpc_address, testnet); if (!s) { - LOG_ERROR("Cant create watch-only supernode wallet for address: " << announce.address); + LOG_ERROR("Cant create watch-only supernode wallet for id: " << announce.supernode_public_id); return Status::Error; } - MINFO("About to add supernode to list [" << s << "]: " << s->walletAddress()); + MINFO("About to add supernode to list [" << s << "]: " << s->idKeyAsString()); if (!fsl->add(s)) { // DO NOT delete "s" here, it will be deleted by smart pointer; - LOG_ERROR("Can't add new supernode to list [" << s << "]" << s->walletAddress()); + LOG_ERROR("Can't add new supernode to list [" << s << "]" << s->idKeyAsString()); } } return Status::Ok; @@ -178,10 +173,10 @@ Status sendAnnounce(const graft::Router::vars_t& vars, const graft::Input& input return graft::Status::Error; } - MDEBUG("about to refresh supernode: " << supernode->walletAddress()); + MDEBUG("about to refresh supernode: " << supernode->idKeyAsString()); if (!supernode->refresh()) { - return errorCustomError(string("failed to refresh supernode: ") + supernode->walletAddress(), + return errorCustomError(string("failed to refresh supernode: ") + supernode->idKeyAsString(), ERROR_INTERNAL_ERROR, output); } @@ -189,7 +184,7 @@ Status sendAnnounce(const graft::Router::vars_t& vars, const graft::Input& input SendSupernodeAnnounceJsonRpcRequest req; if (!supernode->prepareAnnounce(req.params)) { - return errorCustomError(string("failed to prepare announce: ") + supernode->walletAddress(), + return errorCustomError(string("failed to prepare announce: ") + supernode->idKeyAsString(), ERROR_INTERNAL_ERROR, output); } @@ -201,9 +196,8 @@ Status sendAnnounce(const graft::Router::vars_t& vars, const graft::Input& input output.path = "/json_rpc/rta"; // DBG: without cryptonode // output.path = "/dapi/v2.0/send_supernode_announce"; - - MDEBUG("sending announce for address: " << supernode->walletAddress() - << ", stake amount: " << supernode->stakeAmount()); + MDEBUG("sending announce for id: " << supernode->idKeyAsString()); + MDEBUG(output.data()); return graft::Status::Forward; } } diff --git a/src/supernode/requests/send_supernode_stakes.cpp b/src/supernode/requests/send_supernode_stakes.cpp new file mode 100644 index 00000000..64d11f5a --- /dev/null +++ b/src/supernode/requests/send_supernode_stakes.cpp @@ -0,0 +1,118 @@ +// Copyright (c) 2018, The Graft 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 "supernode/requests/send_supernode_stakes.h" +#include "supernode/requestdefines.h" +#include "rta/fullsupernodelist.h" +#include "rta/supernode.h" + +#include +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "supernode.sendsupernodestakesrequest" + +namespace { + static const char* PATH = "/send_supernode_stakes"; +} + +namespace graft::supernode::request { + +namespace +{ + +Status supernodeStakesHandler + (const Router::vars_t& vars, + const graft::Input& input, + graft::Context& ctx, + graft::Output& output) +{ + LOG_PRINT_L1(PATH << " called with payload: " << input.data()); + + boost::shared_ptr fsl = ctx.global.get("fsl", boost::shared_ptr()); + SupernodePtr supernode = ctx.global.get("supernode", SupernodePtr()); + + if (!fsl.get()) { + LOG_ERROR("Internal error. Supernode list object missing"); + return Status::Error; + } + + if (!supernode.get()) { + LOG_ERROR("Internal error. Supernode object missing"); + return Status::Error; + } + + SendSupernodeStakesJsonRpcRequest req; + + if (!input.get(req)) + { + // can't parse request + LOG_ERROR("Failed to parse request"); + return Status::Error; + } + + // handle stakes + + const std::vector& src_stakes = req.params.stakes; + FullSupernodeList::supernode_stake_array dst_stakes; + + dst_stakes.reserve(src_stakes.size()); + + for (const SupernodeStake& src_stake : src_stakes) + { + supernode_stake dst_stake; + + dst_stake.amount = src_stake.amount; + dst_stake.block_height = src_stake.block_height; + dst_stake.unlock_time = src_stake.unlock_time; + dst_stake.supernode_public_id = src_stake.supernode_public_id; + dst_stake.supernode_public_address = src_stake.supernode_public_address; + + dst_stakes.emplace_back(std::move(dst_stake)); + } + + std::string cryptonode_rpc_address = ctx.global["cryptonode_rpc_address"]; + bool testnet = ctx.global["testnet"]; + + fsl->updateStakes(req.params.block_height, dst_stakes, cryptonode_rpc_address, testnet); + + return Status::Ok; +} + +} + +void registerSendSupernodeStakesRequest(graft::Router &router) +{ + Router::Handler3 h3(nullptr, supernodeStakesHandler, nullptr); + + router.addRoute(PATH, METHOD_POST, h3); + + LOG_PRINT_L0("route " << PATH << " registered"); +} + +} diff --git a/src/supernode/server.cpp b/src/supernode/server.cpp index 053999af..d85bf88a 100644 --- a/src/supernode/server.cpp +++ b/src/supernode/server.cpp @@ -13,6 +13,18 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "supernode.server" +namespace consts { + static const char * DATA_PATH = "supernode/data"; +} + +namespace tools { + // TODO: make it crossplatform + std::string getHomeDir() + { + return std::string(getenv("HOME")); + } +} + namespace graft { static std::function int_handler, term_handler, hup_handler; @@ -66,7 +78,7 @@ void GraftServer::getThreadPoolInfo(uint64_t& activeWorkers, uint64_t& expelledW void GraftServer::initGraftlets() { if(m_graftletLoader) return; - m_graftletLoader = std::make_unique(); + m_graftletLoader = std::make_unique(getCopts().common); LOG_PRINT_L1("Searching graftlets"); for(auto& it : getCopts().graftlet_dirs) { @@ -507,6 +519,9 @@ bool GraftServer::initConfigOption(int argc, const char** argv, ConfigOpts& conf configOpts.workers_expelling_interval_ms = server_conf.get("workers-expelling-interval-ms", 1000); configOpts.upstream_request_timeout = server_conf.get("upstream-request-timeout"); configOpts.lru_timeout_ms = server_conf.get("lru-timeout-ms"); + configOpts.common.data_dir = server_conf.get("data-dir"); + configOpts.common.wallet_public_address = server_conf.get("wallet-public-address", ""); + configOpts.common.testnet = server_conf.get("testnet", false); //ipfilter auto opt_ipfilter = config.get_child_optional("ipfilter"); @@ -557,9 +572,21 @@ bool GraftServer::initConfigOption(int argc, const char** argv, ConfigOpts& conf graft::OutHttp::uri_substitutions.emplace(std::move(name), std::make_tuple(std::move(uri), cnt, keepAlive, timeout)); }); + prepareDataDir(configOpts); + return true; } +void GraftServer::prepareDataDir(ConfigOpts& configOpts) +{ + if (configOpts.common.data_dir.empty()) { + boost::filesystem::path p = boost::filesystem::absolute(tools::getHomeDir()); + p /= ".graft/"; + p /= consts::DATA_PATH; + configOpts.common.data_dir = p.string(); + } +} + void GraftServer::initRouters() { diff --git a/src/supernode/supernode.cpp b/src/supernode/supernode.cpp index fea1f5a8..3b59540e 100644 --- a/src/supernode/supernode.cpp +++ b/src/supernode/supernode.cpp @@ -7,6 +7,7 @@ #include "supernode/requests/send_supernode_announce.h" #include "rta/supernode.h" #include "rta/fullsupernodelist.h" +#include "lib/graft/graft_exception.h" #include @@ -14,20 +15,11 @@ #define MONERO_DEFAULT_LOG_CATEGORY "supernode.supernode" namespace consts { - static const char * DATA_PATH = "supernode/data"; static const char * STAKE_WALLET_PATH = "stake-wallet"; static const char * WATCHONLY_WALLET_PATH = "stake-wallet"; static const size_t DEFAULT_STAKE_WALLET_REFRESH_INTERFAL_MS = 5 * 1000; } -namespace tools { - // TODO: make it crossplatform - std::string getHomeDir() - { - return std::string(getenv("HOME")); - } -} - namespace graft { namespace snd @@ -46,25 +38,22 @@ bool Supernode::initConfigOption(int argc, const char** argv, ConfigOpts& config boost::property_tree::ini_parser::read_ini(m_configEx.config_filename, config); const boost::property_tree::ptree& server_conf = config.get_child("server"); - m_configEx.data_dir = server_conf.get("data-dir"); m_configEx.stake_wallet_name = server_conf.get("stake-wallet-name", "stake-wallet"); m_configEx.stake_wallet_refresh_interval_ms = server_conf.get("stake-wallet-refresh-interval-ms", consts::DEFAULT_STAKE_WALLET_REFRESH_INTERFAL_MS); - m_configEx.testnet = server_conf.get("testnet", false); + m_configEx.stake_wallet_refresh_interval_random_factor = server_conf.get("stake-wallet-refresh-interval-random-factor", 0); + + if(m_configEx.common.wallet_public_address.empty()) + { + throw graft::exit_error("Configuration parameter 'wallet-public-address' cannot be empty."); + } return res; } -void Supernode::prepareDataDir() +void Supernode::prepareSupernode() { - if (m_configEx.data_dir.empty()) { - boost::filesystem::path p = boost::filesystem::absolute(tools::getHomeDir()); - p /= ".graft/"; - p /= consts::DATA_PATH; - m_configEx.data_dir = p.string(); - } - // create data directory if not exists - boost::filesystem::path data_path(m_configEx.data_dir); + boost::filesystem::path data_path(m_configEx.common.data_dir); boost::filesystem::path stake_wallet_path = data_path / "stake-wallet"; boost::filesystem::path watchonly_wallets_path = data_path / "watch-only-wallets"; @@ -83,8 +72,6 @@ void Supernode::prepareDataDir() } } - - m_configEx.watchonly_wallets_path = watchonly_wallets_path.string(); MINFO("data path: " << data_path.string()); @@ -92,25 +79,33 @@ void Supernode::prepareDataDir() // create supernode instance and put it into global context graft::SupernodePtr supernode = boost::make_shared( - (stake_wallet_path / m_configEx.stake_wallet_name).string(), - "", // TODO + m_configEx.common.wallet_public_address, + crypto::public_key(), m_configEx.cryptonode_rpc_address, - m_configEx.testnet + m_configEx.common.testnet ); + std::string keyfilename = (data_path / "supernode.keys").string(); + if (!supernode->loadKeys(keyfilename)) { + // supernode is not initialized, generating key + supernode->initKeys(); + if (!supernode->saveKeys(keyfilename)) { + MERROR("Failed to save keys"); + throw std::runtime_error("Failed to save keys"); + } + } supernode->setNetworkAddress(m_configEx.http_address + "/dapi/v2.0"); // create fullsupernode list instance and put it into global context graft::FullSupernodeListPtr fsl = boost::make_shared( - m_configEx.cryptonode_rpc_address, m_configEx.testnet); - + m_configEx.cryptonode_rpc_address, m_configEx.common.testnet); fsl->add(supernode); //put fsl into global context Context ctx(getLooper().getGcm()); - ctx.global["supernode"] = supernode; + ctx.global[CONTEXT_KEY_SUPERNODE] = supernode; ctx.global[CONTEXT_KEY_FULLSUPERNODELIST] = fsl; - ctx.global["testnet"] = m_configEx.testnet; + ctx.global["testnet"] = m_configEx.common.testnet; ctx.global["watchonly_wallets_path"] = m_configEx.watchonly_wallets_path; ctx.global["cryptonode_rpc_address"] = m_configEx.cryptonode_rpc_address; } @@ -119,7 +114,8 @@ void Supernode::initMisc(ConfigOpts& configOpts) { ConfigOptsEx& coptsex = static_cast(configOpts); assert(&m_configEx == &coptsex); - prepareDataDir(); + + prepareSupernode(); startSupernodePeriodicTasks(); std::chrono::milliseconds duration( 5000 ); @@ -137,9 +133,36 @@ void Supernode::startSupernodePeriodicTasks() getLooper().addPeriodicTask( graft::Router::Handler3(nullptr, graft::supernode::request::sendAnnounce, nullptr), std::chrono::milliseconds(m_configEx.stake_wallet_refresh_interval_ms), - std::chrono::milliseconds(initial_interval_ms) + std::chrono::milliseconds(initial_interval_ms), + m_configEx.stake_wallet_refresh_interval_random_factor ); } + + // sync with cryptonode + + auto handler = [](const graft::Router::vars_t& vars, const graft::Input& input, graft::Context& ctx, graft::Output& output)->graft::Status + { + graft::SupernodePtr supernode = ctx.global.get(CONTEXT_KEY_SUPERNODE, graft::SupernodePtr(nullptr)); + + if (!supernode.get()) { + LOG_ERROR("supernode is not set in global context"); + return graft::Status::Error; + } + + if (FullSupernodeListPtr fsl = ctx.global.get(CONTEXT_KEY_FULLSUPERNODELIST, FullSupernodeListPtr())) + { + fsl->synchronizeWithCryptonode(supernode->networkAddress().c_str(), supernode->idKeyAsString().c_str()); + } + + return graft::Status::Ok; + }; + + static const size_t CRYPTONODE_SYNCHRONIZATION_PERIOD_MS = 1000; + + getConnectionBase().getLooper().addPeriodicTask( + graft::Router::Handler3(nullptr, handler, nullptr), + std::chrono::milliseconds(CRYPTONODE_SYNCHRONIZATION_PERIOD_MS) + ); } void Supernode::setHttpRouters(ConnectionManager& httpcm) diff --git a/src/version.h.in b/src/version.h.in index 4f7b12cd..8732f571 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define GRAFT_SUPERNODE_VERSION_TAG "@VERSIONTAG@" -#define GRAFT_SUPERNODE_VERSION "0.4.1" +#define GRAFT_SUPERNODE_VERSION "1.0.0" #define GRAFT_SUPERNODE_RELEASE_NAME "" #define GRAFT_SUPERNODE_VERSION_FULL GRAFT_SUPERNODE_VERSION "-" GRAFT_SUPERNODE_VERSION_TAG diff --git a/src/walletnode/server.cpp b/src/walletnode/server.cpp index 2dee4575..771b9e2b 100644 --- a/src/walletnode/server.cpp +++ b/src/walletnode/server.cpp @@ -44,8 +44,8 @@ void WalletServer::initMisc(ConfigOpts& configOpts) { Context ctx(getLooper().getGcm()); - ctx.global["testnet"] = m_configEx.testnet; - ctx.global["cryptonode_rpc_address"] = m_configEx.cryptonode_rpc_address; + ctx.global["testnet"] = m_configOpts.common.testnet; + ctx.global["cryptonode_rpc_address"] = m_configOpts.cryptonode_rpc_address; initWalletManager(); @@ -56,7 +56,7 @@ bool WalletServer::run(int argc, const char** argv) { for(;;) { - if(!init(argc, argv, m_configEx)) + if(!init(argc, argv, m_configOpts)) return false; argc = 1; @@ -73,13 +73,6 @@ bool WalletServer::initConfigOption(int argc, const char** argv, ConfigOpts& con if (!GraftServer::initConfigOption(argc, argv, configOpts)) return false; - boost::property_tree::ptree config; - boost::property_tree::ini_parser::read_ini(m_configEx.config_filename, config); - - const boost::property_tree::ptree& server_conf = config.get_child("server"); - - m_configEx.testnet = server_conf.get("testnet", false); - return true; } @@ -87,7 +80,7 @@ void WalletServer::initWalletManager() { assert(!m_walletManager); - m_walletManager = std::make_unique(getLooper(), m_configEx.testnet); + m_walletManager = std::make_unique(getLooper(), m_configOpts.common.testnet); } void WalletServer::initRouters() diff --git a/test/graft_server_test.cpp b/test/graft_server_test.cpp index 50983b5e..63e13124 100644 --- a/test/graft_server_test.cpp +++ b/test/graft_server_test.cpp @@ -1256,7 +1256,7 @@ TEST_F(GraftServerTest, genericCallback) case graft::Status::None: { //find webhook endpoint - auto it = std::find_if(input.headers.begin(), input.headers.end(), [](auto& v)->bool { v.first == "X-Callback"; } ); + auto it = std::find_if(input.headers.begin(), input.headers.end(), [](auto& v)->bool { return v.first == "X-Callback"; } ); assert(it != input.headers.end()); std::string path = it->second; //"http://0.0.0.0:port/callback/" diff --git a/test/graftlets_test.cpp b/test/graftlets_test.cpp index fe8d11dc..fb16583e 100644 --- a/test/graftlets_test.cpp +++ b/test/graftlets_test.cpp @@ -176,7 +176,8 @@ TEST(DependencyGraph, dependencies) TEST(Graftlets, calls) { - graftlet::GraftletLoader loader; + graft::CommonOpts opts; + graftlet::GraftletLoader loader(opts); loader.findGraftletsInDirectory("./", "so"); loader.findGraftletsInDirectory("./graftlets", "so"); @@ -259,25 +260,27 @@ TEST(Graftlets, calls) TEST(Graftlets, exceptionList) { + graft::CommonOpts opts; + #define VER(a,b) GRAFTLET_MKVER(a,b) using GL = graftlet::GraftletLoader; { GL::setGraftletsExceptionList({}); - GL loader; + GL loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 4); } { GL::setGraftletsExceptionList({ {"myGraftlet", {{VER(4,2), VER(5,1)}, {VER(1,0), VER(1,0)}} } }); - GL loader; + GL loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 4); } { GL::setGraftletsExceptionList({ {"myGraftlet1", {{VER(4,2), VER(5,1)}, {VER(1,0), VER(1,0)}} } }); - GL loader; + GL loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 2); @@ -286,7 +289,7 @@ TEST(Graftlets, exceptionList) GL::setGraftletsExceptionList({ {"myGraftlet", {{VER(4,2), VER(5,1)}, {VER(1,0), VER(1,1)}} }, {"myGraftlet1", {{VER(4,2), VER(5,1)}, {VER(1,0), VER(1,0)}} } }); - GL loader; + GL loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 0); @@ -299,13 +302,15 @@ TEST(Graftlets, exceptionList) TEST(Graftlets, checkFwVersion) { + graft::CommonOpts opts; + #define VER(a,b) GRAFTLET_MKVER(a,b) using Version = graftlet::GraftletLoader::Version; Version fwVersion = graftlet::GraftletLoader::getFwVersion(); Version save_ver = fwVersion; { - graftlet::GraftletLoader loader; + graftlet::GraftletLoader loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 4); @@ -313,7 +318,7 @@ TEST(Graftlets, checkFwVersion) { graftlet::GraftletLoader::setFwVersion( VER(0,5) ); - graftlet::GraftletLoader loader; + graftlet::GraftletLoader loader(opts); loader.findGraftletsInDirectory("./graftlets", "so"); IGraftlet::EndpointsVec endpoints = loader.getEndpoints(); EXPECT_EQ(endpoints.size(), 2); diff --git a/test/rta_classes_test.cpp b/test/rta_classes_test.cpp index b8f58993..e965ac1b 100644 --- a/test/rta_classes_test.cpp +++ b/test/rta_classes_test.cpp @@ -53,6 +53,7 @@ struct SupernodeTest : public ::testing::Test } }; +#if 0 TEST_F(SupernodeTest, open) { MGINFO_YELLOW("*** This test requires running cryptonode RPC on localhost:28881. If not running, test will fail ***"); @@ -338,8 +339,8 @@ TEST_F(FullSupernodeListTest, buildAuthSample) // test if result is reproducable for (size_t i = 0; i < 100; ++i) { - sn_list.buildAuthSample(10000 + i, auth_sample); - sn_list.buildAuthSample(10000 + i, auth_sample2); + sn_list.buildAuthSample(10000 + i, "aabbccddeeff", auth_sample); + sn_list.buildAuthSample(10000 + i, "aabbccddeeff", auth_sample2); ASSERT_EQ(auth_sample.size(), FullSupernodeList::AUTH_SAMPLE_SIZE); ASSERT_EQ(auth_sample2.size(), FullSupernodeList::AUTH_SAMPLE_SIZE); @@ -544,4 +545,5 @@ TEST_F(FullSupernodeListTest, announce1) ASSERT_TRUE(watch_only_sn2.get() == nullptr); } +#endif diff --git a/test/sys_info.cpp b/test/sys_info.cpp index 3b18a41d..845a10bf 100644 --- a/test/sys_info.cpp +++ b/test/sys_info.cpp @@ -151,8 +151,9 @@ class HandlerAPIImpl : public graft::HandlerAPI public: virtual void sendUpstreamBlocking(Output& output, Input& input, std::string& err) override { } virtual bool addPeriodicTask(const Router::Handler& h_worker, - std::chrono::milliseconds interval_ms, - std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max()) override { } + std::chrono::milliseconds interval_ms, + std::chrono::milliseconds initial_interval_ms = std::chrono::milliseconds::max(), + double random_factor = 0) override { } virtual graft::request::system_info::Counter& runtimeSysInfo() override { return m_sic;