From 8c0e5dd5f277d0114a2d934d748b7d169842a74b Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 4 Sep 2023 10:59:42 +1000 Subject: [PATCH 01/97] Load ethyl library and basic sepolia parameters --- .gitmodules | 3 +++ external/CMakeLists.txt | 4 ++++ external/ethyl | 1 + src/cryptonote_config.h | 16 ++++++++++++++++ src/cryptonote_core/CMakeLists.txt | 1 + 5 files changed, 25 insertions(+) create mode 160000 external/ethyl diff --git a/.gitmodules b/.gitmodules index b44fd0784e..96ad3c5957 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ path = external/pybind11 url = https://github.com/pybind/pybind11 branch = stable +[submodule "external/ethyl"] + path = external/ethyl + url = git@github.com:oxen-io/ethyl.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 750a28e1de..d25357ab2c 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -175,3 +175,7 @@ if(BUILD_PYBIND) add_subdirectory(pybind11 EXCLUDE_FROM_ALL) endif() + +set(ethyl_ENABLE_CRYPTO_LIBRARY FALSE CACHE BOOL "" FORCE) +add_subdirectory(ethyl) + diff --git a/external/ethyl b/external/ethyl new file mode 160000 index 0000000000..6665dc7b04 --- /dev/null +++ b/external/ethyl @@ -0,0 +1 @@ +Subproject commit 6665dc7b0442286668d182e8dbfaa9c7b9b2d0c1 diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index d007c6df56..1828bcab85 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -407,6 +407,10 @@ namespace config { // batching and SNL will save the state every STORE_LONG_TERM_STATE_INTERVAL blocks inline constexpr uint64_t STORE_LONG_TERM_STATE_INTERVAL = 10000; + // Details of the ethereum smart contract managing rewards and chain its kept on + inline constexpr uint32_t ETHEREUM_CHAIN_ID = 11155111; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xf85468442B4904cde8D526745369C07CE8F612eA"; + namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; inline constexpr time_t HEIGHT_ESTIMATE_TIMESTAMP = 1595360006; @@ -542,6 +546,9 @@ struct network_config { uint64_t STORE_LONG_TERM_STATE_INTERVAL; + uint32_t ETHEREUM_CHAIN_ID; + std::string_view ETHEREUM_REWARDS_CONTRACT; + inline constexpr std::string_view governance_wallet_address(hf hard_fork_version) const { const auto wallet_switch = (NETWORK_TYPE == network_type::MAINNET || NETWORK_TYPE == network_type::FAKECHAIN) @@ -577,6 +584,8 @@ inline constexpr network_config mainnet_config{ config::SERVICE_NODE_PAYABLE_AFTER_BLOCKS, config::HARDFORK_DEREGISTRATION_GRACE_PERIOD, config::STORE_LONG_TERM_STATE_INTERVAL, + config::ETHEREUM_CHAIN_ID, + config::ETHEREUM_REWARDS_CONTRACT, }; inline constexpr network_config testnet_config{ network_type::TESTNET, @@ -605,6 +614,8 @@ inline constexpr network_config testnet_config{ config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS, config::HARDFORK_DEREGISTRATION_GRACE_PERIOD, config::STORE_LONG_TERM_STATE_INTERVAL, + config::ETHEREUM_CHAIN_ID, + config::ETHEREUM_REWARDS_CONTRACT, }; inline constexpr network_config devnet_config{ network_type::DEVNET, @@ -632,6 +643,9 @@ inline constexpr network_config devnet_config{ config::LIMIT_BATCH_OUTPUTS, config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS, config::HARDFORK_DEREGISTRATION_GRACE_PERIOD, + config::STORE_LONG_TERM_STATE_INTERVAL, + config::ETHEREUM_CHAIN_ID, + config::ETHEREUM_REWARDS_CONTRACT, }; inline constexpr network_config fakenet_config{ network_type::FAKECHAIN, @@ -660,6 +674,8 @@ inline constexpr network_config fakenet_config{ config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS, config::HARDFORK_DEREGISTRATION_GRACE_PERIOD, config::STORE_LONG_TERM_STATE_INTERVAL, + config::ETHEREUM_CHAIN_ID, + config::ETHEREUM_REWARDS_CONTRACT, }; inline constexpr const network_config& get_config(network_type nettype) { diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 784eaf9466..e13f827a93 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(cryptonote_core device checkpoints SQLite::SQLite3 + ethyl PRIVATE Boost::program_options SQLiteCpp From 0fd9b419dc8de64e74c25eb98f8f79fdde5b9268 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 7 Sep 2023 10:37:00 +1000 Subject: [PATCH 02/97] adds a L2 tracker to core, which checks the smart contract every 10 secs for it state and maintains the history so block verifiers can check --- src/CMakeLists.txt | 1 + src/cryptonote_basic/cryptonote_basic.h | 8 +++++ src/cryptonote_config.h | 3 +- src/cryptonote_core/CMakeLists.txt | 1 + src/cryptonote_core/cryptonote_core.cpp | 3 ++ src/cryptonote_core/cryptonote_core.h | 7 ++++ src/l2_tracker/CMakeLists.txt | 41 +++++++++++++++++++++++ src/l2_tracker/l2_tracker.cpp | 43 +++++++++++++++++++++++++ src/l2_tracker/l2_tracker.h | 27 ++++++++++++++++ src/l2_tracker/rewards_contract.cpp | 14 ++++++++ src/l2_tracker/rewards_contract.h | 23 +++++++++++++ 11 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/l2_tracker/CMakeLists.txt create mode 100644 src/l2_tracker/l2_tracker.cpp create mode 100644 src/l2_tracker/l2_tracker.h create mode 100644 src/l2_tracker/rewards_contract.cpp create mode 100644 src/l2_tracker/rewards_contract.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8f9d2e3483..d82404cb7f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_core) add_subdirectory(logging) add_subdirectory(lmdb) +add_subdirectory(l2_tracker) add_subdirectory(multisig) add_subdirectory(net) add_subdirectory(mnemonics) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 2af8af38bf..de71568d2a 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -420,6 +420,10 @@ struct block : public block_header { // hash cache mutable crypto::hash hash; std::vector signatures; + uint64_t l2_height; + //TODO sean check that hash is 32 bytes in ethereum side + crypto::hash l2_state; + }; template @@ -452,6 +456,10 @@ void serialize_value(Archive& ar, block& b) { field(ar, "service_node_winner_key", b.service_node_winner_key); field(ar, "reward", b.reward); } + if (b.major_version >= feature::ETH_BLS) { //HF20 + field_varint(ar, "l2_height", b.l2_height); + field(ar, "l2_state", b.l2_state); + } } /************************************************************************/ diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 1828bcab85..6380504e35 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -242,6 +242,7 @@ namespace feature { constexpr auto PULSE = hf::hf16_pulse; constexpr auto CLSAG = hf::hf16_pulse; constexpr auto PROOF_BTENC = hf::hf18; + constexpr auto ETH_BLS = hf::hf20; } // namespace feature enum class network_type : uint8_t { MAINNET = 0, TESTNET, DEVNET, FAKECHAIN, UNDEFINED = 255 }; @@ -409,7 +410,7 @@ namespace config { // Details of the ethereum smart contract managing rewards and chain its kept on inline constexpr uint32_t ETHEREUM_CHAIN_ID = 11155111; - inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xf85468442B4904cde8D526745369C07CE8F612eA"; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xfCE78c64E91aFC35CD61C3121aAb1d8984657FFa"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index e13f827a93..480461d5b8 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(cryptonote_core device checkpoints SQLite::SQLite3 + l2_tracker ethyl PRIVATE Boost::program_options diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 8dcbe31486..ac32560a60 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -251,6 +251,9 @@ core::core() : m_last_storage_server_ping(0), m_last_lokinet_ping(0), m_pad_transactions(false), + //TODO sean load this from config data + m_provider(std::make_shared("Sepolia Client", std::string("https://eth-sepolia.g.alchemy.com/v2/xjUjCAfxli88pqe7UjR4Tt1Jp2GKPJvy"))), + m_l2_tracker(std::make_shared(get_nettype(), m_provider)), ss_version{0}, lokinet_version{0} { m_checkpoints_updating.clear(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index c886be4ac4..5330cd0a1e 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -32,6 +32,8 @@ #include +#include + #include #include #include @@ -54,6 +56,7 @@ #include "service_node_quorum_cop.h" #include "service_node_voting.h" #include "tx_pool.h" +#include "l2_tracker/l2_tracker.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) @@ -1242,6 +1245,10 @@ class core : public i_miner_handler { tx_memory_pool m_mempool; //!< transaction pool instance Blockchain m_blockchain_storage; //!< Blockchain instance + + // Ethereum client for communicating with L2 blockchain + std::shared_ptr m_provider; + std::shared_ptr m_l2_tracker; service_nodes::service_node_list m_service_node_list; service_nodes::quorum_cop m_quorum_cop; diff --git a/src/l2_tracker/CMakeLists.txt b/src/l2_tracker/CMakeLists.txt new file mode 100644 index 0000000000..e039cb86fd --- /dev/null +++ b/src/l2_tracker/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright (c) 2023, The Oxen 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. + +add_library(l2_tracker + rewards_contract.cpp + l2_tracker.cpp + ) + +target_link_libraries(l2_tracker + PRIVATE + common + ethyl + cryptonote_basic + filesystem + logging + extra) diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp new file mode 100644 index 0000000000..189f7425e2 --- /dev/null +++ b/src/l2_tracker/l2_tracker.cpp @@ -0,0 +1,43 @@ +#include "l2_tracker.h" + +L2Tracker::L2Tracker() { +} + +L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) + : rewards_contract(std::make_shared(get_contract_address(nettype), _provider)), + stop_thread(false) { + update_thread = std::thread(&L2Tracker::update_state_thread, this); +} + +L2Tracker::~L2Tracker() { + stop_thread.store(true); + if (update_thread.joinable()) { + update_thread.join(); + } +} + +void L2Tracker::update_state_thread() { + while (!stop_thread.load()) { + update_state(); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } +} + +void L2Tracker::update_state() { + state_history.emplace_back(rewards_contract->State()); + StateResponse new_state = rewards_contract->State(); + // Check if the state with the same height already exists + auto it = std::find_if(state_history.begin(), state_history.end(), + [&new_state](const StateResponse& state) { + return state.height == new_state.height; + }); + + // If it doesn't exist, emplace it back + if (it == state_history.end()) { + state_history.emplace_back(new_state); + } +} + +std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { + return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); +} diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h new file mode 100644 index 0000000000..fa650032ee --- /dev/null +++ b/src/l2_tracker/l2_tracker.h @@ -0,0 +1,27 @@ +#pragma once + +#include "rewards_contract.h" +#include "l2_tracker.h" + +#include "cryptonote_config.h" + +class L2Tracker { +private: + std::shared_ptr rewards_contract; + std::vector state_history; + std::atomic stop_thread; + std::thread update_thread; + +public: + L2Tracker(); + L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& client); + ~L2Tracker(); + + void L2Tracker::update_state_thread(); + void update_state(); + +private: + static std::string get_contract_address(const cryptonote::network_type nettype); + +// END +}; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp new file mode 100644 index 0000000000..365a0c98cb --- /dev/null +++ b/src/l2_tracker/rewards_contract.cpp @@ -0,0 +1,14 @@ +#include "rewards_contract.h" + + + +RewardsContract::RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider) + : contractAddress(_contractAddress), provider(_provider) {} + +StateResponse RewardsContract::State() { + ReadCallData callData; + callData.contractAddress = contractAddress; + std::string functionSelector = utils::getFunctionSignature("state()"); + callData.data = functionSelector; + return StateResponse{0, provider->callReadFunction(callData)}; +} diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h new file mode 100644 index 0000000000..a17d003ef3 --- /dev/null +++ b/src/l2_tracker/rewards_contract.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +//#include "transaction.hpp" +#include + +struct StateResponse { + uint64_t height; + std::string state; +}; + +class RewardsContract { +public: + // Constructor + RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider); + + StateResponse State(); + +private: + std::string contractAddress; + std::shared_ptr provider; +}; From 8478ea0a217785906626f86f03cf6599cd935dcc Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 12:10:09 +1000 Subject: [PATCH 03/97] hardforks for devnet --- src/cryptonote_basic/hardfork.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index f15365b729..bc7acbdef5 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -151,11 +151,11 @@ static constexpr std::array devnet_hard_forks = { hard_fork{hf::hf13_enforce_checkpoints, 0, 4, 1653500577}, hard_fork{hf::hf14_blink, 0, 5, 1653500577}, hard_fork{hf::hf15_ons, 0, 6, 1653500577}, - hard_fork{hf::hf16_pulse, 0, 100, 1653500577}, - hard_fork{hf::hf17, 0, 151, 1653500577}, - hard_fork{hf::hf18, 0, 152, 1653500577}, - hard_fork{hf::hf19_reward_batching, 0, 153, 1653500577}, - hard_fork{hf::hf19_reward_batching, 1, 154, 1653500577}, + hard_fork{hf::hf16_pulse, 0, 200, 1653500577}, + hard_fork{hf::hf17, 0, 201, 1653500577}, + hard_fork{hf::hf18, 0, 202, 1653500577}, + hard_fork{hf::hf19_reward_batching, 0, 203, 1653500577}, + hard_fork{hf::hf19_reward_batching, 1, 204, 1653500577}, }; template From a5b7654a9cea78d9eea7d6ee5017d01033a19bde Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 12:13:51 +1000 Subject: [PATCH 04/97] rpc commands needing updating to new format --- src/rpc/common/param_parser.hpp | 1 + src/rpc/core_rpc_server.cpp | 52 ++++++++++++++-------- src/rpc/core_rpc_server.h | 5 +-- src/rpc/core_rpc_server_command_parser.cpp | 12 +++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 12 ----- src/rpc/core_rpc_server_commands_defs.h | 26 +++-------- 7 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index 256df7d49a..3413f69adb 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -148,6 +148,7 @@ void load_value(BTConsumer& c, T& val) { } else static_assert(std::is_same_v, "Unsupported load_value type"); } + // Copies the next value from the json range into `val`, and advances the iterator. Throws // on unconvertible values. template diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index c4ec110469..8c5974d80e 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2457,9 +2457,14 @@ void core_rpc_server::invoke( return; } //------------------------------------------------------------------------------------------------------------------------------ -GET_SERVICE_NODE_REGISTRATION_CMD::response core_rpc_server::invoke( - GET_SERVICE_NODE_REGISTRATION_CMD::request&& req, rpc_context context) { - GET_SERVICE_NODE_REGISTRATION_CMD::response res{}; +void core_rpc_server::invoke( + GET_SERVICE_NODE_REGISTRATION_CMD& get_service_node_registration_cmd, + rpc_context context) { + if (!m_core.service_node()) + throw rpc_error{ + ERROR_WRONG_PARAM, + "Daemon has not been started in service node mode, please relaunch with " + "--service-node flag."}; std::vector args; @@ -2470,29 +2475,40 @@ GET_SERVICE_NODE_REGISTRATION_CMD::response core_rpc_server::invoke( { try { args.emplace_back( - std::to_string(service_nodes::percent_to_basis_points(req.operator_cut))); + std::to_string(service_nodes::percent_to_basis_points(get_service_node_registration_cmd.request.operator_cut))); } catch (const std::exception& e) { - res.status = "Invalid value: "s + e.what(); - log::error(logcat, res.status); - return res; + get_service_node_registration_cmd.response["status"] = "Invalid value: "s + e.what(); + log::error(logcat, get_service_node_registration_cmd.response["status"]); + return; } } - for (const auto& [address, amount] : req.contributions) { - args.push_back(address); - args.push_back(std::to_string(amount)); + auto& addresses = get_service_node_registration_cmd.request.contributor_addresses; + auto& amounts = get_service_node_registration_cmd.request.contributor_amounts; + + if (addresses.size() != amounts.size()) { + throw std::runtime_error("Mismatch in sizes of addresses and amounts"); } - GET_SERVICE_NODE_REGISTRATION_CMD_RAW req_old{}; + for (size_t i = 0; i < addresses.size(); ++i) { + args.push_back(addresses[i]); + args.push_back(std::to_string(amounts[i])); + } - req_old.request.staking_requirement = req.staking_requirement; - req_old.request.args = std::move(args); - req_old.request.make_friendly = false; + std::string registration_cmd; + if (!service_nodes::make_registration_cmd( + m_core.get_nettype(), + hf_version, + staking_requirement, + args, + m_core.get_service_keys(), + registration_cmd, + false /*Make friendly*/)) + throw rpc_error{ERROR_INTERNAL, "Failed to make registration command"}; - invoke(req_old, context); - res.status = req_old.response["status"]; - res.registration_cmd = req_old.response["registration_cmd"]; - return res; + get_service_node_registration_cmd.response["registration_cmd"] = registration_cmd; + get_service_node_registration_cmd.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke( diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 47a0da45da..1887b3090f 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -187,6 +187,7 @@ class core_rpc_server { void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); void invoke(GET_BLOCK& get_block, rpc_context context); + void invoke( GET_SERVICE_NODE_REGISTRATION_CMD& get_service_node_registration_cmd, rpc_context context); void invoke( GET_SERVICE_NODE_REGISTRATION_CMD_RAW& get_service_node_registration_cmd_raw, rpc_context context); @@ -216,10 +217,6 @@ class core_rpc_server { GET_OUTPUT_DISTRIBUTION::response invoke( GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - // FIXME: unconverted JSON RPC endpoints: - GET_SERVICE_NODE_REGISTRATION_CMD::response invoke( - GET_SERVICE_NODE_REGISTRATION_CMD::request&& req, rpc_context context); - private: bool check_core_ready(); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 4585988f33..b814a4e03f 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -475,4 +475,16 @@ void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { "staking_requirement", required{cmd.request.staking_requirement}); } +void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in) { + get_values( + in, + "contributor_addresses", + required{cmd.request.contributor_addresses}, + "contributor_amounts", + required{cmd.request.contributor_amounts}, + "operator_cut", + required{cmd.request.operator_cut}, + "staking_requirement", + required{cmd.request.staking_requirement}); +} } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 2450fc47f4..6767e946c1 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -30,6 +30,7 @@ void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); void parse_request(GET_PEER_LIST& bh, rpc_input in); void parse_request(GET_SERVICE_NODES& sns, rpc_input in); +void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in); void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); void parse_request(GET_SN_STATE_CHANGES& get_sn_state_changes, rpc_input in); void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 8758ecb269..c41dd92cca 100644 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -231,16 +231,4 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::response) KV_SERIALIZE(status) KV_SERIALIZE(distributions) KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_SERVICE_NODE_REGISTRATION_CMD::contribution_t) -KV_SERIALIZE(address) -KV_SERIALIZE(amount) -KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_SERVICE_NODE_REGISTRATION_CMD::request) -KV_SERIALIZE(operator_cut) -KV_SERIALIZE(contributions) -KV_SERIALIZE(staking_requirement) -KV_SERIALIZE_MAP_CODE_END() - } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b2230e94e1..8f351ed229 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1835,35 +1835,18 @@ struct GET_SERVICE_NODE_REGISTRATION_CMD_RAW : RPC_COMMAND { } request; }; -OXEN_RPC_DOC_INTROSPECT struct GET_SERVICE_NODE_REGISTRATION_CMD : RPC_COMMAND { static constexpr auto names() { return NAMES("get_service_node_registration_cmd"); } - struct contribution_t { - std::string address; // The wallet address for the contributor - uint64_t amount; // The amount that the contributor will reserve in Loki atomic units - // towards the staking requirement - - KV_MAP_SERIALIZABLE - }; - struct request { std::string operator_cut; // The percentage of cut per reward the operator receives // expressed as a string, i.e. "1.1%" - std::vector contributions; // Array of contributors for this Service Node + std::vector contributor_addresses; + std::vector contributor_amounts; uint64_t staking_requirement; // The staking requirement to become a Service Node the // registration command will be generated upon - KV_MAP_SERIALIZABLE - }; - - struct response { - std::string status; // Generic RPC error code. "OK" is the success value. - std::string registration_cmd; // The command to execute in the wallet CLI to register the - // queried daemon as a Service Node. - - KV_MAP_SERIALIZABLE - }; + } request; }; /// RPC: service_node/get_service_keys @@ -2691,6 +2674,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_KEYS, GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, + GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, GET_SERVICE_NODE_STATUS, GET_SERVICE_PRIVKEYS, @@ -2728,6 +2712,6 @@ using core_rpc_types = tools::type_list< ONS_NAMES_TO_OWNERS>; using FIXME_old_rpc_types = - tools::type_list; + tools::type_list; } // namespace cryptonote::rpc From 31e3dbdf950d57c803043f694dda601f4b0eee24 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 12:14:16 +1000 Subject: [PATCH 05/97] wallet2 updates --- src/wallet/node_rpc_proxy.cpp | 13 ++++++++++--- src/wallet/wallet2.cpp | 3 +-- src/wallet/wallet_rpc_server.cpp | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 11a168b106..7393222dd4 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -113,10 +113,14 @@ bool NodeRPCProxy::get_info() const { m_block_weight_limit = res.at("block_weight_limit"); else m_block_weight_limit = res.at("block_size_limit"); - m_immutable_height = res.at("immutable_height").get(); + auto it_immutable_height = res.find("immutable_height"); + if (it_immutable_height != res.end()) + m_immutable_height = res.at("immutable_height").get(); + else m_get_info_time = now; m_height_time = now; - } catch (...) { + } catch (const std::exception& e) { + log::error(logcat, "Failed to get info message: {}", e.what()); return false; } } @@ -126,8 +130,10 @@ bool NodeRPCProxy::get_info() const { bool NodeRPCProxy::get_height(uint64_t& height) const { auto now = std::chrono::steady_clock::now(); if (now >= m_height_time + 30s) // re-cache every 30 seconds + { if (!get_info()) return false; + } height = m_height; return true; @@ -162,7 +168,8 @@ bool NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t& earliest_heigh try { auto res = m_http_client.json_rpc("hard_fork_info", req_params); m_earliest_height[version] = res.at("earliest_height").get(); - } catch (...) { + } catch (const std::exception& e) { + log::error(logcat, "Failed to get earliest height: {}", e.what()); return false; } } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d2cc1b1712..bb5e5a41f0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3685,8 +3685,6 @@ void wallet2::pull_and_parse_next_blocks( error = true; break; } - // TODO sean -> parsed_blocks o_indices is now a nlohmann::json and o_indices is the - // struct from the binary bleh parsed_blocks[i].o_indices = std::move(o_indices[i]); } @@ -9488,6 +9486,7 @@ wallet2::register_service_node_result wallet2::create_register_service_node_tx( tr("Could not convert registration args: ") + std::string{e.what()}}; } + auto address = registration.reserved[0].first; if (!contains_address(address)) return {register_service_node_result_status::first_address_must_be_primary_address, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 03bd6d83f5..5d0605947f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -612,8 +612,9 @@ bool wallet_rpc_server::init() { } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::require_open() { - if (!m_wallet) + if (!m_wallet) { throw wallet_rpc_error{error_code::NOT_OPEN, "No wallet file"}; + } } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::close_wallet(bool save_current) { @@ -3428,7 +3429,6 @@ ONS_HASH_NAME::response wallet_rpc_server::invoke(ONS_HASH_NAME::request&& req) } ONS_KNOWN_NAMES::response wallet_rpc_server::invoke(ONS_KNOWN_NAMES::request&& req) { - // TODO sean this needs to fit the new request format require_open(); ONS_KNOWN_NAMES::response res{}; From 59dc53dfa0d4473993868a596cd1da476a75985b Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 12:14:38 +1000 Subject: [PATCH 06/97] local devnet updates to latest hardfork --- utils/local-devnet/daemons.py | 8 +++++--- utils/local-devnet/service_node_network.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index f56ceb6c55..0100dbbcc5 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -260,7 +260,7 @@ def __init__( datadir=None, listen_ip=None, rpc_port=None, - log_level=3): + log_level=4): self.listen_ip = listen_ip or LISTEN_IP self.rpc_port = rpc_port or next_port() @@ -363,8 +363,9 @@ def find_tx(txid): def register_sn(self, sn): r = sn.json_rpc("get_service_node_registration_cmd", { + "contributor_addresses": [self.address()], + "contributor_amounts": [100000000000], "operator_cut": "100", - "contributions": [{"address": self.address(), "amount": 100000000000}], "staking_requirement": 100000000000 }).json() if 'error' in r: @@ -376,8 +377,9 @@ def register_sn(self, sn): def register_sn_for_contributions(self, sn, cut, amount): r = sn.json_rpc("get_service_node_registration_cmd", { + "contributor_addresses": [self.address()], + "contributor_amounts": [amount], "operator_cut": str(cut), - "contributions": [{"address": self.address(), "amount": amount}], "staking_requirement": 100000000000 }).json() if 'error' in r: diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 9aad09c4c3..3f394326e3 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -139,7 +139,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): # of 18.9, which means each registration requires 6 inputs. Thus we need a bare minimum of # 6(N-5) blocks, plus the 30 lock time on coinbase TXes = 6N more blocks (after the initial # 5 registrations). - self.mine(200) + self.mine(204) vprint("Submitting first round of service node registrations: ", end="", flush=True) for sn in self.sns[0:5]: self.mike.register_sn(sn) From 23a2fd900fc970e2de6e722159a4c4143558d1c5 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 12:18:24 +1000 Subject: [PATCH 07/97] l2 tracker data --- src/blockchain_db/CMakeLists.txt | 1 + src/cryptonote_core/blockchain.cpp | 6 ++++++ src/cryptonote_core/blockchain.h | 6 ++++++ src/cryptonote_core/cryptonote_core.cpp | 3 --- src/cryptonote_core/cryptonote_core.h | 6 ------ src/l2_tracker/l2_tracker.cpp | 11 +++++++++++ src/l2_tracker/l2_tracker.h | 5 ++++- 7 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index e96a98e8b6..fbeab73353 100644 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -43,6 +43,7 @@ target_link_libraries(blockchain_db filesystem Boost::thread logging + ethyl extra) target_compile_definitions(blockchain_db PRIVATE diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 67864dabba..249893c616 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -116,6 +116,8 @@ Blockchain::block_extended_info::block_extended_info( //------------------------------------------------------------------ Blockchain::Blockchain( tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list) : + //TODO sean load this from config data + m_provider(std::make_shared("Sepolia Client", std::string("https://eth-sepolia.g.alchemy.com/v2/xjUjCAfxli88pqe7UjR4Tt1Jp2GKPJvy"))), m_db(), m_tx_pool(tx_pool), m_current_block_cumul_weight_limit(0), @@ -531,6 +533,8 @@ bool Blockchain::init( if (!m_checkpoints.init(m_nettype, m_db)) throw std::runtime_error("Failed to initialize checkpoints"); + m_l2_tracker = std::make_shared(m_nettype, m_provider); + m_offline = offline; m_fixed_difficulty = fixed_difficulty; @@ -1874,6 +1878,8 @@ bool Blockchain::create_block_template_internal( b.service_node_winner_key = crypto::null; b.reward = block_rewards; + //TODO sean + std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); b.height = height; return true; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 4b67868b58..ba993b469b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -49,6 +49,7 @@ #include #include #include +#include #include "blockchain_db/blockchain_db.h" #include "blockchain_db/sqlite/db_sqlite.h" @@ -67,6 +68,7 @@ #include "pulse.h" #include "rpc/core_rpc_server_binary_commands.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "l2_tracker/l2_tracker.h" struct sqlite3; namespace service_nodes { @@ -1280,6 +1282,10 @@ class Blockchain { checkpoints m_checkpoints; + // Ethereum client for communicating with L2 blockchain + std::shared_ptr m_provider; + std::shared_ptr m_l2_tracker; + network_type m_nettype; bool m_offline; difficulty_type m_fixed_difficulty; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index ac32560a60..8dcbe31486 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -251,9 +251,6 @@ core::core() : m_last_storage_server_ping(0), m_last_lokinet_ping(0), m_pad_transactions(false), - //TODO sean load this from config data - m_provider(std::make_shared("Sepolia Client", std::string("https://eth-sepolia.g.alchemy.com/v2/xjUjCAfxli88pqe7UjR4Tt1Jp2GKPJvy"))), - m_l2_tracker(std::make_shared(get_nettype(), m_provider)), ss_version{0}, lokinet_version{0} { m_checkpoints_updating.clear(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 5330cd0a1e..79595262e2 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -32,8 +32,6 @@ #include -#include - #include #include #include @@ -56,7 +54,6 @@ #include "service_node_quorum_cop.h" #include "service_node_voting.h" #include "tx_pool.h" -#include "l2_tracker/l2_tracker.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) @@ -1246,9 +1243,6 @@ class core : public i_miner_handler { tx_memory_pool m_mempool; //!< transaction pool instance Blockchain m_blockchain_storage; //!< Blockchain instance - // Ethereum client for communicating with L2 blockchain - std::shared_ptr m_provider; - std::shared_ptr m_l2_tracker; service_nodes::service_node_list m_service_node_list; service_nodes::quorum_cop m_quorum_cop; diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 189f7425e2..503c14374a 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -1,4 +1,5 @@ #include "l2_tracker.h" +#include L2Tracker::L2Tracker() { } @@ -38,6 +39,16 @@ void L2Tracker::update_state() { } } +std::pair L2Tracker::latest_state() { + if(state_history.empty()) { + throw std::runtime_error("Internal error getting latest state from l2 tracker"); + } + crypto::hash return_hash; + tools::hex_to_type(state_history.back().state, return_hash); + return std::make_pair(6969, return_hash); +} + + std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index fa650032ee..3e902d292f 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -2,6 +2,7 @@ #include "rewards_contract.h" #include "l2_tracker.h" +#include "crypto/hash.h" #include "cryptonote_config.h" @@ -17,9 +18,11 @@ class L2Tracker { L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& client); ~L2Tracker(); - void L2Tracker::update_state_thread(); + void update_state_thread(); void update_state(); + std::pair latest_state(); + private: static std::string get_contract_address(const cryptonote::network_type nettype); From cfbe3615bdfb8a6ec83f0c425f749873b3e2c070 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 16:27:39 +1000 Subject: [PATCH 08/97] hardfork devnet to HF20 --- src/cryptonote_basic/hardfork.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index bc7acbdef5..8dc25080ce 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -155,7 +155,7 @@ static constexpr std::array devnet_hard_forks = { hard_fork{hf::hf17, 0, 201, 1653500577}, hard_fork{hf::hf18, 0, 202, 1653500577}, hard_fork{hf::hf19_reward_batching, 0, 203, 1653500577}, - hard_fork{hf::hf19_reward_batching, 1, 204, 1653500577}, + hard_fork{hf::hf20, 0, 204, 1653500577}, }; template From b2f447a6dd049a00c34ed6ca2ed42d51caec2133 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 13 Sep 2023 17:11:29 +1000 Subject: [PATCH 09/97] more block header info --- src/cryptonote_basic/cryptonote_basic.cpp | 12 ++++++++++-- .../cryptonote_boost_serialization.h | 4 ++++ src/rpc/core_rpc_server.cpp | 2 ++ src/rpc/core_rpc_server_commands_defs.cpp | 4 ++++ src/rpc/core_rpc_server_commands_defs.h | 2 ++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_basic.cpp b/src/cryptonote_basic/cryptonote_basic.cpp index 0a409f0ad3..ae0cad5c20 100644 --- a/src/cryptonote_basic/cryptonote_basic.cpp +++ b/src/cryptonote_basic/cryptonote_basic.cpp @@ -102,7 +102,9 @@ block::block(const block& b) : signatures{b.signatures}, height{b.height}, service_node_winner_key{b.service_node_winner_key}, - reward{b.reward} { + reward{b.reward}, + l2_height{b.l2_height}, + l2_state{b.l2_state} { copy_hash(b); } @@ -113,7 +115,9 @@ block::block(block&& b) : signatures{std::move(b.signatures)}, height{std::move(b.height)}, service_node_winner_key{std::move(b.service_node_winner_key)}, - reward{std::move(b.reward)} { + reward{std::move(b.reward)}, + l2_height{std::move(b.l2_height)}, + l2_state{std::move(b.l2_state)} { copy_hash(b); } @@ -125,6 +129,8 @@ block& block::operator=(const block& b) { height = b.height; service_node_winner_key = b.service_node_winner_key; reward = b.reward; + l2_height = b.l2_height; + l2_state = b.l2_state; copy_hash(b); return *this; } @@ -136,6 +142,8 @@ block& block::operator=(block&& b) { height = std::move(b.height); service_node_winner_key = std::move(b.service_node_winner_key); reward = std::move(b.reward); + l2_height = std::move(b.l2_height); + l2_state = std::move(b.l2_state); copy_hash(b); return *this; } diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 1f7dc02e26..8e2eaf81da 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -226,6 +226,10 @@ namespace boost { namespace serialization { a& b.height; a& b.service_node_winner_key; a& b.reward; + if (ver < 20) + return; + a& b.l2_height; + a& b.l2_state; } template diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 8c5974d80e..8c22dad157 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1465,6 +1465,8 @@ void core_rpc_server::fill_block_header_response( cryptonote::get_service_node_winner_from_tx_extra(blk.miner_tx.extra)) : tools::type_to_hex(blk.service_node_winner_key); response.coinbase_payouts = get_block_reward(blk); + response.l2_state = tools::type_to_hex(blk.l2_state); + response.l2_height = blk.l2_height; if (blk.miner_tx.vout.size() > 0) response.miner_tx_hash = tools::type_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); if (get_tx_hashes) { diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index c41dd92cca..25f01455dd 100644 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -82,6 +82,8 @@ void to_json(nlohmann::json& j, const block_header_response& h) { {"miner_tx_hash", h.miner_tx_hash}, {"tx_hashes", h.tx_hashes}, {"service_node_winner", h.service_node_winner}, + {"l2_height", h.l2_height}, + {"l2_state", h.l2_state}, }; if (h.pow_hash) j["pow_hash"] = *h.pow_hash; @@ -113,6 +115,8 @@ void from_json(const nlohmann::json& j, block_header_response& h) { j.at("miner_tx_hash").get_to(h.miner_tx_hash); j.at("tx_hashes").get_to(h.tx_hashes); j.at("service_node_winner").get_to(h.service_node_winner); + j.at("l2_height").get_to(h.l2_height); + j.at("l2_state").get_to(h.l2_state); }; void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q) { diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 8f351ed229..4a0fb95823 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -682,6 +682,8 @@ struct block_header_response { std::string miner_tx_hash; std::vector tx_hashes; std::string service_node_winner; + uint64_t l2_height; + std::string l2_state; }; void to_json(nlohmann::json& j, const block_header_response& h); From 684220f225cdeff4cb08b8b05501e27409ee6ddc Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 14 Sep 2023 14:43:28 +1000 Subject: [PATCH 10/97] rewards actually reads --- src/l2_tracker/rewards_contract.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 365a0c98cb..b97e9321cb 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -1,7 +1,5 @@ #include "rewards_contract.h" - - RewardsContract::RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider) : contractAddress(_contractAddress), provider(_provider) {} @@ -10,5 +8,13 @@ StateResponse RewardsContract::State() { callData.contractAddress = contractAddress; std::string functionSelector = utils::getFunctionSignature("state()"); callData.data = functionSelector; - return StateResponse{0, provider->callReadFunction(callData)}; + + std::string result = provider->callReadFunction(callData); + std::string blockHeightHex = result.substr(2, 64); + std::string blockHash = result.substr(66, 64); + + // Convert blockHeightHex to a decimal number + uint64_t blockHeight = std::stoull(blockHeightHex, nullptr, 16); + + return StateResponse{blockHeight, blockHash}; } From 605df7015f73148386c943088d267c705121e746 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 14 Sep 2023 17:00:15 +1000 Subject: [PATCH 11/97] update ethyl to include height in eth calls --- external/ethyl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ethyl b/external/ethyl index 6665dc7b04..3f44f14881 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit 6665dc7b0442286668d182e8dbfaa9c7b9b2d0c1 +Subproject commit 3f44f148814c6d7857aedb90d57b4eabd5348ae3 From a1f1200a87f2a7efdd3839a0cc406c0d6f3dc3b6 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 14 Sep 2023 17:09:32 +1000 Subject: [PATCH 12/97] l2 tracker keeps state up to date --- src/cryptonote_config.h | 2 +- src/l2_tracker/l2_tracker.cpp | 43 ++++++++++++++++++++++++----- src/l2_tracker/l2_tracker.h | 1 + src/l2_tracker/rewards_contract.cpp | 19 +++++++++++-- src/l2_tracker/rewards_contract.h | 1 + 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 6380504e35..85c58d75a7 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -410,7 +410,7 @@ namespace config { // Details of the ethereum smart contract managing rewards and chain its kept on inline constexpr uint32_t ETHEREUM_CHAIN_ID = 11155111; - inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xfCE78c64E91aFC35CD61C3121aAb1d8984657FFa"; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xf3e5eDC802784D72E910a51390E0dF431F575C24"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 503c14374a..2db103cc27 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -1,5 +1,8 @@ #include "l2_tracker.h" #include +#include "logging/oxen_logger.h" + +static auto logcat = oxen::log::Cat("daemon"); L2Tracker::L2Tracker() { } @@ -24,18 +27,43 @@ void L2Tracker::update_state_thread() { } } -void L2Tracker::update_state() { - state_history.emplace_back(rewards_contract->State()); - StateResponse new_state = rewards_contract->State(); +void L2Tracker::insert_in_order(const StateResponse& new_state) { // Check if the state with the same height already exists auto it = std::find_if(state_history.begin(), state_history.end(), [&new_state](const StateResponse& state) { return state.height == new_state.height; }); - // If it doesn't exist, emplace it back + // If it doesn't exist, insert it in the appropriate location if (it == state_history.end()) { - state_history.emplace_back(new_state); + auto insert_loc = std::upper_bound(state_history.begin(), state_history.end(), new_state, + [](const StateResponse& a, const StateResponse& b) { + return a.height > b.height; + }); + + state_history.insert(insert_loc, new_state); + } +} + +void L2Tracker::update_state() { + try { + // Get latest state + StateResponse new_state = rewards_contract->State(); + insert_in_order(new_state); + + // Check for missing heights between the first and second entries + std::vector missing_heights; + if (state_history.size() > 1) { + uint64_t first_height = state_history[0].height; + uint64_t second_height = state_history[1].height; + + for (uint64_t h = first_height - 1; h > second_height; --h) { + new_state = rewards_contract->State(h); + insert_in_order(new_state); + } + } + } catch (const std::exception& e) { + oxen::log::error(logcat, "Failed to update state: {}", e.what()); } } @@ -44,8 +72,9 @@ std::pair L2Tracker::latest_state() { throw std::runtime_error("Internal error getting latest state from l2 tracker"); } crypto::hash return_hash; - tools::hex_to_type(state_history.back().state, return_hash); - return std::make_pair(6969, return_hash); + auto& latest_state = state_history.back(); + tools::hex_to_type(latest_state.state, return_hash); + return std::make_pair(latest_state.height, return_hash); } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 3e902d292f..c25a6f0472 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -20,6 +20,7 @@ class L2Tracker { void update_state_thread(); void update_state(); + void insert_in_order(const StateResponse& new_state); std::pair latest_state(); diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index b97e9321cb..494c79a681 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -4,17 +4,32 @@ RewardsContract::RewardsContract(const std::string& _contractAddress, std::share : contractAddress(_contractAddress), provider(_provider) {} StateResponse RewardsContract::State() { + return State(std::nullopt); +} + +StateResponse RewardsContract::State(std::optional height) { ReadCallData callData; callData.contractAddress = contractAddress; std::string functionSelector = utils::getFunctionSignature("state()"); callData.data = functionSelector; - std::string result = provider->callReadFunction(callData); + std::string result; + if (height) { + result = provider->callReadFunction(callData, *height); + } else { + result = provider->callReadFunction(callData); + } + + if (result.size() != 130) { + throw std::runtime_error("L2 State returned invalid data"); + } + std::string blockHeightHex = result.substr(2, 64); std::string blockHash = result.substr(66, 64); // Convert blockHeightHex to a decimal number uint64_t blockHeight = std::stoull(blockHeightHex, nullptr, 16); - + return StateResponse{blockHeight, blockHash}; } + diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index a17d003ef3..00cfab5957 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -16,6 +16,7 @@ class RewardsContract { RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider); StateResponse State(); + StateResponse State(std::optional height); private: std::string contractAddress; From 60c52da0509f65192b26b80d05a656a1f1f0d28f Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 18 Sep 2023 08:06:24 +1000 Subject: [PATCH 13/97] checks state before including it in block --- src/cryptonote_core/blockchain.cpp | 7 +++++++ src/l2_tracker/l2_tracker.cpp | 14 ++++++++++++++ src/l2_tracker/l2_tracker.h | 3 +++ 3 files changed, 24 insertions(+) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 249893c616..4a4d86d38c 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5093,6 +5093,13 @@ bool Blockchain::handle_block_to_main_chain( return false; } + if (is_hard_fork_at_least(m_nettype, feature::ETH_BLS, bl.height) && + !m_l2_tracker->check_state_in_history(bl.l2_height, bl.l2_state)) { + log::info(logcat, fg(fmt::terminal_color::red), "Failed to find state in l2 tracker."); + bvc.m_verifivation_failed = true; + return false; + } + if (!m_ons_db.add_block(bl, only_txs)) { log::info(logcat, fg(fmt::terminal_color::red), "Failed to add block to ONS DB."); bvc.m_verifivation_failed = true; diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 2db103cc27..c160ed7047 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -46,6 +46,7 @@ void L2Tracker::insert_in_order(const StateResponse& new_state) { } void L2Tracker::update_state() { + //TODO sean, create counter for failed state updates, if it fails too many times then throw try { // Get latest state StateResponse new_state = rewards_contract->State(); @@ -77,6 +78,19 @@ std::pair L2Tracker::latest_state() { return std::make_pair(latest_state.height, return_hash); } +bool L2Tracker::check_state_in_history(uint64_t height, const crypto::hash& state) { + std::string state_str = tools::type_to_hex(state); + return check_state_in_history(height, state_str); +} + +bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state) { + auto it = std::find_if(state_history.begin(), state_history.end(), + [height, &state](const StateResponse& stateResponse) { + return stateResponse.height == height && stateResponse.state == state; + }); + return it != state_history.end(); +} + std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index c25a6f0472..d1e3bb9772 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -22,6 +22,9 @@ class L2Tracker { void update_state(); void insert_in_order(const StateResponse& new_state); + bool check_state_in_history(uint64_t height, const crypto::hash& state); + bool check_state_in_history(uint64_t height, const std::string& state); + std::pair latest_state(); private: From 53de7b82ba0f5d1bc1d8055b80456690bc02c0b9 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 18 Sep 2023 14:01:48 +1000 Subject: [PATCH 14/97] start on BLS signing --- external/CMakeLists.txt | 3 + src/CMakeLists.txt | 1 + src/bls/CMakeLists.txt | 40 +++++++++++ src/bls/bls_aggregator.cpp | 68 +++++++++++++++++++ src/bls/bls_aggregator.h | 35 ++++++++++ src/bls/bls_signer.cpp | 82 +++++++++++++++++++++++ src/bls/bls_signer.h | 40 +++++++++++ src/bls/bls_utils.cpp | 37 ++++++++++ src/bls/bls_utils.h | 22 ++++++ src/cryptonote_core/CMakeLists.txt | 1 + src/cryptonote_core/cryptonote_core.cpp | 3 +- src/cryptonote_core/cryptonote_core.h | 4 +- src/cryptonote_core/service_node_list.cpp | 21 ++++++ src/l2_tracker/l2_tracker.cpp | 2 +- 14 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 src/bls/CMakeLists.txt create mode 100644 src/bls/bls_aggregator.cpp create mode 100644 src/bls/bls_aggregator.h create mode 100644 src/bls/bls_signer.cpp create mode 100644 src/bls/bls_signer.h create mode 100644 src/bls/bls_utils.cpp create mode 100644 src/bls/bls_utils.h diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index d25357ab2c..3340644646 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -66,6 +66,9 @@ add_subdirectory(db_drivers) add_subdirectory(randomx EXCLUDE_FROM_ALL) add_subdirectory(date EXCLUDE_FROM_ALL) +set(BLS_ETH ON CACHE BOOL "" FORCE) +add_subdirectory(bls) + set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_MultipleHeaders ON CACHE BOOL "") # Allows multi-header nlohmann use add_subdirectory(nlohmann-json EXCLUDE_FROM_ALL) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d82404cb7f..ace8a4d761 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,7 @@ add_subdirectory(cryptonote_core) add_subdirectory(logging) add_subdirectory(lmdb) add_subdirectory(l2_tracker) +add_subdirectory(bls) add_subdirectory(multisig) add_subdirectory(net) add_subdirectory(mnemonics) diff --git a/src/bls/CMakeLists.txt b/src/bls/CMakeLists.txt new file mode 100644 index 0000000000..b9e590d99e --- /dev/null +++ b/src/bls/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (c) 2023, The Oxen 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. + +add_library(bls + bls_signer.cpp + bls_aggregator.cpp + bls_utils.cpp + ) + +target_link_libraries(bls + PRIVATE + common + cryptonote_basic + logging + extra) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp new file mode 100644 index 0000000000..10459fd46b --- /dev/null +++ b/src/bls/bls_aggregator.cpp @@ -0,0 +1,68 @@ +#include "eth-bls/bls_aggregator.h" +#include "eth-bls/bls_signer.h" +#include "eth-bls/bls_utils.h" + +BLSAggregator::BLSAggregator(service_nodes::service_node_list& snl, std::shared_ptr omq_ptr) + : service_node_list(snl), omq(omq_ptr) { + bls_signer = std::make_shared(); +} + +ServiceNodeList::~ServiceNodeList() { +} + +std::string BLSAggregator::aggregatePubkeyHex() { + bls::PublicKey aggregate_pubkey; + aggregate_pubkey.clear(); + for(auto& node : nodes) { + aggregate_pubkey.add(node.getPublicKey()); + } + return utils::PublicKeyToHex(aggregate_pubkey); +} + +std::string BLSAggregator::aggregateSignatures(const std::string& message) { + const std::array hash = BLSSigner::hash(message); + bls::Signature aggSig; + aggSig.clear(); + + const std::vector signers; + + auto it = service_node_list.get_first_pubkey_iterator(); + auto end_it = service_node_list.get_end_pubkey_iterator(); + crypto::x25519_public_key x_pkey{0}; + int64_t signers_index = 0; + while (it != end_it) { + service_node_list->access_proof(*it, [&x_pkey](auto& proof) { + x_pkey = proof.pubkey_x25519; + }); + omq->request( + tools::view_guts(x_pkey), + "bls.signature_request, + [this, signers_index](bool success, std::vector data) { + log::debug( + logcat, + "bls signature response received: {}", + data[0]); + if (success) { + bls::signature external_signature; + int64_t signers_index = 0; + if (tools::parse_string(data[0], external_signature)) { + aggSig.add(external_signature); + signers.push_back(signers_index); + } + } + }); + it = service_node_list_instance.get_next_pubkey_iterator(it); + signers_index++; + } + return aggregateResponse{ findNonSigners(signers), utils::SignatureToHex(aggSig) } +}; + +std::vector BLSAggregator::findNonSigners(const std::vector& indices) { + std::vector nonSignerIndices = {}; + for (int64_t i = 0; i < static_cast(nodes.size()); ++i) { + if (std::find(indices.begin(), indices.end(), i) == indices.end()) { + nonSignerIndices.push_back(i); + } + } + return nonSignerIndices; +} diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h new file mode 100644 index 0000000000..d28839d4fc --- /dev/null +++ b/src/bls/bls_aggregator.h @@ -0,0 +1,35 @@ +#pragma once + +#define BLS_ETH +#define MCLBN_FP_UNIT_SIZE 4 +#define MCLBN_FR_UNIT_SIZE 4 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#include +#include +#undef MCLBN_NO_AUTOLINK +#pragma GCC diagnostic pop + +#include +#include + +class BLSAggregator { +private: + std::shared_ptr bls_signer; + std::shared_ptr omq; + service_nodes::service_node_list& service_node_list; +public: + BLSAggregator(service_nodes::service_node_list& snl, std::shared_ptr omq_ptr); + ~BLSAggregator(); + + std::string aggregatePubkeyHex(); + std::string aggregateSignatures(const std::string& message); + + std::vector findNonSigners(const std::vector& indices); + +// End Service Node List +}; diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp new file mode 100644 index 0000000000..9e22d3985f --- /dev/null +++ b/src/bls/bls_signer.cpp @@ -0,0 +1,82 @@ +#include "bls_signer.h" +#include "logging/oxen_logger.h" + +static auto logcat = oxen::log::Cat("bls_signer"); + +BLSSigner::BLSSigner() { + initCurve(); + // This init function generates a secret key calling blsSecretKeySetByCSPRNG + secretKey.init(); +} + +BLSSigner::BLSSigner(bls::SecretKey _secretKey) { + initCurve(); + secretKey = _secretKey; +} + +BLSSigner::~BLSSigner() { +} + +void BLSSigner::initCurve() { + bls::init(mclBn_CurveSNARK1); + mclBn_setMapToMode(MCL_MAP_TO_MODE_TRY_AND_INC); + mcl::bn::G1 gen; + bool b; + mcl::bn::mapToG1(&b, gen, 1); + blsPublicKey publicKey; + publicKey.v = *reinterpret_cast(&gen); // Cast gen to mclBnG1 and assign it to publicKey.v + + blsSetGeneratorOfPublicKey(&publicKey); +} + +bls::Signature BLSSigner::signHash(const std::array& hash) { + bls::Signature sig; + secretKey.signHash(sig, hash.data(), hash.size()); + return sig; +} + +std::string BLSSigner::proofOfPossession() { + const std::array hash = BLSSigner::hash("0x" + getPublicKeyHex()); // Get the hash of the publickey + bls::Signature sig; + secretKey.signHash(sig, hash.data(), hash.size()); + return bls_utils::SignatureToHex(sig); +} + +std::string BLSSigner::getPublicKeyHex() { + bls::PublicKey publicKey; + secretKey.getPublicKey(publicKey); + return bls_utils::PublicKeyToHex(publicKey); +} + +bls::PublicKey BLSSigner::getPublicKey() { + bls::PublicKey publicKey; + secretKey.getPublicKey(publicKey); + return publicKey; +} + +std::array BLSSigner::hash(std::string in) { + std::vector bytes; + + // Check for "0x" prefix and if exists, convert the hex to bytes + if(in.size() >= 2 && in[0] == '0' && in[1] == 'x') { + bytes = oxenc::from_hex(in); + in = std::string(bytes.begin(), bytes.end()); + } + + std::array hash; + keccak(reinterpret_cast(in.c_str()), in.size(), hash.data(), 32); + return hash; +} + +std::array BLSSigner::hashModulus(std::string message) { + std::array hash = BLSSigner::hash(message); + mcl::bn::Fp x; + x.clear(); + x.setArrayMask(hash.data(), hash.size()); + std::array serialized_hash; + uint8_t *hdst = serialized_hash.data(); + mclSize serializedSignatureSize = 32; + if (x.serialize(hdst, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of x is zero"); + return serialized_hash; +} diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h new file mode 100644 index 0000000000..2d6b68cd28 --- /dev/null +++ b/src/bls/bls_signer.h @@ -0,0 +1,40 @@ +#pragma once + +#define BLS_ETH +#define MCLBN_FP_UNIT_SIZE 4 +#define MCLBN_FR_UNIT_SIZE 4 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#include +#include +#undef MCLBN_NO_AUTOLINK +#pragma GCC diagnostic pop + +class BLSSigner { +private: + bls::SecretKey secretKey; + + void initCurve(); + +public: + BLSSigner(); + BLSSigner(bls::SecretKey _secretKey); + ~BLSSigner(); + + bls::Signature signHash(const std::array& hash); + std::string proofOfPossession(); + std::string getPublicKeyHex(); + bls::PublicKey getPublicKey(); + + static std::array hash(std::string in); + static std::array hashModulus(std::string message); + + +private: + +// END +}; diff --git a/src/bls/bls_utils.cpp b/src/bls/bls_utils.cpp new file mode 100644 index 0000000000..1eb0d5f77b --- /dev/null +++ b/src/bls/bls_utils.cpp @@ -0,0 +1,37 @@ +#include "eth-bls/bls_utils.h" + +std::string bls_utils::SignatureToHex(bls::Signature sig) { + mclSize serializedSignatureSize = 32; + std::vector serialized_signature(serializedSignatureSize*4); + uint8_t *dst = serialized_signature.data(); + const blsSignature* blssig = sig.getPtr(); + const mcl::bn::G2* g2Point = reinterpret_cast(&blssig->v); + mcl::bn::G2 g2Point2 = *g2Point; + g2Point2.normalize(); + if (g2Point2.x.a.serialize(dst, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of x.a is zero"); + if (g2Point2.x.b.serialize(dst + serializedSignatureSize, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of x.b is zero"); + if (g2Point2.y.a.serialize(dst + serializedSignatureSize * 2, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of y.a is zero"); + if (g2Point2.y.b.serialize(dst + serializedSignatureSize * 3, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of y.b is zero"); + return utils::toHexString(serialized_signature); +} + +std::string bls_utils::PublicKeyToHex(bls::PublicKey publicKey) { + mclSize serializedPublicKeySize = 32; + std::vector serialized_pubkey(serializedPublicKeySize*2); + uint8_t *dst = serialized_pubkey.data(); + const blsPublicKey* pub = publicKey.getPtr(); + const mcl::bn::G1* g1Point = reinterpret_cast(&pub->v); + mcl::bn::G1 g1Point2 = *g1Point; + g1Point2.normalize(); + if (g1Point2.x.serialize(dst, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of x is zero"); + if (g1Point2.y.serialize(dst + serializedPublicKeySize, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + throw std::runtime_error("size of y is zero"); + + return utils::toHexString(serialized_pubkey); +} + diff --git a/src/bls/bls_utils.h b/src/bls/bls_utils.h new file mode 100644 index 0000000000..92daa3e59e --- /dev/null +++ b/src/bls/bls_utils.h @@ -0,0 +1,22 @@ +#pragma once + +#define BLS_ETH +#define MCLBN_FP_UNIT_SIZE 4 +#define MCLBN_FR_UNIT_SIZE 4 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#include +#include +#undef MCLBN_NO_AUTOLINK +#pragma GCC diagnostic pop + +namespace bls_utils +{ + std::string PublicKeyToHex(bls::PublicKey publicKey); + std::string SignatureToHex(bls::Signature sig); +// END +} diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 480461d5b8..109b62ef2e 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(cryptonote_core device checkpoints SQLite::SQLite3 + bls l2_tracker ethyl PRIVATE diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 8dcbe31486..f80c2b54e7 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -765,6 +765,7 @@ bool core::init( return false; init_oxenmq(vm); + m_bls_aggregator = std::make_shared(m_service_node_list, m_omq); const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty); r = m_blockchain_storage.init( @@ -1035,7 +1036,7 @@ oxenmq::AuthLevel core::omq_allow( void core::init_oxenmq(const boost::program_options::variables_map& vm) { using namespace oxenmq; log::info(omqlogcat, "Starting oxenmq"); - m_omq = std::make_unique( + m_omq = std::make_shared( tools::copy_guts(m_service_keys.pub_x25519), tools::copy_guts(m_service_keys.key_x25519), m_service_node, diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 79595262e2..af9f610979 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1247,6 +1247,8 @@ class core : public i_miner_handler { service_nodes::service_node_list m_service_node_list; service_nodes::quorum_cop m_quorum_cop; + std::unique_ptr m_bls_aggregator; + i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance cryptonote_protocol_stub m_protocol_stub; //!< cryptonote protocol stub instance @@ -1300,7 +1302,7 @@ class core : public i_miner_handler { uint16_t m_quorumnet_port; /// OxenMQ main object. Gets created during init(). - std::unique_ptr m_omq; + std::shared_ptr m_omq; // Internal opaque data object managed by cryptonote_protocol/quorumnet.cpp. void pointer to // avoid linking issues (protocol does not link against core). diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 85e685fa59..3066184b5c 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -3918,6 +3918,27 @@ crypto::public_key service_node_list::get_random_pubkey() { } } +// Function to get an iterator for the first service node info +crypto::public_key::iterator service_node_list::get_first_pubkey_iterator() { + std::lock_guard lock{m_sn_mutex}; + return m_state.service_nodes_infos.begin(); +} + +// Function to get the next iterator in the list +crypto::public_key::iterator service_node_list::get_next_pubkey_iterator(crypto::public_key::iterator current_it) { + std::lock_guard lock{m_sn_mutex}; + if (current_it != m_state.service_nodes_infos.end()) { + return ++current_it; + } else { + return current_it; + } +} + +crypto::public_key::iterator service_node_list::get_end_pubkey_iterator() { + std::lock_guard lock{m_sn_mutex}; + return m_state.service_nodes_infos.end(); +} + void service_node_list::initialize_x25519_map() { auto locks = tools::unique_locks(m_sn_mutex, m_x25519_map_mutex); diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index c160ed7047..52557a6d9e 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -2,7 +2,7 @@ #include #include "logging/oxen_logger.h" -static auto logcat = oxen::log::Cat("daemon"); +static auto logcat = oxen::log::Cat("l2_tracker"); L2Tracker::L2Tracker() { } From 7664bc9c539fb67ffcabc63872992f2374954d78 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 18 Sep 2023 14:01:59 +1000 Subject: [PATCH 15/97] update local devnet codes --- utils/local-devnet/daemons.py | 2 +- utils/local-devnet/service_node_network.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index 0100dbbcc5..36e864b5d9 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -95,7 +95,7 @@ def json_rpc(self, method, params=None, *, timeout=100): } if params: json["params"] = params - + print(json) return requests.post('http://{}:{}/json_rpc'.format(self.listen_ip, self.rpc_port), json=json, timeout=timeout) diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 3f394326e3..1057130967 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -139,8 +139,9 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): # of 18.9, which means each registration requires 6 inputs. Thus we need a bare minimum of # 6(N-5) blocks, plus the 30 lock time on coinbase TXes = 6N more blocks (after the initial # 5 registrations). - self.mine(204) + self.sync_nodes(self.mine(206), timeout=120) vprint("Submitting first round of service node registrations: ", end="", flush=True) + # time.sleep(40) for sn in self.sns[0:5]: self.mike.register_sn(sn) vprint(".", end="", flush=True, timestamp=False) From 4ddca3baee4194ab4dceefadb6d0c6e0b20b6e87 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 22 Sep 2023 07:21:53 +1000 Subject: [PATCH 16/97] bls library --- .gitmodules | 3 +++ external/bls | 1 + 2 files changed, 4 insertions(+) create mode 160000 external/bls diff --git a/.gitmodules b/.gitmodules index 96ad3c5957..5695f91a5d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -50,3 +50,6 @@ [submodule "external/ethyl"] path = external/ethyl url = git@github.com:oxen-io/ethyl.git +[submodule "external/bls"] + path = external/bls + url = git@github.com:herumi/bls.git diff --git a/external/bls b/external/bls new file mode 160000 index 0000000000..c6fb5264e9 --- /dev/null +++ b/external/bls @@ -0,0 +1 @@ +Subproject commit c6fb5264e9970a7508da2ef0cd48b8945380d277 From 71a702076bb258e708365849657b385131a23bae Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 22 Sep 2023 07:22:11 +1000 Subject: [PATCH 17/97] bls signing --- src/bls/CMakeLists.txt | 7 ++- src/bls/bls_aggregator.cpp | 77 ++++++++++++++--------- src/bls/bls_aggregator.h | 13 +++- src/bls/bls_signer.cpp | 28 ++++++++- src/bls/bls_signer.h | 5 ++ src/bls/bls_utils.cpp | 7 ++- src/crypto/keccak.h | 9 +++ src/cryptonote_core/CMakeLists.txt | 2 +- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/cryptonote_core.h | 2 + src/cryptonote_core/service_node_list.cpp | 6 +- src/cryptonote_core/service_node_list.h | 4 ++ src/l2_tracker/rewards_contract.cpp | 2 +- 13 files changed, 121 insertions(+), 45 deletions(-) diff --git a/src/bls/CMakeLists.txt b/src/bls/CMakeLists.txt index b9e590d99e..5fa0fd2263 100644 --- a/src/bls/CMakeLists.txt +++ b/src/bls/CMakeLists.txt @@ -26,14 +26,17 @@ # 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. -add_library(bls +add_library(oxen_bls bls_signer.cpp bls_aggregator.cpp bls_utils.cpp ) -target_link_libraries(bls +target_link_libraries(oxen_bls + PUBLIC + bls::bls256 PRIVATE + cncrypto common cryptonote_basic logging diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 10459fd46b..d49edad4fa 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -1,65 +1,84 @@ -#include "eth-bls/bls_aggregator.h" -#include "eth-bls/bls_signer.h" -#include "eth-bls/bls_utils.h" +#include "bls_aggregator.h" +#include "bls_signer.h" +#include "bls_utils.h" -BLSAggregator::BLSAggregator(service_nodes::service_node_list& snl, std::shared_ptr omq_ptr) - : service_node_list(snl), omq(omq_ptr) { - bls_signer = std::make_shared(); +#include "common/string_util.h" +#include "logging/oxen_logger.h" + +static auto logcat = oxen::log::Cat("bls_aggregator"); + +BLSAggregator::BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer) + : service_node_list(_snl), omq(std::move(_omq)), bls_signer(std::move(_bls_signer)) { } -ServiceNodeList::~ServiceNodeList() { +BLSAggregator::~BLSAggregator() { } std::string BLSAggregator::aggregatePubkeyHex() { - bls::PublicKey aggregate_pubkey; - aggregate_pubkey.clear(); - for(auto& node : nodes) { - aggregate_pubkey.add(node.getPublicKey()); - } - return utils::PublicKeyToHex(aggregate_pubkey); + //bls::PublicKey aggregate_pubkey; + //aggregate_pubkey.clear(); + //for(auto& node : nodes) { + //aggregate_pubkey.add(node.getPublicKey()); + //} + //return bls_utils::PublicKeyToHex(aggregate_pubkey); + return ""; } -std::string BLSAggregator::aggregateSignatures(const std::string& message) { +aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) { const std::array hash = BLSSigner::hash(message); + std::mutex signers_mutex, connection_mutex; + std::condition_variable cv; + size_t active_connections = 0; + const size_t MAX_CONNECTIONS = 900; bls::Signature aggSig; aggSig.clear(); - const std::vector signers; + std::vector signers; auto it = service_node_list.get_first_pubkey_iterator(); auto end_it = service_node_list.get_end_pubkey_iterator(); crypto::x25519_public_key x_pkey{0}; int64_t signers_index = 0; while (it != end_it) { - service_node_list->access_proof(*it, [&x_pkey](auto& proof) { + service_node_list.access_proof(it->first, [&x_pkey](auto& proof) { x_pkey = proof.pubkey_x25519; }); + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + ++active_connections; + // TODO sean this should call connect_remote instead of request, request leaves it open omq->request( tools::view_guts(x_pkey), - "bls.signature_request, - [this, signers_index](bool success, std::vector data) { - log::debug( + "bls.signature_request", + [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv](bool success, std::vector data) { + std::unique_lock connection_lock(connection_mutex); + --active_connections; + oxen::log::debug( logcat, "bls signature response received: {}", data[0]); if (success) { - bls::signature external_signature; - int64_t signers_index = 0; - if (tools::parse_string(data[0], external_signature)) { - aggSig.add(external_signature); - signers.push_back(signers_index); - } + bls::Signature external_signature; + external_signature.setStr(data[0]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); } - }); - it = service_node_list_instance.get_next_pubkey_iterator(it); + cv.notify_all(); + }, + message + ); + it = service_node_list.get_next_pubkey_iterator(it); signers_index++; } - return aggregateResponse{ findNonSigners(signers), utils::SignatureToHex(aggSig) } + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { return active_connections == 0; }); + return aggregateResponse{findNonSigners(signers), bls_utils::SignatureToHex(aggSig)}; }; std::vector BLSAggregator::findNonSigners(const std::vector& indices) { std::vector nonSignerIndices = {}; - for (int64_t i = 0; i < static_cast(nodes.size()); ++i) { + for (int64_t i = 0; i < static_cast(service_node_list.get_service_node_count()); ++i) { if (std::find(indices.begin(), indices.end(), i) == indices.end()) { nonSignerIndices.push_back(i); } diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index d28839d4fc..60f4b2ddc8 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -17,17 +17,26 @@ #include #include +#include "cryptonote_core/service_node_list.h" +#include "bls_signer.h" +#include + +struct aggregateResponse { + std::vector non_signers; + std::string signature; +}; + class BLSAggregator { private: std::shared_ptr bls_signer; std::shared_ptr omq; service_nodes::service_node_list& service_node_list; public: - BLSAggregator(service_nodes::service_node_list& snl, std::shared_ptr omq_ptr); + BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer); ~BLSAggregator(); std::string aggregatePubkeyHex(); - std::string aggregateSignatures(const std::string& message); + aggregateResponse aggregateSignatures(const std::string& message); std::vector findNonSigners(const std::vector& indices); diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 9e22d3985f..9498a7dbf1 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -1,5 +1,8 @@ #include "bls_signer.h" +#include "bls_utils.h" #include "logging/oxen_logger.h" +#include "crypto/keccak.h" +#include static auto logcat = oxen::log::Cat("bls_signer"); @@ -18,17 +21,35 @@ BLSSigner::~BLSSigner() { } void BLSSigner::initCurve() { + // Initialize parameters for BN256 curve, this has a different name in our library bls::init(mclBn_CurveSNARK1); + // Try and Inc method for hashing to the curve mclBn_setMapToMode(MCL_MAP_TO_MODE_TRY_AND_INC); + // Our generator point was created using the old hash to curve method, redo it again using Try and Inc method mcl::bn::G1 gen; bool b; mcl::bn::mapToG1(&b, gen, 1); blsPublicKey publicKey; - publicKey.v = *reinterpret_cast(&gen); // Cast gen to mclBnG1 and assign it to publicKey.v + publicKey.v = *reinterpret_cast(&gen); blsSetGeneratorOfPublicKey(&publicKey); } +void BLSSigner::initOMQ(std::shared_ptr omq) { + omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) + .add_request_command("signature_request", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq signature request"); + if (m.data.size() != 1) + m.send_reply( + "400", + "Bad request: BLS commands must have only one data part " + "(received " + + std::to_string(m.data.size()) + ")"); + const auto h = hash(std::string(m.data[0])); + m.send_reply(signHash(h).getStr()); + }); +} + bls::Signature BLSSigner::signHash(const std::array& hash) { bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); @@ -58,9 +79,10 @@ std::array BLSSigner::hash(std::string in) { std::vector bytes; // Check for "0x" prefix and if exists, convert the hex to bytes + // TODO sean from_hex wont work with 0x prefix if(in.size() >= 2 && in[0] == '0' && in[1] == 'x') { - bytes = oxenc::from_hex(in); - in = std::string(bytes.begin(), bytes.end()); + std::string bytes = oxenc::from_hex(in); + in = bytes; } std::array hash; diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 2d6b68cd28..3f6f7dcfeb 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -14,6 +14,9 @@ #undef MCLBN_NO_AUTOLINK #pragma GCC diagnostic pop +#include +#include + class BLSSigner { private: bls::SecretKey secretKey; @@ -25,6 +28,8 @@ class BLSSigner { BLSSigner(bls::SecretKey _secretKey); ~BLSSigner(); + void initOMQ(std::shared_ptr omq); + bls::Signature signHash(const std::array& hash); std::string proofOfPossession(); std::string getPublicKeyHex(); diff --git a/src/bls/bls_utils.cpp b/src/bls/bls_utils.cpp index 1eb0d5f77b..2ee451134c 100644 --- a/src/bls/bls_utils.cpp +++ b/src/bls/bls_utils.cpp @@ -1,4 +1,5 @@ -#include "eth-bls/bls_utils.h" +#include "bls_utils.h" +#include std::string bls_utils::SignatureToHex(bls::Signature sig) { mclSize serializedSignatureSize = 32; @@ -16,7 +17,7 @@ std::string bls_utils::SignatureToHex(bls::Signature sig) { throw std::runtime_error("size of y.a is zero"); if (g2Point2.y.b.serialize(dst + serializedSignatureSize * 3, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of y.b is zero"); - return utils::toHexString(serialized_signature); + return oxenc::to_hex(serialized_signature.begin(), serialized_signature.end()); } std::string bls_utils::PublicKeyToHex(bls::PublicKey publicKey) { @@ -32,6 +33,6 @@ std::string bls_utils::PublicKeyToHex(bls::PublicKey publicKey) { if (g1Point2.y.serialize(dst + serializedPublicKeySize, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of y is zero"); - return utils::toHexString(serialized_pubkey); + return oxenc::to_hex(serialized_pubkey.begin(), serialized_pubkey.end()); } diff --git a/src/crypto/keccak.h b/src/crypto/keccak.h index 89a8aed795..9493e6c058 100644 --- a/src/crypto/keccak.h +++ b/src/crypto/keccak.h @@ -4,6 +4,10 @@ #ifndef KECCAK_H #define KECCAK_H +#ifdef __cplusplus +extern "C" { +#endif + #include #include @@ -36,4 +40,9 @@ void keccak1600(const uint8_t* in, size_t inlen, uint8_t* md); void keccak_init(KECCAK_CTX* ctx); void keccak_update(KECCAK_CTX* ctx, const uint8_t* in, size_t inlen); void keccak_finish(KECCAK_CTX* ctx, uint8_t* md); + +#ifdef __cplusplus +} #endif +#endif + diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 109b62ef2e..2f7a7a8716 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,7 +53,7 @@ target_link_libraries(cryptonote_core device checkpoints SQLite::SQLite3 - bls + oxen_bls l2_tracker ethyl PRIVATE diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index f80c2b54e7..3b7110dfab 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -251,6 +251,7 @@ core::core() : m_last_storage_server_ping(0), m_last_lokinet_ping(0), m_pad_transactions(false), + m_bls_signer(std::make_shared()), ss_version{0}, lokinet_version{0} { m_checkpoints_updating.clear(); @@ -765,7 +766,7 @@ bool core::init( return false; init_oxenmq(vm); - m_bls_aggregator = std::make_shared(m_service_node_list, m_omq); + m_bls_aggregator = std::make_unique(m_service_node_list, m_omq, m_bls_signer); const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty); r = m_blockchain_storage.init( @@ -1075,6 +1076,7 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { } quorumnet_init(*this, m_quorumnet_state); + m_bls_signer->initOMQ(m_omq); } void core::start_oxenmq() { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index af9f610979..e9eb95c7a0 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -54,6 +54,7 @@ #include "service_node_quorum_cop.h" #include "service_node_voting.h" #include "tx_pool.h" +#include "bls/bls_aggregator.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) @@ -1247,6 +1248,7 @@ class core : public i_miner_handler { service_nodes::service_node_list m_service_node_list; service_nodes::quorum_cop m_quorum_cop; + std::shared_ptr m_bls_signer; std::unique_ptr m_bls_aggregator; i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 3066184b5c..c2995e95e4 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -3919,13 +3919,13 @@ crypto::public_key service_node_list::get_random_pubkey() { } // Function to get an iterator for the first service node info -crypto::public_key::iterator service_node_list::get_first_pubkey_iterator() { +service_nodes::service_nodes_infos_t::iterator service_node_list::get_first_pubkey_iterator() { std::lock_guard lock{m_sn_mutex}; return m_state.service_nodes_infos.begin(); } // Function to get the next iterator in the list -crypto::public_key::iterator service_node_list::get_next_pubkey_iterator(crypto::public_key::iterator current_it) { +service_nodes::service_nodes_infos_t::iterator service_node_list::get_next_pubkey_iterator(service_nodes::service_nodes_infos_t::iterator current_it) { std::lock_guard lock{m_sn_mutex}; if (current_it != m_state.service_nodes_infos.end()) { return ++current_it; @@ -3934,7 +3934,7 @@ crypto::public_key::iterator service_node_list::get_next_pubkey_iterator(crypto: } } -crypto::public_key::iterator service_node_list::get_end_pubkey_iterator() { +service_nodes::service_nodes_infos_t::iterator service_node_list::get_end_pubkey_iterator() { std::lock_guard lock{m_sn_mutex}; return m_state.service_nodes_infos.end(); } diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 02f6b88d56..83e7944fcd 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -518,6 +518,10 @@ class service_node_list { // Returns a pubkey of a random service node in the service node list crypto::public_key get_random_pubkey(); + service_nodes::service_nodes_infos_t::iterator get_first_pubkey_iterator(); + service_nodes::service_nodes_infos_t::iterator get_next_pubkey_iterator(service_nodes::service_nodes_infos_t::iterator current_it); + service_nodes::service_nodes_infos_t::iterator get_end_pubkey_iterator(); + /// Initializes the x25519 map from current pubkey state; called during initialization void initialize_x25519_map(); diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 494c79a681..2528cd25cc 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -1,7 +1,7 @@ #include "rewards_contract.h" RewardsContract::RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider) - : contractAddress(_contractAddress), provider(_provider) {} + : contractAddress(_contractAddress), provider(std::move(_provider)) {} StateResponse RewardsContract::State() { return State(std::nullopt); From a62d376f8871edc5beed3027b30cff1f4906dfa6 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 22 Sep 2023 12:07:22 +1000 Subject: [PATCH 18/97] use connection for omq --- src/bls/bls_aggregator.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index d49edad4fa..60d4251753 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -38,17 +38,30 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) auto it = service_node_list.get_first_pubkey_iterator(); auto end_it = service_node_list.get_end_pubkey_iterator(); crypto::x25519_public_key x_pkey{0}; + uint32_t ip; + uint16_t port; int64_t signers_index = 0; while (it != end_it) { - service_node_list.access_proof(it->first, [&x_pkey](auto& proof) { + service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { x_pkey = proof.pubkey_x25519; + ip = proof.proof->public_ip; + port = proof.proof->qnet_port; }); std::unique_lock connection_lock(connection_mutex); cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - ++active_connections; - // TODO sean this should call connect_remote instead of request, request leaves it open + oxenmq::address addr{"tcp://{}:{}"_format(ip, port), tools::view_guts(x_pkey)}; + auto conn = omq->connect_remote( + addr, + [&active_connections, &connection_mutex](oxenmq::ConnectionID c) { + std::unique_lock connection_lock(connection_mutex); + ++active_connections; + }, + [](oxenmq::ConnectionID c, std::string_view err) { + //Failed to connect + }, + oxenmq::AuthLevel::basic); omq->request( - tools::view_guts(x_pkey), + conn, "bls.signature_request", [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv](bool success, std::vector data) { std::unique_lock connection_lock(connection_mutex); From f1707f977e495bd53be87050ac00390a779e61ca Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 22 Sep 2023 13:57:20 +1000 Subject: [PATCH 19/97] rpc call for bls --- src/cryptonote_core/cryptonote_core.cpp | 16 ++++++++++++ src/cryptonote_core/cryptonote_core.h | 2 ++ src/rpc/core_rpc_server.cpp | 8 ++++++ src/rpc/core_rpc_server.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 14 ++++++++++ utils/local-devnet/commands/blsrequest.py | 31 +++++++++++++++++++++++ 6 files changed, 72 insertions(+) create mode 100755 utils/local-devnet/commands/blsrequest.py diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 3b7110dfab..9a8f886f72 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2672,6 +2672,22 @@ core::get_service_node_blacklisted_key_images() const { return m_service_node_list.get_blacklisted_key_images(); } //----------------------------------------------------------------------------------------------- +const aggregateResponse& core::bls_request() const { + //TODO sean remove this, just generating random string + const auto length = 20; + srand(static_cast(time(nullptr))); // Seed the random number generator + const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const int64_t max_index = sizeof(charset) - 1; + + std::string randomString; + + for (size_t i = 0; i < length; ++i) { + randomString += charset[static_cast(rand() % max_index)]; + } + + return m_bls_aggregator->aggregateSignatures(randomString); +} +//----------------------------------------------------------------------------------------------- std::vector core::get_service_node_list_state( const std::vector& service_node_pubkeys) const { return m_service_node_list.get_service_node_list_state(service_node_pubkeys); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index e9eb95c7a0..0c9bed0ba5 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -951,6 +951,8 @@ class core : public i_miner_handler { const std::vector& get_service_node_blacklisted_key_images() const; + const aggregateResponse& bls_request() const; + /** * @brief get a snapshot of the service node list state at the time of the call. * diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 8c22dad157..d2ce7fc96d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2523,6 +2523,14 @@ void core_rpc_server::invoke( return; } //------------------------------------------------------------------------------------------------------------------------------ +void core_rpc_server::invoke( BLS_REQUEST& bls_request, rpc_context context) { + auto bls_signature_response = m_core.bls_request(); + bls_request.response["status"] = STATUS_OK; + bls_request.response["signature"] = bls_signature_response.signature; + bls_request.response["non_signers"] = bls_signature_response.non_signers; + return; +} +//------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_SERVICE_KEYS& get_service_keys, rpc_context context) { const auto& keys = m_core.get_service_keys(); if (keys.pub) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 1887b3090f..02dab1058e 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -183,6 +183,7 @@ class core_rpc_server { void invoke( GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES& get_service_node_blacklisted_key_images, rpc_context context); + void invoke(BLS_REQUEST& bls_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 4a0fb95823..6d3d691ece 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2284,6 +2284,19 @@ struct GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_service_node_blacklisted_key_images"); } }; + +/// RPC: bls request +/// +/// Sends a request out for all nodes to sign a BLS signature +/// +/// Inputs: None +/// +/// Outputs: None +/// +struct BLS_REQUEST : PUBLIC, NO_ARGS { + static constexpr auto names() { return NAMES("bls_request"); } +}; + /// RPC: blockchain/get_checkpoints /// /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to @@ -2676,6 +2689,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_KEYS, GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, + BLS_REQUEST, GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, GET_SERVICE_NODE_STATUS, diff --git a/utils/local-devnet/commands/blsrequest.py b/utils/local-devnet/commands/blsrequest.py new file mode 100755 index 0000000000..9a4bf5fcb2 --- /dev/null +++ b/utils/local-devnet/commands/blsrequest.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +import sys +sys.path.append('../testdata') +import config + +import requests +import argparse +import json +from datetime import datetime + + +def instruct_daemon(method, params): + payload = json.dumps({"method": method, "params": params}, skipkeys=False) + # print(payload) + headers = {'content-type': "application/json"} + try: + response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers) + return json.loads(response.text) + except requests.exceptions.RequestException as e: + print(e) + except: + print('No response from daemon, check daemon is running on this machine') + + +params = {} +answer = instruct_daemon('bls_request', params) + +# print(json.dumps(answer['result']['block_header']['timestamp'], indent=4, sort_keys=True)) + +print(answer) From 34c967dfa504b474f17c86db9f0b78b217bae600 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 25 Sep 2023 17:35:10 +1000 Subject: [PATCH 20/97] bls signing not working --- src/bls/bls_aggregator.cpp | 83 ++++++++++++++++--------- src/bls/bls_signer.cpp | 1 + src/cryptonote_core/cryptonote_core.cpp | 9 ++- src/rpc/core_rpc_server.cpp | 6 +- 4 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 60d4251753..69680b557e 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -4,6 +4,8 @@ #include "common/string_util.h" #include "logging/oxen_logger.h" +#include +#include static auto logcat = oxen::log::Cat("bls_aggregator"); @@ -47,46 +49,65 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) ip = proof.proof->public_ip; port = proof.proof->qnet_port; }); - std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - oxenmq::address addr{"tcp://{}:{}"_format(ip, port), tools::view_guts(x_pkey)}; + //{ + //std::unique_lock connection_lock(connection_mutex); + //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + //} + boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); + oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + + { + std::lock_guard connection_lock(connection_mutex); + ++active_connections; + } auto conn = omq->connect_remote( addr, - [&active_connections, &connection_mutex](oxenmq::ConnectionID c) { - std::unique_lock connection_lock(connection_mutex); - ++active_connections; + [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &message](oxenmq::ConnectionID c) { + // Successfully connected + oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); + omq->request( + c, + "bls.signature_request", + [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &c](bool success, std::vector data) { + oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); + oxen::log::debug( logcat, "bls signature response received"); + if (success) { + bls::Signature external_signature; + external_signature.setStr(data[0]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + std::lock_guard connection_lock(connection_mutex); + oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }, + message + ); }, - [](oxenmq::ConnectionID c, std::string_view err) { - //Failed to connect + [&active_connections, &cv, &connection_mutex](oxenmq::ConnectionID c, std::string_view err) { + // Failed to connect + oxen::log::debug(logcat, "Failed to connect {}", err); + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); }, oxenmq::AuthLevel::basic); - omq->request( - conn, - "bls.signature_request", - [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv](bool success, std::vector data) { - std::unique_lock connection_lock(connection_mutex); - --active_connections; - oxen::log::debug( - logcat, - "bls signature response received: {}", - data[0]); - if (success) { - bls::Signature external_signature; - external_signature.setStr(data[0]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - cv.notify_all(); - }, - message - ); it = service_node_list.get_next_pubkey_iterator(it); signers_index++; } std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { return active_connections == 0; }); - return aggregateResponse{findNonSigners(signers), bls_utils::SignatureToHex(aggSig)}; + cv.wait(connection_lock, [&active_connections] { + oxen::log::info(logcat, "TODO sean remove this: {}", active_connections); + return active_connections == 0; + }); + const auto non_signers = findNonSigners(signers); + const auto my_signature = bls_signer->signHash(hash); + aggSig.add(my_signature); + const auto sig_str = bls_utils::SignatureToHex(aggSig); + return aggregateResponse{non_signers, sig_str}; }; std::vector BLSAggregator::findNonSigners(const std::vector& indices) { diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 9498a7dbf1..01dacf30a7 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -36,6 +36,7 @@ void BLSSigner::initCurve() { } void BLSSigner::initOMQ(std::shared_ptr omq) { + omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) .add_request_command("signature_request", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq signature request"); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 9a8f886f72..0677c733d1 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1058,7 +1058,9 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { m.send_reply("pong"); }); + if (m_service_node) { + // Service nodes always listen for quorumnet data on the p2p IP, quorumnet port std::string listen_ip = vm["p2p-bind-ip"].as(); if (listen_ip.empty()) @@ -1073,10 +1075,11 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { }); m_quorumnet_state = quorumnet_new(*this); + + m_bls_signer->initOMQ(m_omq); } quorumnet_init(*this, m_quorumnet_state); - m_bls_signer->initOMQ(m_omq); } void core::start_oxenmq() { @@ -2685,7 +2688,9 @@ const aggregateResponse& core::bls_request() const { randomString += charset[static_cast(rand() % max_index)]; } - return m_bls_aggregator->aggregateSignatures(randomString); + const auto resp = m_bls_aggregator->aggregateSignatures(randomString); + oxen::log::info(logcat, "TODO sean remove this: {}", "aaaaaaaaa"); + return resp; } //----------------------------------------------------------------------------------------------- std::vector core::get_service_node_list_state( diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d2ce7fc96d..3d280f664b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2524,7 +2524,11 @@ void core_rpc_server::invoke( } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke( BLS_REQUEST& bls_request, rpc_context context) { - auto bls_signature_response = m_core.bls_request(); + const aggregateResponse bls_signature_response = m_core.bls_request(); + oxen::log::info(logcat, "TODO sean remove this: {}", "ddddddddd"); + oxen::log::info(logcat, "TODO sean remove this: {}", bls_signature_response.signature); + for (auto& x: bls_signature_response.non_signers) + oxen::log::info(logcat, "TODO sean remove this: {}", x); bls_request.response["status"] = STATUS_OK; bls_request.response["signature"] = bls_signature_response.signature; bls_request.response["non_signers"] = bls_signature_response.non_signers; From 2d9ff1a6a23f84f500e41c3ddd19e41d66a5d7df Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Tue, 26 Sep 2023 08:43:33 +1000 Subject: [PATCH 21/97] aggregate signing works --- src/bls/bls_aggregator.cpp | 55 ++++++++++++------------- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/cryptonote_core/cryptonote_core.h | 2 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 69680b557e..461866fec6 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -37,6 +37,7 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) std::vector signers; + // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda auto it = service_node_list.get_first_pubkey_iterator(); auto end_it = service_node_list.get_end_pubkey_iterator(); crypto::x25519_public_key x_pkey{0}; @@ -53,6 +54,7 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) //std::unique_lock connection_lock(connection_mutex); //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); //} + // TODO sean epee is alway little, this will not work on big endian host boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; @@ -62,39 +64,36 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) } auto conn = omq->connect_remote( addr, - [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &message](oxenmq::ConnectionID c) { + [](oxenmq::ConnectionID c) { // Successfully connected - oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); - omq->request( - c, - "bls.signature_request", - [this, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &c](bool success, std::vector data) { - oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); - oxen::log::debug( logcat, "bls signature response received"); - if (success) { - bls::Signature external_signature; - external_signature.setStr(data[0]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - std::lock_guard connection_lock(connection_mutex); - oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }, - message - ); + //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); }, - [&active_connections, &cv, &connection_mutex](oxenmq::ConnectionID c, std::string_view err) { + [](oxenmq::ConnectionID c, std::string_view err) { // Failed to connect - oxen::log::debug(logcat, "Failed to connect {}", err); - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); + //oxen::log::debug(logcat, "Failed to connect {}", err); }, oxenmq::AuthLevel::basic); + omq->request( + conn, + "bls.signature_request", + [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn](bool success, std::vector data) { + oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); + oxen::log::debug( logcat, "bls signature response received"); + if (success) { + bls::Signature external_signature; + external_signature.setStr(data[0]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + std::lock_guard connection_lock(connection_mutex); + oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }, + message + ); it = service_node_list.get_next_pubkey_iterator(it); signers_index++; } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 0677c733d1..1ca1e0a9f6 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2675,7 +2675,7 @@ core::get_service_node_blacklisted_key_images() const { return m_service_node_list.get_blacklisted_key_images(); } //----------------------------------------------------------------------------------------------- -const aggregateResponse& core::bls_request() const { +aggregateResponse core::bls_request() const { //TODO sean remove this, just generating random string const auto length = 20; srand(static_cast(time(nullptr))); // Seed the random number generator diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 0c9bed0ba5..117ca29c33 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -951,7 +951,7 @@ class core : public i_miner_handler { const std::vector& get_service_node_blacklisted_key_images() const; - const aggregateResponse& bls_request() const; + aggregateResponse bls_request() const; /** * @brief get a snapshot of the service node list state at the time of the call. From 4bef42f1d2946c1111055d1041a6ff76f60a3a0a Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Tue, 10 Oct 2023 14:26:46 +1100 Subject: [PATCH 22/97] folders for local devnet to exclude changing ip addresses --- utils/local-devnet/daemons.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index 36e864b5d9..c1904fef28 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -155,7 +155,8 @@ def __init__(self, *, self.args = [oxend] + list(self.__class__.base_args) self.args += ( - '--data-dir={}/oxen-{}-{}'.format(datadir or '.', self.listen_ip, self.rpc_port), + # '--data-dir={}/oxen-{}-{}'.format(datadir or '.', self.listen_ip, self.rpc_port), + '--data-dir={}/oxen-{}'.format(datadir or '.', self.rpc_port), '--log-level={}'.format(log_level), '--log-file=oxen.log'.format(self.listen_ip, self.p2p_port), '--p2p-bind-ip={}'.format(self.listen_ip), @@ -269,7 +270,8 @@ def __init__( self.name = name or 'wallet@{}'.format(self.rpc_port) super().__init__(self.name) - self.walletdir = '{}/wallet-{}-{}'.format(datadir or '.', self.listen_ip, self.rpc_port) + # self.walletdir = '{}/wallet-{}-{}'.format(datadir or '.', self.listen_ip, self.rpc_port) + self.walletdir = '{}/wallet-{}'.format(datadir or '.', self.rpc_port) self.args = [rpc_wallet] + list(self.__class__.base_args) self.args += ( '--rpc-bind-ip={}'.format(self.listen_ip), From ee278742f1a83a4e2c55448ba2d5a364058d8757 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Tue, 10 Oct 2023 16:36:51 +1100 Subject: [PATCH 23/97] merkle tree creator --- src/CMakeLists.txt | 1 + src/merkle/CMakeLists.txt | 37 + src/merkle/merkle_tree_creator.cpp | 92 ++ src/merkle/merkle_tree_creator.hpp | 37 + src/merkle/merklecpp.h | 2030 ++++++++++++++++++++++++++++ 5 files changed, 2197 insertions(+) create mode 100644 src/merkle/CMakeLists.txt create mode 100644 src/merkle/merkle_tree_creator.cpp create mode 100644 src/merkle/merkle_tree_creator.hpp create mode 100644 src/merkle/merklecpp.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ace8a4d761..e3d5ee6868 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ add_subdirectory(logging) add_subdirectory(lmdb) add_subdirectory(l2_tracker) add_subdirectory(bls) +add_subdirectory(merkle) add_subdirectory(multisig) add_subdirectory(net) add_subdirectory(mnemonics) diff --git a/src/merkle/CMakeLists.txt b/src/merkle/CMakeLists.txt new file mode 100644 index 0000000000..0838a6f99b --- /dev/null +++ b/src/merkle/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (c) 2023, The Oxen 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. + +add_library(merkle + merkle_tree_creator.cpp + ) + +target_link_libraries(merkle + PRIVATE + common + ethyl + extra) diff --git a/src/merkle/merkle_tree_creator.cpp b/src/merkle/merkle_tree_creator.cpp new file mode 100644 index 0000000000..2339d3c3e1 --- /dev/null +++ b/src/merkle/merkle_tree_creator.cpp @@ -0,0 +1,92 @@ +#include "merkle_tree_creator.hpp" +#include "iostream" +#include + +extern "C" { +#include "crypto/keccak.h" +} + +MerkleTreeCreator::MerkleTreeCreator() {} + +void MerkleTreeCreator::addLeaf(const std::string& input) { + tree.insert(createMerkleKeccakHash(input)); +} + +void MerkleTreeCreator::addLeaves(const std::map& data) { + for (const auto& [address, balance] : data) { + std::string combined = abiEncode(address, balance); + addLeaf(combined); + } +} + +std::string MerkleTreeCreator::abiEncode(const std::string& address, uint64_t balance) { + + std::string sanitized_address = address; + // Check if input starts with "0x" prefix + if (sanitized_address.substr(0, 2) == "0x") { + sanitized_address = sanitized_address.substr(2); // remove "0x" prefix for now + } + std::string sanitized_address_padded = utils::padTo32Bytes(sanitized_address, utils::PaddingDirection::LEFT); + std::string balance_padded = utils::padTo32Bytes(utils::decimalToHex(balance), utils::PaddingDirection::LEFT); + + return "0x" + sanitized_address_padded + balance_padded; +} + +merkle::Tree::Hash MerkleTreeCreator::createMerkleKeccakHash(const std::string& input) { + // Compute Keccak hash using utils::hash + std::array hash_result = utils::hash(input); + + // Convert std::array to std::vector + std::vector hash_vector(hash_result.begin(), hash_result.end()); + return merkle::Tree::Hash(hash_vector); +} + +void MerkleTreeCreator::cncryptoCompressKeccak256( + const merkle::HashT<32>& l, + const merkle::HashT<32>& r, + merkle::HashT<32>& out) +{ + uint8_t block[32 * 2]; + memcpy(&block[0], l.bytes, 32); + memcpy(&block[32], r.bytes, 32); + + // Assuming keccak function signature remains the same as in the provided utils::hash function + keccak(block, sizeof(block), out.bytes, 32); +} + +std::string MerkleTreeCreator::getRoot() { + return tree.root().to_string(); +} + +size_t MerkleTreeCreator::getPathSize(size_t index) { + return tree.path(index)->size(); +} + +std::string MerkleTreeCreator::getPath(size_t index) { + return tree.path(index)->to_eth_string(); +} + +size_t MerkleTreeCreator::findIndex(const std::string& input) { + return tree.find_leaf_index(createMerkleKeccakHash(input)); +} + +std::string MerkleTreeCreator::updateRewardsMerkleRoot() { + //function updateRewardsMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + std::string functionSelector = utils::getFunctionSignature("updateRewardsMerkleRoot(bytes32)"); + + // Concatenate the function selector and the encoded arguments + return functionSelector + getRoot(); +} + +std::string MerkleTreeCreator::validateProof(size_t index, int64_t amount) { + //function validateProof(uint256 _quantity, bytes32[] calldata _merkleProof) external { + std::string functionSelector = utils::getFunctionSignature("validateProof(uint256,bytes32[])"); + + // Convert amount to hex string and pad it to 32 bytes + std::string amount_padded = utils::padTo32Bytes(utils::decimalToHex(amount), utils::PaddingDirection::LEFT); + std::string proof_location_padded = utils::padTo32Bytes(utils::decimalToHex(64), utils::PaddingDirection::LEFT); + std::string proof_length_padded = utils::padTo32Bytes(utils::decimalToHex(getPathSize(index)), utils::PaddingDirection::LEFT); + + // Concatenate the function selector and the encoded arguments + return functionSelector + amount_padded + proof_location_padded + proof_length_padded + getPath(index); +} diff --git a/src/merkle/merkle_tree_creator.hpp b/src/merkle/merkle_tree_creator.hpp new file mode 100644 index 0000000000..44a371e3ac --- /dev/null +++ b/src/merkle/merkle_tree_creator.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include "merklecpp.h" + +class MerkleTreeCreator { +public: + MerkleTreeCreator(); + + void addLeaf(const std::string& input); + void addLeaves(const std::map& data); + + merkle::Tree::Hash createMerkleKeccakHash(const std::string& input); + + std::string getRoot(); + std::string getPath(size_t index); + size_t getPathSize(size_t index); + size_t findIndex(const std::string& input); + + // For interacting with smart contract + std::string updateRewardsMerkleRoot(); + std::string validateProof(size_t index, int64_t amount); + + std::string abiEncode(const std::string& address, uint64_t balance); + + static inline void cncryptoCompressKeccak256( + const merkle::HashT<32>& l, + const merkle::HashT<32>& r, + merkle::HashT<32>& out + ); + + merkle::TreeT<32, cncryptoCompressKeccak256> tree; +}; + diff --git a/src/merkle/merklecpp.h b/src/merkle/merklecpp.h new file mode 100644 index 0000000000..8a1a8268af --- /dev/null +++ b/src/merkle/merklecpp.h @@ -0,0 +1,2030 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENSSL +# include +# include +#endif + +#ifdef HAVE_MBEDTLS +# include +#endif + +#ifdef MERKLECPP_TRACE_ENABLED +// Hashes in the trace output are truncated to TRACE_HASH_SIZE bytes. +# define TRACE_HASH_SIZE 3 + +# ifndef MERKLECPP_TRACE +# include +# define MERKLECPP_TOUT std::cout +# define MERKLECPP_TRACE(X) \ + { \ + X; \ + MERKLECPP_TOUT.flush(); \ + }; +# endif +#else +# define MERKLECPP_TRACE(X) +#endif + +#define MERKLECPP_VERSION_MAJOR 1 +#define MERKLECPP_VERSION_MINOR 0 +#define MERKLECPP_VERSION_PATCH 0 + +namespace merkle +{ + static inline uint32_t convert_endianness(uint32_t n) + { + const uint32_t sz = sizeof(uint32_t); +#if defined(htobe32) + // If htobe32 happens to be a macro, use it. + return htobe32(n); +#elif defined(__LITTLE_ENDIAN__) || defined(__LITTLE_ENDIAN) + // Just as fast. + uint32_t r = 0; + for (size_t i = 0; i < sz; i++) + r |= ((n >> (8 * ((sz - 1) - i))) & 0xFF) << (8 * i); + return *reinterpret_cast(&r); +#else + // A little slower, but works for both endiannesses. + uint8_t r[8]; + for (size_t i = 0; i < sz; i++) + r[i] = (n >> (8 * ((sz - 1) - i))) & 0xFF; + return *reinterpret_cast(&r); +#endif + } + + static inline void serialise_uint64_t(uint64_t n, std::vector& bytes) + { + size_t sz = sizeof(uint64_t); + bytes.reserve(bytes.size() + sz); + for (uint64_t i = 0; i < sz; i++) + bytes.push_back((n >> (8 * (sz - i - 1))) & 0xFF); + } + + static inline uint64_t deserialise_uint64_t( + const std::vector& bytes, size_t& index) + { + uint64_t r = 0; + uint64_t sz = sizeof(uint64_t); + for (uint64_t i = 0; i < sz; i++) + r |= static_cast(bytes.at(index++)) << (8 * (sz - i - 1)); + return r; + } + + /// @brief Template for fixed-size hashes + /// @tparam SIZE Size of the hash in number of bytes + template + struct HashT + { + /// Holds the hash bytes + uint8_t bytes[SIZE]; + + /// @brief Constructs a Hash with all bytes set to zero + HashT() + { + std::fill(bytes, bytes + SIZE, 0); + } + + /// @brief Constructs a Hash from a byte buffer + /// @param bytes Buffer with hash value + HashT(const uint8_t* bytes) + { + std::copy(bytes, bytes + SIZE, this->bytes); + } + + /// @brief Constructs a Hash from a string + /// @param s String to read the hash value from + HashT(const std::string& s) + { + if (s.length() != 2 * SIZE) + throw std::runtime_error("invalid hash string"); + for (size_t i = 0; i < SIZE; i++) + { + int tmp; + sscanf(s.c_str() + 2 * i, "%02x", &tmp); + bytes[i] = tmp; + } + } + + /// @brief Deserialises a Hash from a vector of bytes + /// @param bytes Vector to read the hash value from + HashT(const std::vector& bytes) + { + if (bytes.size() < SIZE) + throw std::runtime_error("not enough bytes"); + deserialise(bytes); + } + + /// @brief Deserialises a Hash from a vector of bytes + /// @param bytes Vector to read the hash value from + /// @param position Position of the first byte in @p bytes + HashT(const std::vector& bytes, size_t& position) + { + if (bytes.size() - position < SIZE) + throw std::runtime_error("not enough bytes"); + deserialise(bytes, position); + } + + /// @brief Deserialises a Hash from an array of bytes + /// @param bytes Array to read the hash value from + HashT(const std::array& bytes) + { + std::copy(bytes.data(), bytes.data() + SIZE, this->bytes); + } + + /// @brief The size of the hash (in number of bytes) + size_t size() const + { + return SIZE; + } + + /// @brief zeros out all bytes in the hash + void zero() + { + std::fill(bytes, bytes + SIZE, 0); + } + + /// @brief The size of the serialisation of the hash (in number of bytes) + size_t serialised_size() const + { + return SIZE; + } + + /// @brief Convert a hash to a hex-encoded string + /// @param num_bytes The maximum number of bytes to convert + /// @param lower_case Enables lower-case hex characters + std::string to_string(size_t num_bytes = SIZE, bool lower_case = true) const + { + size_t num_chars = 2 * num_bytes; + std::string r(num_chars, '_'); + for (size_t i = 0; i < num_bytes; i++) + snprintf( + const_cast(r.data() + 2 * i), + num_chars + 1 - 2 * i, + lower_case ? "%02x" : "%02X", + bytes[i]); + return r; + } + + /// @brief Hash assignment operator + HashT operator=(const HashT& other) + { + std::copy(other.bytes, other.bytes + SIZE, bytes); + return *this; + } + + /// @brief Hash equality operator + bool operator==(const HashT& other) const + { + return memcmp(bytes, other.bytes, SIZE) == 0; + } + + /// @brief Hash inequality operator + bool operator!=(const HashT& other) const + { + return memcmp(bytes, other.bytes, SIZE) != 0; + } + + /// @brief Hash less than operator + bool operator<(const HashT& other) const + { + for (size_t i = 0; i < SIZE; i++) + { + if (bytes[i] < other.bytes[i]) return true; + if (bytes[i] > other.bytes[i]) return false; + } + return false; // equal + } + + /// @brief Serialises a hash + /// @param buffer Buffer to serialise to + void serialise(std::vector& buffer) const + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> HashT::serialise " << std::endl); + for (auto& b : bytes) + buffer.push_back(b); + } + + /// @brief Deserialises a hash + /// @param buffer Buffer to read the hash from + /// @param position Position of the first byte in @p bytes + void deserialise(const std::vector& buffer, size_t& position) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> HashT::deserialise " << std::endl); + if (buffer.size() - position < SIZE) + throw std::runtime_error("not enough bytes"); + for (size_t i = 0; i < sizeof(bytes); i++) + bytes[i] = buffer[position++]; + } + + /// @brief Deserialises a hash + /// @param buffer Buffer to read the hash from + void deserialise(const std::vector& buffer) + { + size_t position = 0; + deserialise(buffer, position); + } + + /// @brief Conversion operator to vector of bytes + operator std::vector() const + { + std::vector bytes; + serialise(bytes); + return bytes; + } + }; + + /// @brief Template for Merkle paths + /// @tparam HASH_SIZE Size of each hash in number of bytes + /// @tparam HASH_FUNCTION The hash function + template < + size_t HASH_SIZE, + void HASH_FUNCTION( + const HashT& l, + const HashT& r, + HashT& out)> + class PathT + { + public: + /// @brief Path direction + typedef enum + { + PATH_LEFT, + PATH_RIGHT + } Direction; + + /// @brief Path element + typedef struct + { + /// @brief The hash of the path element + HashT hash; + + /// @brief The direction at which @p hash joins at this path element + /// @note If @p direction == PATH_LEFT, @p hash joins at the left, i.e. + /// if t is the current hash, e.g. a leaf, then t' = Hash( @p hash, t ); + Direction direction; + } Element; + + /// @brief Path constructor + /// @param leaf + /// @param leaf_index + /// @param elements + /// @param max_index + PathT( + const HashT& leaf, + size_t leaf_index, + std::list&& elements, + size_t max_index) : + _leaf(leaf), + _leaf_index(leaf_index), + _max_index(max_index), + elements(elements) + {} + + /// @brief Path copy constructor + /// @param other Path to copy + PathT(const PathT& other) + { + _leaf = other._leaf; + elements = other.elements; + } + + /// @brief Path move constructor + /// @param other Path to move + PathT(PathT&& other) + { + _leaf = std::move(other._leaf); + elements = std::move(other.elements); + } + + /// @brief Deserialises a path + /// @param bytes Vector to deserialise from + PathT(const std::vector& bytes) + { + deserialise(bytes); + } + + /// @brief Deserialises a path + /// @param bytes Vector to deserialise from + /// @param position Position of the first byte in @p bytes + PathT(const std::vector& bytes, size_t& position) + { + deserialise(bytes, position); + } + + /// @brief Computes the root at the end of the path + /// @note This (re-)computes the root by hashing the path elements, it does + /// not return a previously saved root hash. + std::shared_ptr> root() const + { + std::shared_ptr> result = + std::make_shared>(_leaf); + MERKLECPP_TRACE( + MERKLECPP_TOUT << "> PathT::root " << _leaf.to_string(TRACE_HASH_SIZE) + << std::endl); + for (const Element& e : elements) + { + if (e.direction == PATH_LEFT) + { + MERKLECPP_TRACE( + MERKLECPP_TOUT << " - " << e.hash.to_string(TRACE_HASH_SIZE) + << " x " << result->to_string(TRACE_HASH_SIZE) + << std::endl); + HASH_FUNCTION(e.hash, *result, *result); + } + else + { + MERKLECPP_TRACE( + MERKLECPP_TOUT << " - " << result->to_string(TRACE_HASH_SIZE) + << " x " << e.hash.to_string(TRACE_HASH_SIZE) + << std::endl); + HASH_FUNCTION(*result, e.hash, *result); + } + } + MERKLECPP_TRACE( + MERKLECPP_TOUT << " = " << result->to_string(TRACE_HASH_SIZE) + << std::endl); + return result; + } + + /// @brief Verifies that the root at the end of the path is expected + /// @param expected_root The root hash that the elements on the path are + /// expected to hash to. + bool verify(const HashT& expected_root) const + { + return *root() == expected_root; + } + + /// @brief Serialises a path + /// @param bytes Vector of bytes to serialise to + void serialise(std::vector& bytes) const + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::serialise " << std::endl); + _leaf.serialise(bytes); + serialise_uint64_t(_leaf_index, bytes); + serialise_uint64_t(_max_index, bytes); + serialise_uint64_t(elements.size(), bytes); + for (auto& e : elements) + { + e.hash.serialise(bytes); + bytes.push_back(e.direction == PATH_LEFT ? 1 : 0); + } + } + + /// @brief Deserialises a path + /// @param bytes Vector of bytes to serialise from + /// @param position Position of the first byte in @p bytes + void deserialise(const std::vector& bytes, size_t& position) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::deserialise " << std::endl); + elements.clear(); + _leaf.deserialise(bytes, position); + _leaf_index = deserialise_uint64_t(bytes, position); + _max_index = deserialise_uint64_t(bytes, position); + size_t num_elements = deserialise_uint64_t(bytes, position); + for (size_t i = 0; i < num_elements; i++) + { + HashT hash(bytes, position); + PathT::Direction direction = + bytes.at(position++) != 0 ? PATH_LEFT : PATH_RIGHT; + PathT::Element e; + e.hash = hash; + e.direction = direction; + elements.push_back(std::move(e)); + } + } + + /// @brief Deserialises a path + /// @param bytes Vector of bytes to serialise from + void deserialise(const std::vector& bytes) + { + size_t position = 0; + deserialise(bytes, position); + } + + /// @brief Conversion operator to vector of bytes + operator std::vector() const + { + std::vector bytes; + serialise(bytes); + return bytes; + } + + /// @brief The number of elements on the path + size_t size() const + { + return elements.size(); + } + + /// @brief The size of the serialised path in number of bytes + size_t serialised_size() const + { + return sizeof(_leaf) + elements.size() * sizeof(Element); + } + + /// @brief Index of the leaf of the path + size_t leaf_index() const + { + return _leaf_index; + } + + /// @brief Maximum index of the tree at the time the path was extracted + size_t max_index() const + { + return _max_index; + } + + /// @brief Operator to extract the hash of a given path element + /// @param i Index of the path element + const HashT& operator[](size_t i) const + { + return std::next(begin(), i)->hash; + } + + /// @brief Iterator for path elements + typedef typename std::list::const_iterator const_iterator; + + /// @brief Start iterator for path elements + const_iterator begin() const + { + return elements.begin(); + } + + /// @brief End iterator for path elements + const_iterator end() const + { + return elements.end(); + } + + /// @brief Convert a path to a string + /// @param num_bytes The maximum number of bytes to convert + /// @param lower_case Enables lower-case hex characters + std::string to_string( + size_t num_bytes = HASH_SIZE, bool lower_case = true) const + { + std::stringstream stream; + stream << _leaf.to_string(num_bytes); + for (auto& e : elements) + stream << " " << e.hash.to_string(num_bytes, lower_case) + << (e.direction == PATH_LEFT ? "(L)" : "(R)"); + return stream.str(); + } + + std::string to_eth_string( + size_t num_bytes = HASH_SIZE, bool lower_case = true) const + { + std::stringstream stream; + for (auto& e : elements) + stream << e.hash.to_string(num_bytes, lower_case); + return stream.str(); + } + + /// @brief The leaf hash of the path + const HashT& leaf() const + { + return _leaf; + } + + /// @brief Equality operator for paths + bool operator==(const PathT& other) const + { + if (_leaf != other._leaf || elements.size() != other.elements.size()) + return false; + auto it = elements.begin(); + auto other_it = other.elements.begin(); + while (it != elements.end() && other_it != other.elements.end()) + { + if (it->hash != other_it->hash || it->direction != other_it->direction) + return false; + it++; + other_it++; + } + return true; + } + + /// @brief Inequality operator for paths + bool operator!=(const PathT& other) + { + return !this->operator==(other); + } + + protected: + /// @brief The leaf hash + HashT _leaf; + + /// @brief The index of the leaf + size_t _leaf_index; + + /// @brief The maximum leaf index of the tree at the time of path extraction + size_t _max_index; + + /// @brief The elements of the path + std::list elements; + }; + + /// @brief Template for Merkle trees + /// @tparam HASH_SIZE Size of each hash in number of bytes + /// @tparam HASH_FUNCTION The hash function + template < + size_t HASH_SIZE, + void HASH_FUNCTION( + const HashT& l, + const HashT& r, + HashT& out)> + class TreeT + { + protected: + /// @brief The structure of tree nodes + struct Node + { + /// @brief Constructs a new tree node + /// @param hash The hash of the node + static Node* make(const HashT& hash) + { + auto r = new Node(); + r->left = r->right = nullptr; + r->hash = hash; + r->dirty = false; + r->swapped = false; + r->update_sizes(); + assert(r->invariant()); + return r; + } + + /// @brief Constructs a new tree node + /// @param left The left child of the new node + /// @param right The right child of the new node + static Node* make(Node* left, Node* right) + { + assert(left && right); + auto r = new Node(); + + r->swapped = false; + //Ensure left child's hash is less than the right child's hash + if (left->hash < right->hash) + { + r->left = left; + r->right = right; + } + else + { + r->swapped = true; + r->left = right; + r->right = left; + } + + r->dirty = true; + r->update_sizes(); + assert(r->invariant()); + return r; + } + + /// @brief Copies a tree node + /// @param from Node to copy + /// @param leaf_nodes Current leaf nodes of the tree + /// @param num_flushed Number of flushed nodes of the tree + /// @param min_index Minimum leaf index of the tree + /// @param max_index Maximum leaf index of the tree + /// @param indent Indentation of trace output + static Node* copy_node( + const Node* from, + std::vector* leaf_nodes = nullptr, + size_t* num_flushed = nullptr, + size_t min_index = 0, + size_t max_index = SIZE_MAX, + size_t indent = 0) + { + if (from == nullptr) + return nullptr; + + Node* r = make(from->hash); + r->size = from->size; + r->height = from->height; + r->dirty = from->dirty; + r->swapped = from->swapped; + r->left = copy_node( + from->left, + leaf_nodes, + num_flushed, + min_index, + max_index, + indent + 1); + r->right = copy_node( + from->right, + leaf_nodes, + num_flushed, + min_index, + max_index, + indent + 1); + if (leaf_nodes && r->size == 1 && !r->left && !r->right) + { + if (*num_flushed == 0) + leaf_nodes->push_back(r); + else + *num_flushed = *num_flushed - 1; + } + return r; + } + + /// @brief Checks invariant of a tree node + /// @note This indicates whether some basic properties of the tree + /// construction are violated. + bool invariant() + { + bool c1 = (left && right) || (!left && !right); + bool c2 = !left || !right || (size == left->size + right->size + 1); + bool cl = !left || left->invariant(); + bool cr = !right || right->invariant(); + bool ch = height <= sizeof(size) * 8; + bool r = c1 && c2 && cl && cr && ch; + return r; + } + + ~Node() + { + assert(invariant()); + // Potential future improvement: remove recursion and keep nodes for + // future insertions + delete (left); + delete (right); + } + + /// @brief Indicates whether a subtree is full + /// @note A subtree is full if the number of nodes under a tree is + /// 2**height-1. + bool is_full() const + { + size_t max_size = (1 << height) - 1; + assert(size <= max_size); + return size == max_size; + } + + /// @brief Updates the tree size and height of the subtree under a node + void update_sizes() + { + if (left && right) + { + size = left->size + right->size + 1; + height = std::max(left->height, right->height) + 1; + } + else + size = height = 1; + } + + void sort_subnodes() + { + if (left && right) + { + if (right->hash < left->hash) + { + // Swap left and right nodes + Node* temp = left; + left = right; + right = temp; + swapped = true; + } + } + } + + + /// @brief The Hash of the node + HashT hash; + + /// @brief The left child of the node + Node* left; + + /// @brief The right child of the node + Node* right; + + /// @brief The size of the subtree + size_t size; + + /// @brief The height of the subtree + uint8_t height; + + /// @brief Dirty flag for the hash + /// @note The @p hash is only correct if this flag is false, otherwise + /// it needs to be computed by calling hash() on the node. + bool dirty; + + bool swapped; + }; + + public: + /// @brief The type of hashes in the tree + typedef HashT Hash; + + /// @brief The type of paths in the tree + typedef PathT Path; + + /// @brief The type of the tree + typedef TreeT Tree; + + /// @brief Constructs an empty tree + TreeT() {} + + /// @brief Copies a tree + TreeT(const TreeT& other) + { + *this = other; + } + + /// @brief Moves a tree + /// @param other Tree to move + TreeT(TreeT&& other) : + leaf_nodes(std::move(other.leaf_nodes)), + uninserted_leaf_nodes(std::move(other.uninserted_leaf_nodes)), + _root(std::move(other._root)), + num_flushed(other.num_flushed), + insertion_stack(std::move(other.insertion_stack)), + hashing_stack(std::move(other.hashing_stack)), + walk_stack(std::move(other.walk_stack)) + {} + + /// @brief Deserialises a tree + /// @param bytes Byte buffer containing a serialised tree + TreeT(const std::vector& bytes) + { + deserialise(bytes); + } + + /// @brief Deserialises a tree + /// @param bytes Byte buffer containing a serialised tree + /// @param position Position of the first byte within @p bytes + TreeT(const std::vector& bytes, size_t& position) + { + deserialise(bytes, position); + } + + /// @brief Constructs a tree containing one root hash + /// @param root Root hash of the tree + TreeT(const Hash& root) + { + insert(root); + } + + /// @brief Deconstructor + ~TreeT() + { + delete (_root); + for (auto n : uninserted_leaf_nodes) + delete (n); + } + + /// @brief Invariant of the tree + bool invariant() + { + return _root ? _root->invariant() : true; + } + + /// @brief Inserts a hash into the tree + /// @param hash Hash to insert + void insert(const uint8_t* hash) + { + insert(Hash(hash)); + } + + /// @brief Inserts a hash into the tree + /// @param hash Hash to insert + void insert(const Hash& hash) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> insert " + << hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + uninserted_leaf_nodes.push_back(Node::make(hash)); + statistics.num_insert++; + } + + /// @brief Inserts multiple hashes into the tree + /// @param hashes Vector of hashes to insert + void insert(const std::vector& hashes) + { + for (auto hash : hashes) + insert(hash); + } + + /// @brief Inserts multiple hashes into the tree + /// @param hashes List of hashes to insert + void insert(const std::list& hashes) + { + for (auto hash : hashes) + insert(hash); + } + + size_t find_leaf_index(const merkle::HashT& target_hash) const + { + for (size_t i = 0; i < leaf_nodes.size(); i++) + { + if (leaf_nodes[i]->hash == target_hash) // Directly compare HashT objects now + return static_cast(i); + } + + return -1; // Return -1 if not found + } + + /// @brief Flush the tree to some leaf + /// @param index Leaf index to flush the tree to + /// @note This invalidates all indicies smaller than @p index and + /// no paths from them to the root can be extracted anymore. + void flush_to(size_t index) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> flush_to " << index << std::endl;); + statistics.num_flush++; + + if (index <= min_index()) + return; + + walk_to(index, false, [this](Node*& n, bool go_right) { + if (go_right && n->left) + { + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - conflate " + << n->left->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + if (n->left && n->left->dirty) + hash(n->left); + delete (n->left->left); + n->left->left = nullptr; + delete (n->left->right); + n->left->right = nullptr; + } + return true; + }); + + size_t num_newly_flushed = index - num_flushed; + leaf_nodes.erase( + leaf_nodes.begin(), leaf_nodes.begin() + num_newly_flushed); + num_flushed += num_newly_flushed; + } + + /// @brief Retracts a tree up to some leaf index + /// @param index Leaf index to retract the tree to + /// @note This invalidates all indicies greater than @p index and + /// no paths from them to the root can be extracted anymore. + void retract_to(size_t index) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> retract_to " << index << std::endl;); + statistics.num_retract++; + + if (max_index() < index) + return; + + if (index < min_index()) + throw std::runtime_error("leaf index out of bounds"); + + if (index >= num_flushed + leaf_nodes.size()) + { + size_t over = index - (num_flushed + leaf_nodes.size()) + 1; + while (uninserted_leaf_nodes.size() > over) + { + delete (uninserted_leaf_nodes.back()); + uninserted_leaf_nodes.pop_back(); + } + return; + } + + Node* new_leaf_node = + walk_to(index, true, [this](Node*& n, bool go_right) { + bool go_left = !go_right; + n->dirty = true; + if (go_left && n->right) + { + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - eliminate " + << n->right->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + bool is_root = n == _root; + + Node* old_left = n->left; + delete (n->right); + n->right = nullptr; + + *n = *old_left; + + old_left->left = old_left->right = nullptr; + delete (old_left); + old_left = nullptr; + + if (n->left && n->right) + n->dirty = true; + + if (is_root) + { + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - new root: " + << n->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + assert(_root == n); + } + + assert(n->invariant()); + + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - after elimination: " << std::endl + << to_string(TRACE_HASH_SIZE) << std::endl;); + return false; + } + else + return true; + }); + + // The leaf is now elsewhere, save the pointer. + leaf_nodes.at(index - num_flushed) = new_leaf_node; + + size_t num_retracted = num_leaves() - index - 1; + if (num_retracted < leaf_nodes.size()) + leaf_nodes.resize(leaf_nodes.size() - num_retracted); + else + leaf_nodes.clear(); + + assert(num_leaves() == index + 1); + } + + /// @brief Assigns a tree + /// @param other The tree to assign + /// @return The tree + Tree& operator=(const Tree& other) + { + leaf_nodes.clear(); + for (auto n : uninserted_leaf_nodes) + delete (n); + uninserted_leaf_nodes.clear(); + insertion_stack.clear(); + hashing_stack.clear(); + walk_stack.clear(); + + size_t to_skip = (other.num_flushed % 2 == 0) ? 0 : 1; + _root = Node::copy_node( + other._root, + &leaf_nodes, + &to_skip, + other.min_index(), + other.max_index()); + for (auto n : other.uninserted_leaf_nodes) + uninserted_leaf_nodes.push_back(Node::copy_node(n)); + num_flushed = other.num_flushed; + assert(min_index() == other.min_index()); + assert(max_index() == other.max_index()); + return *this; + } + + /// @brief Extracts the root hash of the tree + /// @return The root hash + const Hash& root() + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> root" << std::endl;); + statistics.num_root++; + compute_root(); + assert(_root && !_root->dirty); + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - root: " << _root->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + return _root->hash; + } + + /// @brief Extracts a past root hash + /// @param index The last leaf index to consider + /// @return The root hash + /// @note This extracts the root hash of the tree at a past state, when + /// @p index was the last, right-most leaf index in the tree. It is + /// equivalent to retracting the tree to @p index and then extracting the + /// root. + std::shared_ptr past_root(size_t index) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> past_root " << index << std::endl;); + statistics.num_past_root++; + + auto p = path(index); + auto result = std::make_shared(p->leaf()); + + MERKLECPP_TRACE( + MERKLECPP_TOUT << " - " << p->to_string(TRACE_HASH_SIZE) << std::endl; + MERKLECPP_TOUT << " - " << result->to_string(TRACE_HASH_SIZE) + << std::endl;); + + for (auto e : *p) + if (e.direction == Path::Direction::PATH_LEFT) + HASH_FUNCTION(e.hash, *result, *result); + + return result; + } + + /// @brief Walks along the path from the root of a tree to a leaf + /// @param index The leaf index to walk to + /// @param update Flag to enable re-computation of node fields (like + /// subtree size) while walking + /// @param f Function to call for each node on the path; the Boolean + /// indicates whether the current step is a right or left turn. + /// @return The final leaf node in the walk + inline Node* walk_to( + size_t index, bool update, const std::function&& f) + { + if (index < min_index() || max_index() < index) + throw std::runtime_error("invalid leaf index"); + + compute_root(); + + assert(index < _root->size); + + Node* cur = _root; + size_t it = 0; + if (_root->height > 1) + it = index << (sizeof(index) * 8 - _root->height + 1); + assert(walk_stack.empty()); + + for (uint8_t height = _root->height; height > 1;) + { + assert(cur->invariant()); + bool go_right = (it >> (8 * sizeof(it) - 1)) & 0x01; + if (cur->swapped) + go_right = !go_right; + if (update) + walk_stack.push_back(cur); + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - at " << cur->hash.to_string(TRACE_HASH_SIZE) + << " (" << cur->size << "/" << (unsigned)cur->height + << ")" + << " (" << (go_right ? "R" : "L") << ")" + << std::endl;); + if (cur->height == height) + { + if (!f(cur, go_right)) + continue; + cur = (go_right ? cur->right : cur->left); + } + it <<= 1; + height--; + } + + if (update) + while (!walk_stack.empty()) + { + walk_stack.back()->update_sizes(); + walk_stack.pop_back(); + } + + return cur; + } + + /// @brief Extracts the path from a leaf index to the root of the tree + /// @param index The leaf index of the path to extract + /// @return The path + std::shared_ptr path(size_t index) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> path from " << index << std::endl;); + statistics.num_paths++; + std::list elements; + + walk_to(index, false, [&elements](Node* n, bool go_right) { + typename Path::Element e; + e.hash = go_right ? n->left->hash : n->right->hash; + e.direction = go_right ? Path::PATH_LEFT : Path::PATH_RIGHT; + elements.push_front(std::move(e)); + return true; + }); + + return std::make_shared( + leaf_node(index)->hash, index, std::move(elements), max_index()); + } + + /// @brief Extracts a past path from a leaf index to the root of the tree + /// @param index The leaf index of the path to extract + /// @param as_of The maximum leaf index to consider + /// @return The past path + /// @note This extracts a path at a past state, when @p as_of was the last, + /// right-most leaf index in the tree. It is equivalent to retracting the + /// tree to @p as_of and then extracting the path of @p index. + std::shared_ptr past_path(size_t index, size_t as_of) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> past_path from " << index + << " as of " << as_of << std::endl;); + statistics.num_past_paths++; + + if ( + (index < min_index() || max_index() < index) || + (as_of < min_index() || max_index() < as_of) || index > as_of) + throw std::runtime_error("invalid leaf indices"); + + compute_root(); + + assert(index < _root->size && as_of < _root->size); + + // Walk down the tree toward `index` and `as_of` from the root. First to + // the node at which they fork (recorded in `root_to_fork`), then + // separately to `index` and `as_of`, recording their paths + // in `fork_to_index` and `fork_to_as_of`. + std::list root_to_fork, fork_to_index, + fork_to_as_of; + Node* fork_node = nullptr; + + Node *cur_i = _root, *cur_a = _root; + size_t it_i = 0, it_a = 0; + if (_root->height > 1) + { + it_i = index << (sizeof(index) * 8 - _root->height + 1); + it_a = as_of << (sizeof(index) * 8 - _root->height + 1); + } + + for (uint8_t height = _root->height; height > 1;) + { + assert(cur_i->invariant() && cur_a->invariant()); + bool go_right_i = (it_i >> (8 * sizeof(it_i) - 1)) & 0x01; + bool go_right_a = (it_a >> (8 * sizeof(it_a) - 1)) & 0x01; + + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - at " << (unsigned)height << ": " + << cur_i->hash.to_string(TRACE_HASH_SIZE) << " (" + << cur_i->size << "/" << (unsigned)cur_i->height + << "/" << (go_right_i ? "R" : "L") << ")" + << " / " << cur_a->hash.to_string(TRACE_HASH_SIZE) + << " (" << cur_a->size << "/" + << (unsigned)cur_a->height << "/" + << (go_right_a ? "R" : "L") << ")" << std::endl;); + + if (!fork_node && go_right_i != go_right_a) + { + assert(cur_i == cur_a); + assert(!go_right_i && go_right_a); + MERKLECPP_TRACE(MERKLECPP_TOUT + << " - split at " + << cur_i->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + fork_node = cur_i; + } + + if (!fork_node) + { + // Still on the path to the fork + assert(cur_i == cur_a); + if (cur_i->height == height) + { + if (go_right_i) + { + typename Path::Element e; + e.hash = go_right_i ? cur_i->left->hash : cur_i->right->hash; + e.direction = go_right_i ? Path::PATH_LEFT : Path::PATH_RIGHT; + root_to_fork.push_back(std::move(e)); + } + cur_i = cur_a = (go_right_i ? cur_i->right : cur_i->left); + } + } + else + { + // After the fork, record paths to `index` and `as_of`. + if (cur_i->height == height) + { + typename Path::Element e; + e.hash = go_right_i ? cur_i->left->hash : cur_i->right->hash; + e.direction = go_right_i ? Path::PATH_LEFT : Path::PATH_RIGHT; + fork_to_index.push_back(std::move(e)); + cur_i = (go_right_i ? cur_i->right : cur_i->left); + } + if (cur_a->height == height) + { + // The right path does not take into account anything to the right + // of `as_of`, as those nodes were inserted into the tree after + // `as_of`. + if (go_right_a) + { + typename Path::Element e; + e.hash = go_right_a ? cur_a->left->hash : cur_a->right->hash; + e.direction = go_right_a ? Path::PATH_LEFT : Path::PATH_RIGHT; + fork_to_as_of.push_back(std::move(e)); + } + cur_a = (go_right_a ? cur_a->right : cur_a->left); + } + } + + it_i <<= 1; + it_a <<= 1; + height--; + } + + MERKLECPP_TRACE({ + MERKLECPP_TOUT << " - root to split:"; + for (auto e : root_to_fork) + MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" + << e.direction << ")"; + MERKLECPP_TOUT << std::endl; + MERKLECPP_TOUT << " - split to index:"; + for (auto e : fork_to_index) + MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" + << e.direction << ")"; + MERKLECPP_TOUT << std::endl; + MERKLECPP_TOUT << " - split to as_of:"; + for (auto e : fork_to_as_of) + MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" + << e.direction << ")"; + MERKLECPP_TOUT << std::endl; + }); + + // Reconstruct the past path from the three path segments recorded. + std::list path; + + // The hashes along the path from the fork to `index` remain unchanged. + if (!fork_to_index.empty()) + fork_to_index.pop_front(); + for (auto it = fork_to_index.rbegin(); it != fork_to_index.rend(); it++) + path.push_back(std::move(*it)); + + if (fork_node) + { + // The final hash of the path from the fork to `as_of` needs to be + // computed because that path skipped past tree nodes younger than + // `as_of`. + Hash as_of_hash = cur_a->hash; + if (!fork_to_as_of.empty()) + fork_to_as_of.pop_front(); + for (auto it = fork_to_as_of.rbegin(); it != fork_to_as_of.rend(); it++) + HASH_FUNCTION(it->hash, as_of_hash, as_of_hash); + + MERKLECPP_TRACE({ + MERKLECPP_TOUT << " - as_of hash: " + << as_of_hash.to_string(TRACE_HASH_SIZE) << std::endl; + }); + + typename Path::Element e; + e.hash = as_of_hash; + e.direction = Path::PATH_RIGHT; + path.push_back(std::move(e)); + } + + // The hashes along the path from the fork (now with new fork hash) to the + // (past) root remains unchanged. + for (auto it = root_to_fork.rbegin(); it != root_to_fork.rend(); it++) + path.push_back(std::move(*it)); + + return std::make_shared( + leaf_node(index)->hash, index, std::move(path), as_of); + } + + /// @brief Serialises the tree + /// @param bytes The vector of bytes to serialise to + void serialise(std::vector& bytes) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> serialise " << std::endl;); + + serialise_uint64_t( + leaf_nodes.size() + uninserted_leaf_nodes.size(), bytes); + serialise_uint64_t(num_flushed, bytes); + for (auto& n : leaf_nodes) + n->hash.serialise(bytes); + for (auto& n : uninserted_leaf_nodes) + n->hash.serialise(bytes); + + if (!empty()) + { + // Find conflated/flushed nodes along the left edge of the tree. + + compute_root(); + + MERKLECPP_TRACE(MERKLECPP_TOUT << to_string(TRACE_HASH_SIZE) + << std::endl;); + + std::vector extras; + walk_to(min_index(), false, [&extras](Node*& n, bool go_right) { + if (go_right) + extras.push_back(n->left); + return true; + }); + + for (size_t i = extras.size() - 1; i != SIZE_MAX; i--) + extras.at(i)->hash.serialise(bytes); + } + } + + /// @brief Serialises a segment of the tree + /// @param from Smalles leaf index to include + /// @param to Greatest leaf index to include + /// @param bytes The vector of bytes to serialise to + void serialise(size_t from, size_t to, std::vector& bytes) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> serialise from " << from << " to " + << to << std::endl;); + + if ( + (from < min_index() || max_index() < from) || + (to < min_index() || max_index() < to) || from > to) + throw std::runtime_error("invalid leaf indices"); + + serialise_uint64_t(to - from + 1, bytes); + serialise_uint64_t(from, bytes); + for (size_t i = from; i <= to; i++) + leaf(i).serialise(bytes); + + if (!empty()) + { + // Find nodes to conflate/flush along the left edge of the tree. + + compute_root(); + + MERKLECPP_TRACE(MERKLECPP_TOUT << to_string(TRACE_HASH_SIZE) + << std::endl;); + + std::vector extras; + walk_to(from, false, [&extras](Node*& n, bool go_right) { + if (go_right) + extras.push_back(n->left); + return true; + }); + + for (size_t i = extras.size() - 1; i != SIZE_MAX; i--) + extras.at(i)->hash.serialise(bytes); + } + } + + /// @brief Deserialises a tree + /// @param bytes The vector of bytes to deserialise from + void deserialise(const std::vector& bytes) + { + size_t position = 0; + deserialise(bytes, position); + } + + /// @brief Deserialises a tree + /// @param bytes The vector of bytes to deserialise from + /// @param position Position of the first byte in @p bytes + void deserialise(const std::vector& bytes, size_t& position) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> deserialise " << std::endl;); + + delete (_root); + leaf_nodes.clear(); + for (auto n : uninserted_leaf_nodes) + delete (n); + uninserted_leaf_nodes.clear(); + insertion_stack.clear(); + hashing_stack.clear(); + walk_stack.clear(); + _root = nullptr; + + size_t num_leaf_nodes = deserialise_uint64_t(bytes, position); + num_flushed = deserialise_uint64_t(bytes, position); + + leaf_nodes.reserve(num_leaf_nodes); + for (size_t i = 0; i < num_leaf_nodes; i++) + { + Node* n = Node::make(bytes.data() + position); + position += HASH_SIZE; + leaf_nodes.push_back(n); + } + + std::vector level = leaf_nodes, next_level; + size_t it = num_flushed; + uint8_t level_no = 0; + while (it != 0 || level.size() > 1) + { + // Restore extra hashes on the left edge of the tree + if (it & 0x01) + { + Hash h(bytes, position); + MERKLECPP_TRACE(MERKLECPP_TOUT << "+";); + auto n = Node::make(h); + n->height = level_no + 1; + n->size = (1 << n->height) - 1; + assert(n->invariant()); + level.insert(level.begin(), n); + } + + MERKLECPP_TRACE(for (auto& n + : level) MERKLECPP_TOUT + << " " << n->hash.to_string(TRACE_HASH_SIZE); + MERKLECPP_TOUT << std::endl;); + + // Rebuild the level + for (size_t i = 0; i < level.size(); i += 2) + { + if (i + 1 >= level.size()) + next_level.push_back(level.at(i)); + else + next_level.push_back(Node::make(level.at(i), level.at(i + 1))); + } + + level.swap(next_level); + next_level.clear(); + + it >>= 1; + level_no++; + } + + assert(level.size() == 0 || level.size() == 1); + + if (level.size() == 1) + { + _root = level.at(0); + assert(_root->invariant()); + } + } + + /// @brief Operator to serialise the tree + operator std::vector() const + { + std::vector bytes; + serialise(bytes); + return bytes; + } + + /// @brief Operator to extract a leaf hash from the tree + /// @param index Leaf index of the leaf to extract + /// @return The leaf hash + const Hash& operator[](size_t index) const + { + return leaf(index); + } + + /// @brief Extract a leaf hash from the tree + /// @param index Leaf index of the leaf to extract + /// @return The leaf hash + const Hash& leaf(size_t index) const + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> leaf " << index << std::endl;); + if (index >= num_leaves()) + throw std::runtime_error("leaf index out of bounds"); + if (index - num_flushed >= leaf_nodes.size()) + return uninserted_leaf_nodes + .at(index - num_flushed - leaf_nodes.size()) + ->hash; + else + return leaf_nodes.at(index - num_flushed)->hash; + } + + /// @brief Number of leaves in the tree + /// @note This is the abstract number of leaves in the tree (including + /// flushed leaves), not the number of nodes in memory. + /// @return The number of leaves in the tree + size_t num_leaves() const + { + return num_flushed + leaf_nodes.size() + uninserted_leaf_nodes.size(); + } + + /// @brief Minimum leaf index + /// @note The smallest leaf index for which it is safe to extract roots and + /// paths. + /// @return The minumum leaf index + size_t min_index() const + { + return num_flushed; + } + + /// @brief Maximum leaf index + /// @note The greatest leaf index for which it is safe to extract roots and + /// paths. + /// @return The maximum leaf index + size_t max_index() const + { + auto n = num_leaves(); + return n == 0 ? 0 : n - 1; + } + + /// @brief Indicates whether the tree is empty + /// @return Boolean that indicates whether the tree is empty + bool empty() const + { + return num_leaves() == 0; + } + + /// @brief Computes the size of the tree + /// @note This is the number of nodes in the tree, including leaves and + /// internal nodes. + /// @return The size of the tree + size_t size() + { + if (!uninserted_leaf_nodes.empty()) + insert_leaves(); + return _root ? _root->size : 0; + } + + /// @brief Computes the minumal number of bytes required to serialise the + /// tree + /// @return The number of bytes required to serialise the tree + size_t serialised_size() + { + size_t num_extras = 0; + + if (!empty()) + { + walk_to(min_index(), false, [&num_extras](Node*&, bool go_right) { + if (go_right) + num_extras++; + return true; + }); + } + + return sizeof(leaf_nodes.size()) + sizeof(num_flushed) + + leaf_nodes.size() * sizeof(Hash) + num_extras * sizeof(Hash); + } + + /// @brief The number of bytes required to serialise a segment of the tree + /// @param from The smallest leaf index to include + /// @param to The greatest leaf index to include + /// @return The number of bytes required to serialise the tree segment + size_t serialised_size(size_t from, size_t to) + { + size_t num_extras = 0; + walk_to(from, false, [&num_extras](Node*&, bool go_right) { + if (go_right) + num_extras++; + return true; + }); + + return sizeof(leaf_nodes.size()) + sizeof(num_flushed) + + (to - from + 1) * sizeof(Hash) + num_extras * sizeof(Hash); + } + + /// @brief Structure to hold statistical information + mutable struct Statistics + { + /// @brief The number of hashes taken by the tree via hash() + size_t num_hash = 0; + + /// @brief The number of insert() opertations performed on the tree + size_t num_insert = 0; + + /// @brief The number of root() opertations performed on the tree + size_t num_root = 0; + + /// @brief The number of past_root() opertations performed on the tree + size_t num_past_root = 0; + + /// @brief The number of flush_to() opertations performed on the tree + size_t num_flush = 0; + + /// @brief The number of retract_to() opertations performed on the tree + size_t num_retract = 0; + + /// @brief The number of paths extracted from the tree via path() + size_t num_paths = 0; + + /// @brief The number of past paths extracted from the tree via + /// past_path() + size_t num_past_paths = 0; + + /// @brief String representation of the statistics + std::string to_string() const + { + std::stringstream stream; + stream << "num_insert=" << num_insert << " num_hash=" << num_hash + << " num_root=" << num_root << " num_retract=" << num_retract + << " num_flush=" << num_flush << " num_paths=" << num_paths + << " num_past_paths=" << num_past_paths; + return stream.str(); + } + } + /// @brief Statistics + statistics; + + /// @brief Prints an ASCII representation of the tree to a stream + /// @param num_bytes The number of bytes of each node hash to print + /// @return A string representing the tree + std::string to_string(size_t num_bytes = HASH_SIZE) const + { + static const std::string dirty_hash(2 * num_bytes, '?'); + std::stringstream stream; + std::vector level, next_level; + + if (num_leaves() == 0) + { + stream << "" << std::endl; + return stream.str(); + } + + if (!_root) + { + stream << "No root." << std::endl; + } + else + { + size_t level_no = 0; + level.push_back(_root); + while (!level.empty()) + { + stream << level_no++ << ": "; + for (auto n : level) + { + stream << (n->dirty ? dirty_hash : n->hash.to_string(num_bytes)); + stream << "(" << n->size << "," << (unsigned)n->height << ")"; + if (n->left) + next_level.push_back(n->left); + if (n->right) + next_level.push_back(n->right); + stream << " "; + } + stream << std::endl << std::flush; + std::swap(level, next_level); + next_level.clear(); + } + } + + stream << "+: " + << "leaves=" << leaf_nodes.size() << ", " + << "uninserted leaves=" << uninserted_leaf_nodes.size() << ", " + << "flushed=" << num_flushed << std::endl; + stream << "S: " << statistics.to_string() << std::endl; + + return stream.str(); + } + + protected: + /// @brief Vector of leaf nodes current in the tree + std::vector leaf_nodes; + + /// @brief Vector of leaf nodes to be inserted in the tree + /// @note These nodes are conceptually inserted, but no Node objects have + /// been inserted for them yet. + std::vector uninserted_leaf_nodes; + + /// @brief Number of flushed nodes + size_t num_flushed = 0; + + /// @brief Current root node of the tree + Node* _root = nullptr; + + private: + /// @brief The structure of elements on the insertion stack + typedef struct + { + /// @brief The tree node to insert + Node* n; + /// @brief Flag to indicate whether @p n should be inserted into the + /// left or the right subtree of the current position in the tree. + bool left; + } InsertionStackElement; + + /// @brief The insertion stack + /// @note To avoid actual recursion, this holds the stack/continuation for + /// tree node insertion. + mutable std::vector insertion_stack; + + /// @brief The hashing stack + /// @note To avoid actual recursion, this holds the stack/continuation for + /// hashing (parts of the) nodes of a tree. + mutable std::vector hashing_stack; + + /// @brief The walk stack + /// @note To avoid actual recursion, this holds the stack/continuation for + /// walking down the tree from the root to a leaf. + mutable std::vector walk_stack; + + protected: + /// @brief Finds the leaf node corresponding to @p index + /// @param index The leaf node index + const Node* leaf_node(size_t index) const + { + MERKLECPP_TRACE(MERKLECPP_TOUT << "> leaf_node " << index << std::endl;); + if (index >= num_leaves()) + throw std::runtime_error("leaf index out of bounds"); + if (index - num_flushed >= leaf_nodes.size()) + return uninserted_leaf_nodes.at( + index - num_flushed - leaf_nodes.size()); + else + return leaf_nodes.at(index - num_flushed); + } + + /// @brief Computes the hash of a tree node + /// @param n The tree node + /// @param indent Indentation of trace output + /// @note This recurses down the child nodes to compute intermediate + /// hashes, if required. + void hash(Node* n, size_t indent = 2) const + { +#ifndef MERKLECPP_WITH_TRACE + (void)indent; +#endif + + assert(hashing_stack.empty()); + hashing_stack.reserve(n->height); + hashing_stack.push_back(n); + + while (!hashing_stack.empty()) + { + n = hashing_stack.back(); + assert((n->left && n->right) || (!n->left && !n->right)); + + if (n->left && n->left->dirty) + hashing_stack.push_back(n->left); + else if (n->right && n->right->dirty) + hashing_stack.push_back(n->right); + else + { + assert(n->left && n->right); + n->sort_subnodes(); + HASH_FUNCTION(n->left->hash, n->right->hash, n->hash); + statistics.num_hash++; + MERKLECPP_TRACE( + MERKLECPP_TOUT << std::string(indent, ' ') << "+ h(" + << n->left->hash.to_string(TRACE_HASH_SIZE) << ", " + << n->right->hash.to_string(TRACE_HASH_SIZE) + << ") == " << n->hash.to_string(TRACE_HASH_SIZE) + << " (" << n->size << "/" << (unsigned)n->height + << ")" << std::endl); + n->dirty = false; + hashing_stack.pop_back(); + } + } + } + + /// @brief Computes the root hash of the tree + void compute_root() + { + insert_leaves(true); + if (num_leaves() == 0) + throw std::runtime_error("empty tree does not have a root"); + assert(_root); + assert(_root->invariant()); + if (_root->dirty) + { + hash(_root); + assert(_root && !_root->dirty); + } + } + + /// @brief Inserts one new leaf into the insertion stack + /// @param n Current root node + /// @param new_leaf New leaf node to insert + /// @note This adds one new Node to the insertion stack/continuation for + /// efficient processing by process_insertion_stack() later. + void continue_insertion_stack(Node* n, Node* new_leaf) + { + while (true) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << " @ " + << n->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + assert(n->invariant()); + + if (n->is_full()) + { + Node* result = Node::make(n, new_leaf); + insertion_stack.push_back(InsertionStackElement()); + insertion_stack.back().n = result; + return; + } + else + { + assert(n->left && n->right); + insertion_stack.push_back(InsertionStackElement()); + InsertionStackElement& se = insertion_stack.back(); + se.n = n; + n->dirty = true; + if (!n->left->is_full()) + { + se.left = true; + n = n->left; + } + else + { + se.left = false; + n = n->right; + } + } + } + } + + /// @brief Processes the insertion stack/continuation + /// @param complete Indicates whether one element or the entire stack + /// should be processed + Node* process_insertion_stack(bool complete = true) + { + MERKLECPP_TRACE({ + std::string nodes; + for (size_t i = 0; i < insertion_stack.size(); i++) + nodes += + " " + insertion_stack.at(i).n->hash.to_string(TRACE_HASH_SIZE); + MERKLECPP_TOUT << " X " << (complete ? "complete" : "continue") << ":" + << nodes << std::endl; + }); + + Node* result = insertion_stack.back().n; + insertion_stack.pop_back(); + + assert(result->dirty); + result->update_sizes(); + + while (!insertion_stack.empty()) + { + InsertionStackElement& top = insertion_stack.back(); + Node* n = top.n; + bool left = top.left; + insertion_stack.pop_back(); + + if (left) + n->left = result; + else + n->right = result; + n->dirty = true; + n->update_sizes(); + + result = n; + + if (!complete && !result->is_full()) + { + MERKLECPP_TRACE(MERKLECPP_TOUT + << " X save " + << result->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + return result; + } + } + + assert(result->invariant()); + + return result; + } + + /// @brief Inserts a new leaf into the tree + /// @param root Current root node + /// @param n New leaf node to insert + void insert_leaf(Node*& root, Node* n) + { + MERKLECPP_TRACE(MERKLECPP_TOUT << " - insert_leaf " + << n->hash.to_string(TRACE_HASH_SIZE) + << std::endl;); + leaf_nodes.push_back(n); + if (insertion_stack.empty() && !root) + root = n; + else + { + continue_insertion_stack(root, n); + root = process_insertion_stack(false); + } + } + + /// @brief Inserts multiple new leaves into the tree + /// @param complete Indicates whether the insertion stack should be + /// processed to completion after insertion + void insert_leaves(bool complete = false) + { + if (!uninserted_leaf_nodes.empty()) + { + MERKLECPP_TRACE(MERKLECPP_TOUT + << "* insert_leaves " << leaf_nodes.size() << " +" + << uninserted_leaf_nodes.size() << std::endl;); + + // Sort the leaves based on their hash values before inserting + std::sort(uninserted_leaf_nodes.begin(), uninserted_leaf_nodes.end(), + [](const Node* a, const Node* b) { return a->hash < b->hash; }); + + for (auto& n : uninserted_leaf_nodes) + insert_leaf(_root, n); + uninserted_leaf_nodes.clear(); + } + if (complete && !insertion_stack.empty()) + _root = process_insertion_stack(); + } + }; + + // clang-format off + /// @brief SHA256 compression function for tree node hashes + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + /// @details This function is the compression function of SHA256, which, for + /// the special case of hashing two hashes, is more efficient than a full + /// SHA256 while providing similar guarantees. + static inline void sha256_compress(const HashT<32> &l, const HashT<32> &r, HashT<32> &out) { + static const uint32_t constants[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + uint8_t block[32 * 2]; + memcpy(&block[0], l.bytes, 32); + memcpy(&block[32], r.bytes, 32); + + static const uint32_t s[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; + + uint32_t cws[64] = {0}; + + for (int i=0; i < 16; i++) + cws[i] = convert_endianness(((int32_t *)block)[i]); + + for (int i = 16; i < 64; i++) { + uint32_t t16 = cws[i - 16]; + uint32_t t15 = cws[i - 15]; + uint32_t t7 = cws[i - 7]; + uint32_t t2 = cws[i - 2]; + uint32_t s1 = (t2 >> 17 | t2 << 15) ^ ((t2 >> 19 | t2 << 13) ^ t2 >> 10); + uint32_t s0 = (t15 >> 7 | t15 << 25) ^ ((t15 >> 18 | t15 << 14) ^ t15 >> 3); + cws[i] = (s1 + t7 + s0 + t16); + } + + uint32_t h[8]; + for (int i=0; i < 8; i++) + h[i] = s[i]; + + for (int i=0; i < 64; i++) { + uint32_t a0 = h[0], b0 = h[1], c0 = h[2], d0 = h[3], e0 = h[4], f0 = h[5], g0 = h[6], h03 = h[7]; + uint32_t w = cws[i]; + uint32_t t1 = h03 + ((e0 >> 6 | e0 << 26) ^ ((e0 >> 11 | e0 << 21) ^ (e0 >> 25 | e0 << 7))) + ((e0 & f0) ^ (~e0 & g0)) + constants[i] + w; + uint32_t t2 = ((a0 >> 2 | a0 << 30) ^ ((a0 >> 13 | a0 << 19) ^ (a0 >> 22 | a0 << 10))) + ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0))); + h[0] = t1 + t2; + h[1] = a0; + h[2] = b0; + h[3] = c0; + h[4] = d0 + t1; + h[5] = e0; + h[6] = f0; + h[7] = g0; + } + + for (int i=0; i < 8; i++) + ((uint32_t*)out.bytes)[i] = convert_endianness(s[i] + h[i]); + } + // clang-format on + +#ifdef HAVE_OPENSSL + /// @brief OpenSSL SHA256 + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + /// @note Some versions of OpenSSL may not provide SHA256_Transform. + static inline void sha256_openssl( + const merkle::HashT<32>& l, + const merkle::HashT<32>& r, + merkle::HashT<32>& out) + { + uint8_t block[32 * 2]; + memcpy(&block[0], l.bytes, 32); + memcpy(&block[32], r.bytes, 32); + + const EVP_MD* md = EVP_sha256(); + int rc = + EVP_Digest(&block[0], sizeof(block), out.bytes, nullptr, md, nullptr); + if (rc != 1) + { + throw std::runtime_error("EVP_Digest failed: " + std::to_string(rc)); + } + } +#endif + +#ifdef HAVE_MBEDTLS + /// @brief mbedTLS SHA256 compression function + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + /// @note Technically, mbedtls_internal_sha256_process is marked for internal + /// use only. + static inline void sha256_compress_mbedtls( + const HashT<32>& l, const HashT<32>& r, HashT<32>& out) + { + unsigned char block[32 * 2]; + memcpy(&block[0], l.bytes, 32); + memcpy(&block[32], r.bytes, 32); + + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts_ret(&ctx, false); + mbedtls_internal_sha256_process(&ctx, &block[0]); + + for (int i = 0; i < 8; i++) + ((uint32_t*)out.bytes)[i] = htobe32(ctx.state[i]); + } + + /// @brief mbedTLS SHA256 + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + static inline void sha256_mbedtls( + const merkle::HashT<32>& l, + const merkle::HashT<32>& r, + merkle::HashT<32>& out) + { + uint8_t block[32 * 2]; + memcpy(&block[0], l.bytes, 32); + memcpy(&block[32], r.bytes, 32); + mbedtls_sha256_ret(block, sizeof(block), out.bytes, false); + } +#endif + + /// @brief Type of hashes in the default tree type + typedef HashT<32> Hash; + + /// @brief Type of paths in the default tree type + typedef PathT<32, sha256_compress> Path; + + /// @brief Default tree with default hash size and function + typedef TreeT<32, sha256_compress> Tree; +}; From 7fb7ee58a35808b1638ff9da97dcc4ba85886abf Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 12 Oct 2023 12:25:11 +1100 Subject: [PATCH 24/97] merkle tree from batching rewards --- src/blockchain_db/sqlite/db_sqlite.cpp | 3 +- src/bls/bls_aggregator.cpp | 81 +++++++++++++++++++++++++ src/bls/bls_aggregator.h | 7 +++ src/bls/bls_signer.cpp | 16 ----- src/bls/bls_signer.h | 2 - src/cryptonote_core/CMakeLists.txt | 1 + src/cryptonote_core/cryptonote_core.cpp | 41 ++++++++++++- src/cryptonote_core/cryptonote_core.h | 1 + src/merkle/merkle_tree_creator.cpp | 4 ++ src/merkle/merkle_tree_creator.hpp | 1 + 10 files changed, 135 insertions(+), 22 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index 0ebb206170..eab0ea6135 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -383,8 +383,7 @@ uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) { return static_cast(earnings.value_or(0) / 1000); } -std::pair, std::vector> -BlockchainSQLite::get_all_accrued_earnings() { +std::pair, std::vector> BlockchainSQLite::get_all_accrued_earnings() { log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__); std::pair, std::vector> result; diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 461866fec6..852e9a6f86 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -109,6 +109,87 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) return aggregateResponse{non_signers, sig_str}; }; +aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& our_merkle_root) { + const std::array hash = BLSSigner::hash(our_merkle_root); + std::mutex signers_mutex, connection_mutex; + std::condition_variable cv; + size_t active_connections = 0; + const size_t MAX_CONNECTIONS = 900; + bls::Signature aggSig; + aggSig.clear(); + + std::vector signers; + + // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda + auto it = service_node_list.get_first_pubkey_iterator(); + auto end_it = service_node_list.get_end_pubkey_iterator(); + crypto::x25519_public_key x_pkey{0}; + uint32_t ip; + uint16_t port; + int64_t signers_index = 0; + while (it != end_it) { + service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { + x_pkey = proof.pubkey_x25519; + ip = proof.proof->public_ip; + port = proof.proof->qnet_port; + }); + //{ + //std::unique_lock connection_lock(connection_mutex); + //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + //} + // TODO sean epee is alway little, this will not work on big endian host + boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); + oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + + { + std::lock_guard connection_lock(connection_mutex); + ++active_connections; + } + auto conn = omq->connect_remote( + addr, + [](oxenmq::ConnectionID c) { + // Successfully connected + //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); + }, + [](oxenmq::ConnectionID c, std::string_view err) { + // Failed to connect + //oxen::log::debug(logcat, "Failed to connect {}", err); + }, + oxenmq::AuthLevel::basic); + omq->request( + conn, + "bls.rewards_merkle", + [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn, &our_merkle_root](bool success, std::vector data) { + oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); + oxen::log::debug( logcat, "bls signature response received"); + if (success && data[0] == our_merkle_root) { + bls::Signature external_signature; + external_signature.setStr(data[1]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + std::lock_guard connection_lock(connection_mutex); + oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }); + it = service_node_list.get_next_pubkey_iterator(it); + signers_index++; + } + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { + oxen::log::info(logcat, "TODO sean remove this: {}", active_connections); + return active_connections == 0; + }); + const auto non_signers = findNonSigners(signers); + const auto my_signature = bls_signer->signHash(hash); + aggSig.add(my_signature); + const auto sig_str = bls_utils::SignatureToHex(aggSig); + return aggregateMerkleResponse{our_merkle_root, non_signers, sig_str}; +}; + std::vector BLSAggregator::findNonSigners(const std::vector& indices) { std::vector nonSignerIndices = {}; for (int64_t i = 0; i < static_cast(service_node_list.get_service_node_count()); ++i) { diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 60f4b2ddc8..02f247d51e 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -26,6 +26,12 @@ struct aggregateResponse { std::string signature; }; +struct aggregateMerkleResponse { + std::string merkle_root; + std::vector non_signers; + std::string signature; +}; + class BLSAggregator { private: std::shared_ptr bls_signer; @@ -37,6 +43,7 @@ class BLSAggregator { std::string aggregatePubkeyHex(); aggregateResponse aggregateSignatures(const std::string& message); + aggregateMerkleResponse aggregateMerkleRewards(const std::string& our_merkle_root); std::vector findNonSigners(const std::vector& indices); diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 01dacf30a7..af2effc2e1 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -35,22 +35,6 @@ void BLSSigner::initCurve() { blsSetGeneratorOfPublicKey(&publicKey); } -void BLSSigner::initOMQ(std::shared_ptr omq) { - - omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) - .add_request_command("signature_request", [&](oxenmq::Message& m) { - oxen::log::debug(logcat, "Received omq signature request"); - if (m.data.size() != 1) - m.send_reply( - "400", - "Bad request: BLS commands must have only one data part " - "(received " + - std::to_string(m.data.size()) + ")"); - const auto h = hash(std::string(m.data[0])); - m.send_reply(signHash(h).getStr()); - }); -} - bls::Signature BLSSigner::signHash(const std::array& hash) { bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 3f6f7dcfeb..33f2d31aed 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -28,8 +28,6 @@ class BLSSigner { BLSSigner(bls::SecretKey _secretKey); ~BLSSigner(); - void initOMQ(std::shared_ptr omq); - bls::Signature signHash(const std::array& hash); std::string proofOfPossession(); std::string getPublicKeyHex(); diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 2f7a7a8716..53a68ec2fc 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -55,6 +55,7 @@ target_link_libraries(cryptonote_core SQLite::SQLite3 oxen_bls l2_tracker + merkle ethyl PRIVATE Boost::program_options diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 1ca1e0a9f6..8d20c99db0 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -74,6 +74,7 @@ extern "C" { #include "ringct/rctTypes.h" #include "uptime_proof.h" #include "version.h" +#include "merkle/merkle_tree_creator.hpp" DISABLE_VS_WARNINGS(4355) @@ -1076,7 +1077,34 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { m_quorumnet_state = quorumnet_new(*this); - m_bls_signer->initOMQ(m_omq); + m_omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) + .add_request_command("signature_request", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq signature request"); + if (m.data.size() != 1) + m.send_reply( + "400", + "Bad request: BLS commands must have only one data part " + "(received " + + std::to_string(m.data.size()) + ")"); + const auto h = m_bls_signer->hash(std::string(m.data[0])); + m.send_reply(m_bls_signer->signHash(h).getStr()); + }) + .add_request_command("rewards_merkle", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq signature request"); + if (m.data.size() != 0) + m.send_reply( + "400", + "Bad request: BLS rewards merkle command must have no data parts " + "(received " + + std::to_string(m.data.size()) + ")"); + auto [addresses, amounts] = get_blockchain_storage().sqlite_db()->get_all_accrued_earnings(); + MerkleTreeCreator rewards_merkle_tree = {}; + for (size_t i = 0; i < addresses.size(); i++) + rewards_merkle_tree.addRewardsLeaf(addresses[i], amounts[i]); + const auto rewards_merkle_root = rewards_merkle_tree.getRoot(); + const auto h = m_bls_signer->hash(rewards_merkle_root); + m.send_reply(rewards_merkle_root, m_bls_signer->signHash(h).getStr()); + }); } quorumnet_init(*this, m_quorumnet_state); @@ -2675,6 +2703,7 @@ core::get_service_node_blacklisted_key_images() const { return m_service_node_list.get_blacklisted_key_images(); } //----------------------------------------------------------------------------------------------- +//TODO sean this whole function needs to disappear before release, otherwise people can sign arbitrary messages aggregateResponse core::bls_request() const { //TODO sean remove this, just generating random string const auto length = 20; @@ -2689,7 +2718,15 @@ aggregateResponse core::bls_request() const { } const auto resp = m_bls_aggregator->aggregateSignatures(randomString); - oxen::log::info(logcat, "TODO sean remove this: {}", "aaaaaaaaa"); + return resp; +} +//----------------------------------------------------------------------------------------------- +aggregateMerkleResponse core::aggregate_merkle_rewards() { + auto [addresses, amounts] = m_blockchain_storage.sqlite_db()->get_all_accrued_earnings(); + MerkleTreeCreator rewards_merkle_tree = {}; + for (size_t i = 0; i < addresses.size(); i++) + rewards_merkle_tree.addRewardsLeaf(addresses[i], amounts[i]); + const auto resp = m_bls_aggregator->aggregateMerkleRewards(rewards_merkle_tree.getRoot()); return resp; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 117ca29c33..71c1340fda 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -952,6 +952,7 @@ class core : public i_miner_handler { get_service_node_blacklisted_key_images() const; aggregateResponse bls_request() const; + aggregateMerkleResponse aggregate_merkle_rewards(); /** * @brief get a snapshot of the service node list state at the time of the call. diff --git a/src/merkle/merkle_tree_creator.cpp b/src/merkle/merkle_tree_creator.cpp index 2339d3c3e1..cd099bbc97 100644 --- a/src/merkle/merkle_tree_creator.cpp +++ b/src/merkle/merkle_tree_creator.cpp @@ -12,6 +12,10 @@ void MerkleTreeCreator::addLeaf(const std::string& input) { tree.insert(createMerkleKeccakHash(input)); } +void MerkleTreeCreator::addRewardsLeaf(const std::string& address, const uint64_t balance) { + addLeaf(abiEncode(address, balance)); +} + void MerkleTreeCreator::addLeaves(const std::map& data) { for (const auto& [address, balance] : data) { std::string combined = abiEncode(address, balance); diff --git a/src/merkle/merkle_tree_creator.hpp b/src/merkle/merkle_tree_creator.hpp index 44a371e3ac..52ee902af3 100644 --- a/src/merkle/merkle_tree_creator.hpp +++ b/src/merkle/merkle_tree_creator.hpp @@ -11,6 +11,7 @@ class MerkleTreeCreator { MerkleTreeCreator(); void addLeaf(const std::string& input); + void addRewardsLeaf(const std::string& address, const uint64_t balance); void addLeaves(const std::map& data); merkle::Tree::Hash createMerkleKeccakHash(const std::string& input); From e6f2aad089c23826130f7b1e8a3013ebaa332f77 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 13 Oct 2023 11:41:01 +1100 Subject: [PATCH 25/97] rpc request for merkle tree --- src/bls/bls_aggregator.cpp | 40 ++++++++++------------- src/cryptonote_core/cryptonote_core.cpp | 3 +- src/merkle/merkle_tree_creator.cpp | 3 ++ src/rpc/core_rpc_server.cpp | 13 +++++--- src/rpc/core_rpc_server.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 23 ++++++++++++- utils/local-devnet/commands/blsrequest.py | 2 +- 7 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 852e9a6f86..116e73910b 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -77,7 +77,6 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) conn, "bls.signature_request", [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn](bool success, std::vector data) { - oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); oxen::log::debug( logcat, "bls signature response received"); if (success) { bls::Signature external_signature; @@ -87,7 +86,6 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) signers.push_back(signers_index); } std::lock_guard connection_lock(connection_mutex); - oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); --active_connections; cv.notify_all(); //omq->disconnect(c); @@ -99,7 +97,6 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) } std::unique_lock connection_lock(connection_mutex); cv.wait(connection_lock, [&active_connections] { - oxen::log::info(logcat, "TODO sean remove this: {}", active_connections); return active_connections == 0; }); const auto non_signers = findNonSigners(signers); @@ -156,31 +153,28 @@ aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& //oxen::log::debug(logcat, "Failed to connect {}", err); }, oxenmq::AuthLevel::basic); - omq->request( - conn, - "bls.rewards_merkle", - [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn, &our_merkle_root](bool success, std::vector data) { - oxen::log::info(logcat, "TODO sean remove this: successuflly response {}", signers_index); - oxen::log::debug( logcat, "bls signature response received"); - if (success && data[0] == our_merkle_root) { - bls::Signature external_signature; - external_signature.setStr(data[1]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - std::lock_guard connection_lock(connection_mutex); - oxen::log::info(logcat, "TODO sean remove this: decrementing active connections"); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }); + omq->request( + conn, + "bls.rewards_merkle", + [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn, &our_merkle_root](bool success, std::vector data) { + oxen::log::debug( logcat, "bls signature response received"); + if (success && data[0] == our_merkle_root) { + bls::Signature external_signature; + external_signature.setStr(data[1]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }); it = service_node_list.get_next_pubkey_iterator(it); signers_index++; } std::unique_lock connection_lock(connection_mutex); cv.wait(connection_lock, [&active_connections] { - oxen::log::info(logcat, "TODO sean remove this: {}", active_connections); return active_connections == 0; }); const auto non_signers = findNonSigners(signers); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 8d20c99db0..36071a44ca 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2724,8 +2724,9 @@ aggregateResponse core::bls_request() const { aggregateMerkleResponse core::aggregate_merkle_rewards() { auto [addresses, amounts] = m_blockchain_storage.sqlite_db()->get_all_accrued_earnings(); MerkleTreeCreator rewards_merkle_tree = {}; - for (size_t i = 0; i < addresses.size(); i++) + for (size_t i = 0; i < addresses.size(); i++) { rewards_merkle_tree.addRewardsLeaf(addresses[i], amounts[i]); + } const auto resp = m_bls_aggregator->aggregateMerkleRewards(rewards_merkle_tree.getRoot()); return resp; } diff --git a/src/merkle/merkle_tree_creator.cpp b/src/merkle/merkle_tree_creator.cpp index cd099bbc97..0152ee3e99 100644 --- a/src/merkle/merkle_tree_creator.cpp +++ b/src/merkle/merkle_tree_creator.cpp @@ -1,11 +1,14 @@ #include "merkle_tree_creator.hpp" #include "iostream" #include +#include "logging/oxen_logger.h" extern "C" { #include "crypto/keccak.h" } +static auto logcat = oxen::log::Cat("merkle_tree_creator"); + MerkleTreeCreator::MerkleTreeCreator() {} void MerkleTreeCreator::addLeaf(const std::string& input) { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 3d280f664b..152e9b4eb0 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2525,16 +2525,21 @@ void core_rpc_server::invoke( //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke( BLS_REQUEST& bls_request, rpc_context context) { const aggregateResponse bls_signature_response = m_core.bls_request(); - oxen::log::info(logcat, "TODO sean remove this: {}", "ddddddddd"); - oxen::log::info(logcat, "TODO sean remove this: {}", bls_signature_response.signature); - for (auto& x: bls_signature_response.non_signers) - oxen::log::info(logcat, "TODO sean remove this: {}", x); bls_request.response["status"] = STATUS_OK; bls_request.response["signature"] = bls_signature_response.signature; bls_request.response["non_signers"] = bls_signature_response.non_signers; return; } //------------------------------------------------------------------------------------------------------------------------------ +void core_rpc_server::invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context context) { + const aggregateMerkleResponse bls_merkle_signature_response = m_core.aggregate_merkle_rewards(); + bls_merkle_request.response["status"] = STATUS_OK; + bls_merkle_request.response["merkle_root"] = bls_merkle_signature_response.merkle_root; + bls_merkle_request.response["signature"] = bls_merkle_signature_response.signature; + bls_merkle_request.response["non_signers"] = bls_merkle_signature_response.non_signers; + return; +} +//------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_SERVICE_KEYS& get_service_keys, rpc_context context) { const auto& keys = m_core.get_service_keys(); if (keys.pub) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 02dab1058e..384454ff54 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -184,6 +184,7 @@ class core_rpc_server { GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES& get_service_node_blacklisted_key_images, rpc_context context); void invoke(BLS_REQUEST& bls_request, rpc_context context); + void invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 6d3d691ece..6d4843ebf1 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2291,12 +2291,32 @@ struct GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS { /// /// Inputs: None /// -/// Outputs: None +/// Outputs: /// +/// - `status` -- generic RPC error code; "OK" means the request was successful. +/// - `merkle_root` -- The Root that has been signed by the network +/// - `signature` -- BLS signature of the merkle root +/// - `non_signers` -- array of indices of the nodes that did not sign struct BLS_REQUEST : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("bls_request"); } }; +/// RPC: bls request +/// +/// Sends a request out for all nodes to sign a BLS signature of the merkle root +/// +/// Inputs: None +/// +/// Outputs: +/// +/// - `status` -- generic RPC error code; "OK" means the request was successful. +/// - `merkle_root` -- The Root that has been signed by the network +/// - `signature` -- BLS signature of the merkle root +/// - `non_signers` -- array of indices of the nodes that did not sign +struct BLS_MERKLE_REQUEST : PUBLIC, NO_ARGS { + static constexpr auto names() { return NAMES("bls_merkle_request"); } +}; + /// RPC: blockchain/get_checkpoints /// /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to @@ -2690,6 +2710,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, BLS_REQUEST, + BLS_MERKLE_REQUEST, GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, GET_SERVICE_NODE_STATUS, diff --git a/utils/local-devnet/commands/blsrequest.py b/utils/local-devnet/commands/blsrequest.py index 9a4bf5fcb2..91e9c5738c 100755 --- a/utils/local-devnet/commands/blsrequest.py +++ b/utils/local-devnet/commands/blsrequest.py @@ -24,7 +24,7 @@ def instruct_daemon(method, params): params = {} -answer = instruct_daemon('bls_request', params) +answer = instruct_daemon('bls_merkle_request', params) # print(json.dumps(answer['result']['block_header']['timestamp'], indent=4, sort_keys=True)) From 35cfe373f899f783d253ce2ff8b94fa2b09bd8e7 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 13 Oct 2023 11:55:25 +1100 Subject: [PATCH 26/97] update to latest ethyl --- external/ethyl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ethyl b/external/ethyl index 3f44f14881..724e2a87b4 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit 3f44f148814c6d7857aedb90d57b4eabd5348ae3 +Subproject commit 724e2a87b4e58a4b027386341bdf99806af1bd3b From 0f3ea136010ee59abba95bd199ec2e90abcac329 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Tue, 17 Oct 2023 09:18:37 +1100 Subject: [PATCH 27/97] cli command to get link to claim rewards --- src/daemon/command_parser_executor.cpp | 4 ++++ src/daemon/command_parser_executor.h | 2 ++ src/daemon/command_server.cpp | 5 +++++ src/daemon/rpc_command_executor.cpp | 24 ++++++++++++++++++++++++ src/daemon/rpc_command_executor.h | 2 ++ 5 files changed, 37 insertions(+) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index b00d874282..0a5f81ad75 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -775,4 +775,8 @@ bool command_parser_executor::flush_cache(const std::vector& args) return true; } +bool command_parser_executor::claim_rewards(const std::vector& args) { + return m_executor.claim_rewards(); +} + } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 5893f78350..a0e37f0cd5 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -142,6 +142,8 @@ class command_parser_executor final { bool flush_cache(const std::vector& args); + bool claim_rewards(const std::vector& args); + void test_trigger_uptime_proof() { m_executor.test_trigger_uptime_proof(); } }; diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 9838bd92e4..3d0c579ebb 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -290,6 +290,11 @@ or "default" to return the limit to its default value.)"); [this](const auto& x) { return m_parser.flush_cache(x); }, "flush_cache [bad-txs] [bad-blocks]", "Flush the specified cache(s)."); + m_command_lookup.set_handler( + "claim_rewards", + [this](const auto& x) { return m_parser.claim_rewards(x); }, + "claim_rewards", + "Provides a link to the website that allows claiming of ERC-20 rewards"); m_command_lookup.set_handler( "test_trigger_uptime_proof", [this](const auto&) { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 115532c65d..8b26b4cbc2 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2127,6 +2127,30 @@ bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { return true; } +bool rpc_command_executor::claim_rewards() { + auto maybe_merkle_response = try_running( [this] { return invoke(); }, + "Failed to get merkle root"); + if (!maybe_merkle_response) + return false; + auto& merkle_root_response = *maybe_merkle_response; + + std::ostringstream link; + link << "https://oxen-eth-webpage.vercel.app"; + link << "/?merkleRoot=" << merkle_root_response["merkle_root"]; + link << "&sig=" << merkle_root_response["signature"]; + for (const auto& non_signer : merkle_root_response["non_signers"]) { + link << "&indices=" << non_signer; + } + + tools::msg_writer( + "Merkle Root: {}\n Signature: {}\n Link to claim rewards: {}\n", + merkle_root_response["merkle_root"], + merkle_root_response["signature"], + link.str() + ); + return true; +} + bool rpc_command_executor::print_sn_status(std::vector args) { return print_sn(std::move(args), true); } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index ece2ca0400..2cfd3da99d 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -276,6 +276,8 @@ class rpc_command_executor final { bool flush_cache(bool bad_txs, bool invalid_blocks); + bool claim_rewards(); + bool version(); bool test_trigger_uptime_proof(); From b96a1fabe74aee1abe8e4f8be367bc682f64c4fc Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 19 Oct 2023 10:57:54 +1100 Subject: [PATCH 28/97] remove coinbase rewards --- src/blockchain_db/sqlite/db_sqlite.cpp | 7 +++++-- src/cryptonote_core/blockchain.cpp | 5 +++-- src/cryptonote_core/cryptonote_tx_utils.cpp | 3 +++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index eab0ea6135..aeddccb740 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -554,8 +554,11 @@ bool BlockchainSQLite::add_block( // We query our own database as a source of truth to verify the blocks payments against. The // calculated_rewards variable contains a known good list of who should have been paid in this - // block - auto calculated_rewards = get_sn_payments(block_height); + // block this only applies before the ETH BLS hard fork. After that the rewards are claimed by the users when they wish + std::vector calculated_rewards; + if (hf_version < cryptonote::feature::ETH_BLS) { + calculated_rewards = get_sn_payments(block_height); + } // We iterate through the block's coinbase payments and build a copy of our own list of the // payments miner_tx_vouts this will be compared against calculated_rewards and if they match we diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 4a4d86d38c..147e3826e7 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1453,7 +1453,8 @@ bool Blockchain::validate_miner_transaction( std::vector batched_sn_payments; if (m_sqlite_db) { - batched_sn_payments = m_sqlite_db->get_sn_payments(height); + if (version < cryptonote::feature::ETH_BLS) + batched_sn_payments = m_sqlite_db->get_sn_payments(height); } else { if (m_nettype != network_type::FAKECHAIN) throw std::logic_error("Blockchain missing SQLite Database"); @@ -1790,7 +1791,7 @@ bool Blockchain::create_block_template_internal( // This will check the batching database for who is due to be paid out in this block std::vector sn_rwds; - if (hf_version >= hf::hf19_reward_batching) { + if (hf_version < cryptonote::feature::ETH_BLS && hf_version >= hf::hf19_reward_batching) { sn_rwds = m_sqlite_db->get_sn_payments(height); // Rewards to pay out } diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 1b5f9d6cd1..aabde56f6c 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -168,6 +168,9 @@ bool height_has_governance_output(network_type nettype, hf hard_fork_version, ui if (height == 0) return false; + if (hard_fork_version >= hf::hf20) + return false; + if (hard_fork_version <= hf::hf9_service_nodes || hard_fork_version >= hf::hf19_reward_batching) return true; From cd7dc4f21563b5a0e2b8f2bb12ecb85e588dce8e Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 19 Oct 2023 11:44:33 +1100 Subject: [PATCH 29/97] delay hardfork in devnet cause no longer getting rewards --- src/cryptonote_basic/hardfork.cpp | 10 +++++----- utils/local-devnet/service_node_network.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 8dc25080ce..af58956266 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -151,11 +151,11 @@ static constexpr std::array devnet_hard_forks = { hard_fork{hf::hf13_enforce_checkpoints, 0, 4, 1653500577}, hard_fork{hf::hf14_blink, 0, 5, 1653500577}, hard_fork{hf::hf15_ons, 0, 6, 1653500577}, - hard_fork{hf::hf16_pulse, 0, 200, 1653500577}, - hard_fork{hf::hf17, 0, 201, 1653500577}, - hard_fork{hf::hf18, 0, 202, 1653500577}, - hard_fork{hf::hf19_reward_batching, 0, 203, 1653500577}, - hard_fork{hf::hf20, 0, 204, 1653500577}, + hard_fork{hf::hf16_pulse, 0, 250, 1653500577}, + hard_fork{hf::hf17, 0, 251, 1653500577}, + hard_fork{hf::hf18, 0, 252, 1653500577}, + hard_fork{hf::hf19_reward_batching, 0, 253, 1653500577}, + hard_fork{hf::hf20, 0, 254, 1653500577}, }; template diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 1057130967..26b80ec7f5 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -139,7 +139,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): # of 18.9, which means each registration requires 6 inputs. Thus we need a bare minimum of # 6(N-5) blocks, plus the 30 lock time on coinbase TXes = 6N more blocks (after the initial # 5 registrations). - self.sync_nodes(self.mine(206), timeout=120) + self.sync_nodes(self.mine(256), timeout=120) vprint("Submitting first round of service node registrations: ", end="", flush=True) # time.sleep(40) for sn in self.sns[0:5]: From 99eb9fbec0459208d61ded90e3197360f3822866 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 23 Oct 2023 10:20:40 +1100 Subject: [PATCH 30/97] rpc request to get all of the bls pubkeys --- src/bls/bls_aggregator.cpp | 66 +++++++++++++++++++++++ src/bls/bls_aggregator.h | 1 + src/cryptonote_core/cryptonote_core.cpp | 15 ++++++ src/cryptonote_core/cryptonote_core.h | 1 + src/rpc/core_rpc_server.cpp | 7 +++ src/rpc/core_rpc_server.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 15 ++++++ utils/local-devnet/commands/blsrequest.py | 2 +- 8 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 116e73910b..722e604beb 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -26,6 +26,72 @@ std::string BLSAggregator::aggregatePubkeyHex() { return ""; } +std::vector BLSAggregator::getPubkeys() { + std::mutex pubkeys_mutex, connection_mutex; + std::condition_variable cv; + size_t active_connections = 0; + const size_t MAX_CONNECTIONS = 900; + + std::vector pubkeys; + + // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda + auto it = service_node_list.get_first_pubkey_iterator(); + auto end_it = service_node_list.get_end_pubkey_iterator(); + crypto::x25519_public_key x_pkey{0}; + uint32_t ip; + uint16_t port; + while (it != end_it) { + service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { + x_pkey = proof.pubkey_x25519; + ip = proof.proof->public_ip; + port = proof.proof->qnet_port; + }); + //{ + //std::unique_lock connection_lock(connection_mutex); + //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + //} + // TODO sean epee is alway little, this will not work on big endian host + boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); + oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + + { + std::lock_guard connection_lock(connection_mutex); + ++active_connections; + } + auto conn = omq->connect_remote( + addr, + [](oxenmq::ConnectionID c) { + // Successfully connected + //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); + }, + [](oxenmq::ConnectionID c, std::string_view err) { + // Failed to connect + //oxen::log::debug(logcat, "Failed to connect {}", err); + }, + oxenmq::AuthLevel::basic); + omq->request( + conn, + "bls.pubkey_request", + [this, &logcat, &pubkeys, &pubkeys_mutex, &connection_mutex, &active_connections, &cv, &conn](bool success, std::vector data) { + oxen::log::debug( logcat, "bls pubkey response received"); + if (success) { + std::lock_guard lock(pubkeys_mutex); + pubkeys.emplace_back(data[0]); + } + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }); + it = service_node_list.get_next_pubkey_iterator(it); + } + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { + return active_connections == 0; + }); + return pubkeys; +} + aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) { const std::array hash = BLSSigner::hash(message); std::mutex signers_mutex, connection_mutex; diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 02f247d51e..83e2cc4c14 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -42,6 +42,7 @@ class BLSAggregator { ~BLSAggregator(); std::string aggregatePubkeyHex(); + std::vector getPubkeys(); aggregateResponse aggregateSignatures(const std::string& message); aggregateMerkleResponse aggregateMerkleRewards(const std::string& our_merkle_root); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 36071a44ca..1fab42ddb3 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1104,6 +1104,17 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { const auto rewards_merkle_root = rewards_merkle_tree.getRoot(); const auto h = m_bls_signer->hash(rewards_merkle_root); m.send_reply(rewards_merkle_root, m_bls_signer->signHash(h).getStr()); + }) + .add_request_command("pubkey_request", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq bls pubkey request"); + if (m.data.size() != 0) + m.send_reply( + "400", + "Bad request: BLS pubkey request must have no data parts" + "(received " + + std::to_string(m.data.size()) + ")"); + const auto h = m_bls_signer->hash(std::string(m.data[0])); + m.send_reply(m_bls_signer->getPublicKeyHex()); }); } @@ -2731,6 +2742,10 @@ aggregateMerkleResponse core::aggregate_merkle_rewards() { return resp; } //----------------------------------------------------------------------------------------------- +std::vector core::get_bls_pubkeys() const { + return m_bls_aggregator->getPubkeys(); +} +//----------------------------------------------------------------------------------------------- std::vector core::get_service_node_list_state( const std::vector& service_node_pubkeys) const { return m_service_node_list.get_service_node_list_state(service_node_pubkeys); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 71c1340fda..6d903833c1 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -953,6 +953,7 @@ class core : public i_miner_handler { aggregateResponse bls_request() const; aggregateMerkleResponse aggregate_merkle_rewards(); + std::vector get_bls_pubkeys() const; /** * @brief get a snapshot of the service node list state at the time of the call. diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 152e9b4eb0..71d66f6a26 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2540,6 +2540,13 @@ void core_rpc_server::invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context return; } //------------------------------------------------------------------------------------------------------------------------------ +void core_rpc_server::invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context) { + const std::vector bls_pubkeys = m_core.get_bls_pubkeys(); + bls_pubkey_request.response["status"] = STATUS_OK; + bls_pubkey_request.response["pubkeys"] = bls_pubkeys; + return; +} +//------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_SERVICE_KEYS& get_service_keys, rpc_context context) { const auto& keys = m_core.get_service_keys(); if (keys.pub) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 384454ff54..5b055df22f 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -185,6 +185,7 @@ class core_rpc_server { rpc_context context); void invoke(BLS_REQUEST& bls_request, rpc_context context); void invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context context); + void invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 6d4843ebf1..7fbb046c24 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2317,6 +2317,20 @@ struct BLS_MERKLE_REQUEST : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("bls_merkle_request"); } }; +/// RPC: bls pubkey request +/// +/// Sends a request out to get all the bls pubkeys from the network +/// +/// Inputs: None +/// +/// Outputs: +/// +/// - `status` -- generic RPC error code; "OK" means the request was successful. +/// - `pubkeys` -- The pubkeys for the whole network +struct BLS_PUBKEYS: PUBLIC, NO_ARGS { + static constexpr auto names() { return NAMES("bls_pubkey_request"); } +}; + /// RPC: blockchain/get_checkpoints /// /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to @@ -2711,6 +2725,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, BLS_REQUEST, BLS_MERKLE_REQUEST, + BLS_PUBKEYS, GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, GET_SERVICE_NODE_STATUS, diff --git a/utils/local-devnet/commands/blsrequest.py b/utils/local-devnet/commands/blsrequest.py index 91e9c5738c..e9fcfd8025 100755 --- a/utils/local-devnet/commands/blsrequest.py +++ b/utils/local-devnet/commands/blsrequest.py @@ -24,7 +24,7 @@ def instruct_daemon(method, params): params = {} -answer = instruct_daemon('bls_merkle_request', params) +answer = instruct_daemon('bls_pubkey_request', params) # print(json.dumps(answer['result']['block_header']['timestamp'], indent=4, sort_keys=True)) From 2f01bc6992498a669164905af10186a2b1d72276 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 30 Oct 2023 14:59:43 +1100 Subject: [PATCH 31/97] silence warnings --- .../portable_storage_template_helper.h | 2 +- .../cryptonote_boost_serialization.h | 72 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/contrib/epee/include/epee/storages/portable_storage_template_helper.h b/contrib/epee/include/epee/storages/portable_storage_template_helper.h index 17ab01ac5f..4aa58d2f1c 100644 --- a/contrib/epee/include/epee/storages/portable_storage_template_helper.h +++ b/contrib/epee/include/epee/storages/portable_storage_template_helper.h @@ -87,7 +87,7 @@ namespace epee } //----------------------------------------------------------------------------------------------------------- template - bool store_t_to_binary(t_struct& str_in, std::string& binary_buff, size_t indent = 0) + bool store_t_to_binary(t_struct& str_in, std::string& binary_buff, [[maybe_unused]]size_t indent = 0) { portable_storage ps; str_in.store(ps); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 8e2eaf81da..b412fb8dca 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -52,38 +52,38 @@ namespace boost { namespace serialization { //--------------------------------------------------- template inline void serialize( - Archive& a, crypto::public_key& x, const boost::serialization::version_type ver) { + Archive& a, crypto::public_key& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::secret_key& x, const boost::serialization::version_type ver) { + Archive& a, crypto::secret_key& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::key_derivation& x, const boost::serialization::version_type ver) { + Archive& a, crypto::key_derivation& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::key_image& x, const boost::serialization::version_type ver) { + Archive& a, crypto::key_image& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::signature& x, const boost::serialization::version_type ver) { + Archive& a, crypto::signature& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::hash& x, const boost::serialization::version_type ver) { + Archive& a, crypto::hash& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template inline void serialize( - Archive& a, crypto::hash8& x, const boost::serialization::version_type ver) { + Archive& a, crypto::hash8& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } @@ -91,14 +91,14 @@ namespace boost { namespace serialization { inline void serialize( Archive& a, cryptonote::txout_to_script& x, - const boost::serialization::version_type ver) { + [[maybe_unused]] const boost::serialization::version_type ver) { a& x.keys; a& x.script; } template inline void serialize( - Archive& a, cryptonote::txout_to_key& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::txout_to_key& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.key; } @@ -106,13 +106,13 @@ namespace boost { namespace serialization { inline void serialize( Archive& a, cryptonote::txout_to_scripthash& x, - const boost::serialization::version_type ver) { + [[maybe_unused]] const boost::serialization::version_type ver) { a& x.hash; } template inline void serialize( - Archive& a, cryptonote::txin_gen& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::txin_gen& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.height; } @@ -120,7 +120,7 @@ namespace boost { namespace serialization { inline void serialize( Archive& a, cryptonote::txin_to_script& x, - const boost::serialization::version_type ver) { + [[maybe_unused]] const boost::serialization::version_type ver) { a& x.prev; a& x.prevout; a& x.sigset; @@ -130,7 +130,7 @@ namespace boost { namespace serialization { inline void serialize( Archive& a, cryptonote::txin_to_scripthash& x, - const boost::serialization::version_type ver) { + [[maybe_unused]] const boost::serialization::version_type ver) { a& x.prev; a& x.prevout; a& x.script; @@ -139,7 +139,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, cryptonote::txin_to_key& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::txin_to_key& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.amount; a& x.key_offsets; a& x.k_image; @@ -147,14 +147,14 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, cryptonote::tx_out& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::tx_out& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.amount; a& x.target; } template inline void serialize( - Archive& a, cryptonote::txversion& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::txversion& x, [[maybe_unused]] const boost::serialization::version_type ver) { uint16_t v = static_cast(x); a& v; if (v >= tools::enum_count) @@ -165,7 +165,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, cryptonote::txtype& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::txtype& x, [[maybe_unused]] const boost::serialization::version_type ver) { uint16_t txtype = static_cast(x); a& txtype; if (txtype >= tools::enum_count) @@ -178,7 +178,7 @@ namespace boost { namespace serialization { inline void serialize( Archive& a, cryptonote::transaction_prefix& x, - const boost::serialization::version_type ver) { + [[maybe_unused]] const boost::serialization::version_type ver) { a& x.version; if (x.version >= cryptonote::txversion::v3_per_output_unlock_times) { a& x.output_unlock_times; @@ -199,7 +199,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, cryptonote::transaction& x, const boost::serialization::version_type ver) { + Archive& a, cryptonote::transaction& x, [[maybe_unused]] const boost::serialization::version_type ver) { serialize(a, static_cast(x), ver); if (x.version == cryptonote::txversion::v1) { a& x.signatures; @@ -212,7 +212,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, cryptonote::block& b, const boost::serialization::version_type ver) { + Archive& a, cryptonote::block& b, [[maybe_unused]] const boost::serialization::version_type ver) { a& b.major_version; a& b.minor_version; a& b.timestamp; @@ -233,26 +233,26 @@ namespace boost { namespace serialization { } template - inline void serialize(Archive& a, rct::key& x, const boost::serialization::version_type ver) { + inline void serialize(Archive& a, rct::key& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& reinterpret_cast(x); } template - inline void serialize(Archive& a, rct::ctkey& x, const boost::serialization::version_type ver) { + inline void serialize(Archive& a, rct::ctkey& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.dest; a& x.mask; } template inline void serialize( - Archive& a, rct::rangeSig& x, const boost::serialization::version_type ver) { + Archive& a, rct::rangeSig& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.asig; a& x.Ci; } template inline void serialize( - Archive& a, rct::Bulletproof& x, const boost::serialization::version_type ver) { + Archive& a, rct::Bulletproof& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.V; a& x.A; a& x.S; @@ -269,21 +269,21 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::boroSig& x, const boost::serialization::version_type ver) { + Archive& a, rct::boroSig& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.s0; a& x.s1; a& x.ee; } template - inline void serialize(Archive& a, rct::mgSig& x, const boost::serialization::version_type ver) { + inline void serialize(Archive& a, rct::mgSig& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.ss; a& x.cc; // a & x.II; // not serialized, we can recover it from the tx vin } template - inline void serialize(Archive& a, rct::clsag& x, const boost::serialization::version_type ver) { + inline void serialize(Archive& a, rct::clsag& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.s; a& x.c1; // a & x.I; // not serialized, we can recover it from the tx vin @@ -292,14 +292,14 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::ecdhTuple& x, const boost::serialization::version_type ver) { + Archive& a, rct::ecdhTuple& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.mask; a& x.amount; } template inline void serialize( - Archive& a, rct::multisig_kLRki& x, const boost::serialization::version_type ver) { + Archive& a, rct::multisig_kLRki& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.k; a& x.L; a& x.R; @@ -308,7 +308,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::multisig_out& x, const boost::serialization::version_type ver) { + Archive& a, rct::multisig_out& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.c; if (ver < 1) return; @@ -317,7 +317,7 @@ namespace boost { namespace serialization { template inline typename std::enable_if::type serializeOutPk( - Archive& a, rct::ctkeyV& outPk_, const boost::serialization::version_type ver) { + Archive& a, rct::ctkeyV& outPk_, [[maybe_unused]] const boost::serialization::version_type ver) { rct::keyV outPk; a& outPk; outPk_.resize(outPk.size()); @@ -329,7 +329,7 @@ namespace boost { namespace serialization { template inline typename std::enable_if::type serializeOutPk( - Archive& a, rct::ctkeyV& outPk_, const boost::serialization::version_type ver) { + Archive& a, rct::ctkeyV& outPk_, [[maybe_unused]] const boost::serialization::version_type ver) { rct::keyV outPk(outPk_.size()); for (size_t n = 0; n < outPk_.size(); ++n) outPk[n] = outPk_[n].mask; @@ -338,7 +338,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::rctSigBase& x, const boost::serialization::version_type ver) { + Archive& a, rct::rctSigBase& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.type; if (x.type == rct::RCTType::Null) return; @@ -362,7 +362,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::rctSigPrunable& x, const boost::serialization::version_type ver) { + Archive& a, rct::rctSigPrunable& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.rangeSigs; if (x.rangeSigs.empty()) a& x.bulletproofs; @@ -375,7 +375,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::rctSig& x, const boost::serialization::version_type ver) { + Archive& a, rct::rctSig& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.type; if (x.type == rct::RCTType::Null) return; @@ -408,7 +408,7 @@ namespace boost { namespace serialization { template inline void serialize( - Archive& a, rct::RCTConfig& x, const boost::serialization::version_type ver) { + Archive& a, rct::RCTConfig& x, [[maybe_unused]] const boost::serialization::version_type ver) { a& x.range_proof_type; a& x.bp_version; } From ea78e8ff9071c44dd04eab2295cce19a29e4d408 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 1 Nov 2023 11:41:09 +1100 Subject: [PATCH 32/97] Adds a new ethereum transaction to update nodes about eth address This will fill in an item on the service_node_list contributors struct and also add to a mapping in the batching database. So if you want to query for who to pay it will be able to return an ethereum address to payout. --- src/blockchain_db/sqlite/db_sqlite.cpp | 64 ++++++++++++++--- src/blockchain_db/sqlite/db_sqlite.h | 3 +- src/cryptonote_basic/cryptonote_basic.h | 6 +- src/cryptonote_basic/cryptonote_basic_impl.h | 1 + src/cryptonote_basic/tx_extra.h | 18 +++++ src/cryptonote_basic/txtypes.h | 2 + src/cryptonote_core/CMakeLists.txt | 1 + src/cryptonote_core/blockchain.cpp | 22 +++++- src/cryptonote_core/cryptonote_tx_utils.cpp | 1 + src/cryptonote_core/ethereum_transactions.cpp | 72 +++++++++++++++++++ src/cryptonote_core/ethereum_transactions.h | 18 +++++ src/cryptonote_core/service_node_list.cpp | 47 +++++++++++- src/cryptonote_core/service_node_list.h | 11 ++- src/rpc/core_rpc_server.cpp | 6 +- 14 files changed, 253 insertions(+), 19 deletions(-) create mode 100644 src/cryptonote_core/ethereum_transactions.cpp create mode 100644 src/cryptonote_core/ethereum_transactions.h diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index aeddccb740..85fd2b38cd 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -176,6 +176,27 @@ void BlockchainSQLite::upgrade_schema() { } transaction.commit(); + } + + const auto eth_mapping_table_count = prepared_get( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND " + "name='eth_mapping';"); + if (!eth_mapping_table_count == 0) { + log::info(logcat, "Adding eth mapping table to batching db"); + auto& netconf = get_config(m_nettype); + SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE}; + db.exec(fmt::format( + R"( + CREATE TABLE eth_mapping( + oxen_address VARCHAR NOT NULL, + eth_address VARCHAR NOT NULL, + height BIGINT NOT NULL, + PRIMARY KEY (oxen_address, height) + CHECK(height >= 0) + ); + + CREATE INDEX eth_mapping_eth_address_idx ON eth_mapping(eth_address); + )")); } const auto archive_table_count = prepared_get( @@ -288,13 +309,24 @@ void BlockchainSQLite::blockchain_detached(uint64_t new_height) { } // Must be called with the address_str_cache_mutex held! -const std::string& BlockchainSQLite::get_address_str(const account_public_address& addr) { - auto& address_str = address_str_cache[addr]; +const std::string& BlockchainSQLite::get_address_str(const cryptonote::batch_sn_payment& addr) { + if (addr.eth_address.has_value()) + return *addr.eth_address; + auto& address_str = address_str_cache[addr.address_info.address]; if (address_str.empty()) - address_str = cryptonote::get_account_address_as_str(m_nettype, 0, addr); + address_str = cryptonote::get_account_address_as_str(m_nettype, 0, addr.address_info.address); return address_str; } +bool BlockchainSQLite::update_sn_rewards_address(const std::string& oxen_address, const std::string& eth_address) { + auto update_address = prepared_st( + "UPDATE batched_payments_accrued SET address = ? WHERE address = ?" + " ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount" + ); + db::exec_query(update_address, eth_address, oxen_address); + return true; +} + bool BlockchainSQLite::add_sn_rewards(const std::vector& payments) { log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__); auto insert_payment = prepared_st( @@ -307,7 +339,7 @@ bool BlockchainSQLite::add_sn_rewards(const std::vector(payment.address_info.address.modulus(netconf.BATCHING_INTERVAL)); auto amt = static_cast(payment.amount); - const auto& address_str = get_address_str(payment.address_info.address); + const auto& address_str = get_address_str(payment); log::trace( logcat, "Adding record for SN reward contributor {} to database with amount {}", @@ -327,7 +359,7 @@ bool BlockchainSQLite::subtract_sn_rewards( "UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?"); for (auto& payment : payments) { - const auto& address_str = get_address_str(payment.address_info.address); + const auto& address_str = get_address_str(payment); auto result = db::exec_query(update_payment, static_cast(payment.amount), address_str); if (!result) { @@ -377,9 +409,21 @@ std::vector BlockchainSQLite::get_sn_payments(uint uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) { log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__); - - auto earnings = prepared_maybe_get( - "SELECT amount FROM batched_payments_accrued WHERE address = ?", address); + auto earnings = prepared_maybe_get(R"( + WITH RelevantOxenAddress AS ( + SELECT oxen_address + FROM eth_mapping + WHERE eth_address = ? + HAVING height = MAX(height) + ) + SELECT amount + FROM batched_payments_accrued + WHERE address = ? + UNION + SELECT bpa.amount + FROM batched_payments_accrued bpa + JOIN RelevantOxenAddress roa ON bpa.address = roa.oxen_address; + )", address, address); return static_cast(earnings.value_or(0) / 1000); } @@ -433,7 +477,7 @@ void BlockchainSQLite::calculate_rewards( uint64_t c_reward = mul128_div64( contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn); if (c_reward > 0) - payments.emplace_back(contributor.address, c_reward); + payments.emplace_back(*contributor.address, c_reward); } } @@ -706,7 +750,7 @@ bool BlockchainSQLite::save_payments( std::lock_guard a_s_lock{address_str_cache_mutex}; for (const auto& payment : paid_amounts) { - const auto& address_str = get_address_str(payment.address_info.address); + const auto& address_str = get_address_str(payment); if (auto maybe_amount = db::exec_and_maybe_get(select_sum, address_str)) { // Truncate the thousanths amount to an atomic OXEN: auto amount = static_cast(*maybe_amount) / BATCH_REWARD_FACTOR * diff --git a/src/blockchain_db/sqlite/db_sqlite.h b/src/blockchain_db/sqlite/db_sqlite.h index 2dc4355460..4e78c6b150 100644 --- a/src/blockchain_db/sqlite/db_sqlite.h +++ b/src/blockchain_db/sqlite/db_sqlite.h @@ -65,6 +65,7 @@ class BlockchainSQLite : public db::Database { // exist it will be created. bool add_sn_rewards(const std::vector& payments); bool subtract_sn_rewards(const std::vector& payments); + bool update_sn_rewards_address(const std::string& oxen_address, const std::string& eth_address); private: bool reward_handler( @@ -74,7 +75,7 @@ class BlockchainSQLite : public db::Database { std::unordered_map address_str_cache; std::pair parsed_governance_addr = {hf::none, {}}; - const std::string& get_address_str(const account_public_address& addr); + const std::string& get_address_str(const cryptonote::batch_sn_payment& addr); std::mutex address_str_cache_mutex; public: diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index de71568d2a..bfd65bee97 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -168,7 +168,7 @@ class transaction_prefix { bool is_transfer() const { return type == txtype::standard || type == txtype::stake || - type == txtype::oxen_name_system; + type == txtype::oxen_name_system || type == txtype::ethereum; } // not used after version 2, but remains for compatibility @@ -538,7 +538,9 @@ inline txversion transaction_prefix::get_max_version_for_hf(hf hf_version) { constexpr txtype transaction_prefix::get_max_type_for_hf(hf hf_version) { txtype result = txtype::standard; - if (hf_version >= hf::hf15_ons) + if (hf_version >= cryptonote::feature::ETH_BLS) + result = txtype::ethereum; + else if (hf_version >= hf::hf15_ons) result = txtype::oxen_name_system; else if (hf_version >= hf::hf14_blink) result = txtype::stake; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 2b7be964af..c32b2f2704 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -76,6 +76,7 @@ struct address_parse_info { struct batch_sn_payment { cryptonote::address_parse_info address_info; + std::optional eth_address; uint64_t amount; batch_sn_payment() = default; batch_sn_payment(const cryptonote::address_parse_info& addr_info, uint64_t amt) : diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 062dfcee79..999f21d1de 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -53,6 +53,7 @@ constexpr uint8_t TX_EXTRA_TAG_PADDING = 0x00, TX_EXTRA_TAG_PUBKEY = 0x01, TX_EX TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS = 0x76, TX_EXTRA_TAG_TX_KEY_IMAGE_UNLOCK = 0x77, TX_EXTRA_TAG_SERVICE_NODE_STATE_CHANGE = 0x78, TX_EXTRA_TAG_BURN = 0x79, TX_EXTRA_TAG_OXEN_NAME_SYSTEM = 0x7A, + TX_EXTRA_TAG_ETHEREUM = 0x7B, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG = 0xDE; @@ -619,6 +620,20 @@ struct tx_extra_oxen_name_system { END_SERIALIZE() }; +struct tx_extra_ethereum { + uint8_t version = 0; + std::string eth_address; + std::string oxen_address; + std::string signature; + + BEGIN_SERIALIZE() + FIELD(version) + FIELD(eth_address); + FIELD(oxen_address); + FIELD(signature); + END_SERIALIZE() +}; + // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: // varint tag; // varint size; @@ -644,6 +659,7 @@ using tx_extra_field = std::variant< tx_extra_burn, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate, + tx_extra_ethereum, tx_extra_padding>; } // namespace cryptonote @@ -681,3 +697,5 @@ BINARY_VARIANT_TAG( BINARY_VARIANT_TAG(cryptonote::tx_extra_burn, cryptonote::TX_EXTRA_TAG_BURN); BINARY_VARIANT_TAG( cryptonote::tx_extra_oxen_name_system, cryptonote::TX_EXTRA_TAG_OXEN_NAME_SYSTEM); +BINARY_VARIANT_TAG( + cryptonote::tx_extra_ethereum, cryptonote::TX_EXTRA_TAG_ETHEREUM); diff --git a/src/cryptonote_basic/txtypes.h b/src/cryptonote_basic/txtypes.h index c42b8d4d02..f7c1fec22a 100644 --- a/src/cryptonote_basic/txtypes.h +++ b/src/cryptonote_basic/txtypes.h @@ -24,6 +24,7 @@ enum class txtype : uint16_t { key_image_unlock, stake, oxen_name_system, + ethereum, _count }; @@ -44,6 +45,7 @@ inline constexpr std::string_view to_string(txtype type) { case txtype::key_image_unlock: return "key_image_unlock"sv; case txtype::stake: return "stake"sv; case txtype::oxen_name_system: return "oxen_name_system"sv; + case txtype::ethereum: return "ethereum"sv; default: assert(false); return "xx_unhandled_type"sv; } } diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 53a68ec2fc..8b60f78de8 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(cryptonote_core tx_pool.cpp tx_sanity_check.cpp cryptonote_tx_utils.cpp + ethereum_transactions.cpp pulse.cpp uptime_proof.cpp) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 147e3826e7..af4a631daf 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -71,6 +71,7 @@ #include "service_node_list.h" #include "service_node_voting.h" #include "tx_pool.h" +#include "ethereum_transactions.h" #ifdef ENABLE_SYSTEMD extern "C" { @@ -1538,6 +1539,8 @@ bool Blockchain::validate_miner_transaction( base_reward = money_in_use - reward_parts.miner_fee; } + //TODO sean check here that L2 height is reasonable (doesnt go backwards, isn't in future assuming eth blocks issued once every 6 seconds) + if (b.reward > reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total) { log::error( @@ -1551,6 +1554,7 @@ bool Blockchain::validate_miner_transaction( return false; } + return true; } //------------------------------------------------------------------ @@ -3997,6 +4001,16 @@ bool Blockchain::check_tx_inputs( return false; } } + + if (tx.type == txtype::ethereum) { + cryptonote::tx_extra_ethereum entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum TX reason: {}", fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } + } } else { CHECK_AND_ASSERT_MES( tx.vin.size() == 0, @@ -5026,7 +5040,7 @@ bool Blockchain::handle_block_to_main_chain( uint64_t long_term_block_weight = get_next_long_term_block_weight(block_weight); std::string bd = cryptonote::block_to_blob(bl); new_height = m_db->add_block( - std::make_pair(std::move(bl), std::move(bd)), + std::make_pair(bl, std::move(bd)), block_weight, long_term_block_weight, cumulative_difficulty, @@ -5113,11 +5127,17 @@ bool Blockchain::handle_block_to_main_chain( bvc.m_verifivation_failed = true; return false; } + if (!m_service_node_list.process_ethereum_transactions(m_nettype, bl, only_txs)) { + log::info(logcat, fg(fmt::terminal_color::red), "Failed to add ethereum transactions"); + bvc.m_verifivation_failed = true; + return false; + } } else { if (m_nettype != network_type::FAKECHAIN) throw std::logic_error("Blockchain missing SQLite Database"); } + block_add_info hook_data{bl, only_txs, checkpoint}; for (const auto& hook : m_block_add_hooks) { try { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index aabde56f6c..231b3d3e66 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -219,6 +219,7 @@ uint64_t derive_governance_from_block_reward( } uint64_t service_node_reward_formula(uint64_t base_reward, hf hard_fork_version) { + //TODO sean go to ethereum pool and get block rate return hard_fork_version >= hf::hf15_ons ? oxen::SN_REWARD_HF15 : hard_fork_version >= hf::hf9_service_nodes ? base_reward / 2 diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp new file mode 100644 index 0000000000..9bc98c5bc2 --- /dev/null +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -0,0 +1,72 @@ +#include "ethereum_transactions.h" + +#include "cryptonote_basic/cryptonote_format_utils.h" + +using cryptonote::hf; + +namespace ethereum { + +template +static bool check_condition( + bool condition, std::string* reason, std::string_view format, T&&... args) { + if (condition && reason) + *reason = fmt::format(format, std::forward(args)...); + return condition; +} + +bool validate_ethereum_tx( + hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum& eth_extra, + std::string* reason) { + + // Extract Ethereum Extra from TX + { + if (check_condition( + tx.type != cryptonote::txtype::ethereum, + reason, + "{} uses wrong tx type, expected={}", + tx, + cryptonote::txtype::ethereum)) + return false; + + if (check_condition( + !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), + reason, + "{} didn't have ethereum data in the tx_extra", + tx)) + return false; + } + + // Validate Ethereum Address + { + const size_t ETH_ADDRESS_SIZE = 20; // 20 bytes, 40 hex characters + if (check_condition( + eth_extra.eth_address.size() != ETH_ADDRESS_SIZE * 2, // Multiplied by 2 because it's hex representation + reason, + "{} invalid ethereum address size", + tx)) + return false; + + // TODO sean potentially add more rigorous eth address checking here + } + + // Validate Ethereum Signature + { + //TODO sean this verify signature stuff + //bool signature_valid = verify_ethereum_signature(eth_extra.eth_address, eth_extra.signature, eth_extra.pub_key); + bool signature_valid = true; + if (check_condition( + !signature_valid, + reason, + "{} invalid signature over new ethereum address", + tx)) + return false; + } + + + return true; +} + +} diff --git a/src/cryptonote_core/ethereum_transactions.h b/src/cryptonote_core/ethereum_transactions.h new file mode 100644 index 0000000000..a96402c420 --- /dev/null +++ b/src/cryptonote_core/ethereum_transactions.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#include "cryptonote_config.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/tx_extra.h" + +namespace ethereum { + +bool validate_ethereum_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum& eth_extra, + std::string* reason); +} // ethereum diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index c2995e95e4..effb094eb8 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -64,6 +64,7 @@ extern "C" { #include "service_node_rules.h" #include "service_node_swarm.h" #include "uptime_proof.h" +#include "ethereum_transactions.h" #include "version.h" using cryptonote::hf; @@ -2086,6 +2087,50 @@ bool service_node_list::pop_batching_rewards_block(const cryptonote::block& bloc return m_blockchain.sqlite_db()->pop_block(block, m_state); } +bool service_node_list::process_ethereum_transactions(const cryptonote::network_type nettype, const cryptonote::block& block, const std::vector& txs) { + if (block.major_version < cryptonote::feature::ETH_BLS) + return true; + uint64_t block_height = cryptonote::get_block_height(block); + for (const cryptonote::transaction& tx : txs) { + if (tx.type != cryptonote::txtype::ethereum) + continue; + + cryptonote::tx_extra_ethereum entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_tx(block.major_version, block_height, tx, entry, &fail_reason)) { + log::error( + logcat, + "ETH TX: Failed to validate for tx={}. This should have failed validation " + "earlier reason={}", + get_transaction_hash(tx), + fail_reason); + assert("Failed to validate ethereum transaction. Should already have failed " + "validation prior" == nullptr); + return false; + } + cryptonote::address_parse_info addr_info; + if (!cryptonote::get_account_address_from_str(addr_info, nettype, entry.oxen_address)) + throw std::invalid_argument{tr("Failed to parse address: ") + entry.oxen_address}; + const auto active_service_nodes = m_state.active_service_nodes_infos(); + for (const auto& [node_pubkey, node_info] : active_service_nodes) { + const auto active_service_node = m_state.service_nodes_infos.find(node_pubkey); + if (active_service_node == m_state.service_nodes_infos.end()) + continue; + service_node_info& new_info = duplicate_info(active_service_node->second); + for (service_node_info::contributor_t& contributor : new_info.contributors) + { + //TODO sean does this test for equality work on an account_public_address, looks like it does have the equality operator + // but will it check correctly + if (contributor.address == addr_info.address) + contributor.ethereum_address = entry.eth_address; + } + } + + return m_blockchain.sqlite_db()->update_sn_rewards_address(entry.oxen_address, entry.eth_address); + } + return true; +} + static std::mt19937_64 quorum_rng(hf hf_version, crypto::hash const& hash, quorum_type type) { std::mt19937_64 result; if (hf_version >= hf::hf16_pulse) { @@ -4684,7 +4729,7 @@ payout service_node_payout_portions(const crypto::public_key& key, const service if (contributor.address == info.operator_address) portion += info.portions_for_operator; - result.payouts.push_back({contributor.address, portion}); + result.payouts.push_back({*contributor.address, portion}); } return result; diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 83e7944fcd..2037752919 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -242,10 +242,11 @@ struct service_node_info // registration information }; struct contributor_t { - uint8_t version = 0; + uint8_t version = 1; uint64_t amount = 0; uint64_t reserved = 0; - cryptonote::account_public_address address{}; + std::optional address{}; + std::optional ethereum_address{}; std::vector locked_contributions; contributor_t() = default; @@ -260,8 +261,11 @@ struct service_node_info // registration information VARINT_FIELD(version) VARINT_FIELD(amount) VARINT_FIELD(reserved) - FIELD(address) + FIELD(*address) FIELD(locked_contributions) + //TODO sean serialize this + //if (version >= 1) + //FIELD(*ethereum_address); END_SERIALIZE() }; @@ -459,6 +463,7 @@ class service_node_list { bool state_history_exists(uint64_t height); bool process_batching_rewards(const cryptonote::block& block); bool pop_batching_rewards_block(const cryptonote::block& block); + bool process_ethereum_transactions(const cryptonote::network_type nettype, const cryptonote::block& block, const std::vector& txs); void blockchain_detached(uint64_t height); void init(); void validate_miner_tx(const cryptonote::miner_tx_info& info) const; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 71d66f6a26..9e162130c8 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -745,6 +745,10 @@ namespace { _load_owner(ons, "owner", x.owner); _load_owner(ons, "backup_owner", x.backup_owner); } + void operator()(const tx_extra_ethereum& x) { + set("eth_address", x.eth_address); + set("signature", x.signature); + } // Ignore these fields: void operator()(const tx_extra_padding&) {} @@ -2817,7 +2821,7 @@ void core_rpc_server::fill_sn_response_entry( {"amount", contributor.amount}, {"address", cryptonote::get_account_address_as_str( - m_core.get_nettype(), false /*subaddress*/, contributor.address)}}); + m_core.get_nettype(), false /*subaddress*/, *contributor.address)}}); if (contributor.reserved != contributor.amount) c["reserved"] = contributor.reserved; if (want_locked_c) { From b041ecd7c7e6b25962d67acd12f9005a5f312308 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 10 Nov 2023 11:39:06 +1100 Subject: [PATCH 33/97] epee cstdint --- contrib/epee/include/epee/storages/parserse_base_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/epee/include/epee/storages/parserse_base_utils.h b/contrib/epee/include/epee/storages/parserse_base_utils.h index 21b0ba365e..cfd3f5c098 100644 --- a/contrib/epee/include/epee/storages/parserse_base_utils.h +++ b/contrib/epee/include/epee/storages/parserse_base_utils.h @@ -28,6 +28,7 @@ #include #include +#include namespace epee::misc_utils::parse { From d689cb889c8f93ac06538b3374babf9e6f2a0f8d Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 2 Nov 2023 07:56:06 +1100 Subject: [PATCH 34/97] put query for nodes into its own function --- src/bls/bls_aggregator.cpp | 254 ++++++++----------------------------- src/bls/bls_aggregator.h | 92 ++++++++++++++ 2 files changed, 146 insertions(+), 200 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 722e604beb..cc3ad9857e 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -1,11 +1,6 @@ #include "bls_aggregator.h" -#include "bls_signer.h" -#include "bls_utils.h" -#include "common/string_util.h" #include "logging/oxen_logger.h" -#include -#include static auto logcat = oxen::log::Cat("bls_aggregator"); @@ -27,228 +22,87 @@ std::string BLSAggregator::aggregatePubkeyHex() { } std::vector BLSAggregator::getPubkeys() { - std::mutex pubkeys_mutex, connection_mutex; - std::condition_variable cv; - size_t active_connections = 0; - const size_t MAX_CONNECTIONS = 900; - std::vector pubkeys; + std::mutex pubkeys_mutex; + + processNodes( + "bls.pubkey_request", + [this, &pubkeys, &pubkeys_mutex](bool success, std::vector data) { + if (success) { + std::lock_guard lock(pubkeys_mutex); + pubkeys.emplace_back(data[0]); + } + }, + [](){} + ); - // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda - auto it = service_node_list.get_first_pubkey_iterator(); - auto end_it = service_node_list.get_end_pubkey_iterator(); - crypto::x25519_public_key x_pkey{0}; - uint32_t ip; - uint16_t port; - while (it != end_it) { - service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { - x_pkey = proof.pubkey_x25519; - ip = proof.proof->public_ip; - port = proof.proof->qnet_port; - }); - //{ - //std::unique_lock connection_lock(connection_mutex); - //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - //} - // TODO sean epee is alway little, this will not work on big endian host - boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); - oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; - - { - std::lock_guard connection_lock(connection_mutex); - ++active_connections; - } - auto conn = omq->connect_remote( - addr, - [](oxenmq::ConnectionID c) { - // Successfully connected - //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); - }, - [](oxenmq::ConnectionID c, std::string_view err) { - // Failed to connect - //oxen::log::debug(logcat, "Failed to connect {}", err); - }, - oxenmq::AuthLevel::basic); - omq->request( - conn, - "bls.pubkey_request", - [this, &logcat, &pubkeys, &pubkeys_mutex, &connection_mutex, &active_connections, &cv, &conn](bool success, std::vector data) { - oxen::log::debug( logcat, "bls pubkey response received"); - if (success) { - std::lock_guard lock(pubkeys_mutex); - pubkeys.emplace_back(data[0]); - } - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }); - it = service_node_list.get_next_pubkey_iterator(it); - } - std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { - return active_connections == 0; - }); return pubkeys; } aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) { const std::array hash = BLSSigner::hash(message); - std::mutex signers_mutex, connection_mutex; - std::condition_variable cv; - size_t active_connections = 0; - const size_t MAX_CONNECTIONS = 900; bls::Signature aggSig; aggSig.clear(); - std::vector signers; - - // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda - auto it = service_node_list.get_first_pubkey_iterator(); - auto end_it = service_node_list.get_end_pubkey_iterator(); - crypto::x25519_public_key x_pkey{0}; - uint32_t ip; - uint16_t port; + std::mutex signers_mutex; int64_t signers_index = 0; - while (it != end_it) { - service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { - x_pkey = proof.pubkey_x25519; - ip = proof.proof->public_ip; - port = proof.proof->qnet_port; - }); - //{ - //std::unique_lock connection_lock(connection_mutex); - //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - //} - // TODO sean epee is alway little, this will not work on big endian host - boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); - oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; - { - std::lock_guard connection_lock(connection_mutex); - ++active_connections; - } - auto conn = omq->connect_remote( - addr, - [](oxenmq::ConnectionID c) { - // Successfully connected - //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); - }, - [](oxenmq::ConnectionID c, std::string_view err) { - // Failed to connect - //oxen::log::debug(logcat, "Failed to connect {}", err); - }, - oxenmq::AuthLevel::basic); - omq->request( - conn, - "bls.signature_request", - [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn](bool success, std::vector data) { - oxen::log::debug( logcat, "bls signature response received"); - if (success) { - bls::Signature external_signature; - external_signature.setStr(data[0]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }, - message - ); - it = service_node_list.get_next_pubkey_iterator(it); - signers_index++; - } - std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { - return active_connections == 0; - }); + processNodes( + "bls.signature_request", + [this, &aggSig, &signers, &signers_mutex, &signers_index](bool success, std::vector data) { + if (success) { + bls::Signature external_signature; + external_signature.setStr(data[0]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + signers_index++; + }, + [this, &aggSig, &hash] { + const auto my_signature = bls_signer->signHash(hash); + aggSig.add(my_signature); + }, + message + ); + const auto non_signers = findNonSigners(signers); - const auto my_signature = bls_signer->signHash(hash); - aggSig.add(my_signature); const auto sig_str = bls_utils::SignatureToHex(aggSig); return aggregateResponse{non_signers, sig_str}; -}; +} aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& our_merkle_root) { const std::array hash = BLSSigner::hash(our_merkle_root); - std::mutex signers_mutex, connection_mutex; - std::condition_variable cv; - size_t active_connections = 0; - const size_t MAX_CONNECTIONS = 900; bls::Signature aggSig; aggSig.clear(); - std::vector signers; - - // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda - auto it = service_node_list.get_first_pubkey_iterator(); - auto end_it = service_node_list.get_end_pubkey_iterator(); - crypto::x25519_public_key x_pkey{0}; - uint32_t ip; - uint16_t port; + std::mutex signers_mutex; int64_t signers_index = 0; - while (it != end_it) { - service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { - x_pkey = proof.pubkey_x25519; - ip = proof.proof->public_ip; - port = proof.proof->qnet_port; - }); - //{ - //std::unique_lock connection_lock(connection_mutex); - //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - //} - // TODO sean epee is alway little, this will not work on big endian host - boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); - oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; - { - std::lock_guard connection_lock(connection_mutex); - ++active_connections; + processNodes( + "bls.rewards_merkle", + [this, &aggSig, &signers, &signers_mutex, &our_merkle_root, &signers_index](bool success, std::vector data) { + if (success && data[0] == our_merkle_root) { + bls::Signature external_signature; + external_signature.setStr(data[1]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(signers_index); + } + signers_index++; + }, + [this, &aggSig, &hash] { + const auto my_signature = bls_signer->signHash(hash); + aggSig.add(my_signature); } - auto conn = omq->connect_remote( - addr, - [](oxenmq::ConnectionID c) { - // Successfully connected - //oxen::log::info(logcat, "TODO sean remove this: successuflly connected"); - }, - [](oxenmq::ConnectionID c, std::string_view err) { - // Failed to connect - //oxen::log::debug(logcat, "Failed to connect {}", err); - }, - oxenmq::AuthLevel::basic); - omq->request( - conn, - "bls.rewards_merkle", - [this, &logcat, &aggSig, &signers, &signers_mutex, &connection_mutex, signers_index, &active_connections, &cv, &conn, &our_merkle_root](bool success, std::vector data) { - oxen::log::debug( logcat, "bls signature response received"); - if (success && data[0] == our_merkle_root) { - bls::Signature external_signature; - external_signature.setStr(data[1]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }); - it = service_node_list.get_next_pubkey_iterator(it); - signers_index++; - } - std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { - return active_connections == 0; - }); + ); + const auto non_signers = findNonSigners(signers); - const auto my_signature = bls_signer->signHash(hash); - aggSig.add(my_signature); const auto sig_str = bls_utils::SignatureToHex(aggSig); return aggregateMerkleResponse{our_merkle_root, non_signers, sig_str}; -}; +} + + std::vector BLSAggregator::findNonSigners(const std::vector& indices) { std::vector nonSignerIndices = {}; diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 83e2cc4c14..18f38dfb32 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -19,8 +19,13 @@ #include "cryptonote_core/service_node_list.h" #include "bls_signer.h" +#include "bls_utils.h" #include +#include +#include +#include "common/string_util.h" + struct aggregateResponse { std::vector non_signers; std::string signature; @@ -37,6 +42,7 @@ class BLSAggregator { std::shared_ptr bls_signer; std::shared_ptr omq; service_nodes::service_node_list& service_node_list; + public: BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer); ~BLSAggregator(); @@ -48,5 +54,91 @@ class BLSAggregator { std::vector findNonSigners(const std::vector& indices); +private: + // Goes out to the nodes on the network and makes oxenmq requests to all of them, when getting the reply + // `callback` will be called to process their reply and after everyone has been received it will then call + // `postProcess` + template + void processNodes(const std::string& request_name, Callback callback, PostProcess postProcess, const std::optional& message = std::nullopt) { + std::mutex connection_mutex; + std::condition_variable cv; + size_t active_connections = 0; + const size_t MAX_CONNECTIONS = 900; + + // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda + auto it = service_node_list.get_first_pubkey_iterator(); + auto end_it = service_node_list.get_end_pubkey_iterator(); + crypto::x25519_public_key x_pkey{0}; + uint32_t ip; + uint16_t port; + + while (it != end_it) { + service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { + x_pkey = proof.pubkey_x25519; + ip = proof.proof->public_ip; + port = proof.proof->qnet_port; + }); + + //{ + //std::unique_lock connection_lock(connection_mutex); + //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + //} + // TODO sean epee is always little, this will not work on big endian host + boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); + oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + + { + std::lock_guard connection_lock(connection_mutex); + ++active_connections; + } + + auto conn = omq->connect_remote( + addr, + [](oxenmq::ConnectionID c) { + // Successfully connected + }, + [](oxenmq::ConnectionID c, std::string_view err) { + // Failed to connect + }, + oxenmq::AuthLevel::basic + ); + + if (message) { + omq->request( + conn, + request_name, + [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { + callback(success, data); + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }, + *message + ); + } else { + omq->request( + conn, + request_name, + [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { + callback(success, data); + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + } + ); + } + + it = service_node_list.get_next_pubkey_iterator(it); + } + + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { + return active_connections == 0; + }); + + postProcess(); + } // End Service Node List }; From 2d826673328648b2c74f267f5648955832e9968e Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 3 Jan 2024 11:25:51 +1100 Subject: [PATCH 35/97] new transaction types for ethereum --- src/CMakeLists.txt | 1 - src/bls/bls_aggregator.cpp | 21 +- src/bls/bls_aggregator.h | 9 +- src/cryptonote_basic/cryptonote_basic.h | 4 +- src/cryptonote_basic/tx_extra.h | 56 +- src/cryptonote_basic/txtypes.h | 10 +- src/cryptonote_core/CMakeLists.txt | 1 - src/cryptonote_core/blockchain.cpp | 54 +- src/cryptonote_core/cryptonote_core.cpp | 30 +- src/cryptonote_core/cryptonote_core.h | 2 +- src/cryptonote_core/ethereum_transactions.cpp | 101 +- src/cryptonote_core/ethereum_transactions.h | 34 +- src/cryptonote_core/service_node_list.cpp | 6 +- src/daemon/command_parser_executor.cpp | 2 +- src/daemon/rpc_command_executor.cpp | 29 +- src/daemon/rpc_command_executor.h | 2 +- src/l2_tracker/l2_tracker.cpp | 89 +- src/l2_tracker/l2_tracker.h | 48 + src/merkle/CMakeLists.txt | 37 - src/merkle/merkle_tree_creator.cpp | 99 - src/merkle/merkle_tree_creator.hpp | 38 - src/merkle/merklecpp.h | 2030 ----------------- src/rpc/core_rpc_server.cpp | 30 +- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 5 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 21 +- utils/local-devnet/commands/blsrequest.py | 2 +- 28 files changed, 472 insertions(+), 2292 deletions(-) delete mode 100644 src/merkle/CMakeLists.txt delete mode 100644 src/merkle/merkle_tree_creator.cpp delete mode 100644 src/merkle/merkle_tree_creator.hpp delete mode 100644 src/merkle/merklecpp.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3d5ee6868..ace8a4d761 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,7 +57,6 @@ add_subdirectory(logging) add_subdirectory(lmdb) add_subdirectory(l2_tracker) add_subdirectory(bls) -add_subdirectory(merkle) add_subdirectory(multisig) add_subdirectory(net) add_subdirectory(mnemonics) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index cc3ad9857e..d0f4d909e7 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -71,8 +71,9 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) return aggregateResponse{non_signers, sig_str}; } -aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& our_merkle_root) { - const std::array hash = BLSSigner::hash(our_merkle_root); +aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& address) { + //TODO sean hash something different + const std::array hash = BLSSigner::hash(address); bls::Signature aggSig; aggSig.clear(); std::vector signers; @@ -80,9 +81,9 @@ aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& int64_t signers_index = 0; processNodes( - "bls.rewards_merkle", - [this, &aggSig, &signers, &signers_mutex, &our_merkle_root, &signers_index](bool success, std::vector data) { - if (success && data[0] == our_merkle_root) { + "bls.get_reward_balance", + [this, &aggSig, &signers, &signers_mutex, &signers_index](bool success, std::vector data) { + if (success) { bls::Signature external_signature; external_signature.setStr(data[1]); std::lock_guard lock(signers_mutex); @@ -91,15 +92,17 @@ aggregateMerkleResponse BLSAggregator::aggregateMerkleRewards(const std::string& } signers_index++; }, - [this, &aggSig, &hash] { - const auto my_signature = bls_signer->signHash(hash); - aggSig.add(my_signature); + [] { + // TODO sean does this need to happen? if we are making the request to all nodes then ive made a request to myself already? + //const auto my_signature = bls_signer->signHash(hash); + //aggSig.add(my_signature); } ); const auto non_signers = findNonSigners(signers); const auto sig_str = bls_utils::SignatureToHex(aggSig); - return aggregateMerkleResponse{our_merkle_root, non_signers, sig_str}; + // TODO sean fill this properly + return aggregateWithdrawalResponse{address, 0, 0, "", non_signers, sig_str}; } diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 18f38dfb32..049cc845d4 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -31,8 +31,11 @@ struct aggregateResponse { std::string signature; }; -struct aggregateMerkleResponse { - std::string merkle_root; +struct aggregateWithdrawalResponse { + std::string address; + uint64_t amount; + uint64_t height; + std::string signed_message; std::vector non_signers; std::string signature; }; @@ -50,7 +53,7 @@ class BLSAggregator { std::string aggregatePubkeyHex(); std::vector getPubkeys(); aggregateResponse aggregateSignatures(const std::string& message); - aggregateMerkleResponse aggregateMerkleRewards(const std::string& our_merkle_root); + aggregateWithdrawalResponse aggregateRewards(const std::string& address); std::vector findNonSigners(const std::vector& indices); diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index bfd65bee97..bf4e16e4c3 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -168,7 +168,7 @@ class transaction_prefix { bool is_transfer() const { return type == txtype::standard || type == txtype::stake || - type == txtype::oxen_name_system || type == txtype::ethereum; + type == txtype::oxen_name_system || type == txtype::ethereum_address_notification; } // not used after version 2, but remains for compatibility @@ -539,7 +539,7 @@ inline txversion transaction_prefix::get_max_version_for_hf(hf hf_version) { constexpr txtype transaction_prefix::get_max_type_for_hf(hf hf_version) { txtype result = txtype::standard; if (hf_version >= cryptonote::feature::ETH_BLS) - result = txtype::ethereum; + result = txtype::ethereum_service_node_decommission; else if (hf_version >= hf::hf15_ons) result = txtype::oxen_name_system; else if (hf_version >= hf::hf14_blink) diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 999f21d1de..d395ee53be 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -53,7 +53,10 @@ constexpr uint8_t TX_EXTRA_TAG_PADDING = 0x00, TX_EXTRA_TAG_PUBKEY = 0x01, TX_EX TX_EXTRA_TAG_TX_KEY_IMAGE_PROOFS = 0x76, TX_EXTRA_TAG_TX_KEY_IMAGE_UNLOCK = 0x77, TX_EXTRA_TAG_SERVICE_NODE_STATE_CHANGE = 0x78, TX_EXTRA_TAG_BURN = 0x79, TX_EXTRA_TAG_OXEN_NAME_SYSTEM = 0x7A, - TX_EXTRA_TAG_ETHEREUM = 0x7B, + TX_EXTRA_TAG_ETHEREUM_ADDRESS_NOTIFICATION = 0x7B, + TX_EXTRA_TAG_ETHEREUM_NEW_SERVICE_NODE = 0x7C, + TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST= 0x7D, + TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DECOMMISSION = 0x7E, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG = 0xDE; @@ -620,7 +623,7 @@ struct tx_extra_oxen_name_system { END_SERIALIZE() }; -struct tx_extra_ethereum { +struct tx_extra_ethereum_address_notification { uint8_t version = 0; std::string eth_address; std::string oxen_address; @@ -634,6 +637,42 @@ struct tx_extra_ethereum { END_SERIALIZE() }; +struct tx_extra_ethereum_new_service_node { + uint8_t version = 0; + std::string bls_key; + std::string eth_address; + std::string service_node_pubkey; + + BEGIN_SERIALIZE() + FIELD(version) + FIELD(bls_key) + FIELD(eth_address) + FIELD(service_node_pubkey) + END_SERIALIZE() +}; + +struct tx_extra_ethereum_service_node_leave_request { + uint8_t version = 0; + std::string bls_key; + + BEGIN_SERIALIZE() + FIELD(version) + FIELD(bls_key) + END_SERIALIZE() +}; + +struct tx_extra_ethereum_service_node_decommission { + uint8_t version = 0; + std::string bls_key; + bool refund_stake; + + BEGIN_SERIALIZE() + FIELD(version) + FIELD(bls_key) + FIELD(refund_stake) + END_SERIALIZE() +}; + // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: // varint tag; // varint size; @@ -659,7 +698,10 @@ using tx_extra_field = std::variant< tx_extra_burn, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate, - tx_extra_ethereum, + tx_extra_ethereum_address_notification, + tx_extra_ethereum_new_service_node, + tx_extra_ethereum_service_node_leave_request, + tx_extra_ethereum_service_node_decommission, tx_extra_padding>; } // namespace cryptonote @@ -698,4 +740,10 @@ BINARY_VARIANT_TAG(cryptonote::tx_extra_burn, cryptonote::TX_EXTRA_TAG_BURN); BINARY_VARIANT_TAG( cryptonote::tx_extra_oxen_name_system, cryptonote::TX_EXTRA_TAG_OXEN_NAME_SYSTEM); BINARY_VARIANT_TAG( - cryptonote::tx_extra_ethereum, cryptonote::TX_EXTRA_TAG_ETHEREUM); + cryptonote::tx_extra_ethereum_address_notification, cryptonote::TX_EXTRA_TAG_ETHEREUM_ADDRESS_NOTIFICATION); +BINARY_VARIANT_TAG( + cryptonote::tx_extra_ethereum_new_service_node, cryptonote::TX_EXTRA_TAG_ETHEREUM_NEW_SERVICE_NODE); +BINARY_VARIANT_TAG( + cryptonote::tx_extra_ethereum_service_node_leave_request, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST); +BINARY_VARIANT_TAG( + cryptonote::tx_extra_ethereum_service_node_decommission, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DECOMMISSION); diff --git a/src/cryptonote_basic/txtypes.h b/src/cryptonote_basic/txtypes.h index f7c1fec22a..8bac2017e7 100644 --- a/src/cryptonote_basic/txtypes.h +++ b/src/cryptonote_basic/txtypes.h @@ -24,7 +24,10 @@ enum class txtype : uint16_t { key_image_unlock, stake, oxen_name_system, - ethereum, + ethereum_address_notification, + ethereum_new_service_node, + ethereum_service_node_leave_request, + ethereum_service_node_decommission, _count }; @@ -45,7 +48,10 @@ inline constexpr std::string_view to_string(txtype type) { case txtype::key_image_unlock: return "key_image_unlock"sv; case txtype::stake: return "stake"sv; case txtype::oxen_name_system: return "oxen_name_system"sv; - case txtype::ethereum: return "ethereum"sv; + case txtype::ethereum_address_notification: return "ethereum_address_notification"sv; + case txtype::ethereum_new_service_node: return "ethereum_new_service_node"sv; + case txtype::ethereum_service_node_leave_request: return "ethereum_service_node_leave_request"sv; + case txtype::ethereum_service_node_decommission: return "ethereum_service_node_decommission"sv; default: assert(false); return "xx_unhandled_type"sv; } } diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 8b60f78de8..70a3d30df7 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -56,7 +56,6 @@ target_link_libraries(cryptonote_core SQLite::SQLite3 oxen_bls l2_tracker - merkle ethyl PRIVATE Boost::program_options diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index af4a631daf..3cc5bc146e 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4002,11 +4002,11 @@ bool Blockchain::check_tx_inputs( } } - if (tx.type == txtype::ethereum) { - cryptonote::tx_extra_ethereum entry = {}; + if (tx.type == txtype::ethereum_address_notification) { + cryptonote::tx_extra_ethereum_address_notification entry = {}; std::string fail_reason; - if (!ethereum::validate_ethereum_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason)) { - log::error(log::Cat("verify"), "Failed to validate Ethereum TX reason: {}", fail_reason); + if (!ethereum::validate_ethereum_address_notification_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum Address Notification TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; } @@ -4026,6 +4026,37 @@ bool Blockchain::check_tx_inputs( return false; } + if (tx.type == txtype::ethereum_new_service_node) { + cryptonote::tx_extra_ethereum_new_service_node entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_new_service_node_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || + !m_l2_tracker->processNewServiceNodeTx(entry.bls_key, entry.eth_address, entry.service_node_pubkey, fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum New Service Node TX reason: {}", fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } + } + if (tx.type == txtype::ethereum_service_node_leave_request) { + cryptonote::tx_extra_ethereum_service_node_leave_request entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_service_node_leave_request_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || + !m_l2_tracker->processServiceNodeLeaveRequestTx(entry.bls_key, fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Leave Request TX reason: {}", fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } + } + if (tx.type == txtype::ethereum_service_node_decommission) { + cryptonote::tx_extra_ethereum_service_node_decommission entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_service_node_decommission_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || + !m_l2_tracker->processServiceNodeDecommissionTx(entry.bls_key, entry.refund_stake, fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Decommission TX reason: {}", fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } + } + if (tx.type == txtype::state_change) { tx_extra_service_node_state_change state_change; if (!get_service_node_state_change_from_tx_extra(tx.extra, state_change, hf_version)) { @@ -4855,6 +4886,9 @@ bool Blockchain::handle_block_to_main_chain( // from the tx_pool and validating them. Each is then added // to txs. Keys spent in each are added to by the double spend check. txs.reserve(bl.tx_hashes.size()); + + m_l2_tracker->initialize_transaction_review(bl.l2_height); + for (const crypto::hash& tx_id : bl.tx_hashes) { transaction tx_tmp; std::string txblob; @@ -4987,6 +5021,17 @@ bool Blockchain::handle_block_to_main_chain( fee_summary += fee; cumulative_block_weight += tx_weight; } + if (!m_l2_tracker->finalize_transaction_review()) { + log::info( + logcat, + fg(fmt::terminal_color::red), + "Block {} with id: {} has missing ethereum transactions", + (chain_height - 1), + id); + bvc.m_verifivation_failed = true; + return_tx_to_pool(txs); + return false; + } m_blocks_txs_check.clear(); @@ -5122,6 +5167,7 @@ bool Blockchain::handle_block_to_main_chain( } if (m_sqlite_db) { + // This takes the block that is already validated and records the rewards that should be paid to the service nodes into the batching database if (!m_service_node_list.process_batching_rewards(bl)) { log::error(logcat, "Failed to add block to batch rewards DB."); bvc.m_verifivation_failed = true; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 1fab42ddb3..f109750bb1 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -74,7 +74,6 @@ extern "C" { #include "ringct/rctTypes.h" #include "uptime_proof.h" #include "version.h" -#include "merkle/merkle_tree_creator.hpp" DISABLE_VS_WARNINGS(4355) @@ -1089,21 +1088,19 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { const auto h = m_bls_signer->hash(std::string(m.data[0])); m.send_reply(m_bls_signer->signHash(h).getStr()); }) - .add_request_command("rewards_merkle", [&](oxenmq::Message& m) { + .add_request_command("get_reward_balance", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq signature request"); - if (m.data.size() != 0) + if (m.data.size() != 1) m.send_reply( "400", - "Bad request: BLS rewards merkle command must have no data parts " + "Bad request: BLS rewards command have one data part containing the address" "(received " + std::to_string(m.data.size()) + ")"); - auto [addresses, amounts] = get_blockchain_storage().sqlite_db()->get_all_accrued_earnings(); - MerkleTreeCreator rewards_merkle_tree = {}; - for (size_t i = 0; i < addresses.size(); i++) - rewards_merkle_tree.addRewardsLeaf(addresses[i], amounts[i]); - const auto rewards_merkle_root = rewards_merkle_tree.getRoot(); - const auto h = m_bls_signer->hash(rewards_merkle_root); - m.send_reply(rewards_merkle_root, m_bls_signer->signHash(h).getStr()); + uint64_t amount = get_blockchain_storage().sqlite_db()->get_accrued_earnings(std::string(m.data[0])); + //TODO sean this should concat a bunch of things instead of amount + std::string concatenated_information_for_signing = std::to_string(amount); + const auto h = m_bls_signer->hash(concatenated_information_for_signing); + m.send_reply(concatenated_information_for_signing, m_bls_signer->signHash(h).getStr()); }) .add_request_command("pubkey_request", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq bls pubkey request"); @@ -2732,13 +2729,10 @@ aggregateResponse core::bls_request() const { return resp; } //----------------------------------------------------------------------------------------------- -aggregateMerkleResponse core::aggregate_merkle_rewards() { - auto [addresses, amounts] = m_blockchain_storage.sqlite_db()->get_all_accrued_earnings(); - MerkleTreeCreator rewards_merkle_tree = {}; - for (size_t i = 0; i < addresses.size(); i++) { - rewards_merkle_tree.addRewardsLeaf(addresses[i], amounts[i]); - } - const auto resp = m_bls_aggregator->aggregateMerkleRewards(rewards_merkle_tree.getRoot()); +aggregateWithdrawalResponse core::aggregate_withdrawal_request(const std::string& ethereum_address) { + uint64_t rewards = m_blockchain_storage.sqlite_db()->get_accrued_earnings(ethereum_address); + //TODO sean something about combining the rewards and address, needs to be standard message format + const auto resp = m_bls_aggregator->aggregateRewards(std::to_string(rewards)); return resp; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 6d903833c1..399fdd8fdd 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -952,7 +952,7 @@ class core : public i_miner_handler { get_service_node_blacklisted_key_images() const; aggregateResponse bls_request() const; - aggregateMerkleResponse aggregate_merkle_rewards(); + aggregateWithdrawalResponse aggregate_withdrawal_request(const std::string& ethereum_address); std::vector get_bls_pubkeys() const; /** diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp index 9bc98c5bc2..6e8091625d 100644 --- a/src/cryptonote_core/ethereum_transactions.cpp +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -14,21 +14,21 @@ static bool check_condition( return condition; } -bool validate_ethereum_tx( +bool validate_ethereum_address_notification_tx( hf hf_version, uint64_t blockchain_height, cryptonote::transaction const& tx, - cryptonote::tx_extra_ethereum& eth_extra, + cryptonote::tx_extra_ethereum_address_notification& eth_extra, std::string* reason) { // Extract Ethereum Extra from TX { if (check_condition( - tx.type != cryptonote::txtype::ethereum, + tx.type != cryptonote::txtype::ethereum_address_notification, reason, "{} uses wrong tx type, expected={}", tx, - cryptonote::txtype::ethereum)) + cryptonote::txtype::ethereum_address_notification)) return false; if (check_condition( @@ -69,4 +69,97 @@ bool validate_ethereum_tx( return true; } +bool validate_ethereum_new_service_node_tx( + hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_new_service_node& eth_extra, + std::string* reason) { + + { + if (check_condition( + tx.type != cryptonote::txtype::ethereum_new_service_node, + reason, + "{} uses wrong tx type, expected={}", + tx, + cryptonote::txtype::ethereum_new_service_node)) + return false; + + if (check_condition( + !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), + reason, + "{} didn't have ethereum new service node data in the tx_extra", + tx)) + return false; + } + + { + // TODO sean: Add specific validation logic for BLS Key, Ethereum Address, and Service Node Public Key + } + + return true; +} + +bool validate_ethereum_service_node_leave_request_tx( + hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_leave_request& eth_extra, + std::string* reason) { + + { + if (check_condition( + tx.type != cryptonote::txtype::ethereum_service_node_leave_request, + reason, + "{} uses wrong tx type, expected={}", + tx, + cryptonote::txtype::ethereum_service_node_leave_request)) + return false; + + if (check_condition( + !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), + reason, + "{} didn't have ethereum service node leave request data in the tx_extra", + tx)) + return false; + } + + { + // TODO sean: Add specific validation logic for BLS Key + } + + return true; +} + +bool validate_ethereum_service_node_decommission_tx( + hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_decommission& eth_extra, + std::string* reason) { + + { + if (check_condition( + tx.type != cryptonote::txtype::ethereum_service_node_decommission, + reason, + "{} uses wrong tx type, expected={}", + tx, + cryptonote::txtype::ethereum_service_node_decommission)) + return false; + + if (check_condition( + !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), + reason, + "{} didn't have ethereum service node decommission data in the tx_extra", + tx)) + return false; + } + + { + // TODO sean: Add specific validation logic for BLS Key and Refund Stake Flag + } + + return true; +} + } diff --git a/src/cryptonote_core/ethereum_transactions.h b/src/cryptonote_core/ethereum_transactions.h index a96402c420..b200b78cd2 100644 --- a/src/cryptonote_core/ethereum_transactions.h +++ b/src/cryptonote_core/ethereum_transactions.h @@ -9,10 +9,32 @@ namespace ethereum { -bool validate_ethereum_tx( - cryptonote::hf hf_version, - uint64_t blockchain_height, - cryptonote::transaction const& tx, - cryptonote::tx_extra_ethereum& eth_extra, - std::string* reason); +bool validate_ethereum_address_notification_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_address_notification& eth_extra, + std::string* reason); + +bool validate_ethereum_new_service_node_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_new_service_node& eth_extra, + std::string* reason); + +bool validate_ethereum_service_node_leave_request_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_leave_request& eth_extra, + std::string* reason); + +bool validate_ethereum_service_node_decommission_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_decommission& eth_extra, + std::string* reason); + } // ethereum diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index effb094eb8..bcf0dc88ae 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -2092,12 +2092,12 @@ bool service_node_list::process_ethereum_transactions(const cryptonote::network_ return true; uint64_t block_height = cryptonote::get_block_height(block); for (const cryptonote::transaction& tx : txs) { - if (tx.type != cryptonote::txtype::ethereum) + if (tx.type != cryptonote::txtype::ethereum_address_notification) continue; - cryptonote::tx_extra_ethereum entry = {}; + cryptonote::tx_extra_ethereum_address_notification entry = {}; std::string fail_reason; - if (!ethereum::validate_ethereum_tx(block.major_version, block_height, tx, entry, &fail_reason)) { + if (!ethereum::validate_ethereum_address_notification_tx(block.major_version, block_height, tx, entry, &fail_reason)) { log::error( logcat, "ETH TX: Failed to validate for tx={}. This should have failed validation " diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 0a5f81ad75..b02e3acb7d 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -776,7 +776,7 @@ bool command_parser_executor::flush_cache(const std::vector& args) } bool command_parser_executor::claim_rewards(const std::vector& args) { - return m_executor.claim_rewards(); + return m_executor.claim_rewards(args[0]); } } // namespace daemonize diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 8b26b4cbc2..e5b16442c1 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2127,25 +2127,32 @@ bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { return true; } -bool rpc_command_executor::claim_rewards() { - auto maybe_merkle_response = try_running( [this] { return invoke(); }, - "Failed to get merkle root"); - if (!maybe_merkle_response) +bool rpc_command_executor::claim_rewards(const std::string& address) { + auto maybe_withdrawal_response = try_running( + [this, address] { + return invoke(json{{"address", address}}); + }, + "Failed to get withdrawal rewards"); + if (!maybe_withdrawal_response) return false; - auto& merkle_root_response = *maybe_merkle_response; + auto& withdrawal_response = *maybe_withdrawal_response; std::ostringstream link; link << "https://oxen-eth-webpage.vercel.app"; - link << "/?merkleRoot=" << merkle_root_response["merkle_root"]; - link << "&sig=" << merkle_root_response["signature"]; - for (const auto& non_signer : merkle_root_response["non_signers"]) { + link << "/?amount=" << withdrawal_response["amount"]; + link << "?address=" << withdrawal_response["address"]; + link << "?height=" << withdrawal_response["height"]; + link << "&sig=" << withdrawal_response["signature"]; + for (const auto& non_signer : withdrawal_response["non_signers"]) { link << "&indices=" << non_signer; } tools::msg_writer( - "Merkle Root: {}\n Signature: {}\n Link to claim rewards: {}\n", - merkle_root_response["merkle_root"], - merkle_root_response["signature"], + "Address: {}\n Amount: {}\n Height: {}\n Signature: {}\n Link to claim rewards: {}\n", + withdrawal_response["address"], + withdrawal_response["amount"], + withdrawal_response["height"], + withdrawal_response["signature"], link.str() ); return true; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 2cfd3da99d..8d166c7901 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -276,7 +276,7 @@ class rpc_command_executor final { bool flush_cache(bool bad_txs, bool invalid_blocks); - bool claim_rewards(); + bool claim_rewards(const std::string& address); bool version(); diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 52557a6d9e..18b72962fd 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -9,7 +9,7 @@ L2Tracker::L2Tracker() { L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) : rewards_contract(std::make_shared(get_contract_address(nettype), _provider)), - stop_thread(false) { + stop_thread(false), review_block_height(0) { update_thread = std::thread(&L2Tracker::update_state_thread, this); } @@ -19,7 +19,6 @@ L2Tracker::~L2Tracker() { update_thread.join(); } } - void L2Tracker::update_state_thread() { while (!stop_thread.load()) { update_state(); @@ -91,7 +90,93 @@ bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state return it != state_history.end(); } +void L2Tracker::initialize_transaction_review(uint64_t ethereum_height) { + if (review_block_height != 0) { + throw std::runtime_error( + "Review not finalized from last block, block height currently reviewing: " + + std::to_string(review_block_height) + + " new review height: " + + std::to_string(ethereum_height) + ); + } + review_block_height = ethereum_height; + get_review_transactions(); // Fills new_service_nodes, leave_requests, decommissions +} + +bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { + if (review_block_height == 0) { + fail_reason = "Review not initialized"; + oxen::log::error(logcat, "Failed to process new service node tx height {}", review_block_height); + return false; + } + + for (auto it = new_service_nodes.begin(); it != new_service_nodes.end(); ++it) { + if (it->bls_key == bls_key && it->eth_address == eth_address && it->service_node_pubkey == service_node_pubkey) { + new_service_nodes.erase(it); + return true; + } + } + + fail_reason = "New Service Node Transaction not found bls_key: " + bls_key + " eth_address: " + eth_address + " service_node_pubkey: " + service_node_pubkey; + return false; +} + +bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason) { + if (review_block_height == 0) { + fail_reason = "Review not initialized"; + oxen::log::error(logcat, "Failed to process service node leave request tx height {}", review_block_height); + return false; + } + + for (auto it = leave_requests.begin(); it != leave_requests.end(); ++it) { + if (it->bls_key == bls_key) { + leave_requests.erase(it); + return true; + } + } + + fail_reason = "Leave Request Transaction not found bls_key: " + bls_key; + return false; +} + +bool L2Tracker::processServiceNodeDecommissionTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { + if (review_block_height == 0) { + fail_reason = "Review not initialized"; + oxen::log::error(logcat, "Failed to process decommission tx height {}", review_block_height); + return false; + } + + for (auto it = decommissions.begin(); it != decommissions.end(); ++it) { + if (it->bls_key == bls_key && it->refund_stake == refund_stake) { + decommissions.erase(it); + return true; + } + } + + fail_reason = "Decommission Transaction not found bls_key: " + bls_key; + return false; +} + +bool L2Tracker::finalize_transaction_review() { + if (new_service_nodes.empty() && leave_requests.empty() && decommissions.empty()) { + review_block_height = 0; + return true; + } + return false; +} + std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); } + +void L2Tracker::get_review_transactions() { + new_service_nodes.clear(); + leave_requests.clear(); + decommissions.clear(); + if (review_block_height == 0) { + oxen::log::warning(logcat, "get_review_transactions called with 0 block height"); + return; + } + //TODO sean make this function +} diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index d1e3bb9772..38211e97ed 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -6,6 +6,36 @@ #include "cryptonote_config.h" +class NewServiceNodeTx { +public: + std::string bls_key; + std::string eth_address; + std::string service_node_pubkey; + + NewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey) + : bls_key(bls_key), eth_address(eth_address), service_node_pubkey(service_node_pubkey) {} +}; + +class ServiceNodeLeaveRequestTx { +public: + uint8_t version; + std::string bls_key; + + ServiceNodeLeaveRequestTx(uint8_t version, const std::string& bls_key) + : version(version), bls_key(bls_key) {} +}; + +class ServiceNodeDecommissionTx { +public: + uint8_t version; + std::string bls_key; + bool refund_stake; + + ServiceNodeDecommissionTx(uint8_t version, const std::string& bls_key, bool refund_stake) + : version(version), bls_key(bls_key), refund_stake(refund_stake) {} +}; + + class L2Tracker { private: std::shared_ptr rewards_contract; @@ -25,10 +55,28 @@ class L2Tracker { bool check_state_in_history(uint64_t height, const crypto::hash& state); bool check_state_in_history(uint64_t height, const std::string& state); + // These functions check whether transactions on the oxen chain should be there. + // Call initialize before we loop, then for each transaction call processTransactionType + // and the tracker will make sure that it should actually be on the oxen blockchain + // at that height. When done looping call the finalize function which will + // then check that all transactions have been accounted for. + void initialize_transaction_review(uint64_t ethereum_height); + bool processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); + bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); + bool processServiceNodeDecommissionTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); + + bool finalize_transaction_review(); + std::pair latest_state(); private: static std::string get_contract_address(const cryptonote::network_type nettype); + void get_review_transactions(); + + uint64_t review_block_height; + std::vector new_service_nodes; + std::vector leave_requests; + std::vector decommissions; // END }; diff --git a/src/merkle/CMakeLists.txt b/src/merkle/CMakeLists.txt deleted file mode 100644 index 0838a6f99b..0000000000 --- a/src/merkle/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2023, The Oxen 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. - -add_library(merkle - merkle_tree_creator.cpp - ) - -target_link_libraries(merkle - PRIVATE - common - ethyl - extra) diff --git a/src/merkle/merkle_tree_creator.cpp b/src/merkle/merkle_tree_creator.cpp deleted file mode 100644 index 0152ee3e99..0000000000 --- a/src/merkle/merkle_tree_creator.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "merkle_tree_creator.hpp" -#include "iostream" -#include -#include "logging/oxen_logger.h" - -extern "C" { -#include "crypto/keccak.h" -} - -static auto logcat = oxen::log::Cat("merkle_tree_creator"); - -MerkleTreeCreator::MerkleTreeCreator() {} - -void MerkleTreeCreator::addLeaf(const std::string& input) { - tree.insert(createMerkleKeccakHash(input)); -} - -void MerkleTreeCreator::addRewardsLeaf(const std::string& address, const uint64_t balance) { - addLeaf(abiEncode(address, balance)); -} - -void MerkleTreeCreator::addLeaves(const std::map& data) { - for (const auto& [address, balance] : data) { - std::string combined = abiEncode(address, balance); - addLeaf(combined); - } -} - -std::string MerkleTreeCreator::abiEncode(const std::string& address, uint64_t balance) { - - std::string sanitized_address = address; - // Check if input starts with "0x" prefix - if (sanitized_address.substr(0, 2) == "0x") { - sanitized_address = sanitized_address.substr(2); // remove "0x" prefix for now - } - std::string sanitized_address_padded = utils::padTo32Bytes(sanitized_address, utils::PaddingDirection::LEFT); - std::string balance_padded = utils::padTo32Bytes(utils::decimalToHex(balance), utils::PaddingDirection::LEFT); - - return "0x" + sanitized_address_padded + balance_padded; -} - -merkle::Tree::Hash MerkleTreeCreator::createMerkleKeccakHash(const std::string& input) { - // Compute Keccak hash using utils::hash - std::array hash_result = utils::hash(input); - - // Convert std::array to std::vector - std::vector hash_vector(hash_result.begin(), hash_result.end()); - return merkle::Tree::Hash(hash_vector); -} - -void MerkleTreeCreator::cncryptoCompressKeccak256( - const merkle::HashT<32>& l, - const merkle::HashT<32>& r, - merkle::HashT<32>& out) -{ - uint8_t block[32 * 2]; - memcpy(&block[0], l.bytes, 32); - memcpy(&block[32], r.bytes, 32); - - // Assuming keccak function signature remains the same as in the provided utils::hash function - keccak(block, sizeof(block), out.bytes, 32); -} - -std::string MerkleTreeCreator::getRoot() { - return tree.root().to_string(); -} - -size_t MerkleTreeCreator::getPathSize(size_t index) { - return tree.path(index)->size(); -} - -std::string MerkleTreeCreator::getPath(size_t index) { - return tree.path(index)->to_eth_string(); -} - -size_t MerkleTreeCreator::findIndex(const std::string& input) { - return tree.find_leaf_index(createMerkleKeccakHash(input)); -} - -std::string MerkleTreeCreator::updateRewardsMerkleRoot() { - //function updateRewardsMerkleRoot(bytes32 _merkleRoot) external onlyOwner { - std::string functionSelector = utils::getFunctionSignature("updateRewardsMerkleRoot(bytes32)"); - - // Concatenate the function selector and the encoded arguments - return functionSelector + getRoot(); -} - -std::string MerkleTreeCreator::validateProof(size_t index, int64_t amount) { - //function validateProof(uint256 _quantity, bytes32[] calldata _merkleProof) external { - std::string functionSelector = utils::getFunctionSignature("validateProof(uint256,bytes32[])"); - - // Convert amount to hex string and pad it to 32 bytes - std::string amount_padded = utils::padTo32Bytes(utils::decimalToHex(amount), utils::PaddingDirection::LEFT); - std::string proof_location_padded = utils::padTo32Bytes(utils::decimalToHex(64), utils::PaddingDirection::LEFT); - std::string proof_length_padded = utils::padTo32Bytes(utils::decimalToHex(getPathSize(index)), utils::PaddingDirection::LEFT); - - // Concatenate the function selector and the encoded arguments - return functionSelector + amount_padded + proof_location_padded + proof_length_padded + getPath(index); -} diff --git a/src/merkle/merkle_tree_creator.hpp b/src/merkle/merkle_tree_creator.hpp deleted file mode 100644 index 52ee902af3..0000000000 --- a/src/merkle/merkle_tree_creator.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "merklecpp.h" - -class MerkleTreeCreator { -public: - MerkleTreeCreator(); - - void addLeaf(const std::string& input); - void addRewardsLeaf(const std::string& address, const uint64_t balance); - void addLeaves(const std::map& data); - - merkle::Tree::Hash createMerkleKeccakHash(const std::string& input); - - std::string getRoot(); - std::string getPath(size_t index); - size_t getPathSize(size_t index); - size_t findIndex(const std::string& input); - - // For interacting with smart contract - std::string updateRewardsMerkleRoot(); - std::string validateProof(size_t index, int64_t amount); - - std::string abiEncode(const std::string& address, uint64_t balance); - - static inline void cncryptoCompressKeccak256( - const merkle::HashT<32>& l, - const merkle::HashT<32>& r, - merkle::HashT<32>& out - ); - - merkle::TreeT<32, cncryptoCompressKeccak256> tree; -}; - diff --git a/src/merkle/merklecpp.h b/src/merkle/merklecpp.h deleted file mode 100644 index 8a1a8268af..0000000000 --- a/src/merkle/merklecpp.h +++ /dev/null @@ -1,2030 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_OPENSSL -# include -# include -#endif - -#ifdef HAVE_MBEDTLS -# include -#endif - -#ifdef MERKLECPP_TRACE_ENABLED -// Hashes in the trace output are truncated to TRACE_HASH_SIZE bytes. -# define TRACE_HASH_SIZE 3 - -# ifndef MERKLECPP_TRACE -# include -# define MERKLECPP_TOUT std::cout -# define MERKLECPP_TRACE(X) \ - { \ - X; \ - MERKLECPP_TOUT.flush(); \ - }; -# endif -#else -# define MERKLECPP_TRACE(X) -#endif - -#define MERKLECPP_VERSION_MAJOR 1 -#define MERKLECPP_VERSION_MINOR 0 -#define MERKLECPP_VERSION_PATCH 0 - -namespace merkle -{ - static inline uint32_t convert_endianness(uint32_t n) - { - const uint32_t sz = sizeof(uint32_t); -#if defined(htobe32) - // If htobe32 happens to be a macro, use it. - return htobe32(n); -#elif defined(__LITTLE_ENDIAN__) || defined(__LITTLE_ENDIAN) - // Just as fast. - uint32_t r = 0; - for (size_t i = 0; i < sz; i++) - r |= ((n >> (8 * ((sz - 1) - i))) & 0xFF) << (8 * i); - return *reinterpret_cast(&r); -#else - // A little slower, but works for both endiannesses. - uint8_t r[8]; - for (size_t i = 0; i < sz; i++) - r[i] = (n >> (8 * ((sz - 1) - i))) & 0xFF; - return *reinterpret_cast(&r); -#endif - } - - static inline void serialise_uint64_t(uint64_t n, std::vector& bytes) - { - size_t sz = sizeof(uint64_t); - bytes.reserve(bytes.size() + sz); - for (uint64_t i = 0; i < sz; i++) - bytes.push_back((n >> (8 * (sz - i - 1))) & 0xFF); - } - - static inline uint64_t deserialise_uint64_t( - const std::vector& bytes, size_t& index) - { - uint64_t r = 0; - uint64_t sz = sizeof(uint64_t); - for (uint64_t i = 0; i < sz; i++) - r |= static_cast(bytes.at(index++)) << (8 * (sz - i - 1)); - return r; - } - - /// @brief Template for fixed-size hashes - /// @tparam SIZE Size of the hash in number of bytes - template - struct HashT - { - /// Holds the hash bytes - uint8_t bytes[SIZE]; - - /// @brief Constructs a Hash with all bytes set to zero - HashT() - { - std::fill(bytes, bytes + SIZE, 0); - } - - /// @brief Constructs a Hash from a byte buffer - /// @param bytes Buffer with hash value - HashT(const uint8_t* bytes) - { - std::copy(bytes, bytes + SIZE, this->bytes); - } - - /// @brief Constructs a Hash from a string - /// @param s String to read the hash value from - HashT(const std::string& s) - { - if (s.length() != 2 * SIZE) - throw std::runtime_error("invalid hash string"); - for (size_t i = 0; i < SIZE; i++) - { - int tmp; - sscanf(s.c_str() + 2 * i, "%02x", &tmp); - bytes[i] = tmp; - } - } - - /// @brief Deserialises a Hash from a vector of bytes - /// @param bytes Vector to read the hash value from - HashT(const std::vector& bytes) - { - if (bytes.size() < SIZE) - throw std::runtime_error("not enough bytes"); - deserialise(bytes); - } - - /// @brief Deserialises a Hash from a vector of bytes - /// @param bytes Vector to read the hash value from - /// @param position Position of the first byte in @p bytes - HashT(const std::vector& bytes, size_t& position) - { - if (bytes.size() - position < SIZE) - throw std::runtime_error("not enough bytes"); - deserialise(bytes, position); - } - - /// @brief Deserialises a Hash from an array of bytes - /// @param bytes Array to read the hash value from - HashT(const std::array& bytes) - { - std::copy(bytes.data(), bytes.data() + SIZE, this->bytes); - } - - /// @brief The size of the hash (in number of bytes) - size_t size() const - { - return SIZE; - } - - /// @brief zeros out all bytes in the hash - void zero() - { - std::fill(bytes, bytes + SIZE, 0); - } - - /// @brief The size of the serialisation of the hash (in number of bytes) - size_t serialised_size() const - { - return SIZE; - } - - /// @brief Convert a hash to a hex-encoded string - /// @param num_bytes The maximum number of bytes to convert - /// @param lower_case Enables lower-case hex characters - std::string to_string(size_t num_bytes = SIZE, bool lower_case = true) const - { - size_t num_chars = 2 * num_bytes; - std::string r(num_chars, '_'); - for (size_t i = 0; i < num_bytes; i++) - snprintf( - const_cast(r.data() + 2 * i), - num_chars + 1 - 2 * i, - lower_case ? "%02x" : "%02X", - bytes[i]); - return r; - } - - /// @brief Hash assignment operator - HashT operator=(const HashT& other) - { - std::copy(other.bytes, other.bytes + SIZE, bytes); - return *this; - } - - /// @brief Hash equality operator - bool operator==(const HashT& other) const - { - return memcmp(bytes, other.bytes, SIZE) == 0; - } - - /// @brief Hash inequality operator - bool operator!=(const HashT& other) const - { - return memcmp(bytes, other.bytes, SIZE) != 0; - } - - /// @brief Hash less than operator - bool operator<(const HashT& other) const - { - for (size_t i = 0; i < SIZE; i++) - { - if (bytes[i] < other.bytes[i]) return true; - if (bytes[i] > other.bytes[i]) return false; - } - return false; // equal - } - - /// @brief Serialises a hash - /// @param buffer Buffer to serialise to - void serialise(std::vector& buffer) const - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> HashT::serialise " << std::endl); - for (auto& b : bytes) - buffer.push_back(b); - } - - /// @brief Deserialises a hash - /// @param buffer Buffer to read the hash from - /// @param position Position of the first byte in @p bytes - void deserialise(const std::vector& buffer, size_t& position) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> HashT::deserialise " << std::endl); - if (buffer.size() - position < SIZE) - throw std::runtime_error("not enough bytes"); - for (size_t i = 0; i < sizeof(bytes); i++) - bytes[i] = buffer[position++]; - } - - /// @brief Deserialises a hash - /// @param buffer Buffer to read the hash from - void deserialise(const std::vector& buffer) - { - size_t position = 0; - deserialise(buffer, position); - } - - /// @brief Conversion operator to vector of bytes - operator std::vector() const - { - std::vector bytes; - serialise(bytes); - return bytes; - } - }; - - /// @brief Template for Merkle paths - /// @tparam HASH_SIZE Size of each hash in number of bytes - /// @tparam HASH_FUNCTION The hash function - template < - size_t HASH_SIZE, - void HASH_FUNCTION( - const HashT& l, - const HashT& r, - HashT& out)> - class PathT - { - public: - /// @brief Path direction - typedef enum - { - PATH_LEFT, - PATH_RIGHT - } Direction; - - /// @brief Path element - typedef struct - { - /// @brief The hash of the path element - HashT hash; - - /// @brief The direction at which @p hash joins at this path element - /// @note If @p direction == PATH_LEFT, @p hash joins at the left, i.e. - /// if t is the current hash, e.g. a leaf, then t' = Hash( @p hash, t ); - Direction direction; - } Element; - - /// @brief Path constructor - /// @param leaf - /// @param leaf_index - /// @param elements - /// @param max_index - PathT( - const HashT& leaf, - size_t leaf_index, - std::list&& elements, - size_t max_index) : - _leaf(leaf), - _leaf_index(leaf_index), - _max_index(max_index), - elements(elements) - {} - - /// @brief Path copy constructor - /// @param other Path to copy - PathT(const PathT& other) - { - _leaf = other._leaf; - elements = other.elements; - } - - /// @brief Path move constructor - /// @param other Path to move - PathT(PathT&& other) - { - _leaf = std::move(other._leaf); - elements = std::move(other.elements); - } - - /// @brief Deserialises a path - /// @param bytes Vector to deserialise from - PathT(const std::vector& bytes) - { - deserialise(bytes); - } - - /// @brief Deserialises a path - /// @param bytes Vector to deserialise from - /// @param position Position of the first byte in @p bytes - PathT(const std::vector& bytes, size_t& position) - { - deserialise(bytes, position); - } - - /// @brief Computes the root at the end of the path - /// @note This (re-)computes the root by hashing the path elements, it does - /// not return a previously saved root hash. - std::shared_ptr> root() const - { - std::shared_ptr> result = - std::make_shared>(_leaf); - MERKLECPP_TRACE( - MERKLECPP_TOUT << "> PathT::root " << _leaf.to_string(TRACE_HASH_SIZE) - << std::endl); - for (const Element& e : elements) - { - if (e.direction == PATH_LEFT) - { - MERKLECPP_TRACE( - MERKLECPP_TOUT << " - " << e.hash.to_string(TRACE_HASH_SIZE) - << " x " << result->to_string(TRACE_HASH_SIZE) - << std::endl); - HASH_FUNCTION(e.hash, *result, *result); - } - else - { - MERKLECPP_TRACE( - MERKLECPP_TOUT << " - " << result->to_string(TRACE_HASH_SIZE) - << " x " << e.hash.to_string(TRACE_HASH_SIZE) - << std::endl); - HASH_FUNCTION(*result, e.hash, *result); - } - } - MERKLECPP_TRACE( - MERKLECPP_TOUT << " = " << result->to_string(TRACE_HASH_SIZE) - << std::endl); - return result; - } - - /// @brief Verifies that the root at the end of the path is expected - /// @param expected_root The root hash that the elements on the path are - /// expected to hash to. - bool verify(const HashT& expected_root) const - { - return *root() == expected_root; - } - - /// @brief Serialises a path - /// @param bytes Vector of bytes to serialise to - void serialise(std::vector& bytes) const - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::serialise " << std::endl); - _leaf.serialise(bytes); - serialise_uint64_t(_leaf_index, bytes); - serialise_uint64_t(_max_index, bytes); - serialise_uint64_t(elements.size(), bytes); - for (auto& e : elements) - { - e.hash.serialise(bytes); - bytes.push_back(e.direction == PATH_LEFT ? 1 : 0); - } - } - - /// @brief Deserialises a path - /// @param bytes Vector of bytes to serialise from - /// @param position Position of the first byte in @p bytes - void deserialise(const std::vector& bytes, size_t& position) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::deserialise " << std::endl); - elements.clear(); - _leaf.deserialise(bytes, position); - _leaf_index = deserialise_uint64_t(bytes, position); - _max_index = deserialise_uint64_t(bytes, position); - size_t num_elements = deserialise_uint64_t(bytes, position); - for (size_t i = 0; i < num_elements; i++) - { - HashT hash(bytes, position); - PathT::Direction direction = - bytes.at(position++) != 0 ? PATH_LEFT : PATH_RIGHT; - PathT::Element e; - e.hash = hash; - e.direction = direction; - elements.push_back(std::move(e)); - } - } - - /// @brief Deserialises a path - /// @param bytes Vector of bytes to serialise from - void deserialise(const std::vector& bytes) - { - size_t position = 0; - deserialise(bytes, position); - } - - /// @brief Conversion operator to vector of bytes - operator std::vector() const - { - std::vector bytes; - serialise(bytes); - return bytes; - } - - /// @brief The number of elements on the path - size_t size() const - { - return elements.size(); - } - - /// @brief The size of the serialised path in number of bytes - size_t serialised_size() const - { - return sizeof(_leaf) + elements.size() * sizeof(Element); - } - - /// @brief Index of the leaf of the path - size_t leaf_index() const - { - return _leaf_index; - } - - /// @brief Maximum index of the tree at the time the path was extracted - size_t max_index() const - { - return _max_index; - } - - /// @brief Operator to extract the hash of a given path element - /// @param i Index of the path element - const HashT& operator[](size_t i) const - { - return std::next(begin(), i)->hash; - } - - /// @brief Iterator for path elements - typedef typename std::list::const_iterator const_iterator; - - /// @brief Start iterator for path elements - const_iterator begin() const - { - return elements.begin(); - } - - /// @brief End iterator for path elements - const_iterator end() const - { - return elements.end(); - } - - /// @brief Convert a path to a string - /// @param num_bytes The maximum number of bytes to convert - /// @param lower_case Enables lower-case hex characters - std::string to_string( - size_t num_bytes = HASH_SIZE, bool lower_case = true) const - { - std::stringstream stream; - stream << _leaf.to_string(num_bytes); - for (auto& e : elements) - stream << " " << e.hash.to_string(num_bytes, lower_case) - << (e.direction == PATH_LEFT ? "(L)" : "(R)"); - return stream.str(); - } - - std::string to_eth_string( - size_t num_bytes = HASH_SIZE, bool lower_case = true) const - { - std::stringstream stream; - for (auto& e : elements) - stream << e.hash.to_string(num_bytes, lower_case); - return stream.str(); - } - - /// @brief The leaf hash of the path - const HashT& leaf() const - { - return _leaf; - } - - /// @brief Equality operator for paths - bool operator==(const PathT& other) const - { - if (_leaf != other._leaf || elements.size() != other.elements.size()) - return false; - auto it = elements.begin(); - auto other_it = other.elements.begin(); - while (it != elements.end() && other_it != other.elements.end()) - { - if (it->hash != other_it->hash || it->direction != other_it->direction) - return false; - it++; - other_it++; - } - return true; - } - - /// @brief Inequality operator for paths - bool operator!=(const PathT& other) - { - return !this->operator==(other); - } - - protected: - /// @brief The leaf hash - HashT _leaf; - - /// @brief The index of the leaf - size_t _leaf_index; - - /// @brief The maximum leaf index of the tree at the time of path extraction - size_t _max_index; - - /// @brief The elements of the path - std::list elements; - }; - - /// @brief Template for Merkle trees - /// @tparam HASH_SIZE Size of each hash in number of bytes - /// @tparam HASH_FUNCTION The hash function - template < - size_t HASH_SIZE, - void HASH_FUNCTION( - const HashT& l, - const HashT& r, - HashT& out)> - class TreeT - { - protected: - /// @brief The structure of tree nodes - struct Node - { - /// @brief Constructs a new tree node - /// @param hash The hash of the node - static Node* make(const HashT& hash) - { - auto r = new Node(); - r->left = r->right = nullptr; - r->hash = hash; - r->dirty = false; - r->swapped = false; - r->update_sizes(); - assert(r->invariant()); - return r; - } - - /// @brief Constructs a new tree node - /// @param left The left child of the new node - /// @param right The right child of the new node - static Node* make(Node* left, Node* right) - { - assert(left && right); - auto r = new Node(); - - r->swapped = false; - //Ensure left child's hash is less than the right child's hash - if (left->hash < right->hash) - { - r->left = left; - r->right = right; - } - else - { - r->swapped = true; - r->left = right; - r->right = left; - } - - r->dirty = true; - r->update_sizes(); - assert(r->invariant()); - return r; - } - - /// @brief Copies a tree node - /// @param from Node to copy - /// @param leaf_nodes Current leaf nodes of the tree - /// @param num_flushed Number of flushed nodes of the tree - /// @param min_index Minimum leaf index of the tree - /// @param max_index Maximum leaf index of the tree - /// @param indent Indentation of trace output - static Node* copy_node( - const Node* from, - std::vector* leaf_nodes = nullptr, - size_t* num_flushed = nullptr, - size_t min_index = 0, - size_t max_index = SIZE_MAX, - size_t indent = 0) - { - if (from == nullptr) - return nullptr; - - Node* r = make(from->hash); - r->size = from->size; - r->height = from->height; - r->dirty = from->dirty; - r->swapped = from->swapped; - r->left = copy_node( - from->left, - leaf_nodes, - num_flushed, - min_index, - max_index, - indent + 1); - r->right = copy_node( - from->right, - leaf_nodes, - num_flushed, - min_index, - max_index, - indent + 1); - if (leaf_nodes && r->size == 1 && !r->left && !r->right) - { - if (*num_flushed == 0) - leaf_nodes->push_back(r); - else - *num_flushed = *num_flushed - 1; - } - return r; - } - - /// @brief Checks invariant of a tree node - /// @note This indicates whether some basic properties of the tree - /// construction are violated. - bool invariant() - { - bool c1 = (left && right) || (!left && !right); - bool c2 = !left || !right || (size == left->size + right->size + 1); - bool cl = !left || left->invariant(); - bool cr = !right || right->invariant(); - bool ch = height <= sizeof(size) * 8; - bool r = c1 && c2 && cl && cr && ch; - return r; - } - - ~Node() - { - assert(invariant()); - // Potential future improvement: remove recursion and keep nodes for - // future insertions - delete (left); - delete (right); - } - - /// @brief Indicates whether a subtree is full - /// @note A subtree is full if the number of nodes under a tree is - /// 2**height-1. - bool is_full() const - { - size_t max_size = (1 << height) - 1; - assert(size <= max_size); - return size == max_size; - } - - /// @brief Updates the tree size and height of the subtree under a node - void update_sizes() - { - if (left && right) - { - size = left->size + right->size + 1; - height = std::max(left->height, right->height) + 1; - } - else - size = height = 1; - } - - void sort_subnodes() - { - if (left && right) - { - if (right->hash < left->hash) - { - // Swap left and right nodes - Node* temp = left; - left = right; - right = temp; - swapped = true; - } - } - } - - - /// @brief The Hash of the node - HashT hash; - - /// @brief The left child of the node - Node* left; - - /// @brief The right child of the node - Node* right; - - /// @brief The size of the subtree - size_t size; - - /// @brief The height of the subtree - uint8_t height; - - /// @brief Dirty flag for the hash - /// @note The @p hash is only correct if this flag is false, otherwise - /// it needs to be computed by calling hash() on the node. - bool dirty; - - bool swapped; - }; - - public: - /// @brief The type of hashes in the tree - typedef HashT Hash; - - /// @brief The type of paths in the tree - typedef PathT Path; - - /// @brief The type of the tree - typedef TreeT Tree; - - /// @brief Constructs an empty tree - TreeT() {} - - /// @brief Copies a tree - TreeT(const TreeT& other) - { - *this = other; - } - - /// @brief Moves a tree - /// @param other Tree to move - TreeT(TreeT&& other) : - leaf_nodes(std::move(other.leaf_nodes)), - uninserted_leaf_nodes(std::move(other.uninserted_leaf_nodes)), - _root(std::move(other._root)), - num_flushed(other.num_flushed), - insertion_stack(std::move(other.insertion_stack)), - hashing_stack(std::move(other.hashing_stack)), - walk_stack(std::move(other.walk_stack)) - {} - - /// @brief Deserialises a tree - /// @param bytes Byte buffer containing a serialised tree - TreeT(const std::vector& bytes) - { - deserialise(bytes); - } - - /// @brief Deserialises a tree - /// @param bytes Byte buffer containing a serialised tree - /// @param position Position of the first byte within @p bytes - TreeT(const std::vector& bytes, size_t& position) - { - deserialise(bytes, position); - } - - /// @brief Constructs a tree containing one root hash - /// @param root Root hash of the tree - TreeT(const Hash& root) - { - insert(root); - } - - /// @brief Deconstructor - ~TreeT() - { - delete (_root); - for (auto n : uninserted_leaf_nodes) - delete (n); - } - - /// @brief Invariant of the tree - bool invariant() - { - return _root ? _root->invariant() : true; - } - - /// @brief Inserts a hash into the tree - /// @param hash Hash to insert - void insert(const uint8_t* hash) - { - insert(Hash(hash)); - } - - /// @brief Inserts a hash into the tree - /// @param hash Hash to insert - void insert(const Hash& hash) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> insert " - << hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - uninserted_leaf_nodes.push_back(Node::make(hash)); - statistics.num_insert++; - } - - /// @brief Inserts multiple hashes into the tree - /// @param hashes Vector of hashes to insert - void insert(const std::vector& hashes) - { - for (auto hash : hashes) - insert(hash); - } - - /// @brief Inserts multiple hashes into the tree - /// @param hashes List of hashes to insert - void insert(const std::list& hashes) - { - for (auto hash : hashes) - insert(hash); - } - - size_t find_leaf_index(const merkle::HashT& target_hash) const - { - for (size_t i = 0; i < leaf_nodes.size(); i++) - { - if (leaf_nodes[i]->hash == target_hash) // Directly compare HashT objects now - return static_cast(i); - } - - return -1; // Return -1 if not found - } - - /// @brief Flush the tree to some leaf - /// @param index Leaf index to flush the tree to - /// @note This invalidates all indicies smaller than @p index and - /// no paths from them to the root can be extracted anymore. - void flush_to(size_t index) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> flush_to " << index << std::endl;); - statistics.num_flush++; - - if (index <= min_index()) - return; - - walk_to(index, false, [this](Node*& n, bool go_right) { - if (go_right && n->left) - { - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - conflate " - << n->left->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - if (n->left && n->left->dirty) - hash(n->left); - delete (n->left->left); - n->left->left = nullptr; - delete (n->left->right); - n->left->right = nullptr; - } - return true; - }); - - size_t num_newly_flushed = index - num_flushed; - leaf_nodes.erase( - leaf_nodes.begin(), leaf_nodes.begin() + num_newly_flushed); - num_flushed += num_newly_flushed; - } - - /// @brief Retracts a tree up to some leaf index - /// @param index Leaf index to retract the tree to - /// @note This invalidates all indicies greater than @p index and - /// no paths from them to the root can be extracted anymore. - void retract_to(size_t index) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> retract_to " << index << std::endl;); - statistics.num_retract++; - - if (max_index() < index) - return; - - if (index < min_index()) - throw std::runtime_error("leaf index out of bounds"); - - if (index >= num_flushed + leaf_nodes.size()) - { - size_t over = index - (num_flushed + leaf_nodes.size()) + 1; - while (uninserted_leaf_nodes.size() > over) - { - delete (uninserted_leaf_nodes.back()); - uninserted_leaf_nodes.pop_back(); - } - return; - } - - Node* new_leaf_node = - walk_to(index, true, [this](Node*& n, bool go_right) { - bool go_left = !go_right; - n->dirty = true; - if (go_left && n->right) - { - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - eliminate " - << n->right->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - bool is_root = n == _root; - - Node* old_left = n->left; - delete (n->right); - n->right = nullptr; - - *n = *old_left; - - old_left->left = old_left->right = nullptr; - delete (old_left); - old_left = nullptr; - - if (n->left && n->right) - n->dirty = true; - - if (is_root) - { - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - new root: " - << n->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - assert(_root == n); - } - - assert(n->invariant()); - - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - after elimination: " << std::endl - << to_string(TRACE_HASH_SIZE) << std::endl;); - return false; - } - else - return true; - }); - - // The leaf is now elsewhere, save the pointer. - leaf_nodes.at(index - num_flushed) = new_leaf_node; - - size_t num_retracted = num_leaves() - index - 1; - if (num_retracted < leaf_nodes.size()) - leaf_nodes.resize(leaf_nodes.size() - num_retracted); - else - leaf_nodes.clear(); - - assert(num_leaves() == index + 1); - } - - /// @brief Assigns a tree - /// @param other The tree to assign - /// @return The tree - Tree& operator=(const Tree& other) - { - leaf_nodes.clear(); - for (auto n : uninserted_leaf_nodes) - delete (n); - uninserted_leaf_nodes.clear(); - insertion_stack.clear(); - hashing_stack.clear(); - walk_stack.clear(); - - size_t to_skip = (other.num_flushed % 2 == 0) ? 0 : 1; - _root = Node::copy_node( - other._root, - &leaf_nodes, - &to_skip, - other.min_index(), - other.max_index()); - for (auto n : other.uninserted_leaf_nodes) - uninserted_leaf_nodes.push_back(Node::copy_node(n)); - num_flushed = other.num_flushed; - assert(min_index() == other.min_index()); - assert(max_index() == other.max_index()); - return *this; - } - - /// @brief Extracts the root hash of the tree - /// @return The root hash - const Hash& root() - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> root" << std::endl;); - statistics.num_root++; - compute_root(); - assert(_root && !_root->dirty); - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - root: " << _root->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - return _root->hash; - } - - /// @brief Extracts a past root hash - /// @param index The last leaf index to consider - /// @return The root hash - /// @note This extracts the root hash of the tree at a past state, when - /// @p index was the last, right-most leaf index in the tree. It is - /// equivalent to retracting the tree to @p index and then extracting the - /// root. - std::shared_ptr past_root(size_t index) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> past_root " << index << std::endl;); - statistics.num_past_root++; - - auto p = path(index); - auto result = std::make_shared(p->leaf()); - - MERKLECPP_TRACE( - MERKLECPP_TOUT << " - " << p->to_string(TRACE_HASH_SIZE) << std::endl; - MERKLECPP_TOUT << " - " << result->to_string(TRACE_HASH_SIZE) - << std::endl;); - - for (auto e : *p) - if (e.direction == Path::Direction::PATH_LEFT) - HASH_FUNCTION(e.hash, *result, *result); - - return result; - } - - /// @brief Walks along the path from the root of a tree to a leaf - /// @param index The leaf index to walk to - /// @param update Flag to enable re-computation of node fields (like - /// subtree size) while walking - /// @param f Function to call for each node on the path; the Boolean - /// indicates whether the current step is a right or left turn. - /// @return The final leaf node in the walk - inline Node* walk_to( - size_t index, bool update, const std::function&& f) - { - if (index < min_index() || max_index() < index) - throw std::runtime_error("invalid leaf index"); - - compute_root(); - - assert(index < _root->size); - - Node* cur = _root; - size_t it = 0; - if (_root->height > 1) - it = index << (sizeof(index) * 8 - _root->height + 1); - assert(walk_stack.empty()); - - for (uint8_t height = _root->height; height > 1;) - { - assert(cur->invariant()); - bool go_right = (it >> (8 * sizeof(it) - 1)) & 0x01; - if (cur->swapped) - go_right = !go_right; - if (update) - walk_stack.push_back(cur); - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - at " << cur->hash.to_string(TRACE_HASH_SIZE) - << " (" << cur->size << "/" << (unsigned)cur->height - << ")" - << " (" << (go_right ? "R" : "L") << ")" - << std::endl;); - if (cur->height == height) - { - if (!f(cur, go_right)) - continue; - cur = (go_right ? cur->right : cur->left); - } - it <<= 1; - height--; - } - - if (update) - while (!walk_stack.empty()) - { - walk_stack.back()->update_sizes(); - walk_stack.pop_back(); - } - - return cur; - } - - /// @brief Extracts the path from a leaf index to the root of the tree - /// @param index The leaf index of the path to extract - /// @return The path - std::shared_ptr path(size_t index) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> path from " << index << std::endl;); - statistics.num_paths++; - std::list elements; - - walk_to(index, false, [&elements](Node* n, bool go_right) { - typename Path::Element e; - e.hash = go_right ? n->left->hash : n->right->hash; - e.direction = go_right ? Path::PATH_LEFT : Path::PATH_RIGHT; - elements.push_front(std::move(e)); - return true; - }); - - return std::make_shared( - leaf_node(index)->hash, index, std::move(elements), max_index()); - } - - /// @brief Extracts a past path from a leaf index to the root of the tree - /// @param index The leaf index of the path to extract - /// @param as_of The maximum leaf index to consider - /// @return The past path - /// @note This extracts a path at a past state, when @p as_of was the last, - /// right-most leaf index in the tree. It is equivalent to retracting the - /// tree to @p as_of and then extracting the path of @p index. - std::shared_ptr past_path(size_t index, size_t as_of) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> past_path from " << index - << " as of " << as_of << std::endl;); - statistics.num_past_paths++; - - if ( - (index < min_index() || max_index() < index) || - (as_of < min_index() || max_index() < as_of) || index > as_of) - throw std::runtime_error("invalid leaf indices"); - - compute_root(); - - assert(index < _root->size && as_of < _root->size); - - // Walk down the tree toward `index` and `as_of` from the root. First to - // the node at which they fork (recorded in `root_to_fork`), then - // separately to `index` and `as_of`, recording their paths - // in `fork_to_index` and `fork_to_as_of`. - std::list root_to_fork, fork_to_index, - fork_to_as_of; - Node* fork_node = nullptr; - - Node *cur_i = _root, *cur_a = _root; - size_t it_i = 0, it_a = 0; - if (_root->height > 1) - { - it_i = index << (sizeof(index) * 8 - _root->height + 1); - it_a = as_of << (sizeof(index) * 8 - _root->height + 1); - } - - for (uint8_t height = _root->height; height > 1;) - { - assert(cur_i->invariant() && cur_a->invariant()); - bool go_right_i = (it_i >> (8 * sizeof(it_i) - 1)) & 0x01; - bool go_right_a = (it_a >> (8 * sizeof(it_a) - 1)) & 0x01; - - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - at " << (unsigned)height << ": " - << cur_i->hash.to_string(TRACE_HASH_SIZE) << " (" - << cur_i->size << "/" << (unsigned)cur_i->height - << "/" << (go_right_i ? "R" : "L") << ")" - << " / " << cur_a->hash.to_string(TRACE_HASH_SIZE) - << " (" << cur_a->size << "/" - << (unsigned)cur_a->height << "/" - << (go_right_a ? "R" : "L") << ")" << std::endl;); - - if (!fork_node && go_right_i != go_right_a) - { - assert(cur_i == cur_a); - assert(!go_right_i && go_right_a); - MERKLECPP_TRACE(MERKLECPP_TOUT - << " - split at " - << cur_i->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - fork_node = cur_i; - } - - if (!fork_node) - { - // Still on the path to the fork - assert(cur_i == cur_a); - if (cur_i->height == height) - { - if (go_right_i) - { - typename Path::Element e; - e.hash = go_right_i ? cur_i->left->hash : cur_i->right->hash; - e.direction = go_right_i ? Path::PATH_LEFT : Path::PATH_RIGHT; - root_to_fork.push_back(std::move(e)); - } - cur_i = cur_a = (go_right_i ? cur_i->right : cur_i->left); - } - } - else - { - // After the fork, record paths to `index` and `as_of`. - if (cur_i->height == height) - { - typename Path::Element e; - e.hash = go_right_i ? cur_i->left->hash : cur_i->right->hash; - e.direction = go_right_i ? Path::PATH_LEFT : Path::PATH_RIGHT; - fork_to_index.push_back(std::move(e)); - cur_i = (go_right_i ? cur_i->right : cur_i->left); - } - if (cur_a->height == height) - { - // The right path does not take into account anything to the right - // of `as_of`, as those nodes were inserted into the tree after - // `as_of`. - if (go_right_a) - { - typename Path::Element e; - e.hash = go_right_a ? cur_a->left->hash : cur_a->right->hash; - e.direction = go_right_a ? Path::PATH_LEFT : Path::PATH_RIGHT; - fork_to_as_of.push_back(std::move(e)); - } - cur_a = (go_right_a ? cur_a->right : cur_a->left); - } - } - - it_i <<= 1; - it_a <<= 1; - height--; - } - - MERKLECPP_TRACE({ - MERKLECPP_TOUT << " - root to split:"; - for (auto e : root_to_fork) - MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" - << e.direction << ")"; - MERKLECPP_TOUT << std::endl; - MERKLECPP_TOUT << " - split to index:"; - for (auto e : fork_to_index) - MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" - << e.direction << ")"; - MERKLECPP_TOUT << std::endl; - MERKLECPP_TOUT << " - split to as_of:"; - for (auto e : fork_to_as_of) - MERKLECPP_TOUT << " " << e.hash.to_string(TRACE_HASH_SIZE) << "(" - << e.direction << ")"; - MERKLECPP_TOUT << std::endl; - }); - - // Reconstruct the past path from the three path segments recorded. - std::list path; - - // The hashes along the path from the fork to `index` remain unchanged. - if (!fork_to_index.empty()) - fork_to_index.pop_front(); - for (auto it = fork_to_index.rbegin(); it != fork_to_index.rend(); it++) - path.push_back(std::move(*it)); - - if (fork_node) - { - // The final hash of the path from the fork to `as_of` needs to be - // computed because that path skipped past tree nodes younger than - // `as_of`. - Hash as_of_hash = cur_a->hash; - if (!fork_to_as_of.empty()) - fork_to_as_of.pop_front(); - for (auto it = fork_to_as_of.rbegin(); it != fork_to_as_of.rend(); it++) - HASH_FUNCTION(it->hash, as_of_hash, as_of_hash); - - MERKLECPP_TRACE({ - MERKLECPP_TOUT << " - as_of hash: " - << as_of_hash.to_string(TRACE_HASH_SIZE) << std::endl; - }); - - typename Path::Element e; - e.hash = as_of_hash; - e.direction = Path::PATH_RIGHT; - path.push_back(std::move(e)); - } - - // The hashes along the path from the fork (now with new fork hash) to the - // (past) root remains unchanged. - for (auto it = root_to_fork.rbegin(); it != root_to_fork.rend(); it++) - path.push_back(std::move(*it)); - - return std::make_shared( - leaf_node(index)->hash, index, std::move(path), as_of); - } - - /// @brief Serialises the tree - /// @param bytes The vector of bytes to serialise to - void serialise(std::vector& bytes) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> serialise " << std::endl;); - - serialise_uint64_t( - leaf_nodes.size() + uninserted_leaf_nodes.size(), bytes); - serialise_uint64_t(num_flushed, bytes); - for (auto& n : leaf_nodes) - n->hash.serialise(bytes); - for (auto& n : uninserted_leaf_nodes) - n->hash.serialise(bytes); - - if (!empty()) - { - // Find conflated/flushed nodes along the left edge of the tree. - - compute_root(); - - MERKLECPP_TRACE(MERKLECPP_TOUT << to_string(TRACE_HASH_SIZE) - << std::endl;); - - std::vector extras; - walk_to(min_index(), false, [&extras](Node*& n, bool go_right) { - if (go_right) - extras.push_back(n->left); - return true; - }); - - for (size_t i = extras.size() - 1; i != SIZE_MAX; i--) - extras.at(i)->hash.serialise(bytes); - } - } - - /// @brief Serialises a segment of the tree - /// @param from Smalles leaf index to include - /// @param to Greatest leaf index to include - /// @param bytes The vector of bytes to serialise to - void serialise(size_t from, size_t to, std::vector& bytes) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> serialise from " << from << " to " - << to << std::endl;); - - if ( - (from < min_index() || max_index() < from) || - (to < min_index() || max_index() < to) || from > to) - throw std::runtime_error("invalid leaf indices"); - - serialise_uint64_t(to - from + 1, bytes); - serialise_uint64_t(from, bytes); - for (size_t i = from; i <= to; i++) - leaf(i).serialise(bytes); - - if (!empty()) - { - // Find nodes to conflate/flush along the left edge of the tree. - - compute_root(); - - MERKLECPP_TRACE(MERKLECPP_TOUT << to_string(TRACE_HASH_SIZE) - << std::endl;); - - std::vector extras; - walk_to(from, false, [&extras](Node*& n, bool go_right) { - if (go_right) - extras.push_back(n->left); - return true; - }); - - for (size_t i = extras.size() - 1; i != SIZE_MAX; i--) - extras.at(i)->hash.serialise(bytes); - } - } - - /// @brief Deserialises a tree - /// @param bytes The vector of bytes to deserialise from - void deserialise(const std::vector& bytes) - { - size_t position = 0; - deserialise(bytes, position); - } - - /// @brief Deserialises a tree - /// @param bytes The vector of bytes to deserialise from - /// @param position Position of the first byte in @p bytes - void deserialise(const std::vector& bytes, size_t& position) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> deserialise " << std::endl;); - - delete (_root); - leaf_nodes.clear(); - for (auto n : uninserted_leaf_nodes) - delete (n); - uninserted_leaf_nodes.clear(); - insertion_stack.clear(); - hashing_stack.clear(); - walk_stack.clear(); - _root = nullptr; - - size_t num_leaf_nodes = deserialise_uint64_t(bytes, position); - num_flushed = deserialise_uint64_t(bytes, position); - - leaf_nodes.reserve(num_leaf_nodes); - for (size_t i = 0; i < num_leaf_nodes; i++) - { - Node* n = Node::make(bytes.data() + position); - position += HASH_SIZE; - leaf_nodes.push_back(n); - } - - std::vector level = leaf_nodes, next_level; - size_t it = num_flushed; - uint8_t level_no = 0; - while (it != 0 || level.size() > 1) - { - // Restore extra hashes on the left edge of the tree - if (it & 0x01) - { - Hash h(bytes, position); - MERKLECPP_TRACE(MERKLECPP_TOUT << "+";); - auto n = Node::make(h); - n->height = level_no + 1; - n->size = (1 << n->height) - 1; - assert(n->invariant()); - level.insert(level.begin(), n); - } - - MERKLECPP_TRACE(for (auto& n - : level) MERKLECPP_TOUT - << " " << n->hash.to_string(TRACE_HASH_SIZE); - MERKLECPP_TOUT << std::endl;); - - // Rebuild the level - for (size_t i = 0; i < level.size(); i += 2) - { - if (i + 1 >= level.size()) - next_level.push_back(level.at(i)); - else - next_level.push_back(Node::make(level.at(i), level.at(i + 1))); - } - - level.swap(next_level); - next_level.clear(); - - it >>= 1; - level_no++; - } - - assert(level.size() == 0 || level.size() == 1); - - if (level.size() == 1) - { - _root = level.at(0); - assert(_root->invariant()); - } - } - - /// @brief Operator to serialise the tree - operator std::vector() const - { - std::vector bytes; - serialise(bytes); - return bytes; - } - - /// @brief Operator to extract a leaf hash from the tree - /// @param index Leaf index of the leaf to extract - /// @return The leaf hash - const Hash& operator[](size_t index) const - { - return leaf(index); - } - - /// @brief Extract a leaf hash from the tree - /// @param index Leaf index of the leaf to extract - /// @return The leaf hash - const Hash& leaf(size_t index) const - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> leaf " << index << std::endl;); - if (index >= num_leaves()) - throw std::runtime_error("leaf index out of bounds"); - if (index - num_flushed >= leaf_nodes.size()) - return uninserted_leaf_nodes - .at(index - num_flushed - leaf_nodes.size()) - ->hash; - else - return leaf_nodes.at(index - num_flushed)->hash; - } - - /// @brief Number of leaves in the tree - /// @note This is the abstract number of leaves in the tree (including - /// flushed leaves), not the number of nodes in memory. - /// @return The number of leaves in the tree - size_t num_leaves() const - { - return num_flushed + leaf_nodes.size() + uninserted_leaf_nodes.size(); - } - - /// @brief Minimum leaf index - /// @note The smallest leaf index for which it is safe to extract roots and - /// paths. - /// @return The minumum leaf index - size_t min_index() const - { - return num_flushed; - } - - /// @brief Maximum leaf index - /// @note The greatest leaf index for which it is safe to extract roots and - /// paths. - /// @return The maximum leaf index - size_t max_index() const - { - auto n = num_leaves(); - return n == 0 ? 0 : n - 1; - } - - /// @brief Indicates whether the tree is empty - /// @return Boolean that indicates whether the tree is empty - bool empty() const - { - return num_leaves() == 0; - } - - /// @brief Computes the size of the tree - /// @note This is the number of nodes in the tree, including leaves and - /// internal nodes. - /// @return The size of the tree - size_t size() - { - if (!uninserted_leaf_nodes.empty()) - insert_leaves(); - return _root ? _root->size : 0; - } - - /// @brief Computes the minumal number of bytes required to serialise the - /// tree - /// @return The number of bytes required to serialise the tree - size_t serialised_size() - { - size_t num_extras = 0; - - if (!empty()) - { - walk_to(min_index(), false, [&num_extras](Node*&, bool go_right) { - if (go_right) - num_extras++; - return true; - }); - } - - return sizeof(leaf_nodes.size()) + sizeof(num_flushed) + - leaf_nodes.size() * sizeof(Hash) + num_extras * sizeof(Hash); - } - - /// @brief The number of bytes required to serialise a segment of the tree - /// @param from The smallest leaf index to include - /// @param to The greatest leaf index to include - /// @return The number of bytes required to serialise the tree segment - size_t serialised_size(size_t from, size_t to) - { - size_t num_extras = 0; - walk_to(from, false, [&num_extras](Node*&, bool go_right) { - if (go_right) - num_extras++; - return true; - }); - - return sizeof(leaf_nodes.size()) + sizeof(num_flushed) + - (to - from + 1) * sizeof(Hash) + num_extras * sizeof(Hash); - } - - /// @brief Structure to hold statistical information - mutable struct Statistics - { - /// @brief The number of hashes taken by the tree via hash() - size_t num_hash = 0; - - /// @brief The number of insert() opertations performed on the tree - size_t num_insert = 0; - - /// @brief The number of root() opertations performed on the tree - size_t num_root = 0; - - /// @brief The number of past_root() opertations performed on the tree - size_t num_past_root = 0; - - /// @brief The number of flush_to() opertations performed on the tree - size_t num_flush = 0; - - /// @brief The number of retract_to() opertations performed on the tree - size_t num_retract = 0; - - /// @brief The number of paths extracted from the tree via path() - size_t num_paths = 0; - - /// @brief The number of past paths extracted from the tree via - /// past_path() - size_t num_past_paths = 0; - - /// @brief String representation of the statistics - std::string to_string() const - { - std::stringstream stream; - stream << "num_insert=" << num_insert << " num_hash=" << num_hash - << " num_root=" << num_root << " num_retract=" << num_retract - << " num_flush=" << num_flush << " num_paths=" << num_paths - << " num_past_paths=" << num_past_paths; - return stream.str(); - } - } - /// @brief Statistics - statistics; - - /// @brief Prints an ASCII representation of the tree to a stream - /// @param num_bytes The number of bytes of each node hash to print - /// @return A string representing the tree - std::string to_string(size_t num_bytes = HASH_SIZE) const - { - static const std::string dirty_hash(2 * num_bytes, '?'); - std::stringstream stream; - std::vector level, next_level; - - if (num_leaves() == 0) - { - stream << "" << std::endl; - return stream.str(); - } - - if (!_root) - { - stream << "No root." << std::endl; - } - else - { - size_t level_no = 0; - level.push_back(_root); - while (!level.empty()) - { - stream << level_no++ << ": "; - for (auto n : level) - { - stream << (n->dirty ? dirty_hash : n->hash.to_string(num_bytes)); - stream << "(" << n->size << "," << (unsigned)n->height << ")"; - if (n->left) - next_level.push_back(n->left); - if (n->right) - next_level.push_back(n->right); - stream << " "; - } - stream << std::endl << std::flush; - std::swap(level, next_level); - next_level.clear(); - } - } - - stream << "+: " - << "leaves=" << leaf_nodes.size() << ", " - << "uninserted leaves=" << uninserted_leaf_nodes.size() << ", " - << "flushed=" << num_flushed << std::endl; - stream << "S: " << statistics.to_string() << std::endl; - - return stream.str(); - } - - protected: - /// @brief Vector of leaf nodes current in the tree - std::vector leaf_nodes; - - /// @brief Vector of leaf nodes to be inserted in the tree - /// @note These nodes are conceptually inserted, but no Node objects have - /// been inserted for them yet. - std::vector uninserted_leaf_nodes; - - /// @brief Number of flushed nodes - size_t num_flushed = 0; - - /// @brief Current root node of the tree - Node* _root = nullptr; - - private: - /// @brief The structure of elements on the insertion stack - typedef struct - { - /// @brief The tree node to insert - Node* n; - /// @brief Flag to indicate whether @p n should be inserted into the - /// left or the right subtree of the current position in the tree. - bool left; - } InsertionStackElement; - - /// @brief The insertion stack - /// @note To avoid actual recursion, this holds the stack/continuation for - /// tree node insertion. - mutable std::vector insertion_stack; - - /// @brief The hashing stack - /// @note To avoid actual recursion, this holds the stack/continuation for - /// hashing (parts of the) nodes of a tree. - mutable std::vector hashing_stack; - - /// @brief The walk stack - /// @note To avoid actual recursion, this holds the stack/continuation for - /// walking down the tree from the root to a leaf. - mutable std::vector walk_stack; - - protected: - /// @brief Finds the leaf node corresponding to @p index - /// @param index The leaf node index - const Node* leaf_node(size_t index) const - { - MERKLECPP_TRACE(MERKLECPP_TOUT << "> leaf_node " << index << std::endl;); - if (index >= num_leaves()) - throw std::runtime_error("leaf index out of bounds"); - if (index - num_flushed >= leaf_nodes.size()) - return uninserted_leaf_nodes.at( - index - num_flushed - leaf_nodes.size()); - else - return leaf_nodes.at(index - num_flushed); - } - - /// @brief Computes the hash of a tree node - /// @param n The tree node - /// @param indent Indentation of trace output - /// @note This recurses down the child nodes to compute intermediate - /// hashes, if required. - void hash(Node* n, size_t indent = 2) const - { -#ifndef MERKLECPP_WITH_TRACE - (void)indent; -#endif - - assert(hashing_stack.empty()); - hashing_stack.reserve(n->height); - hashing_stack.push_back(n); - - while (!hashing_stack.empty()) - { - n = hashing_stack.back(); - assert((n->left && n->right) || (!n->left && !n->right)); - - if (n->left && n->left->dirty) - hashing_stack.push_back(n->left); - else if (n->right && n->right->dirty) - hashing_stack.push_back(n->right); - else - { - assert(n->left && n->right); - n->sort_subnodes(); - HASH_FUNCTION(n->left->hash, n->right->hash, n->hash); - statistics.num_hash++; - MERKLECPP_TRACE( - MERKLECPP_TOUT << std::string(indent, ' ') << "+ h(" - << n->left->hash.to_string(TRACE_HASH_SIZE) << ", " - << n->right->hash.to_string(TRACE_HASH_SIZE) - << ") == " << n->hash.to_string(TRACE_HASH_SIZE) - << " (" << n->size << "/" << (unsigned)n->height - << ")" << std::endl); - n->dirty = false; - hashing_stack.pop_back(); - } - } - } - - /// @brief Computes the root hash of the tree - void compute_root() - { - insert_leaves(true); - if (num_leaves() == 0) - throw std::runtime_error("empty tree does not have a root"); - assert(_root); - assert(_root->invariant()); - if (_root->dirty) - { - hash(_root); - assert(_root && !_root->dirty); - } - } - - /// @brief Inserts one new leaf into the insertion stack - /// @param n Current root node - /// @param new_leaf New leaf node to insert - /// @note This adds one new Node to the insertion stack/continuation for - /// efficient processing by process_insertion_stack() later. - void continue_insertion_stack(Node* n, Node* new_leaf) - { - while (true) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << " @ " - << n->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - assert(n->invariant()); - - if (n->is_full()) - { - Node* result = Node::make(n, new_leaf); - insertion_stack.push_back(InsertionStackElement()); - insertion_stack.back().n = result; - return; - } - else - { - assert(n->left && n->right); - insertion_stack.push_back(InsertionStackElement()); - InsertionStackElement& se = insertion_stack.back(); - se.n = n; - n->dirty = true; - if (!n->left->is_full()) - { - se.left = true; - n = n->left; - } - else - { - se.left = false; - n = n->right; - } - } - } - } - - /// @brief Processes the insertion stack/continuation - /// @param complete Indicates whether one element or the entire stack - /// should be processed - Node* process_insertion_stack(bool complete = true) - { - MERKLECPP_TRACE({ - std::string nodes; - for (size_t i = 0; i < insertion_stack.size(); i++) - nodes += - " " + insertion_stack.at(i).n->hash.to_string(TRACE_HASH_SIZE); - MERKLECPP_TOUT << " X " << (complete ? "complete" : "continue") << ":" - << nodes << std::endl; - }); - - Node* result = insertion_stack.back().n; - insertion_stack.pop_back(); - - assert(result->dirty); - result->update_sizes(); - - while (!insertion_stack.empty()) - { - InsertionStackElement& top = insertion_stack.back(); - Node* n = top.n; - bool left = top.left; - insertion_stack.pop_back(); - - if (left) - n->left = result; - else - n->right = result; - n->dirty = true; - n->update_sizes(); - - result = n; - - if (!complete && !result->is_full()) - { - MERKLECPP_TRACE(MERKLECPP_TOUT - << " X save " - << result->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - return result; - } - } - - assert(result->invariant()); - - return result; - } - - /// @brief Inserts a new leaf into the tree - /// @param root Current root node - /// @param n New leaf node to insert - void insert_leaf(Node*& root, Node* n) - { - MERKLECPP_TRACE(MERKLECPP_TOUT << " - insert_leaf " - << n->hash.to_string(TRACE_HASH_SIZE) - << std::endl;); - leaf_nodes.push_back(n); - if (insertion_stack.empty() && !root) - root = n; - else - { - continue_insertion_stack(root, n); - root = process_insertion_stack(false); - } - } - - /// @brief Inserts multiple new leaves into the tree - /// @param complete Indicates whether the insertion stack should be - /// processed to completion after insertion - void insert_leaves(bool complete = false) - { - if (!uninserted_leaf_nodes.empty()) - { - MERKLECPP_TRACE(MERKLECPP_TOUT - << "* insert_leaves " << leaf_nodes.size() << " +" - << uninserted_leaf_nodes.size() << std::endl;); - - // Sort the leaves based on their hash values before inserting - std::sort(uninserted_leaf_nodes.begin(), uninserted_leaf_nodes.end(), - [](const Node* a, const Node* b) { return a->hash < b->hash; }); - - for (auto& n : uninserted_leaf_nodes) - insert_leaf(_root, n); - uninserted_leaf_nodes.clear(); - } - if (complete && !insertion_stack.empty()) - _root = process_insertion_stack(); - } - }; - - // clang-format off - /// @brief SHA256 compression function for tree node hashes - /// @param l Left node hash - /// @param r Right node hash - /// @param out Output node hash - /// @details This function is the compression function of SHA256, which, for - /// the special case of hashing two hashes, is more efficient than a full - /// SHA256 while providing similar guarantees. - static inline void sha256_compress(const HashT<32> &l, const HashT<32> &r, HashT<32> &out) { - static const uint32_t constants[] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - }; - - uint8_t block[32 * 2]; - memcpy(&block[0], l.bytes, 32); - memcpy(&block[32], r.bytes, 32); - - static const uint32_t s[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; - - uint32_t cws[64] = {0}; - - for (int i=0; i < 16; i++) - cws[i] = convert_endianness(((int32_t *)block)[i]); - - for (int i = 16; i < 64; i++) { - uint32_t t16 = cws[i - 16]; - uint32_t t15 = cws[i - 15]; - uint32_t t7 = cws[i - 7]; - uint32_t t2 = cws[i - 2]; - uint32_t s1 = (t2 >> 17 | t2 << 15) ^ ((t2 >> 19 | t2 << 13) ^ t2 >> 10); - uint32_t s0 = (t15 >> 7 | t15 << 25) ^ ((t15 >> 18 | t15 << 14) ^ t15 >> 3); - cws[i] = (s1 + t7 + s0 + t16); - } - - uint32_t h[8]; - for (int i=0; i < 8; i++) - h[i] = s[i]; - - for (int i=0; i < 64; i++) { - uint32_t a0 = h[0], b0 = h[1], c0 = h[2], d0 = h[3], e0 = h[4], f0 = h[5], g0 = h[6], h03 = h[7]; - uint32_t w = cws[i]; - uint32_t t1 = h03 + ((e0 >> 6 | e0 << 26) ^ ((e0 >> 11 | e0 << 21) ^ (e0 >> 25 | e0 << 7))) + ((e0 & f0) ^ (~e0 & g0)) + constants[i] + w; - uint32_t t2 = ((a0 >> 2 | a0 << 30) ^ ((a0 >> 13 | a0 << 19) ^ (a0 >> 22 | a0 << 10))) + ((a0 & b0) ^ ((a0 & c0) ^ (b0 & c0))); - h[0] = t1 + t2; - h[1] = a0; - h[2] = b0; - h[3] = c0; - h[4] = d0 + t1; - h[5] = e0; - h[6] = f0; - h[7] = g0; - } - - for (int i=0; i < 8; i++) - ((uint32_t*)out.bytes)[i] = convert_endianness(s[i] + h[i]); - } - // clang-format on - -#ifdef HAVE_OPENSSL - /// @brief OpenSSL SHA256 - /// @param l Left node hash - /// @param r Right node hash - /// @param out Output node hash - /// @note Some versions of OpenSSL may not provide SHA256_Transform. - static inline void sha256_openssl( - const merkle::HashT<32>& l, - const merkle::HashT<32>& r, - merkle::HashT<32>& out) - { - uint8_t block[32 * 2]; - memcpy(&block[0], l.bytes, 32); - memcpy(&block[32], r.bytes, 32); - - const EVP_MD* md = EVP_sha256(); - int rc = - EVP_Digest(&block[0], sizeof(block), out.bytes, nullptr, md, nullptr); - if (rc != 1) - { - throw std::runtime_error("EVP_Digest failed: " + std::to_string(rc)); - } - } -#endif - -#ifdef HAVE_MBEDTLS - /// @brief mbedTLS SHA256 compression function - /// @param l Left node hash - /// @param r Right node hash - /// @param out Output node hash - /// @note Technically, mbedtls_internal_sha256_process is marked for internal - /// use only. - static inline void sha256_compress_mbedtls( - const HashT<32>& l, const HashT<32>& r, HashT<32>& out) - { - unsigned char block[32 * 2]; - memcpy(&block[0], l.bytes, 32); - memcpy(&block[32], r.bytes, 32); - - mbedtls_sha256_context ctx; - mbedtls_sha256_init(&ctx); - mbedtls_sha256_starts_ret(&ctx, false); - mbedtls_internal_sha256_process(&ctx, &block[0]); - - for (int i = 0; i < 8; i++) - ((uint32_t*)out.bytes)[i] = htobe32(ctx.state[i]); - } - - /// @brief mbedTLS SHA256 - /// @param l Left node hash - /// @param r Right node hash - /// @param out Output node hash - static inline void sha256_mbedtls( - const merkle::HashT<32>& l, - const merkle::HashT<32>& r, - merkle::HashT<32>& out) - { - uint8_t block[32 * 2]; - memcpy(&block[0], l.bytes, 32); - memcpy(&block[32], r.bytes, 32); - mbedtls_sha256_ret(block, sizeof(block), out.bytes, false); - } -#endif - - /// @brief Type of hashes in the default tree type - typedef HashT<32> Hash; - - /// @brief Type of paths in the default tree type - typedef PathT<32, sha256_compress> Path; - - /// @brief Default tree with default hash size and function - typedef TreeT<32, sha256_compress> Tree; -}; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9e162130c8..57de16e94a 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -745,10 +745,23 @@ namespace { _load_owner(ons, "owner", x.owner); _load_owner(ons, "backup_owner", x.backup_owner); } - void operator()(const tx_extra_ethereum& x) { + void operator()(const tx_extra_ethereum_address_notification& x) { set("eth_address", x.eth_address); set("signature", x.signature); } + void operator()(const tx_extra_ethereum_new_service_node& x) { + set("bls_key", x.bls_key); + set("eth_address", x.eth_address); + set("service_node_pubkey", x.service_node_pubkey); + } + void operator()(const tx_extra_ethereum_service_node_leave_request& x) { + set("bls_key", x.bls_key); + } + void operator()(const tx_extra_ethereum_service_node_decommission& x) { + set("bls_key", x.bls_key); + set("refund_stake", x.refund_stake); + } + // Ignore these fields: void operator()(const tx_extra_padding&) {} @@ -2535,12 +2548,15 @@ void core_rpc_server::invoke( BLS_REQUEST& bls_request, rpc_context context) { return; } //------------------------------------------------------------------------------------------------------------------------------ -void core_rpc_server::invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context context) { - const aggregateMerkleResponse bls_merkle_signature_response = m_core.aggregate_merkle_rewards(); - bls_merkle_request.response["status"] = STATUS_OK; - bls_merkle_request.response["merkle_root"] = bls_merkle_signature_response.merkle_root; - bls_merkle_request.response["signature"] = bls_merkle_signature_response.signature; - bls_merkle_request.response["non_signers"] = bls_merkle_signature_response.non_signers; +void core_rpc_server::invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context) { + const aggregateWithdrawalResponse bls_withdrawal_signature_response = m_core.aggregate_withdrawal_request(bls_withdrawal_request.request.address); + bls_withdrawal_request.response["status"] = STATUS_OK; + bls_withdrawal_request.response["address"] = bls_withdrawal_signature_response.address; + bls_withdrawal_request.response["height"] = bls_withdrawal_signature_response.height; + bls_withdrawal_request.response["amount"] = bls_withdrawal_signature_response.amount; + bls_withdrawal_request.response["signed_message"] = bls_withdrawal_signature_response.signed_message; + bls_withdrawal_request.response["signature"] = bls_withdrawal_signature_response.signature; + bls_withdrawal_request.response["non_signers"] = bls_withdrawal_signature_response.non_signers; return; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 5b055df22f..8bec711d61 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -184,7 +184,7 @@ class core_rpc_server { GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES& get_service_node_blacklisted_key_images, rpc_context context); void invoke(BLS_REQUEST& bls_request, rpc_context context); - void invoke(BLS_MERKLE_REQUEST& bls_merkle_request, rpc_context context); + void invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context); void invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index b814a4e03f..1428052d91 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -487,4 +487,9 @@ void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in) { "staking_requirement", required{cmd.request.staking_requirement}); } + +void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in) { + get_values( in, "address", required{cmd.request.address}); +} + } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 6767e946c1..400840be3b 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -54,4 +54,5 @@ void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); +void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in); } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7fbb046c24..65c855b8c3 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2303,18 +2303,27 @@ struct BLS_REQUEST : PUBLIC, NO_ARGS { /// RPC: bls request /// -/// Sends a request out for all nodes to sign a BLS signature of the merkle root +/// Sends a request out for all nodes to sign a BLS signature of the amount that an address is allowed to withdraw /// -/// Inputs: None +/// Inputs: +/// +/// - `address` -- this address will be looked up in the batching database at the latest height to see how much they can withdraw /// /// Outputs: /// /// - `status` -- generic RPC error code; "OK" means the request was successful. -/// - `merkle_root` -- The Root that has been signed by the network +/// - `address` -- The requested address +/// - `amount` -- The amount that the address can claim +/// - `height` -- The oxen blockchain height that the rewards have been calculated at +/// - `signed_message` -- The Root that has been signed by the network /// - `signature` -- BLS signature of the merkle root /// - `non_signers` -- array of indices of the nodes that did not sign -struct BLS_MERKLE_REQUEST : PUBLIC, NO_ARGS { - static constexpr auto names() { return NAMES("bls_merkle_request"); } +struct BLS_WITHDRAWAL_REQUEST : PUBLIC { + static constexpr auto names() { return NAMES("bls_withdrawal_request"); } + + struct request_parameters { + std::string address; + } request; }; /// RPC: bls pubkey request @@ -2724,7 +2733,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, BLS_REQUEST, - BLS_MERKLE_REQUEST, + BLS_WITHDRAWAL_REQUEST, BLS_PUBKEYS, GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, diff --git a/utils/local-devnet/commands/blsrequest.py b/utils/local-devnet/commands/blsrequest.py index e9fcfd8025..91e9c5738c 100755 --- a/utils/local-devnet/commands/blsrequest.py +++ b/utils/local-devnet/commands/blsrequest.py @@ -24,7 +24,7 @@ def instruct_daemon(method, params): params = {} -answer = instruct_daemon('bls_pubkey_request', params) +answer = instruct_daemon('bls_merkle_request', params) # print(json.dumps(answer['result']['block_header']['timestamp'], indent=4, sort_keys=True)) From 8b83eb6ad9a6cb11f07aee7fe1486b49716fe972 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 4 Jan 2024 12:21:13 +1100 Subject: [PATCH 36/97] extract logs from block, get state changes as transactions in l2 tracker --- src/l2_tracker/l2_tracker.cpp | 65 ++++++++++++++++++------- src/l2_tracker/l2_tracker.h | 54 ++++++++------------- src/l2_tracker/rewards_contract.cpp | 74 +++++++++++++++++++++++++++++ src/l2_tracker/rewards_contract.h | 49 ++++++++++++++++++- 4 files changed, 190 insertions(+), 52 deletions(-) diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 18b72962fd..e82d5f0837 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -1,5 +1,8 @@ #include "l2_tracker.h" + #include +#include + #include "logging/oxen_logger.h" static auto logcat = oxen::log::Cat("l2_tracker"); @@ -26,40 +29,50 @@ void L2Tracker::update_state_thread() { } } -void L2Tracker::insert_in_order(const StateResponse& new_state) { +void L2Tracker::insert_in_order(State&& new_state) { // Check if the state with the same height already exists auto it = std::find_if(state_history.begin(), state_history.end(), - [&new_state](const StateResponse& state) { + [&new_state](const State& state) { return state.height == new_state.height; }); // If it doesn't exist, insert it in the appropriate location if (it == state_history.end()) { auto insert_loc = std::upper_bound(state_history.begin(), state_history.end(), new_state, - [](const StateResponse& a, const StateResponse& b) { + [](const State& a, const State& b) { return a.height > b.height; }); - state_history.insert(insert_loc, new_state); + state_history.insert(insert_loc, std::move(new_state)); // Use std::move here + } +} + +void L2Tracker::process_logs_for_state(State& state) { + std::vector logs = rewards_contract->Logs(state.height); + for (const auto& log : logs) { + auto transaction = log.getLogTransaction(); + if (transaction) { + state.state_changes.emplace_back(*transaction); + } } } void L2Tracker::update_state() { - //TODO sean, create counter for failed state updates, if it fails too many times then throw try { // Get latest state - StateResponse new_state = rewards_contract->State(); - insert_in_order(new_state); + State new_state(rewards_contract->State()); + process_logs_for_state(new_state); + insert_in_order(std::move(new_state)); // Check for missing heights between the first and second entries - std::vector missing_heights; if (state_history.size() > 1) { uint64_t first_height = state_history[0].height; uint64_t second_height = state_history[1].height; for (uint64_t h = first_height - 1; h > second_height; --h) { - new_state = rewards_contract->State(h); - insert_in_order(new_state); + State missing_state(rewards_contract->State(h)); + process_logs_for_state(missing_state); + insert_in_order(std::move(missing_state)); } } } catch (const std::exception& e) { @@ -77,15 +90,15 @@ std::pair L2Tracker::latest_state() { return std::make_pair(latest_state.height, return_hash); } -bool L2Tracker::check_state_in_history(uint64_t height, const crypto::hash& state) { - std::string state_str = tools::type_to_hex(state); +bool L2Tracker::check_state_in_history(uint64_t height, const crypto::hash& state_root) { + std::string state_str = tools::type_to_hex(state_root); return check_state_in_history(height, state_str); } -bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state) { +bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state_root) { auto it = std::find_if(state_history.begin(), state_history.end(), - [height, &state](const StateResponse& stateResponse) { - return stateResponse.height == height && stateResponse.state == state; + [height, &state_root](const State& state) { + return state.height == height && state.state == state_root; }); return it != state_history.end(); } @@ -178,5 +191,25 @@ void L2Tracker::get_review_transactions() { oxen::log::warning(logcat, "get_review_transactions called with 0 block height"); return; } - //TODO sean make this function + for (const auto& state : state_history) { + if (state.height == review_block_height) { + for (const auto& transactionVariant : state.state_changes) { + std::visit([this](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + new_service_nodes.push_back(arg); + } else if constexpr (std::is_same_v) { + leave_requests.push_back(arg); + } else if constexpr (std::is_same_v) { + decommissions.push_back(arg); + } + }, transactionVariant); + } + break; // Exit the loop once the matching state is processed + } + if (state.height < review_block_height) { + // State history should be ordered, if we go below our desired height then its not there so throw + throw std::runtime_error("Did not find review height in state history"); + } + } } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 38211e97ed..531c18b758 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -6,43 +6,31 @@ #include "cryptonote_config.h" -class NewServiceNodeTx { -public: - std::string bls_key; - std::string eth_address; - std::string service_node_pubkey; - - NewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey) - : bls_key(bls_key), eth_address(eth_address), service_node_pubkey(service_node_pubkey) {} -}; - -class ServiceNodeLeaveRequestTx { -public: - uint8_t version; - std::string bls_key; - ServiceNodeLeaveRequestTx(uint8_t version, const std::string& bls_key) - : version(version), bls_key(bls_key) {} -}; - -class ServiceNodeDecommissionTx { -public: - uint8_t version; - std::string bls_key; - bool refund_stake; +struct State { + uint64_t height; + std::string state; + std::vector state_changes; // List of transactions that changed the state this block - ServiceNodeDecommissionTx(uint8_t version, const std::string& bls_key, bool refund_stake) - : version(version), bls_key(bls_key), refund_stake(refund_stake) {} + State(uint64_t _height, const std::string& _state, const std::vector& _state_changes) + : height(_height), state(_state), state_changes(_state_changes) {} + State(const StateResponse& _state_response) + : height(_state_response.height), state(_state_response.state) {} }; class L2Tracker { private: std::shared_ptr rewards_contract; - std::vector state_history; + std::vector state_history; std::atomic stop_thread; std::thread update_thread; + uint64_t review_block_height; + std::vector new_service_nodes; + std::vector leave_requests; + std::vector decommissions; + public: L2Tracker(); L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& client); @@ -50,10 +38,12 @@ class L2Tracker { void update_state_thread(); void update_state(); - void insert_in_order(const StateResponse& new_state); + void insert_in_order(State&& new_state); - bool check_state_in_history(uint64_t height, const crypto::hash& state); - bool check_state_in_history(uint64_t height, const std::string& state); + void process_logs_for_state(State& state); + + bool check_state_in_history(uint64_t height, const crypto::hash& state_root); + bool check_state_in_history(uint64_t height, const std::string& state_root); // These functions check whether transactions on the oxen chain should be there. // Call initialize before we loop, then for each transaction call processTransactionType @@ -72,11 +62,5 @@ class L2Tracker { private: static std::string get_contract_address(const cryptonote::network_type nettype); void get_review_transactions(); - - uint64_t review_block_height; - std::vector new_service_nodes; - std::vector leave_requests; - std::vector decommissions; - // END }; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 2528cd25cc..28912986e7 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -1,5 +1,67 @@ #include "rewards_contract.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuseless-cast" +#include +#pragma GCC diagnostic pop + +TransactionType RewardsLogEntry::getLogType() const { + if (topics.empty()) { + throw std::runtime_error("No topics in log entry"); + } + // keccak256('NewServiceNode(uint64,address,BN256G1.G1Point,uint256,uint256)') + if (topics[0] == "da543ad9a040217dd88f378dc7fb7759316d2cf046a7eb1106294e6a30761458") { + return TransactionType::NewServiceNode; + // keccak256('ServiceNodeRemovalRequest(uint64,address,BN256G1.G1Point)') + } else if (topics[0] == "cea31df077839a5b6d4f079cb9d9e37a75fd2e0494232d8b3b90c3b77eb2f08d") { + return TransactionType::ServiceNodeLeaveRequest; + // keccak256('ServiceNodeLiquidated(uint64,address,BN256G1.G1Point)') + } else if (topics[0] == "5d7e17cd2edcc6334f540934c0f7150c32f6655120e51ab941b585014b28679a") { + return TransactionType::ServiceNodeDecommission; + } + return TransactionType::Other; +} + +std::optional RewardsLogEntry::getLogTransaction() const { + TransactionType type = getLogType(); + switch (type) { + case TransactionType::NewServiceNode: { + // event NewServiceNode(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey, uint256 serviceNodePubkey, uint256 serviceNodeSignature); + // service node id is a topic so only address and pubkey are in data + // address is 32 bytes , pubkey is 64 bytes and serviceNodePubkey is 32 bytes + // + // pull 32 bytes from start + std::string eth_address = data.substr(2, 64); + // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) + std::string bls_key = data.substr(64 + 2, 128); + // pull 32 bytes (64 characters) + std::string service_node_pubkey =data.substr(128 + 64 + 2, 64); + return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey); + } + case TransactionType::ServiceNodeLeaveRequest: { + // event ServiceNodeRemovalRequest(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey); + // service node id is a topic so only address and pubkey are in data + // address is 32 bytes and pubkey is 64 bytes, + // + // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) + std::string bls_key = data.substr(64 + 2, 128); + return ServiceNodeLeaveRequestTx(bls_key); + } + case TransactionType::ServiceNodeDecommission: { + // event ServiceNodeLiquidated(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey); + // service node id is a topic so only address and pubkey are in data + // address is 32 bytes and pubkey is 64 bytes, + // + // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) + std::string bls_key = data.substr(64 + 2, 128); + bool refund_stake = true; + return ServiceNodeDecommissionTx(bls_key, refund_stake); + } + default: + return std::nullopt; + } +} + RewardsContract::RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider) : contractAddress(_contractAddress), provider(std::move(_provider)) {} @@ -33,3 +95,15 @@ StateResponse RewardsContract::State(std::optional height) { return StateResponse{blockHeight, blockHash}; } +std::vector RewardsContract::Logs(uint64_t height) { + std::vector logEntries; + // Make the RPC call + const auto logs = provider->getLogs(height, contractAddress); + + for (const auto& log : logs) { + logEntries.emplace_back(RewardsLogEntry(log)); + } + + return logEntries; +} + diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index 00cfab5957..29b279e173 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -1,9 +1,54 @@ #pragma once #include #include +#include -//#include "transaction.hpp" #include +#include + +enum class TransactionType { + NewServiceNode, + ServiceNodeLeaveRequest, + ServiceNodeDecommission, + Other +}; + +class NewServiceNodeTx { +public: + std::string bls_key; + std::string eth_address; + std::string service_node_pubkey; + + NewServiceNodeTx(const std::string& _bls_key, const std::string& _eth_address, const std::string& _service_node_pubkey) + : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey) {} +}; + +class ServiceNodeLeaveRequestTx { +public: + std::string bls_key; + + ServiceNodeLeaveRequestTx(const std::string& _bls_key) + : bls_key(_bls_key) {} +}; + +class ServiceNodeDecommissionTx { +public: + std::string bls_key; + bool refund_stake; + + ServiceNodeDecommissionTx(const std::string& _bls_key, bool _refund_stake) + : bls_key(_bls_key), refund_stake(_refund_stake) {} +}; + +using TransactionStateChangeVariant = std::variant; + + +class RewardsLogEntry : public LogEntry { +public: + RewardsLogEntry(const LogEntry& log) : LogEntry(log) {} + TransactionType getLogType() const; + std::optional getLogTransaction() const; +}; struct StateResponse { uint64_t height; @@ -18,6 +63,8 @@ class RewardsContract { StateResponse State(); StateResponse State(std::optional height); + std::vector Logs(uint64_t height); + private: std::string contractAddress; std::shared_ptr provider; From 8811a26fcc9e7cc79a8a9d2f2bdd41951c75d977 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 12 Jan 2024 12:00:44 +1100 Subject: [PATCH 37/97] transactions added to block --- src/cryptonote_basic/cryptonote_basic.h | 2 +- src/cryptonote_basic/tx_extra.h | 14 +- src/cryptonote_basic/txtypes.h | 4 +- src/cryptonote_core/blockchain.cpp | 48 ++++- src/cryptonote_core/blockchain.h | 2 + src/cryptonote_core/cryptonote_tx_utils.cpp | 3 +- src/cryptonote_core/ethereum_transactions.cpp | 10 +- src/cryptonote_core/ethereum_transactions.h | 4 +- src/cryptonote_core/service_node_list.cpp | 191 ++++++++++++++++++ src/cryptonote_core/service_node_list.h | 29 +++ .../service_node_quorum_cop.cpp | 1 + src/l2_tracker/l2_tracker.cpp | 39 +++- src/l2_tracker/l2_tracker.h | 5 +- src/l2_tracker/rewards_contract.cpp | 13 +- src/l2_tracker/rewards_contract.h | 16 +- src/rpc/core_rpc_server.cpp | 8 +- 16 files changed, 333 insertions(+), 56 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index bf4e16e4c3..d78fffd94a 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -539,7 +539,7 @@ inline txversion transaction_prefix::get_max_version_for_hf(hf hf_version) { constexpr txtype transaction_prefix::get_max_type_for_hf(hf hf_version) { txtype result = txtype::standard; if (hf_version >= cryptonote::feature::ETH_BLS) - result = txtype::ethereum_service_node_decommission; + result = txtype::ethereum_service_node_deregister; else if (hf_version >= hf::hf15_ons) result = txtype::oxen_name_system; else if (hf_version >= hf::hf14_blink) diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index d395ee53be..ef88d55b49 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -56,7 +56,7 @@ constexpr uint8_t TX_EXTRA_TAG_PADDING = 0x00, TX_EXTRA_TAG_PUBKEY = 0x01, TX_EX TX_EXTRA_TAG_ETHEREUM_ADDRESS_NOTIFICATION = 0x7B, TX_EXTRA_TAG_ETHEREUM_NEW_SERVICE_NODE = 0x7C, TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST= 0x7D, - TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DECOMMISSION = 0x7E, + TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DEREGISTER = 0x7E, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG = 0xDE; @@ -627,7 +627,7 @@ struct tx_extra_ethereum_address_notification { uint8_t version = 0; std::string eth_address; std::string oxen_address; - std::string signature; + crypto::signature signature; BEGIN_SERIALIZE() FIELD(version) @@ -641,13 +641,15 @@ struct tx_extra_ethereum_new_service_node { uint8_t version = 0; std::string bls_key; std::string eth_address; - std::string service_node_pubkey; + crypto::public_key service_node_pubkey; + crypto::signature signature; BEGIN_SERIALIZE() FIELD(version) FIELD(bls_key) FIELD(eth_address) FIELD(service_node_pubkey) + FIELD(signature) END_SERIALIZE() }; @@ -661,7 +663,7 @@ struct tx_extra_ethereum_service_node_leave_request { END_SERIALIZE() }; -struct tx_extra_ethereum_service_node_decommission { +struct tx_extra_ethereum_service_node_deregister { uint8_t version = 0; std::string bls_key; bool refund_stake; @@ -701,7 +703,7 @@ using tx_extra_field = std::variant< tx_extra_ethereum_address_notification, tx_extra_ethereum_new_service_node, tx_extra_ethereum_service_node_leave_request, - tx_extra_ethereum_service_node_decommission, + tx_extra_ethereum_service_node_deregister, tx_extra_padding>; } // namespace cryptonote @@ -746,4 +748,4 @@ BINARY_VARIANT_TAG( BINARY_VARIANT_TAG( cryptonote::tx_extra_ethereum_service_node_leave_request, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST); BINARY_VARIANT_TAG( - cryptonote::tx_extra_ethereum_service_node_decommission, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DECOMMISSION); + cryptonote::tx_extra_ethereum_service_node_deregister, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DEREGISTER); diff --git a/src/cryptonote_basic/txtypes.h b/src/cryptonote_basic/txtypes.h index 8bac2017e7..a24d4ead34 100644 --- a/src/cryptonote_basic/txtypes.h +++ b/src/cryptonote_basic/txtypes.h @@ -27,7 +27,7 @@ enum class txtype : uint16_t { ethereum_address_notification, ethereum_new_service_node, ethereum_service_node_leave_request, - ethereum_service_node_decommission, + ethereum_service_node_deregister, _count }; @@ -51,7 +51,7 @@ inline constexpr std::string_view to_string(txtype type) { case txtype::ethereum_address_notification: return "ethereum_address_notification"sv; case txtype::ethereum_new_service_node: return "ethereum_new_service_node"sv; case txtype::ethereum_service_node_leave_request: return "ethereum_service_node_leave_request"sv; - case txtype::ethereum_service_node_decommission: return "ethereum_service_node_decommission"sv; + case txtype::ethereum_service_node_deregister: return "ethereum_service_node_deregister"sv; default: assert(false); return "xx_unhandled_type"sv; } } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3cc5bc146e..88dc9e440a 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1646,6 +1646,13 @@ bool Blockchain::create_block_template_internal( uint64_t already_generated_coins; uint64_t pool_cookie; + //TODO sean + std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); + // Fill tx_pool with ethereum transactions before we build the block + // TODO sean get the previous height from somewhere + std::vector eth_transactions = m_l2_tracker->get_block_transactions(b.l2_height -1, b.l2_height); + process_ethereum_transactions(eth_transactions); + auto lock = tools::unique_locks(m_tx_pool, *this); if (m_btc_valid && !from_block) { // The pool cookie is atomic. The lack of locking is OK, as if it changes @@ -1676,6 +1683,7 @@ bool Blockchain::create_block_template_internal( invalidate_block_template_cache(); } + // from_block is usually nullptr, used to build altchains if (from_block) { // build alternative subchain, front -> mainchain, back -> alternative head @@ -1883,8 +1891,6 @@ bool Blockchain::create_block_template_internal( b.service_node_winner_key = crypto::null; b.reward = block_rewards; - //TODO sean - std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); b.height = height; return true; } @@ -1936,6 +1942,32 @@ bool Blockchain::create_next_pulse_block_template( b.pulse.validator_bitset = validator_bitset; return result; } + +void Blockchain::process_ethereum_transactions(const std::vector& transactions) { + auto hf_version = get_network_version(); + for (const auto& tx_variant : transactions) { + transaction tx; + tx.version = transaction_prefix::get_max_version_for_hf(hf_version); + + std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + tx.type = txtype::ethereum_new_service_node; + } else if constexpr (std::is_same_v) { + tx.type = txtype::ethereum_service_node_leave_request; + } else if constexpr (std::is_same_v) { + tx.type = txtype::ethereum_service_node_deregister; + } + }, tx_variant); + const size_t tx_weight = get_transaction_weight(tx); + const crypto::hash tx_hash = get_transaction_hash(tx); + tx_verification_context tvc = {}; + std::string blob; + + // Add transaction to memory pool + m_tx_pool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_pool_options::from_block(), hf_version); + } +} //------------------------------------------------------------------ // for an alternate chain, get the timestamps from the main chain to complete // the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. @@ -4030,7 +4062,7 @@ bool Blockchain::check_tx_inputs( cryptonote::tx_extra_ethereum_new_service_node entry = {}; std::string fail_reason; if (!ethereum::validate_ethereum_new_service_node_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !m_l2_tracker->processNewServiceNodeTx(entry.bls_key, entry.eth_address, entry.service_node_pubkey, fail_reason)) { + !m_l2_tracker->processNewServiceNodeTx(entry.bls_key, entry.eth_address, tools::type_to_hex(entry.service_node_pubkey), fail_reason)) { log::error(log::Cat("verify"), "Failed to validate Ethereum New Service Node TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; @@ -4046,12 +4078,12 @@ bool Blockchain::check_tx_inputs( return false; } } - if (tx.type == txtype::ethereum_service_node_decommission) { - cryptonote::tx_extra_ethereum_service_node_decommission entry = {}; + if (tx.type == txtype::ethereum_service_node_deregister) { + cryptonote::tx_extra_ethereum_service_node_deregister entry = {}; std::string fail_reason; - if (!ethereum::validate_ethereum_service_node_decommission_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !m_l2_tracker->processServiceNodeDecommissionTx(entry.bls_key, entry.refund_stake, fail_reason)) { - log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Decommission TX reason: {}", fail_reason); + if (!ethereum::validate_ethereum_service_node_deregister_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || + !m_l2_tracker->processServiceNodeDeregisterTx(entry.bls_key, entry.refund_stake, fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Deregister TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index ba993b469b..6828dfad37 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1635,6 +1635,8 @@ class Blockchain { */ uint64_t get_adjusted_time() const; + void process_ethereum_transactions(const std::vector& transactions); + /** * @brief finish an alternate chain's timestamp window from the main chain * diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 231b3d3e66..8d76438b88 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -219,7 +219,8 @@ uint64_t derive_governance_from_block_reward( } uint64_t service_node_reward_formula(uint64_t base_reward, hf hard_fork_version) { - //TODO sean go to ethereum pool and get block rate + //TODO sean go to ethereum pool which determines how much the rewards should be for the c++ code and get block rate + // for the time being let it continue at its normal rate return hard_fork_version >= hf::hf15_ons ? oxen::SN_REWARD_HF15 : hard_fork_version >= hf::hf9_service_nodes ? base_reward / 2 diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp index 6e8091625d..aea9129f13 100644 --- a/src/cryptonote_core/ethereum_transactions.cpp +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -131,26 +131,26 @@ bool validate_ethereum_service_node_leave_request_tx( return true; } -bool validate_ethereum_service_node_decommission_tx( +bool validate_ethereum_service_node_deregister_tx( hf hf_version, uint64_t blockchain_height, cryptonote::transaction const& tx, - cryptonote::tx_extra_ethereum_service_node_decommission& eth_extra, + cryptonote::tx_extra_ethereum_service_node_deregister& eth_extra, std::string* reason) { { if (check_condition( - tx.type != cryptonote::txtype::ethereum_service_node_decommission, + tx.type != cryptonote::txtype::ethereum_service_node_deregister, reason, "{} uses wrong tx type, expected={}", tx, - cryptonote::txtype::ethereum_service_node_decommission)) + cryptonote::txtype::ethereum_service_node_deregister)) return false; if (check_condition( !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), reason, - "{} didn't have ethereum service node decommission data in the tx_extra", + "{} didn't have ethereum service node deregister data in the tx_extra", tx)) return false; } diff --git a/src/cryptonote_core/ethereum_transactions.h b/src/cryptonote_core/ethereum_transactions.h index b200b78cd2..691f1c6f57 100644 --- a/src/cryptonote_core/ethereum_transactions.h +++ b/src/cryptonote_core/ethereum_transactions.h @@ -30,11 +30,11 @@ bool validate_ethereum_service_node_leave_request_tx( cryptonote::tx_extra_ethereum_service_node_leave_request& eth_extra, std::string* reason); -bool validate_ethereum_service_node_decommission_tx( +bool validate_ethereum_service_node_deregister_tx( cryptonote::hf hf_version, uint64_t blockchain_height, cryptonote::transaction const& tx, - cryptonote::tx_extra_ethereum_service_node_decommission& eth_extra, + cryptonote::tx_extra_ethereum_service_node_deregister& eth_extra, std::string* reason); } // ethereum diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index bcf0dc88ae..6642e505fc 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -367,6 +367,28 @@ std::optional reg_tx_extract_fields(const cryptonote::tran return reg; } +std::optional eth_reg_tx_extract_fields(const cryptonote::transaction& tx) { + cryptonote::tx_extra_ethereum_new_service_node registration; + if (!get_field_from_tx_extra(tx.extra, registration)) + return std::nullopt; + + registration_details reg{}; + reg.service_node_pubkey = registration.service_node_pubkey; + + // TODO sean this needs to be thought out + //auto& [addr, amount] = reg.reserved.emplace_back(); + //addr.m_spend_public_key = registration.public_spend_keys[i]; + //amount = registration.amounts[i]; + + //reg.hf = registration.hf_or_expiration; + reg.uses_portions = false; + + reg.fee = 0; + reg.signature = registration.signature; + + return reg; +} + uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height) { uint64_t result = height; if (type == quorum_type::checkpointing) { @@ -1006,6 +1028,52 @@ bool service_node_list::state_t::process_state_change_tx( } } +bool service_node_list::state_t::process_ethereum_deregister_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx, + const service_node_keys* my_keys) { + + cryptonote::tx_extra_ethereum_service_node_deregister dereg; + if (!cryptonote::get_field_from_tx_extra(tx.extra, dereg)) { + log::info( + logcat, + "Unlock TX: couldnt process deregister, rejected on height: {} " + "for tx: {}", + block_height, + cryptonote::get_transaction_hash(tx)); + return false; + } + + + crypto::public_key key; + // TODO sean get this public key from the bls key somehow + + auto iter = service_nodes_infos.find(key); + if (iter == service_nodes_infos.end()) { + log::debug( + logcat, + "Received state change tx for non-registered service node {} (perhaps a delayed " + "tx?)", + key); + return false; + } + + bool is_me = my_keys && my_keys->pub == key; + if (is_me) + log::info( + logcat, + fg(fmt::terminal_color::red), + "Deregistration for service node (yours): {}", + key); + else + log::info(logcat, "Deregistration for service node: {}", key); + + service_nodes_infos.erase(iter); + return true; +} + bool service_node_list::state_t::process_key_image_unlock_tx( cryptonote::network_type nettype, cryptonote::hf hf_version, @@ -1106,6 +1174,55 @@ bool service_node_list::state_t::process_key_image_unlock_tx( return false; } +bool service_node_list::state_t::process_ethereum_unlock_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx) { + + cryptonote::tx_extra_ethereum_service_node_leave_request unlock; + if (!cryptonote::get_field_from_tx_extra(tx.extra, unlock)) { + log::info( + logcat, + "Unlock TX: couldnt process unlock request, rejected on height: {} " + "for tx: {}", + block_height, + cryptonote::get_transaction_hash(tx)); + return false; + } + + //TODO sean get this from somewhere using bls key instead + crypto::public_key snode_key; + //if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key)) + //return false; + + auto it = service_nodes_infos.find(snode_key); + if (it == service_nodes_infos.end()) + return false; + + // allow this transaction to exist but change nothing, + // just continue unlock as per usual plan. They had to spend money on eth fees to get here + const service_node_info& node_info = *it->second; + if (node_info.requested_unlock_height != KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) { + log::info( + logcat, + "Unlock TX: Node already requested an unlock at height: {} rejected on height: {} " + "for tx: {}", + node_info.requested_unlock_height, + block_height, + cryptonote::get_transaction_hash(tx)); + return true; + } + + + uint64_t unlock_height = get_locked_key_image_unlock_height(nettype, node_info.registration_height, block_height); + for (const auto& contributor : node_info.contributors) { + // TODO sean, why did only the key image unlocker get this set? + // now im looping over all of them cause there is no more key images + duplicate_info(it->second).requested_unlock_height = unlock_height; + } +} + //------------------------------------------------------------------ // TODO oxen remove this whole function after HF20 has occurred bool service_node_list::state_t::is_premature_unlock( @@ -1309,6 +1426,38 @@ bool is_registration_tx( return true; } +std::pair> validate_and_get_ethereum_registration( + cryptonote::network_type nettype, + hf hf_version, + const cryptonote::transaction& tx, + uint64_t block_timestamp, + uint64_t block_height, + uint32_t index) +{ + auto info = std::make_shared(); + + auto maybe_reg = eth_reg_tx_extract_fields(tx); + if (!maybe_reg) + throw std::runtime_error("Could not extract registration details from transaction"); + auto& reg = *maybe_reg; + + uint64_t staking_requirement = get_staking_requirement(nettype, block_height); + validate_registration(hf_version, nettype, staking_requirement, block_timestamp, reg); + validate_registration_signature(reg); + + info->staking_requirement = staking_requirement; + info->operator_address = reg.reserved[0].first; + info->portions_for_operator = staking_requirement; + info->registration_height = block_height; + info->registration_hf_version = hf_version; + info->last_reward_block_height = block_height; + info->last_reward_transaction_index = index; + info->swarm_id = UNASSIGNED_SWARM_ID; + info->last_ip_change_height = block_height; + + return {reg.service_node_pubkey, info}; +} + bool service_node_list::state_t::process_registration_tx( cryptonote::network_type nettype, const cryptonote::block& block, @@ -1401,6 +1550,41 @@ bool service_node_list::state_t::process_registration_tx( return true; } +bool service_node_list::state_t::process_ethereum_registration_tx( + cryptonote::network_type nettype, + const cryptonote::block& block, + const cryptonote::transaction& tx, + uint32_t index, + const service_node_keys* my_keys) { + const auto hf_version = block.major_version; + uint64_t const block_height = cryptonote::get_block_height(block); + + try { + const auto [key, service_node_info] = validate_and_get_ethereum_registration(nettype, hf_version, tx, block.timestamp, block_height, index); + // TODO sean -> explore what happens if registration contains duplicate service node pubkey? + if (sn_list && !sn_list->m_rescanning) { + auto& proof = sn_list->proofs[key]; + proof = {}; + proof.store(key, sn_list->m_blockchain); + } + if (my_keys && my_keys->pub == key) + log::info( + logcat, + fg(fmt::terminal_color::green), + "Service node registered (yours): {} on height: {}", + key, + block_height); + else + log::info(logcat, "New service node registered: {} on height: {}", key, block_height); + service_nodes_infos[key] = std::move(service_node_info); + return true; + } catch (const std::exception& e) { + log::info(logcat, "Failed to register node from ethereum transaction: {}", e.what()); + } + return false; +} + + bool service_node_list::state_t::process_contribution_tx( cryptonote::network_type nettype, const cryptonote::block& block, @@ -2607,6 +2791,13 @@ void service_node_list::state_t::update_from_block( state_history, state_archive, alt_states, nettype, block, tx, my_keys); } else if (tx.type == cryptonote::txtype::key_image_unlock) { process_key_image_unlock_tx(nettype, hf_version, block_height, tx); + } else if (tx.type == cryptonote::txtype::ethereum_new_service_node) { + process_ethereum_registration_tx(nettype, block, tx, index, my_keys); + } else if (tx.type == cryptonote::txtype::ethereum_service_node_leave_request) { + process_ethereum_unlock_tx(nettype, hf_version, block_height, tx); + } else if (tx.type == cryptonote::txtype::ethereum_service_node_deregister) { + process_ethereum_deregister_tx(nettype, hf_version, block_height, tx, my_keys); + } } diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 2037752919..82d9958be2 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -806,6 +806,24 @@ class service_node_list { cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction& tx) const; + // Returns true if there was a registration: + bool process_ethereum_registration_tx( + cryptonote::network_type nettype, + cryptonote::block const& block, + const cryptonote::transaction& tx, + uint32_t index, + const service_node_keys* my_keys); + bool process_ethereum_unlock_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx); + bool process_ethereum_deregister_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx, + const service_node_keys* my_keys); payout get_block_leader() const; payout get_block_producer(uint8_t pulse_round) const; }; @@ -920,7 +938,18 @@ bool is_registration_tx( uint32_t index, crypto::public_key& key, service_node_info& info); + +std::pair> validate_and_get_ethereum_registration( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + const cryptonote::transaction& tx, + uint64_t block_timestamp, + uint64_t block_height, + uint32_t index); + std::optional reg_tx_extract_fields(const cryptonote::transaction& tx); +std::optional eth_reg_tx_extract_fields(const cryptonote::transaction& tx); + uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height); // Converts string input values into a partially filled `registration_details`; pubkey and diff --git a/src/cryptonote_core/service_node_quorum_cop.cpp b/src/cryptonote_core/service_node_quorum_cop.cpp index 81385567cb..446f7718ad 100644 --- a/src/cryptonote_core/service_node_quorum_cop.cpp +++ b/src/cryptonote_core/service_node_quorum_cop.cpp @@ -615,6 +615,7 @@ void quorum_cop::process_quorums(cryptonote::block const& block) { case quorum_type::pulse: case quorum_type::blink: break; + } } } diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index e82d5f0837..41e3378596 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -113,7 +113,7 @@ void L2Tracker::initialize_transaction_review(uint64_t ethereum_height) { ); } review_block_height = ethereum_height; - get_review_transactions(); // Fills new_service_nodes, leave_requests, decommissions + get_review_transactions(); // Fills new_service_nodes, leave_requests, deregs } bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { @@ -152,26 +152,26 @@ bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std return false; } -bool L2Tracker::processServiceNodeDecommissionTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { +bool L2Tracker::processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { if (review_block_height == 0) { fail_reason = "Review not initialized"; - oxen::log::error(logcat, "Failed to process decommission tx height {}", review_block_height); + oxen::log::error(logcat, "Failed to process deregister tx height {}", review_block_height); return false; } - for (auto it = decommissions.begin(); it != decommissions.end(); ++it) { - if (it->bls_key == bls_key && it->refund_stake == refund_stake) { - decommissions.erase(it); + for (auto it = deregs.begin(); it != deregs.end(); ++it) { + if (it->bls_key == bls_key) { + deregs.erase(it); return true; } } - fail_reason = "Decommission Transaction not found bls_key: " + bls_key; + fail_reason = "Deregister Transaction not found bls_key: " + bls_key; return false; } bool L2Tracker::finalize_transaction_review() { - if (new_service_nodes.empty() && leave_requests.empty() && decommissions.empty()) { + if (new_service_nodes.empty() && leave_requests.empty() && deregs.empty()) { review_block_height = 0; return true; } @@ -186,7 +186,7 @@ std::string L2Tracker::get_contract_address(const cryptonote::network_type netty void L2Tracker::get_review_transactions() { new_service_nodes.clear(); leave_requests.clear(); - decommissions.clear(); + deregs.clear(); if (review_block_height == 0) { oxen::log::warning(logcat, "get_review_transactions called with 0 block height"); return; @@ -200,8 +200,8 @@ void L2Tracker::get_review_transactions() { new_service_nodes.push_back(arg); } else if constexpr (std::is_same_v) { leave_requests.push_back(arg); - } else if constexpr (std::is_same_v) { - decommissions.push_back(arg); + } else if constexpr (std::is_same_v) { + deregs.push_back(arg); } }, transactionVariant); } @@ -213,3 +213,20 @@ void L2Tracker::get_review_transactions() { } } } + +std::vector L2Tracker::get_block_transactions(uint64_t begin_height, uint64_t end_height) { + std::vector all_transactions; + for (const auto& state : state_history) { + if (state.height >= begin_height && state.height <= end_height) { + for (const auto& transactionVariant : state.state_changes) { + all_transactions.push_back(transactionVariant); + } + } + if (state.height < begin_height) { + // If we go below our desired begin height then throw, as state history should be ordered + throw std::runtime_error("Begin height not found in state history"); + } + } + return all_transactions; +} + diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 531c18b758..8a2ea28e70 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -29,7 +29,7 @@ class L2Tracker { uint64_t review_block_height; std::vector new_service_nodes; std::vector leave_requests; - std::vector decommissions; + std::vector deregs; public: L2Tracker(); @@ -53,11 +53,12 @@ class L2Tracker { void initialize_transaction_review(uint64_t ethereum_height); bool processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); - bool processServiceNodeDecommissionTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); + bool processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); bool finalize_transaction_review(); std::pair latest_state(); + std::vector get_block_transactions(uint64_t begin_height, uint64_t end_height); private: static std::string get_contract_address(const cryptonote::network_type nettype); diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 28912986e7..cadba34438 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -17,7 +17,7 @@ TransactionType RewardsLogEntry::getLogType() const { return TransactionType::ServiceNodeLeaveRequest; // keccak256('ServiceNodeLiquidated(uint64,address,BN256G1.G1Point)') } else if (topics[0] == "5d7e17cd2edcc6334f540934c0f7150c32f6655120e51ab941b585014b28679a") { - return TransactionType::ServiceNodeDecommission; + return TransactionType::ServiceNodeDeregister; } return TransactionType::Other; } @@ -35,8 +35,10 @@ std::optional RewardsLogEntry::getLogTransaction( // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) std::string bls_key = data.substr(64 + 2, 128); // pull 32 bytes (64 characters) - std::string service_node_pubkey =data.substr(128 + 64 + 2, 64); - return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey); + std::string service_node_pubkey = data.substr(128 + 64 + 2, 64); + // pull 32 bytes (64 characters) + std::string signature = data.substr(128 + 64 + 64 + 2, 64); + return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey, signature); } case TransactionType::ServiceNodeLeaveRequest: { // event ServiceNodeRemovalRequest(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey); @@ -47,15 +49,14 @@ std::optional RewardsLogEntry::getLogTransaction( std::string bls_key = data.substr(64 + 2, 128); return ServiceNodeLeaveRequestTx(bls_key); } - case TransactionType::ServiceNodeDecommission: { + case TransactionType::ServiceNodeDeregister: { // event ServiceNodeLiquidated(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey); // service node id is a topic so only address and pubkey are in data // address is 32 bytes and pubkey is 64 bytes, // // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) std::string bls_key = data.substr(64 + 2, 128); - bool refund_stake = true; - return ServiceNodeDecommissionTx(bls_key, refund_stake); + return ServiceNodeDeregisterTx(bls_key); } default: return std::nullopt; diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index 29b279e173..aec2d1b412 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -9,7 +9,7 @@ enum class TransactionType { NewServiceNode, ServiceNodeLeaveRequest, - ServiceNodeDecommission, + ServiceNodeDeregister, Other }; @@ -18,9 +18,10 @@ class NewServiceNodeTx { std::string bls_key; std::string eth_address; std::string service_node_pubkey; + std::string signature; - NewServiceNodeTx(const std::string& _bls_key, const std::string& _eth_address, const std::string& _service_node_pubkey) - : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey) {} + NewServiceNodeTx(const std::string& _bls_key, const std::string& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) + : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey), signature(_signature) {} }; class ServiceNodeLeaveRequestTx { @@ -31,16 +32,15 @@ class ServiceNodeLeaveRequestTx { : bls_key(_bls_key) {} }; -class ServiceNodeDecommissionTx { +class ServiceNodeDeregisterTx { public: std::string bls_key; - bool refund_stake; - ServiceNodeDecommissionTx(const std::string& _bls_key, bool _refund_stake) - : bls_key(_bls_key), refund_stake(_refund_stake) {} + ServiceNodeDeregisterTx(const std::string& _bls_key) + : bls_key(_bls_key) {} }; -using TransactionStateChangeVariant = std::variant; +using TransactionStateChangeVariant = std::variant; class RewardsLogEntry : public LogEntry { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 57de16e94a..ee9f3f7388 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -747,19 +747,19 @@ namespace { } void operator()(const tx_extra_ethereum_address_notification& x) { set("eth_address", x.eth_address); - set("signature", x.signature); + set("signature", tools::view_guts(x.signature)); } void operator()(const tx_extra_ethereum_new_service_node& x) { set("bls_key", x.bls_key); set("eth_address", x.eth_address); - set("service_node_pubkey", x.service_node_pubkey); + set("service_node_pubkey", tools::view_guts(x.service_node_pubkey)); + set("signature", tools::view_guts(x.signature)); } void operator()(const tx_extra_ethereum_service_node_leave_request& x) { set("bls_key", x.bls_key); } - void operator()(const tx_extra_ethereum_service_node_decommission& x) { + void operator()(const tx_extra_ethereum_service_node_deregister& x) { set("bls_key", x.bls_key); - set("refund_stake", x.refund_stake); } From e7a49e7a8bf07cbdc5521cd5dc7e5c6870371eb9 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 25 Jan 2024 11:46:14 +1100 Subject: [PATCH 38/97] sends transaction to contract --- src/bls/bls_aggregator.cpp | 4 + src/bls/bls_aggregator.h | 9 + src/cryptonote_core/cryptonote_core.cpp | 10 + src/cryptonote_core/cryptonote_core.h | 1 + src/daemon/rpc_command_executor.cpp | 10 + src/rpc/core_rpc_server.cpp | 11 + src/rpc/core_rpc_server.h | 1 + src/rpc/core_rpc_server_command_parser.cpp | 4 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 25 + utils/local-devnet/daemons.py | 10 + utils/local-devnet/ethereum.py | 1122 ++++++++++++++++++++ utils/local-devnet/service_node_network.py | 12 +- 13 files changed, 1219 insertions(+), 1 deletion(-) create mode 100644 utils/local-devnet/ethereum.py diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index d0f4d909e7..10823923ea 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -71,6 +71,10 @@ aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) return aggregateResponse{non_signers, sig_str}; } +blsRegistrationResponse BLSAggregator::registration() const { + return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(), "","",""}; +} + aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& address) { //TODO sean hash something different const std::array hash = BLSSigner::hash(address); diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 049cc845d4..d82bb538da 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -40,6 +40,14 @@ struct aggregateWithdrawalResponse { std::string signature; }; +struct blsRegistrationResponse { + std::string bls_pubkey; + std::string proof_of_possession; + std::string address; + std::string service_node_pubkey; + std::string service_node_signature; +}; + class BLSAggregator { private: std::shared_ptr bls_signer; @@ -54,6 +62,7 @@ class BLSAggregator { std::vector getPubkeys(); aggregateResponse aggregateSignatures(const std::string& message); aggregateWithdrawalResponse aggregateRewards(const std::string& address); + blsRegistrationResponse registration() const; std::vector findNonSigners(const std::vector& indices); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index f109750bb1..35919589db 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2740,6 +2740,16 @@ std::vector core::get_bls_pubkeys() const { return m_bls_aggregator->getPubkeys(); } //----------------------------------------------------------------------------------------------- +blsRegistrationResponse core::bls_registration(const std::string& ethereum_address) const { + auto resp = m_bls_aggregator->registration(); + const auto& pubkey = get_service_keys().pub; + resp.address = ethereum_address; + resp.service_node_pubkey = tools::type_to_hex(pubkey); + // TODO sean sign this somehow + resp.service_node_signature = ""; + return resp; +} +//----------------------------------------------------------------------------------------------- std::vector core::get_service_node_list_state( const std::vector& service_node_pubkeys) const { return m_service_node_list.get_service_node_list_state(service_node_pubkeys); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 399fdd8fdd..53ecf594b6 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -954,6 +954,7 @@ class core : public i_miner_handler { aggregateResponse bls_request() const; aggregateWithdrawalResponse aggregate_withdrawal_request(const std::string& ethereum_address); std::vector get_bls_pubkeys() const; + blsRegistrationResponse bls_registration(const std::string& ethereum_address) const; /** * @brief get a snapshot of the service node list state at the time of the call. diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index e5b16442c1..5df9297796 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2266,6 +2266,16 @@ bool rpc_command_executor::prepare_registration(bool force_registration) { return false; auto& snode_keys = *maybe_keys; + //TODO sean use the feature flag instead + if (hf_version > hf::hf20) { + tools::success_msg_writer( + "Service Node Pubkey: {}\n" + "Service Node Signature: {}\n", + snode_keys.value("service_node_pubkey", ""), + snode_keys.value("service_node_signature", "")); // Assuming 'service_node_signature' is the key for signature + return true; + } + if (!info.value("devnet", false)) // Devnet doesn't run storage-server / lokinet { auto now = std::chrono::system_clock::now(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ee9f3f7388..7730f455a3 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2567,6 +2567,17 @@ void core_rpc_server::invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context contex return; } //------------------------------------------------------------------------------------------------------------------------------ +void core_rpc_server::invoke(BLS_REGISTRATION& bls_registration_request, rpc_context context) { + const blsRegistrationResponse bls_registration = m_core.bls_registration(bls_registration_request.request.address); + bls_registration_request.response["status"] = STATUS_OK; + bls_registration_request.response["address"] = bls_registration.address; + bls_registration_request.response["bls_pubkey"] = bls_registration.bls_pubkey; + bls_registration_request.response["proof_of_possession"] = bls_registration.proof_of_possession; + bls_registration_request.response["service_node_pubkey"] = bls_registration.service_node_pubkey; + bls_registration_request.response["service_node_signature"] = bls_registration.service_node_signature; + return; +} +//------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_SERVICE_KEYS& get_service_keys, rpc_context context) { const auto& keys = m_core.get_service_keys(); if (keys.pub) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8bec711d61..8d8f97f014 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -186,6 +186,7 @@ class core_rpc_server { void invoke(BLS_REQUEST& bls_request, rpc_context context); void invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context); void invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context); + void invoke(BLS_REGISTRATION& bls_registration_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 1428052d91..05c3dbee7a 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -492,4 +492,8 @@ void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in) { get_values( in, "address", required{cmd.request.address}); } +void parse_request(BLS_REGISTRATION& cmd, rpc_input in) { + get_values( in, "address", required{cmd.request.address}); +} + } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 400840be3b..e848c8eb36 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -55,4 +55,5 @@ void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in); +void parse_request(BLS_REGISTRATION& cmd, rpc_input in); } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 65c855b8c3..2f2ccc951e 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2340,6 +2340,30 @@ struct BLS_PUBKEYS: PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("bls_pubkey_request"); } }; +/// RPC: bls registration request +/// +/// Sends a request out to get parameters necessary to register a service node through the ethereum smart contract +/// +/// Inputs: +/// +/// - `address` -- this address will be looked up in the batching database at the latest height to see how much they can withdraw +/// +/// Outputs: +/// +/// - `status` -- generic RPC error code; "OK" means the request was successful. +/// - `address` -- The requested address +/// - `bls_pubkey` -- The nodes BLS public key +/// - `proof_of_possession` -- A signature over the bls pubkey to prove ownership of the private key +/// - `service_node_pubkey` -- The oxen nodes service node pubkey +/// - `service_node_signature` -- A signature over the registration parameters +struct BLS_REGISTRATION: PUBLIC { + static constexpr auto names() { return NAMES("bls_registration_request"); } + + struct request_parameters { + std::string address; + } request; +}; + /// RPC: blockchain/get_checkpoints /// /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to @@ -2735,6 +2759,7 @@ using core_rpc_types = tools::type_list< BLS_REQUEST, BLS_WITHDRAWAL_REQUEST, BLS_PUBKEYS, + BLS_REGISTRATION, GET_SERVICE_NODE_REGISTRATION_CMD, GET_SERVICE_NODE_REGISTRATION_CMD_RAW, GET_SERVICE_NODE_STATUS, diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index c1904fef28..357c7bd6ba 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -246,6 +246,16 @@ def sn_key(self): def sn_status(self): return self.json_rpc("get_service_node_status").json()["result"] + def get_ethereum_registration_args(self, address): + registration_args = self.json_rpc("bls_registration_request", {"address": address}).json()["result"] + return { + "address" : registration_args["address"], + "bls_pubkey" : registration_args["bls_pubkey"], + "proof_of_possession" : registration_args["proof_of_possession"], + "service_node_pubkey" : registration_args["service_node_pubkey"], + "service_node_signature" : registration_args["service_node_signature"] + } + class Wallet(RPCDaemon): diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py new file mode 100644 index 0000000000..0c97b1ad9a --- /dev/null +++ b/utils/local-devnet/ethereum.py @@ -0,0 +1,1122 @@ +from web3 import Web3 +import json + +class ServiceNodeRewardContract: + def __init__(self): + self.provider_url = "http://127.0.0.1:8545" + self.private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Hardhat account #0 + self.web3 = Web3(Web3.HTTPProvider(self.provider_url)) + self.contract_address = self.getContractDeployedInLatestBlock() + self.contract = self.web3.eth.contract(address=self.contract_address, abi=contract_abi) + self.acc = self.web3.eth.account.from_key(self.private_key) + unsent_tx = self.contract.functions.start().build_transaction({ + "from": self.acc.address, + 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + + def call_function(self, function_name, *args, **kwargs): + contract_function = self.contract.functions[function_name](*args) + return contract_function.call(**kwargs) + + # Add more methods as needed to interact with the smart contract + def getContractDeployedInLatestBlock(self): + latest_block = self.web3.eth.get_block('latest') + + for tx_hash in latest_block['transactions']: + try: + tx_receipt = self.web3.eth.get_transaction_receipt(tx_hash) + if tx_receipt.contractAddress: + return tx_receipt.contractAddress + except TransactionNotFound: + continue + + raise RuntimeError("No contracts deployed in latest block") + + def hardhatAccountAddress(self): + return self.acc.address + + def addBLSPublicKey(self, args): + # function addBLSPublicKey(uint256 pkX, uint256 pkY, uint256 sigs0, uint256 sigs1, uint256 sigs2, uint256 sigs3, uint256 serviceNodePubkey, uint256 serviceNodeSignature) public { + unsent_tx = self.contract.functions.addBLSPublicKey(int(args["bls_pubkey"][:64], 16), + int(args["bls_pubkey"][64:128], 16), + int(args["proof_of_possession"][:64], 16), + int(args["proof_of_possession"][64:128], 16), + int(args["proof_of_possession"][128:192], 16), + int(args["proof_of_possession"][192:256], 16), + int(args["service_node_pubkey"], 16), + # int(args["service_node_signature"], 16) + int(0) + ).build_transaction({ + "from": self.acc.address, + 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + +contract_abi = json.loads(""" +[ { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_foundationPool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_stakingRequirement", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidatorRewardRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_poolShareOfLiquidationRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_recipientRatio", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "BLSPubkeyAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "ContractNotActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "EarlierLeaveRequestMade", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numSigners", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requiredSigners", + "type": "uint256" + } + ], + "name": "InsufficientBLSSignatures", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBLSProofOfPossession", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBLSSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParameter", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currenttime", + "type": "uint256" + } + ], + "name": "LeaveRequestTooEarly", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "expectedRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "providedRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serviceNodeID", + "type": "uint256" + } + ], + "name": "RecipientAddressDoesNotMatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "RecipientAddressNotProvided", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "ServiceNodeDoesntExist", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "NewSeededServiceNode", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "serviceNodeSignature", + "type": "uint256" + } + ], + "name": "NewServiceNode", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + } + ], + "name": "RewardsBalanceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeLiquidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemoval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemovalRequest", + "type": "event" + }, + { + "inputs": [], + "name": "IsActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIST_END", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_SERVICE_NODE_REMOVAL_WAIT_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "pkX", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pkY", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature", + "type": "uint256" + } + ], + "name": "addBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "aggregate_pubkey", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blsNonSignerThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "buildRecipientMessage", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "designatedToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "foundationPool", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "initiateRemoveBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "liquidateBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidateTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextServiceNodeID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofOfPossessionTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "recipients", + "outputs": [ + { + "internalType": "uint256", + "name": "rewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimed", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "removalTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "removeBLSPublicKeyAfterWaitTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "removeBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "pkX", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "pkY", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "seedPublicKeyList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "serviceNodeIDs", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "name": "serviceNodes", + "outputs": [ + { + "internalType": "uint64", + "name": "next", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "previous", + "type": "uint64" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "leaveRequestTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deposit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "serviceNodesLength", + "outputs": [ + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalNodes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "recipientAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "updateRewardsBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateServiceNodesLength", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +""") diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 26b80ec7f5..f5d212cc4c 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 from daemons import Daemon, Wallet +from ethereum import ServiceNodeRewardContract import random import time @@ -51,16 +52,18 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): if not os.path.exists(self.datadir): os.makedirs(self.datadir) self.binpath = binpath + self.servicenodecontract = ServiceNodeRewardContract() vprint("Using '{}' for data files and logs".format(datadir)) nodeopts = dict(oxend=self.binpath+'/oxend', datadir=datadir) + self.ethsns = [Daemon(service_node=True, **nodeopts) for _ in range(1)] self.sns = [Daemon(service_node=True, **nodeopts) for _ in range(sns)] self.nodes = [Daemon(**nodeopts) for _ in range(nodes)] - self.all_nodes = self.sns + self.nodes + self.all_nodes = self.sns + self.nodes + self.ethsns self.wallets = [] for name in ('Alice', 'Bob', 'Mike'): @@ -92,6 +95,9 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): for sn in self.sns: vprint(" {}".format(sn.rpc_port), end="", flush=True, timestamp=False) sn.start() + for sn in self.ethsns: + vprint(" {}".format(sn.rpc_port), end="", flush=True, timestamp=False) + sn.start() vprint(timestamp=False) vprint("Starting new regular oxend nodes with RPC on {} ports".format(self.nodes[0].listen_ip), end="") for d in self.nodes: @@ -204,6 +210,10 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): time.sleep(10) for sn in self.sns: sn.send_uptime_proof() + + ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) + result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) + print(result) vprint("Done.") vprint("Local Devnet SN network setup complete!") From 589ad38d768779ea3025970065b5c8b25dca9363 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Jan 2024 09:41:50 +1100 Subject: [PATCH 39/97] local devnet will spin up a node and submit a register transaction through ethereum --- src/bls/CMakeLists.txt | 1 + src/bls/bls_signer.cpp | 37 +- src/bls/bls_utils.cpp | 3 +- utils/local-devnet/ethereum.py | 1991 +++++++++++--------- utils/local-devnet/service_node_network.py | 1 - 5 files changed, 1120 insertions(+), 913 deletions(-) diff --git a/src/bls/CMakeLists.txt b/src/bls/CMakeLists.txt index 5fa0fd2263..33fb29f437 100644 --- a/src/bls/CMakeLists.txt +++ b/src/bls/CMakeLists.txt @@ -40,4 +40,5 @@ target_link_libraries(oxen_bls common cryptonote_basic logging + ethyl extra) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index af2effc2e1..03745b3c6d 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -3,9 +3,24 @@ #include "logging/oxen_logger.h" #include "crypto/keccak.h" #include +#include "ethyl/utils.hpp" static auto logcat = oxen::log::Cat("bls_signer"); +const std::string proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"; +const std::string rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"; +const std::string removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"; +const std::string liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"; + +std::string buildTag(const std::string& baseTag, uint32_t chainID, const std::string& contractAddress) { + // Check if contractAddress starts with "0x" prefix + std::string contractAddressOutput = contractAddress; + if (contractAddressOutput.substr(0, 2) == "0x") + contractAddressOutput = contractAddressOutput.substr(2); // remove "0x" + std::string concatenatedTag = "0x" + utils::toHexString(baseTag) + utils::padTo32Bytes(utils::decimalToHex(chainID), utils::PaddingDirection::LEFT) + contractAddressOutput; + return utils::toHexString(utils::hash(concatenatedTag)); +} + BLSSigner::BLSSigner() { initCurve(); // This init function generates a secret key calling blsSecretKeySetByCSPRNG @@ -42,7 +57,14 @@ bls::Signature BLSSigner::signHash(const std::array& hash) { } std::string BLSSigner::proofOfPossession() { - const std::array hash = BLSSigner::hash("0x" + getPublicKeyHex()); // Get the hash of the publickey + //TODO sean put constants somewhere, source them + const uint32_t chainID = 31337; + const std::string contractAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; + + std::string fullTag = buildTag(proofOfPossessionTag, chainID, contractAddress); + std::string message = "0x" + fullTag + getPublicKeyHex(); + + const std::array hash = BLSSigner::hash(message); // Get the hash of the publickey bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); return bls_utils::SignatureToHex(sig); @@ -61,18 +83,7 @@ bls::PublicKey BLSSigner::getPublicKey() { } std::array BLSSigner::hash(std::string in) { - std::vector bytes; - - // Check for "0x" prefix and if exists, convert the hex to bytes - // TODO sean from_hex wont work with 0x prefix - if(in.size() >= 2 && in[0] == '0' && in[1] == 'x') { - std::string bytes = oxenc::from_hex(in); - in = bytes; - } - - std::array hash; - keccak(reinterpret_cast(in.c_str()), in.size(), hash.data(), 32); - return hash; + return utils::hash(in); } std::array BLSSigner::hashModulus(std::string message) { diff --git a/src/bls/bls_utils.cpp b/src/bls/bls_utils.cpp index 2ee451134c..14d9253325 100644 --- a/src/bls/bls_utils.cpp +++ b/src/bls/bls_utils.cpp @@ -1,5 +1,6 @@ #include "bls_utils.h" #include +#include "ethyl/utils.hpp" std::string bls_utils::SignatureToHex(bls::Signature sig) { mclSize serializedSignatureSize = 32; @@ -33,6 +34,6 @@ std::string bls_utils::PublicKeyToHex(bls::PublicKey publicKey) { if (g1Point2.y.serialize(dst + serializedPublicKeySize, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of y is zero"); - return oxenc::to_hex(serialized_pubkey.begin(), serialized_pubkey.end()); + return utils::toHexString(serialized_pubkey); } diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py index 0c97b1ad9a..58677c84b9 100644 --- a/utils/local-devnet/ethereum.py +++ b/utils/local-devnet/ethereum.py @@ -14,6 +14,13 @@ def __init__(self): 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.erc20_address = self.contract.functions.designatedToken().call() + self.erc20_contract = self.web3.eth.contract(address=self.erc20_address, abi=erc20_contract_abi) + unsent_tx = self.erc20_contract.functions.approve(self.contract_address, 15001000000000000000000).build_transaction({ + "from": self.acc.address, + 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) def call_function(self, function_name, *args, **kwargs): contract_function = self.contract.functions[function_name](*args) @@ -49,6 +56,7 @@ def addBLSPublicKey(self, args): int(0) ).build_transaction({ "from": self.acc.address, + 'gas': 2000000, 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) @@ -57,1066 +65,1253 @@ def addBLSPublicKey(self, args): contract_abi = json.loads(""" [ { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_foundationPool", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_stakingRequirement", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_liquidatorRewardRatio", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_poolShareOfLiquidationRatio", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_recipientRatio", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" }, { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" + "internalType": "address", + "name": "_foundationPool", + "type": "address" }, { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddressInsufficientBalance", - "type": "error" + "internalType": "uint256", + "name": "_stakingRequirement", + "type": "uint256" }, { - "inputs": [], - "name": "ArrayLengthMismatch", - "type": "error" + "internalType": "uint256", + "name": "_liquidatorRewardRatio", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "BLSPubkeyAlreadyExists", - "type": "error" + "internalType": "uint256", + "name": "_poolShareOfLiquidationRatio", + "type": "uint256" }, { - "inputs": [], - "name": "ContractNotActive", - "type": "error" + "internalType": "uint256", + "name": "_recipientRatio", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" +}, +{ + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "BLSPubkeyAlreadyExists", + "type": "error" +}, +{ + "inputs": [], + "name": "ContractNotActive", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "EarlierLeaveRequestMade", - "type": "error" + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "EarlierLeaveRequestMade", + "type": "error" +}, +{ + "inputs": [], + "name": "FailedInnerCall", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint256", + "name": "numSigners", + "type": "uint256" }, { - "inputs": [], - "name": "FailedInnerCall", - "type": "error" + "internalType": "uint256", + "name": "requiredSigners", + "type": "uint256" + } + ], + "name": "InsufficientBLSSignatures", + "type": "error" +}, +{ + "inputs": [], + "name": "InvalidBLSProofOfPossession", + "type": "error" +}, +{ + "inputs": [], + "name": "InvalidBLSSignature", + "type": "error" +}, +{ + "inputs": [], + "name": "InvalidParameter", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "numSigners", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "requiredSigners", - "type": "uint256" - } - ], - "name": "InsufficientBLSSignatures", - "type": "error" + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" }, { - "inputs": [], - "name": "InvalidBLSProofOfPossession", - "type": "error" + "internalType": "uint256", + "name": "currenttime", + "type": "uint256" + } + ], + "name": "LeaveRequestTooEarly", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "expectedRecipient", + "type": "address" }, { - "inputs": [], - "name": "InvalidBLSSignature", - "type": "error" + "internalType": "address", + "name": "providedRecipient", + "type": "address" }, { - "inputs": [], - "name": "InvalidParameter", - "type": "error" + "internalType": "uint256", + "name": "serviceNodeID", + "type": "uint256" + } + ], + "name": "RecipientAddressDoesNotMatch", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "RecipientAddressNotProvided", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "ServiceNodeDoesntExist", + "type": "error" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, + "components": [ { "internalType": "uint256", - "name": "timestamp", + "name": "X", "type": "uint256" }, { "internalType": "uint256", - "name": "currenttime", + "name": "Y", "type": "uint256" } ], - "name": "LeaveRequestTooEarly", - "type": "error" - }, + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "NewSeededServiceNode", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" }, { - "inputs": [ - { - "internalType": "address", - "name": "expectedRecipient", - "type": "address" - }, + "components": [ { - "internalType": "address", - "name": "providedRecipient", - "type": "address" + "internalType": "uint256", + "name": "X", + "type": "uint256" }, { "internalType": "uint256", - "name": "serviceNodeID", + "name": "Y", "type": "uint256" } ], - "name": "RecipientAddressDoesNotMatch", - "type": "error" + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "RecipientAddressNotProvided", - "type": "error" + "indexed": false, + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" + "indexed": false, + "internalType": "uint256", + "name": "serviceNodeSignature", + "type": "uint256" + } + ], + "name": "NewServiceNode", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "ServiceNodeDoesntExist", - "type": "error" + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "NewSeededServiceNode", - "type": "event" + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - }, + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + } + ], + "name": "RewardsBalanceUpdated", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardsClaimed", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ { - "indexed": false, "internalType": "uint256", - "name": "serviceNodePubkey", + "name": "X", "type": "uint256" }, { - "indexed": false, "internalType": "uint256", - "name": "serviceNodeSignature", + "name": "Y", "type": "uint256" } ], - "name": "NewServiceNode", - "type": "event" + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeLiquidated", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, + "components": [ { - "indexed": false, "internalType": "uint256", - "name": "amount", + "name": "X", "type": "uint256" }, { - "indexed": false, "internalType": "uint256", - "name": "previousBalance", + "name": "Y", "type": "uint256" } ], - "name": "RewardsBalanceUpdated", - "type": "event" + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemoval", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" }, { - "anonymous": false, - "inputs": [ + "components": [ { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" + "internalType": "uint256", + "name": "X", + "type": "uint256" }, { - "indexed": false, "internalType": "uint256", - "name": "amount", + "name": "Y", "type": "uint256" } ], - "name": "RewardsClaimed", - "type": "event" + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemovalRequest", + "type": "event" +}, +{ + "inputs": [], + "name": "IsActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "LIST_END", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "MAX_SERVICE_NODE_REMOVAL_WAIT_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint256", + "name": "pkX", + "type": "uint256" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeLiquidated", - "type": "event" + "internalType": "uint256", + "name": "pkY", + "type": "uint256" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeRemoval", - "type": "event" + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeRemovalRequest", - "type": "event" + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" }, { - "inputs": [], - "name": "IsActive", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" }, { - "inputs": [], - "name": "LIST_END", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" }, { - "inputs": [], - "name": "MAX_SERVICE_NODE_REMOVAL_WAIT_TIME", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "pkX", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "pkY", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "serviceNodeSignature", - "type": "uint256" - } - ], - "name": "addBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "serviceNodeSignature", + "type": "uint256" + } + ], + "name": "addBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "aggregate_pubkey", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" }, { - "inputs": [], - "name": "aggregate_pubkey", - "outputs": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "blsNonSignerThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" }, { - "inputs": [], - "name": "blsNonSignerThreshold", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "buildRecipientMessage", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" +}, +{ + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "designatedToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "foundationPool", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "initiateRemoveBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "name": "buildRecipientMessage", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" }, { - "inputs": [], - "name": "claimRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" }, { - "inputs": [], - "name": "designatedToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" }, { - "inputs": [], - "name": "foundationPool", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "initiateRemoveBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "liquidateBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "liquidateTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "nextServiceNodeID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "proofOfPossessionTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "recipients", + "outputs": [ + { + "internalType": "uint256", + "name": "rewards", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "liquidateBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "claimed", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "removalTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "removeBLSPublicKeyAfterWaitTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" }, { - "inputs": [], - "name": "liquidateTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" }, { - "inputs": [], - "name": "nextServiceNodeID", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" }, { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" }, { - "inputs": [], - "name": "proofOfPossessionTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "recipients", - "outputs": [ - { - "internalType": "uint256", - "name": "rewards", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "claimed", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "removeBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "rewardTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint256[]", + "name": "pkX", + "type": "uint256[]" }, { - "inputs": [], - "name": "removalTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256[]", + "name": "pkY", + "type": "uint256[]" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "removeBLSPublicKeyAfterWaitTime", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "seedPublicKeyList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "serviceNodeIDs", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "name": "serviceNodes", + "outputs": [ + { + "internalType": "uint64", + "name": "next", + "type": "uint64" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, + "internalType": "uint64", + "name": "previous", + "type": "uint64" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ { "internalType": "uint256", - "name": "sigs2", + "name": "X", "type": "uint256" }, { "internalType": "uint256", - "name": "sigs3", + "name": "Y", "type": "uint256" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" } ], - "name": "removeBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" }, { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "leaveRequestTimestamp", + "type": "uint256" }, { - "inputs": [], - "name": "rewardTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "deposit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "serviceNodesLength", + "outputs": [ + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "totalNodes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" }, { - "inputs": [ - { - "internalType": "uint256[]", - "name": "pkX", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "pkY", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "seedPublicKeyList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "recipientAmount", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "serviceNodeIDs", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "name": "serviceNodes", - "outputs": [ - { - "internalType": "uint64", - "name": "next", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "previous", - "type": "uint64" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "leaveRequestTimestamp", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deposit", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" }, { - "inputs": [], - "name": "serviceNodesLength", - "outputs": [ - { - "internalType": "uint256", - "name": "count", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" }, { - "inputs": [], - "name": "start", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" }, { - "inputs": [], - "name": "totalNodes", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "updateRewardsBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [], + "name": "updateServiceNodesLength", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" +} +] +""") +erc20_contract_abi = json.loads(""" +[ +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" }, { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" }, { - "inputs": [ - { - "internalType": "address", - "name": "recipientAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "recipientAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "updateRewardsBalance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" +}, +{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" }, { - "inputs": [], - "name": "updateServiceNodesLength", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" } - ] + ], + "stateMutability": "nonpayable", + "type": "function" +} +] """) diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index f5d212cc4c..a075228b38 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -213,7 +213,6 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) - print(result) vprint("Done.") vprint("Local Devnet SN network setup complete!") From 35adc4ca06ab180ea289b0d3995e4af37eaba422 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 1 Feb 2024 11:09:29 +1100 Subject: [PATCH 40/97] flags for setting provider --- src/bls/bls_signer.cpp | 12 +++++--- src/bls/bls_signer.h | 8 +++-- src/cryptonote_config.h | 4 +-- src/cryptonote_core/blockchain.cpp | 34 +++++++++++++--------- src/cryptonote_core/blockchain.h | 2 ++ src/cryptonote_core/cryptonote_core.cpp | 18 +++++++++++- src/l2_tracker/l2_tracker.cpp | 18 ++++++++++-- src/l2_tracker/l2_tracker.h | 3 +- src/l2_tracker/rewards_contract.cpp | 29 +++--------------- src/l2_tracker/rewards_contract.h | 2 +- utils/local-devnet/daemons.py | 1 + utils/local-devnet/service_node_network.py | 3 ++ 12 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 03745b3c6d..4bd060882b 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -21,15 +21,21 @@ std::string buildTag(const std::string& baseTag, uint32_t chainID, const std::st return utils::toHexString(utils::hash(concatenatedTag)); } -BLSSigner::BLSSigner() { +BLSSigner::BLSSigner(const cryptonote::network_type nettype) { initCurve(); // This init function generates a secret key calling blsSecretKeySetByCSPRNG secretKey.init(); + const auto config = get_config(nettype); + chainID = config.ETHEREUM_CHAIN_ID; + contractAddress = config.ETHEREUM_REWARDS_CONTRACT; } -BLSSigner::BLSSigner(bls::SecretKey _secretKey) { +BLSSigner::BLSSigner(const cryptonote::network_type nettype, bls::SecretKey _secretKey) { initCurve(); secretKey = _secretKey; + const auto config = get_config(nettype); + chainID = config.ETHEREUM_CHAIN_ID; + contractAddress = config.ETHEREUM_REWARDS_CONTRACT; } BLSSigner::~BLSSigner() { @@ -58,8 +64,6 @@ bls::Signature BLSSigner::signHash(const std::array& hash) { std::string BLSSigner::proofOfPossession() { //TODO sean put constants somewhere, source them - const uint32_t chainID = 31337; - const std::string contractAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; std::string fullTag = buildTag(proofOfPossessionTag, chainID, contractAddress); std::string message = "0x" + fullTag + getPublicKeyHex(); diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 33f2d31aed..4eec7980ff 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -16,16 +16,20 @@ #include #include +#include "cryptonote_config.h" class BLSSigner { private: bls::SecretKey secretKey; + uint32_t chainID; + std::string contractAddress; + void initCurve(); public: - BLSSigner(); - BLSSigner(bls::SecretKey _secretKey); + BLSSigner(const cryptonote::network_type nettype); + BLSSigner(const cryptonote::network_type nettype, bls::SecretKey _secretKey); ~BLSSigner(); bls::Signature signHash(const std::array& hash); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 85c58d75a7..28dc745154 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -409,8 +409,8 @@ namespace config { inline constexpr uint64_t STORE_LONG_TERM_STATE_INTERVAL = 10000; // Details of the ethereum smart contract managing rewards and chain its kept on - inline constexpr uint32_t ETHEREUM_CHAIN_ID = 11155111; - inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xf3e5eDC802784D72E910a51390E0dF431F575C24"; + inline constexpr uint32_t ETHEREUM_CHAIN_ID = 31337; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 88dc9e440a..6c3f603bb0 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -117,8 +117,6 @@ Blockchain::block_extended_info::block_extended_info( //------------------------------------------------------------------ Blockchain::Blockchain( tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list) : - //TODO sean load this from config data - m_provider(std::make_shared("Sepolia Client", std::string("https://eth-sepolia.g.alchemy.com/v2/xjUjCAfxli88pqe7UjR4Tt1Jp2GKPJvy"))), m_db(), m_tx_pool(tx_pool), m_current_block_cumul_weight_limit(0), @@ -505,6 +503,7 @@ bool Blockchain::init( bool offline, const cryptonote::test_options* test_options, difficulty_type fixed_difficulty, + const std::string& ethereum_provider, const GetCheckpointsCallback& get_checkpoints /* = nullptr*/) { @@ -534,7 +533,12 @@ bool Blockchain::init( if (!m_checkpoints.init(m_nettype, m_db)) throw std::runtime_error("Failed to initialize checkpoints"); - m_l2_tracker = std::make_shared(m_nettype, m_provider); + m_provider = std::make_shared("Ethereum Client", ethereum_provider); + if (ethereum_provider != "") { + m_l2_tracker = std::make_shared(m_nettype, m_provider); + } else { + m_l2_tracker = std::make_shared(); + } m_offline = offline; m_fixed_difficulty = fixed_difficulty; @@ -1646,13 +1650,6 @@ bool Blockchain::create_block_template_internal( uint64_t already_generated_coins; uint64_t pool_cookie; - //TODO sean - std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); - // Fill tx_pool with ethereum transactions before we build the block - // TODO sean get the previous height from somewhere - std::vector eth_transactions = m_l2_tracker->get_block_transactions(b.l2_height -1, b.l2_height); - process_ethereum_transactions(eth_transactions); - auto lock = tools::unique_locks(m_tx_pool, *this); if (m_btc_valid && !from_block) { // The pool cookie is atomic. The lack of locking is OK, as if it changes @@ -1768,6 +1765,15 @@ bool Blockchain::create_block_template_internal( size_t txs_weight; uint64_t fee; + //TODO sean + if (hf_version >= cryptonote::feature::ETH_BLS) { + std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); + // Fill tx_pool with ethereum transactions before we build the block + // TODO sean get the previous height from somewhere + std::vector eth_transactions = m_l2_tracker->get_block_transactions(b.l2_height -1, b.l2_height); + process_ethereum_transactions(eth_transactions); + } + // Add transactions in mempool to block if (!m_tx_pool.fill_block_template( b, @@ -4919,7 +4925,9 @@ bool Blockchain::handle_block_to_main_chain( // to txs. Keys spent in each are added to by the double spend check. txs.reserve(bl.tx_hashes.size()); - m_l2_tracker->initialize_transaction_review(bl.l2_height); + auto hf_version = bl.major_version; + if (hf_version >= cryptonote::feature::ETH_BLS) + m_l2_tracker->initialize_transaction_review(bl.l2_height); for (const crypto::hash& tx_id : bl.tx_hashes) { transaction tx_tmp; @@ -5053,7 +5061,7 @@ bool Blockchain::handle_block_to_main_chain( fee_summary += fee; cumulative_block_weight += tx_weight; } - if (!m_l2_tracker->finalize_transaction_review()) { + if (hf_version >= cryptonote::feature::ETH_BLS && !m_l2_tracker->finalize_transaction_review()) { log::info( logcat, fg(fmt::terminal_color::red), @@ -5187,7 +5195,7 @@ bool Blockchain::handle_block_to_main_chain( if (is_hard_fork_at_least(m_nettype, feature::ETH_BLS, bl.height) && !m_l2_tracker->check_state_in_history(bl.l2_height, bl.l2_state)) { - log::info(logcat, fg(fmt::terminal_color::red), "Failed to find state in l2 tracker."); + log::error(logcat, fg(fmt::terminal_color::red), "Failed to find state in l2 tracker."); bvc.m_verifivation_failed = true; return false; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 6828dfad37..795d784884 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -149,6 +149,7 @@ class Blockchain { * @param offline true if running offline, else false * @param test_options test parameters * @param fixed_difficulty fixed difficulty for testing purposes; 0 means disabled + * @param ethereum_provider sync ethereum using this provider * @param get_checkpoints if set, will be called to get checkpoints data * * @return true on success, false if any initialization steps fail @@ -161,6 +162,7 @@ class Blockchain { bool offline = false, const cryptonote::test_options* test_options = nullptr, difficulty_type fixed_difficulty = 0, + const std::string& ethereum_provider = "", const GetCheckpointsCallback& get_checkpoints = nullptr); /** diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 35919589db..717b4a0d07 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -155,6 +155,11 @@ static const command_line::arg_descriptor arg_public_ip = { "storage server) are accessible. This IP address will be advertised to the " "network via the service node uptime proofs. Required if operating as a " "service node."}; +static const command_line::arg_descriptor arg_ethereum_provider = { + "ethereum-provider", + "Provider on which this service node will query the ethereum blockchain " + "when tracking the rewards smart contract. Required if operating as a " + "service node."}; static const command_line::arg_descriptor arg_storage_server_port = { "storage-server-port", "Deprecated option, ignored.", 0}; static const command_line::arg_descriptor arg_quorumnet_port = { @@ -251,7 +256,7 @@ core::core() : m_last_storage_server_ping(0), m_last_lokinet_ping(0), m_pad_transactions(false), - m_bls_signer(std::make_shared()), + m_bls_signer(std::make_shared(m_nettype)), ss_version{0}, lokinet_version{0} { m_checkpoints_updating.clear(); @@ -308,6 +313,7 @@ void core::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_max_txpool_weight); command_line::add_arg(desc, arg_service_node); command_line::add_arg(desc, arg_public_ip); + command_line::add_arg(desc, arg_ethereum_provider); command_line::add_arg(desc, arg_storage_server_port); command_line::add_arg(desc, arg_quorumnet_port); @@ -394,6 +400,14 @@ bool core::handle_command_line(const boost::program_options::variables_map& vm) args_okay = false; } + const std::string ethereum_provider = command_line::get_arg(vm, arg_ethereum_provider); + if (!ethereum_provider.size()) { + log::error( + logcat, + "Please specify an ethereum provider from which the service node can access"); + args_okay = false; + } + if (!args_okay) { log::error( logcat, @@ -769,6 +783,7 @@ bool core::init( m_bls_aggregator = std::make_unique(m_service_node_list, m_omq, m_bls_signer); const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty); + const auto ethereum_provider = command_line::get_arg(vm, arg_ethereum_provider); r = m_blockchain_storage.init( db.release(), ons_db, @@ -777,6 +792,7 @@ bool core::init( m_offline, regtest ? ®test_test_options : test_options, fixed_difficulty, + ethereum_provider, get_checkpoints); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 41e3378596..ae9b3c9746 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -8,6 +8,7 @@ static auto logcat = oxen::log::Cat("l2_tracker"); L2Tracker::L2Tracker() { + service_node = false; } L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) @@ -59,7 +60,6 @@ void L2Tracker::process_logs_for_state(State& state) { void L2Tracker::update_state() { try { - // Get latest state State new_state(rewards_contract->State()); process_logs_for_state(new_state); insert_in_order(std::move(new_state)); @@ -76,11 +76,13 @@ void L2Tracker::update_state() { } } } catch (const std::exception& e) { - oxen::log::error(logcat, "Failed to update state: {}", e.what()); + oxen::log::warning(logcat, "Failed to update state: {}", e.what()); } } std::pair L2Tracker::latest_state() { + if (!service_node) + throw std::runtime_error("Non Service node doesn't keep track of state"); if(state_history.empty()) { throw std::runtime_error("Internal error getting latest state from l2 tracker"); } @@ -96,6 +98,8 @@ bool L2Tracker::check_state_in_history(uint64_t height, const crypto::hash& stat } bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state_root) { + if (!service_node) + return true; auto it = std::find_if(state_history.begin(), state_history.end(), [height, &state_root](const State& state) { return state.height == height && state.state == state_root; @@ -117,6 +121,8 @@ void L2Tracker::initialize_transaction_review(uint64_t ethereum_height) { } bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { + if (!service_node) + return true; if (review_block_height == 0) { fail_reason = "Review not initialized"; oxen::log::error(logcat, "Failed to process new service node tx height {}", review_block_height); @@ -135,6 +141,8 @@ bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::s } bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason) { + if (!service_node) + return true; if (review_block_height == 0) { fail_reason = "Review not initialized"; oxen::log::error(logcat, "Failed to process service node leave request tx height {}", review_block_height); @@ -153,6 +161,8 @@ bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std } bool L2Tracker::processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { + if (!service_node) + return true; if (review_block_height == 0) { fail_reason = "Review not initialized"; oxen::log::error(logcat, "Failed to process deregister tx height {}", review_block_height); @@ -171,6 +181,8 @@ bool L2Tracker::processServiceNodeDeregisterTx(const std::string& bls_key, bool } bool L2Tracker::finalize_transaction_review() { + if (!service_node) + return true; if (new_service_nodes.empty() && leave_requests.empty() && deregs.empty()) { review_block_height = 0; return true; @@ -215,6 +227,8 @@ void L2Tracker::get_review_transactions() { } std::vector L2Tracker::get_block_transactions(uint64_t begin_height, uint64_t end_height) { + if (!service_node) + throw std::runtime_error("Non Service node doesn't keep track of state"); std::vector all_transactions; for (const auto& state : state_history) { if (state.height >= begin_height && state.height <= end_height) { diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 8a2ea28e70..0615b1dea1 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -26,7 +26,7 @@ class L2Tracker { std::atomic stop_thread; std::thread update_thread; - uint64_t review_block_height; + uint64_t review_block_height = 0; std::vector new_service_nodes; std::vector leave_requests; std::vector deregs; @@ -63,5 +63,6 @@ class L2Tracker { private: static std::string get_contract_address(const cryptonote::network_type nettype); void get_review_transactions(); + bool service_node = true; // END }; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index cadba34438..e79ae273cc 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -67,33 +67,12 @@ RewardsContract::RewardsContract(const std::string& _contractAddress, std::share : contractAddress(_contractAddress), provider(std::move(_provider)) {} StateResponse RewardsContract::State() { - return State(std::nullopt); + return State(provider->getLatestHeight()); } -StateResponse RewardsContract::State(std::optional height) { - ReadCallData callData; - callData.contractAddress = contractAddress; - std::string functionSelector = utils::getFunctionSignature("state()"); - callData.data = functionSelector; - - std::string result; - if (height) { - result = provider->callReadFunction(callData, *height); - } else { - result = provider->callReadFunction(callData); - } - - if (result.size() != 130) { - throw std::runtime_error("L2 State returned invalid data"); - } - - std::string blockHeightHex = result.substr(2, 64); - std::string blockHash = result.substr(66, 64); - - // Convert blockHeightHex to a decimal number - uint64_t blockHeight = std::stoull(blockHeightHex, nullptr, 16); - - return StateResponse{blockHeight, blockHash}; +StateResponse RewardsContract::State(uint64_t height) { + std::string blockHash = provider->getContractStorageRoot(contractAddress, height); + return StateResponse{height, blockHash}; } std::vector RewardsContract::Logs(uint64_t height) { diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index aec2d1b412..b68afe0f56 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -61,7 +61,7 @@ class RewardsContract { RewardsContract(const std::string& _contractAddress, std::shared_ptr _provider); StateResponse State(); - StateResponse State(std::optional height); + StateResponse State(uint64_t height); std::vector Logs(uint64_t height); diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index 357c7bd6ba..1c7141af6f 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -173,6 +173,7 @@ def __init__(self, *, '--service-node', '--service-node-public-ip={}'.format(self.listen_ip), '--storage-server-port={}'.format(self.ss_port), + '--ethereum-provider="{}"'.format("127.0.0.1:8545"), ) diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index a075228b38..d272a2a114 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -136,6 +136,9 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): with open(configfile, 'w') as filetowrite: filetowrite.write('#!/usr/bin/python3\n# -*- coding: utf-8 -*-\nlisten_ip=\"{}\"\nlisten_port=\"{}\"\nwallet_listen_ip=\"{}\"\nwallet_listen_port=\"{}\"\nwallet_address=\"{}\"\nexternal_address=\"{}\"'.format(self.sns[0].listen_ip,self.sns[0].rpc_port,self.mike.listen_ip,self.mike.rpc_port,self.mike.address(),self.bob.address())) + # TODO sean remove this + # input("Press Enter to continue...") + # Mine some blocks; we need 100 per SN registration, and we can nearly 600 on fakenet before # it hits HF16 and kills mining rewards. This lets us submit the first 5 SN registrations a # SN (at height 40, which is the earliest we can submit them without getting an occasional From 0621c7a5e6f734e0c340b17febf9d71399c13b9a Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 9 Feb 2024 16:56:57 +1100 Subject: [PATCH 41/97] submit the transaction --- src/blockchain_db/lmdb/db_lmdb.cpp | 107 ++++++------ .../cryptonote_format_utils.cpp | 28 +++ .../cryptonote_format_utils.h | 3 + src/cryptonote_core/blockchain.cpp | 93 +++++++--- src/cryptonote_core/blockchain.h | 15 +- src/cryptonote_core/cryptonote_core.cpp | 6 + src/cryptonote_core/ethereum_transactions.cpp | 7 +- src/cryptonote_core/pulse.cpp | 3 + src/cryptonote_core/tx_pool.cpp | 77 +++++++-- src/cryptonote_core/tx_pool.h | 7 +- src/l2_tracker/l2_tracker.cpp | 160 +++++++++--------- src/l2_tracker/l2_tracker.h | 35 ++-- src/l2_tracker/rewards_contract.cpp | 20 ++- utils/local-devnet/daemons.py | 2 +- 14 files changed, 370 insertions(+), 193 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 9319b9ac8c..452a1f126a 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -92,7 +92,7 @@ void throw0(const T& e) { template void throw1(const T& e) { - log::info(logcat, e.what()); + log::error(logcat, e.what()); throw e; } @@ -1020,7 +1020,7 @@ uint64_t BlockchainLMDB::add_transaction_data( .c_str())); } else if (result != MDB_NOTFOUND) { throw1(DB_ERROR(lmdb_error( - "Error checking if tx index exists for tx hash " + tools::type_to_hex(tx_hash) + + "error checking if tx index exists for tx hash " + tools::type_to_hex(tx_hash) + ": ", result))); } @@ -1137,7 +1137,7 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const result = mdb_cursor_del(m_cur_txs_prunable_tip, 0); if (result) throw1(DB_ERROR( - lmdb_error("Error adding removal of tx id to db transaction", result).c_str())); + lmdb_error("error adding removal of tx id to db transaction", result).c_str())); } if (tx.version >= cryptonote::txversion::v2_ringct) { @@ -1310,12 +1310,12 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in throw0(DB_ERROR("Unexpected: global output index not found in m_output_txs")); } else if (result) { throw1(DB_ERROR( - lmdb_error("Error adding removal of output tx to db transaction", result).c_str())); + lmdb_error("error adding removal of output tx to db transaction", result).c_str())); } result = mdb_cursor_del(m_cur_output_txs, 0); if (result) throw0(DB_ERROR(lmdb_error( - std::string("Error deleting output index ") + std::string("error deleting output index ") .append(std::to_string(out_index).append(": ")) .c_str(), result) @@ -1325,7 +1325,7 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in result = mdb_cursor_del(m_cur_output_amounts, 0); if (result) throw0(DB_ERROR(lmdb_error( - std::string("Error deleting amount for output index ") + std::string("error deleting amount for output index ") .append(std::to_string(out_index).append(": ")) .c_str(), result) @@ -1347,7 +1347,7 @@ void BlockchainLMDB::prune_outputs(uint64_t amount) { if (result == MDB_NOTFOUND) return; if (result) - throw0(DB_ERROR(lmdb_error("Error looking up outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error looking up outputs: ", result).c_str())); // gather output ids mdb_size_t num_elems; @@ -1363,23 +1363,23 @@ void BlockchainLMDB::prune_outputs(uint64_t amount) { if (result == MDB_NOTFOUND) break; if (result) - throw0(DB_ERROR(lmdb_error("Error counting outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error counting outputs: ", result).c_str())); } if (output_ids.size() != num_elems) throw0(DB_ERROR("Unexpected number of outputs")); result = mdb_cursor_del(m_cur_output_amounts, MDB_NODUPDATA); if (result) - throw0(DB_ERROR(lmdb_error("Error deleting outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error deleting outputs: ", result).c_str())); for (uint64_t output_id : output_ids) { MDB_val_set(v, output_id); result = mdb_cursor_get(m_cur_output_txs, (MDB_val*)&zerokval, &v, MDB_GET_BOTH); if (result) - throw0(DB_ERROR(lmdb_error("Error looking up output: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error looking up output: ", result).c_str())); result = mdb_cursor_del(m_cur_output_txs, 0); if (result) - throw0(DB_ERROR(lmdb_error("Error deleting output: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error deleting output: ", result).c_str())); } } @@ -1395,7 +1395,7 @@ void BlockchainLMDB::add_spent_key(const crypto::key_image& k_image) { if (result == MDB_KEYEXIST) throw1(KEY_IMAGE_EXISTS("Attempting to add spent key image that's already in the db")); else - throw1(DB_ERROR(lmdb_error("Error adding spent key image to db transaction: ", result) + throw1(DB_ERROR(lmdb_error("error adding spent key image to db transaction: ", result) .c_str())); } } @@ -1410,12 +1410,12 @@ void BlockchainLMDB::remove_spent_key(const crypto::key_image& k_image) { MDB_val k = {sizeof(k_image), (void*)&k_image}; auto result = mdb_cursor_get(m_cur_spent_keys, (MDB_val*)&zerokval, &k, MDB_GET_BOTH); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("Error finding spent key to remove", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding spent key to remove", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_spent_keys, 0); if (result) throw1(DB_ERROR( - lmdb_error("Error adding removal of key image to db transaction", result) + lmdb_error("error adding removal of key image to db transaction", result) .c_str())); } } @@ -1998,16 +1998,19 @@ void BlockchainLMDB::add_txpool_tx( throw1(DB_ERROR("Attempting to add txpool tx metadata that's already in the db")); else throw1(DB_ERROR( - lmdb_error("Error adding txpool tx metadata to db transaction: ", result) + lmdb_error("error adding txpool tx metadata to db transaction: ", result) .c_str())); } MDB_val_sized(blob_val, blob); + //TODO sean remove this + if (blob_val.mv_size == 0) + throw1(DB_ERROR("error adding txpool tx blob: tx is present, but data is empty")); if (auto result = mdb_cursor_put(m_cur_txpool_blob, &k, &blob_val, MDB_NODUPDATA)) { if (result == MDB_KEYEXIST) throw1(DB_ERROR("Attempting to add txpool tx blob that's already in the db")); else throw1(DB_ERROR( - lmdb_error("Error adding txpool tx blob to db transaction: ", result).c_str())); + lmdb_error("error adding txpool tx blob to db transaction: ", result).c_str())); } } @@ -2023,11 +2026,11 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash& txid, const txpool_tx_ MDB_val v; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); if (result != 0) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta to update: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx meta to update: ", result).c_str())); result = mdb_cursor_del(m_cur_txpool_meta, 0); if (result) throw1(DB_ERROR( - lmdb_error("Error adding removal of txpool tx metadata to db transaction: ", result) + lmdb_error("error adding removal of txpool tx metadata to db transaction: ", result) .c_str())); v = MDB_val({sizeof(meta), (void*)&meta}); if ((result = mdb_cursor_put(m_cur_txpool_meta, &k, &v, MDB_NODUPDATA)) != 0) { @@ -2035,7 +2038,7 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash& txid, const txpool_tx_ throw1(DB_ERROR("Attempting to add txpool tx metadata that's already in the db")); else throw1(DB_ERROR( - lmdb_error("Error adding txpool tx metadata to db transaction: ", result) + lmdb_error("error adding txpool tx metadata to db transaction: ", result) .c_str())); } } @@ -2091,7 +2094,7 @@ bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const { MDB_val k = {sizeof(txid), (void*)&txid}; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx meta: ", result).c_str())); return result != MDB_NOTFOUND; } @@ -2106,24 +2109,24 @@ void BlockchainLMDB::remove_txpool_tx(const crypto::hash& txid) { MDB_val k = {sizeof(txid), (void*)&txid}; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta to remove: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx meta to remove: ", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_txpool_meta, 0); if (result) throw1(DB_ERROR(lmdb_error( - "Error adding removal of txpool tx metadata to db " + "error adding removal of txpool tx metadata to db " "transaction: ", result) .c_str())); } result = mdb_cursor_get(m_cur_txpool_blob, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx blob to remove: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx blob to remove: ", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_txpool_blob, 0); if (result) throw1(DB_ERROR( - lmdb_error("Error adding removal of txpool tx blob to db transaction: ", result) + lmdb_error("error adding removal of txpool tx blob to db transaction: ", result) .c_str())); } } @@ -2141,7 +2144,7 @@ bool BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta if (result == MDB_NOTFOUND) return false; if (result != 0) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx meta: ", result).c_str())); meta = *(const txpool_tx_meta_t*)v.mv_data; return true; @@ -2160,10 +2163,10 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, std::string& b if (result == MDB_NOTFOUND) return false; if (result != 0) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx blob: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error finding txpool tx blob: ", result).c_str())); if (v.mv_size == 0) - throw1(DB_ERROR("Error finding txpool tx blob: tx is present, but data is empty")); + throw1(DB_ERROR("error finding txpool tx blob: tx is present, but data is empty")); bd.assign(reinterpret_cast(v.mv_data), v.mv_size); return true; @@ -2390,7 +2393,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable_tip, &kp, &vp, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("Error looking for transaction prunable data: ", result) + lmdb_error("error looking for transaction prunable data: ", result) .c_str())); if (result == MDB_NOTFOUND) log::error( @@ -2404,7 +2407,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_put(c_txs_prunable_tip, &kp, &vp, 0); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("Error looking for transaction prunable data: ", result) + lmdb_error("error looking for transaction prunable data: ", result) .c_str())); } } @@ -2414,7 +2417,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("Error looking for transaction prunable data: ", result) + lmdb_error("error looking for transaction prunable data: ", result) .c_str())); if (mode == prune_mode_check) { if (result != MDB_NOTFOUND) @@ -2452,7 +2455,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("Error looking for transaction prunable data: ", result) + lmdb_error("error looking for transaction prunable data: ", result) .c_str())); if (result == MDB_NOTFOUND) log::error( @@ -2751,7 +2754,7 @@ T BlockchainLMDB::get_and_convert_block_blob_from_height(uint64_t height) const .append(" failed -- block not in db") .c_str())); else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a block from the db")); + throw0(DB_ERROR("error attempting to retrieve a block from the db")); std::string_view blob{reinterpret_cast(value.mv_data), value.mv_size}; @@ -2796,7 +2799,7 @@ uint64_t BlockchainLMDB::get_block_height(const crypto::hash& h) const { "Attempted to retrieve non-existent block height from hash " + tools::type_to_hex(h))); else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a block height from the db")); + throw0(DB_ERROR("error attempting to retrieve a block height from the db")); blk_height* bhp = (blk_height*)key.mv_data; uint64_t ret = bhp->bh_height; @@ -2829,7 +2832,7 @@ uint64_t BlockchainLMDB::get_block_timestamp(const uint64_t& height) const { .append(" failed -- timestamp not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a timestamp from the db")); + throw0(DB_ERROR("error attempting to retrieve a timestamp from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_timestamp; @@ -2888,7 +2891,7 @@ std::vector BlockchainLMDB::get_block_cumulative_rct_outputs( } if (result) throw0(DB_ERROR(lmdb_error( - "Error attempting to retrieve rct distribution from the " + "error attempting to retrieve rct distribution from the " "db: ", result) .c_str())); @@ -2929,7 +2932,7 @@ size_t BlockchainLMDB::get_block_weight(const uint64_t& height) const { .append(" failed -- block size not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a block size from the db")); + throw0(DB_ERROR("error attempting to retrieve a block size from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; size_t ret = bi->bi_weight; @@ -2980,7 +2983,7 @@ std::vector BlockchainLMDB::get_block_info_64bit_fields( } if (result) throw0(DB_ERROR( - lmdb_error("Error attempting to retrieve block_info from the db: ", result) + lmdb_error("error attempting to retrieve block_info from the db: ", result) .c_str())); } ret.push_back( @@ -3064,7 +3067,7 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t& .append(" failed -- difficulty not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a cumulative difficulty from the db")); + throw0(DB_ERROR("error attempting to retrieve a cumulative difficulty from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; difficulty_type ret = bi->bi_diff; @@ -3101,7 +3104,7 @@ uint64_t BlockchainLMDB::get_block_already_generated_coins(const uint64_t& heigh .append(" failed -- block size not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a total generated coins from the db")); + throw0(DB_ERROR("error attempting to retrieve a total generated coins from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_coins; @@ -3123,7 +3126,7 @@ uint64_t BlockchainLMDB::get_block_long_term_weight(const uint64_t& height) cons .append(" failed -- block info not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve a long term block weight from the db")); + throw0(DB_ERROR("error attempting to retrieve a long term block weight from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_long_term_block_weight; @@ -3146,7 +3149,7 @@ crypto::hash BlockchainLMDB::get_block_hash_from_height(const uint64_t& height) .c_str())); } else if (get_result) throw0(DB_ERROR( - lmdb_error("Error attempting to retrieve a block hash from the db: ", get_result) + lmdb_error("error attempting to retrieve a block hash from the db: ", get_result) .c_str())); mdb_block_info* bi = (mdb_block_info*)result.mv_data; @@ -3566,7 +3569,7 @@ output_data_t BlockchainLMDB::get_output_key( std::to_string(amount) + ", index " + std::to_string(index)) .c_str())); else if (get_result) - throw0(DB_ERROR("Error attempting to retrieve an output pubkey from the db")); + throw0(DB_ERROR("error attempting to retrieve an output pubkey from the db")); output_data_t ret; if (amount == 0) { @@ -4493,7 +4496,7 @@ void BlockchainLMDB::get_output_key( } else if (get_result) throw0(DB_ERROR( lmdb_error( - "Error attempting to retrieve an output pubkey from the db", get_result) + "error attempting to retrieve an output pubkey from the db", get_result) .c_str())); if (amount == 0) { @@ -4533,7 +4536,7 @@ void BlockchainLMDB::get_output_tx_and_index( throw1(OUTPUT_DNE("Attempting to get output by index, but key does not exist")); else if (get_result) throw0(DB_ERROR( - lmdb_error("Error attempting to retrieve an output from the db", get_result) + lmdb_error("error attempting to retrieve an output from the db", get_result) .c_str())); const outkey* okp = (const outkey*)v.mv_data; @@ -4780,7 +4783,7 @@ void BlockchainLMDB::add_alt_block( if (result == MDB_KEYEXIST) throw1(DB_ERROR("Attempting to add alternate block that's already in the db")); else - throw1(DB_ERROR(lmdb_error("Error adding alternate block to db transaction: ", result) + throw1(DB_ERROR(lmdb_error("error adding alternate block to db transaction: ", result) .c_str())); } } @@ -4804,7 +4807,7 @@ bool BlockchainLMDB::get_alt_block( if (result) throw0(DB_ERROR(lmdb_error( - "Error attempting to retrieve alternate block " + + "error attempting to retrieve alternate block " + tools::type_to_hex(blkid) + " from the db: ", result) .c_str())); @@ -4825,14 +4828,14 @@ void BlockchainLMDB::remove_alt_block(const crypto::hash& blkid) { int result = mdb_cursor_get(m_cur_alt_blocks, &k, &v, MDB_SET); if (result) throw0(DB_ERROR(lmdb_error( - "Error locating alternate block " + tools::type_to_hex(blkid) + + "error locating alternate block " + tools::type_to_hex(blkid) + " in the db: ", result) .c_str())); result = mdb_cursor_del(m_cur_alt_blocks, 0); if (result) throw0(DB_ERROR(lmdb_error( - "Error deleting alternate block " + tools::type_to_hex(blkid) + + "error deleting alternate block " + tools::type_to_hex(blkid) + " from the db: ", result) .c_str())); @@ -4864,7 +4867,7 @@ void BlockchainLMDB::drop_alt_blocks() { auto result = mdb_drop(*txn_ptr, m_alt_blocks, 0); if (result) - throw1(DB_ERROR(lmdb_error("Error dropping alternative blocks: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("error dropping alternative blocks: ", result).c_str())); TXN_POSTFIX_SUCCESS(); } @@ -4873,7 +4876,7 @@ bool BlockchainLMDB::is_read_only() const { unsigned int flags; auto result = mdb_env_get_flags(m_env, &flags); if (result) - throw0(DB_ERROR(lmdb_error("Error getting database environment info: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("error getting database environment info: ", result).c_str())); if (flags & MDB_RDONLY) return true; @@ -6789,10 +6792,10 @@ bool BlockchainLMDB::remove_service_node_proof(const crypto::public_key& pubkey) if (result == MDB_NOTFOUND) return false; if (result != MDB_SUCCESS) - throw0(DB_ERROR(lmdb_error("Error finding service node proof to remove", result))); + throw0(DB_ERROR(lmdb_error("error finding service node proof to remove", result))); result = mdb_cursor_del(m_cursors->service_node_proofs, 0); if (result) - throw0(DB_ERROR(lmdb_error("Error remove service node proof", result))); + throw0(DB_ERROR(lmdb_error("error remove service node proof", result))); return true; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index ea6cd7f685..c324f3e144 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -228,6 +228,7 @@ bool parse_and_validate_tx_from_blob(const std::string_view tx_blob, transaction } //--------------------------------------------------------------- bool parse_and_validate_tx_base_from_blob(const std::string_view tx_blob, transaction& tx) { + oxen::log::info(logcat, "TODO sean remove this transaction blob size: {}", tx_blob.size()); serialization::binary_string_unarchiver ba{tx_blob}; try { tx.serialize_base(ba); @@ -961,6 +962,33 @@ bool add_burned_amount_to_tx_extra(std::vector& tx_extra, uint64_t burn return true; } //--------------------------------------------------------------- +bool add_new_service_node_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_new_service_node& new_service_node) { + tx_extra_field field = new_service_node; + if (!add_tx_extra_field_to_tx_extra(tx_extra, field)) { + log::info(logcat, "failed to serialize tx extra for new service node transaction"); + return false; + } + return true; +} +//--------------------------------------------------------------- +bool add_service_node_leave_request_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_leave_request& leave_request) { + tx_extra_field field = leave_request; + if (!add_tx_extra_field_to_tx_extra(tx_extra, field)) { + log::info(logcat, "failed to serialize tx extra for service node leave request transaction"); + return false; + } + return true; +} +//--------------------------------------------------------------- +bool add_service_node_deregister_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_deregister& deregister) { + tx_extra_field field = deregister; + if (!add_tx_extra_field_to_tx_extra(tx_extra, field)) { + log::info(logcat, "failed to serialize tx extra for service node deregister transaction"); + return false; + } + return true; +} +//--------------------------------------------------------------- bool get_inputs_money_amount(const transaction& tx, uint64_t& money) { money = 0; for (const auto& in : tx.vin) { diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 3d8fd1adf5..83b2e756f5 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -188,6 +188,9 @@ bool get_payment_id_from_tx_extra_nonce(const std::string& extra_nonce, crypto:: bool get_encrypted_payment_id_from_tx_extra_nonce( const std::string& extra_nonce, crypto::hash8& payment_id); bool add_burned_amount_to_tx_extra(std::vector& tx_extra, uint64_t burn); +bool add_new_service_node_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_new_service_node& new_service_node); +bool add_service_node_leave_request_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_leave_request& leave_request); +bool add_service_node_deregister_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_deregister& deregister); uint64_t get_burned_amount_from_tx_extra(const std::vector& tx_extra); bool is_out_to_acc( const account_keys& acc, diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 6c3f603bb0..aeeccb725f 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -834,6 +834,10 @@ block Blockchain::pop_block_from_blockchain(bool pop_batching_rewards = true) { // return transactions from popped block to the tx_pool size_t pruned = 0; + std::shared_ptr ethereum_transaction_review_session; + if (popped_block.major_version >= cryptonote::feature::ETH_BLS) { + ethereum_transaction_review_session = m_l2_tracker->initialize_mempool_review(); + } for (transaction& tx : popped_txs) { if (tx.pruned) { ++pruned; @@ -849,7 +853,7 @@ block Blockchain::pop_block_from_blockchain(bool pop_batching_rewards = true) { // that might not be always true. Unlikely though, and always relaying // these again might cause a spike of traffic as many nodes re-relay // all the transactions in a popped block when a reorg happens. - bool r = m_tx_pool.add_tx(tx, tvc, tx_pool_options::from_block(), version); + bool r = m_tx_pool.add_tx(tx, tvc, tx_pool_options::from_block(), version, ethereum_transaction_review_session); if (!r) { log::error(logcat, "Error returning transaction to tx_pool"); } @@ -1771,7 +1775,7 @@ bool Blockchain::create_block_template_internal( // Fill tx_pool with ethereum transactions before we build the block // TODO sean get the previous height from somewhere std::vector eth_transactions = m_l2_tracker->get_block_transactions(b.l2_height -1, b.l2_height); - process_ethereum_transactions(eth_transactions); + add_ethereum_transactions_to_tx_pool(eth_transactions); } // Add transactions in mempool to block @@ -1949,8 +1953,10 @@ bool Blockchain::create_next_pulse_block_template( return result; } -void Blockchain::process_ethereum_transactions(const std::vector& transactions) { +void Blockchain::add_ethereum_transactions_to_tx_pool(const std::vector& transactions) { auto hf_version = get_network_version(); + tx_extra_field field; + for (const auto& tx_variant : transactions) { transaction tx; tx.version = transaction_prefix::get_max_version_for_hf(hf_version); @@ -1959,19 +1965,42 @@ void Blockchain::process_ethereum_transactions(const std::vector; if constexpr (std::is_same_v) { tx.type = txtype::ethereum_new_service_node; + crypto::public_key service_node_pubkey; + tools::hex_to_type(arg.service_node_pubkey, service_node_pubkey); + crypto::signature signature; + tools::hex_to_type(arg.signature, signature); + tx_extra_ethereum_new_service_node new_service_node = { 0, arg.bls_key, arg.eth_address, service_node_pubkey, signature }; + cryptonote::add_new_service_node_to_tx_extra(tx.extra, new_service_node); + } else if constexpr (std::is_same_v) { tx.type = txtype::ethereum_service_node_leave_request; + tx_extra_ethereum_service_node_leave_request leave_request = { 0, arg.bls_key }; + cryptonote::add_service_node_leave_request_to_tx_extra(tx.extra, leave_request); } else if constexpr (std::is_same_v) { tx.type = txtype::ethereum_service_node_deregister; + tx_extra_ethereum_service_node_deregister deregister = { 0, arg.bls_key, true }; + cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister); } }, tx_variant); const size_t tx_weight = get_transaction_weight(tx); const crypto::hash tx_hash = get_transaction_hash(tx); tx_verification_context tvc = {}; - std::string blob; + std::shared_ptr ethereum_transaction_review_session = m_l2_tracker->initialize_mempool_review(); // Add transaction to memory pool - m_tx_pool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_pool_options::from_block(), hf_version); + + oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB TX hash created: {}", tx_hash); + oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex: {}", oxenc::to_hex(cryptonote::tx_to_blob(tx))); + if (!m_tx_pool.add_tx(tx, tx_hash, cryptonote::tx_to_blob(tx), tx_weight, tvc, tx_pool_options::new_tx(), hf_version, ethereum_transaction_review_session)) + { + if (tvc.m_verifivation_failed) + log::error(log::Cat("verify"), "Transaction verification failed: {}", tx_hash); + else if (tvc.m_verifivation_impossible) + log::error( + log::Cat("verify"), + "Transaction verification impossible: {}", + tx_hash); + } } } //------------------------------------------------------------------ @@ -3416,11 +3445,12 @@ void Blockchain::on_new_tx_from_block(const cryptonote::transaction& tx) { // as a return-by-reference. bool Blockchain::check_tx_inputs( transaction& tx, - uint64_t& max_used_block_height, - crypto::hash& max_used_block_id, tx_verification_context& tvc, - bool kept_by_block, - std::unordered_set* key_image_conflicts) { + std::shared_ptr ethereum_transaction_review_session, + crypto::hash& max_used_block_id, + uint64_t& max_used_block_height, + std::unordered_set* key_image_conflicts, + bool kept_by_block) { log::trace(logcat, "Blockchain::{}", __func__); std::unique_lock lock{*this}; @@ -3434,7 +3464,8 @@ bool Blockchain::check_tx_inputs( #endif auto a = std::chrono::steady_clock::now(); - bool res = check_tx_inputs(tx, tvc, &max_used_block_height, key_image_conflicts); + oxen::log::info(logcat, "TODO sean remove this: {}", "calling check tx inputs"); + bool res = check_tx_inputs(tx, tvc, ethereum_transaction_review_session, &max_used_block_height, key_image_conflicts); if (m_show_time_stats) { size_t ring_size = 0; if (!tx.vin.empty() && std::holds_alternative(tx.vin[0])) @@ -3673,6 +3704,7 @@ bool Blockchain::expand_transaction_2( bool Blockchain::check_tx_inputs( transaction& tx, tx_verification_context& tvc, + std::shared_ptr ethereum_transaction_review_session, uint64_t* pmax_used_block_height, std::unordered_set* key_image_conflicts) { log::trace(logcat, "Blockchain::{}", __func__); @@ -4068,34 +4100,30 @@ bool Blockchain::check_tx_inputs( cryptonote::tx_extra_ethereum_new_service_node entry = {}; std::string fail_reason; if (!ethereum::validate_ethereum_new_service_node_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !m_l2_tracker->processNewServiceNodeTx(entry.bls_key, entry.eth_address, tools::type_to_hex(entry.service_node_pubkey), fail_reason)) { + !ethereum_transaction_review_session->processNewServiceNodeTx(entry.bls_key, entry.eth_address, tools::type_to_hex(entry.service_node_pubkey), fail_reason)) { log::error(log::Cat("verify"), "Failed to validate Ethereum New Service Node TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; } - } - if (tx.type == txtype::ethereum_service_node_leave_request) { + } else if (tx.type == txtype::ethereum_service_node_leave_request) { cryptonote::tx_extra_ethereum_service_node_leave_request entry = {}; std::string fail_reason; if (!ethereum::validate_ethereum_service_node_leave_request_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !m_l2_tracker->processServiceNodeLeaveRequestTx(entry.bls_key, fail_reason)) { + !ethereum_transaction_review_session->processServiceNodeLeaveRequestTx(entry.bls_key, fail_reason)) { log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Leave Request TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; } - } - if (tx.type == txtype::ethereum_service_node_deregister) { + } else if (tx.type == txtype::ethereum_service_node_deregister) { cryptonote::tx_extra_ethereum_service_node_deregister entry = {}; std::string fail_reason; if (!ethereum::validate_ethereum_service_node_deregister_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !m_l2_tracker->processServiceNodeDeregisterTx(entry.bls_key, entry.refund_stake, fail_reason)) { + !ethereum_transaction_review_session->processServiceNodeDeregisterTx(entry.bls_key, entry.refund_stake, fail_reason)) { log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Deregister TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; } - } - - if (tx.type == txtype::state_change) { + } else if (tx.type == txtype::state_change) { tx_extra_service_node_state_change state_change; if (!get_service_node_state_change_from_tx_extra(tx.extra, state_change, hf_version)) { log::error( @@ -4580,6 +4608,11 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons //------------------------------------------------------------------ void Blockchain::return_tx_to_pool(std::vector>& txs) { auto version = get_network_version(); + std::shared_ptr ethereum_transaction_review_session; + if (version >= cryptonote::feature::ETH_BLS) { + oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); + ethereum_transaction_review_session = m_l2_tracker->initialize_mempool_review(); + } for (auto& tx : txs) { cryptonote::tx_verification_context tvc{}; // We assume that if they were in a block, the transactions are already @@ -4596,7 +4629,8 @@ void Blockchain::return_tx_to_pool(std::vector= cryptonote::feature::ETH_BLS) - m_l2_tracker->initialize_transaction_review(bl.l2_height); + std::shared_ptr ethereum_transaction_review_session; + if (hf_version >= cryptonote::feature::ETH_BLS) { + oxen::log::info(logcat, "TODO sean remove this initializing with l2 height: {}", bl.l2_height); + ethereum_transaction_review_session = m_l2_tracker->initialize_transaction_review(bl.l2_height); + } for (const crypto::hash& tx_id : bl.tx_hashes) { transaction tx_tmp; @@ -5006,7 +5043,8 @@ bool Blockchain::handle_block_to_main_chain( { // validate that transaction inputs and the keys spending them are correct. tx_verification_context tvc{}; - if (!check_tx_inputs(tx, tvc)) { + oxen::log::info(logcat, "TODO sean remove this: {}", "calling check tx inputs"); + if (!check_tx_inputs(tx, tvc, ethereum_transaction_review_session)) { log::info( logcat, fg(fmt::terminal_color::red), @@ -5061,7 +5099,7 @@ bool Blockchain::handle_block_to_main_chain( fee_summary += fee; cumulative_block_weight += tx_weight; } - if (hf_version >= cryptonote::feature::ETH_BLS && !m_l2_tracker->finalize_transaction_review()) { + if (hf_version >= cryptonote::feature::ETH_BLS && !ethereum_transaction_review_session->finalize_review()) { log::info( logcat, fg(fmt::terminal_color::red), @@ -5224,6 +5262,7 @@ bool Blockchain::handle_block_to_main_chain( } + m_l2_tracker->record_block_height_mapping(bl.height, bl.l2_height); block_add_info hook_data{bl, only_txs, checkpoint}; for (const auto& hook : m_block_add_hooks) { try { @@ -5996,7 +6035,11 @@ bool Blockchain::prepare_handle_incoming_blocks( crypto::hash& tx_prefix_hash = txes[tx_index].second; ++tx_index; + if (!parse_and_validate_tx_base_from_blob(tx_blob, tx)) { + oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB TX hash: {}", tx_prefix_hash); + oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex could not parse: {}", oxenc::to_hex(tx_blob)); + oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex size : {}", tx_blob.size()); log::error(log::Cat("verify"), "Could not parse tx from incoming blocks"); m_scan_table.clear(); return false; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 795d784884..5b34139c42 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -702,11 +702,12 @@ class Blockchain { */ bool check_tx_inputs( transaction& tx, - uint64_t& pmax_used_block_height, - crypto::hash& max_used_block_id, tx_verification_context& tvc, - bool kept_by_block = false, - std::unordered_set* key_image_conflicts = nullptr); + std::shared_ptr ethereum_transaction_review_session, + crypto::hash& max_used_block_id, + uint64_t& pmax_used_block_height, + std::unordered_set* key_image_conflicts = nullptr, + bool kept_by_block = false); /** * @brief get fee quantization mask @@ -1162,6 +1163,8 @@ class Blockchain { */ void flush_invalid_blocks(); + std::shared_ptr m_l2_tracker; + #ifndef IN_UNIT_TESTS private: #endif @@ -1286,7 +1289,6 @@ class Blockchain { // Ethereum client for communicating with L2 blockchain std::shared_ptr m_provider; - std::shared_ptr m_l2_tracker; network_type m_nettype; bool m_offline; @@ -1393,6 +1395,7 @@ class Blockchain { bool check_tx_inputs( transaction& tx, tx_verification_context& tvc, + std::shared_ptr ethereum_transaction_review_session, uint64_t* pmax_used_block_height = nullptr, std::unordered_set* key_image_conflicts = nullptr); @@ -1637,7 +1640,7 @@ class Blockchain { */ uint64_t get_adjusted_time() const; - void process_ethereum_transactions(const std::vector& transactions); + void add_ethereum_transactions_to_tx_pool(const std::vector& transactions); /** * @brief finish an alternate chain's timestamp window from the main chain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 717b4a0d07..b6a934c693 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1378,6 +1378,11 @@ bool core::handle_parsed_txs( if (blink_rollback_height) *blink_rollback_height = 0; tx_pool_options tx_opts; + std::shared_ptr ethereum_transaction_review_session; + if (version >= cryptonote::feature::ETH_BLS) { + oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); + ethereum_transaction_review_session = m_blockchain_storage.m_l2_tracker->initialize_mempool_review(); + } for (size_t i = 0; i < parsed_txs.size(); i++) { auto& info = parsed_txs[i]; if (!info.result) { @@ -1406,6 +1411,7 @@ bool core::handle_parsed_txs( info.tvc, *local_opts, version, + ethereum_transaction_review_session, blink_rollback_height)) { log::debug(logcat, "tx added: {}", info.tx_hash); } else { diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp index aea9129f13..777cd55d78 100644 --- a/src/cryptonote_core/ethereum_transactions.cpp +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -4,6 +4,12 @@ using cryptonote::hf; +#include "logging/oxen_logger.h" +//TODO sean delete sstream +#include + +static auto logcat = oxen::log::Cat("l2_tracker"); + namespace ethereum { template @@ -84,7 +90,6 @@ bool validate_ethereum_new_service_node_tx( tx, cryptonote::txtype::ethereum_new_service_node)) return false; - if (check_condition( !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), reason, diff --git a/src/cryptonote_core/pulse.cpp b/src/cryptonote_core/pulse.cpp index 5f836f37c0..3a165f647f 100644 --- a/src/cryptonote_core/pulse.cpp +++ b/src/cryptonote_core/pulse.cpp @@ -256,6 +256,7 @@ namespace { // hash to be generated correctly. crypto::hash msg_signature_hash(crypto::hash const& top_block_hash, pulse::message const& msg) { crypto::hash result = {}; + oxen::log::info(logcat, "TODO sean remove this msg type pulse: {}", msg.type); switch (msg.type) { case pulse::message_type::invalid: assert("Invalid Code Path" == nullptr); break; @@ -274,6 +275,7 @@ namespace { } break; case pulse::message_type::block_template: { + oxen::log::info(logcat, "TODO sean remove this block template message in pulse: {}", msg.block_template.blob.data()); crypto::hash block_hash = blake2b_hash( msg.block_template.blob.data(), msg.block_template.blob.size()); auto buf = tools::memcpy_le(msg.round, block_hash); @@ -296,6 +298,7 @@ namespace { } break; case pulse::message_type::signed_block: { + oxen::log::info(logcat, "TODO sean remove this signed block in pulse: {}", top_block_hash); crypto::signature const& final_signature = msg.signed_block.signature_of_final_block_hash; auto buf = tools::memcpy_le( diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 84cd576355..b407530f9a 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -237,6 +237,37 @@ bool tx_memory_pool::have_duplicated_non_standard_tx( return true; } } + } else if (tx.type == txtype::ethereum_new_service_node) { + cryptonote::tx_extra_ethereum_new_service_node data = {}; + // TODO sean bring this check back in + //if (!cryptonote::get_field_from_tx_extra(tx.extra, data)) { + //log::error( + //logcat, + //"Could not get ethereum new service node data from tx: {}, tx to add is possibly " + //"invalid, rejecting", + //get_transaction_hash(tx)); + //return true; + //} + } else if (tx.type == txtype::ethereum_service_node_leave_request) { + cryptonote::tx_extra_ethereum_service_node_leave_request data = {}; + if (!cryptonote::get_field_from_tx_extra(tx.extra, data)) { + log::error( + logcat, + "Could not get ethereum service node leave request data from tx: {}, tx to add is possibly " + "invalid, rejecting", + get_transaction_hash(tx)); + return true; + } + } else if (tx.type == txtype::ethereum_service_node_deregister) { + cryptonote::tx_extra_ethereum_service_node_deregister data = {}; + if (!cryptonote::get_field_from_tx_extra(tx.extra, data)) { + log::error( + logcat, + "Could not get ethereum service node leave request data from tx: {}, tx to add is possibly " + "invalid, rejecting", + get_transaction_hash(tx)); + return true; + } } else { if (tx.type != txtype::standard && tx.type != txtype::stake) { // NOTE(oxen): This is a developer error. If we come across this in production, be @@ -267,6 +298,7 @@ bool tx_memory_pool::add_tx( tx_verification_context& tvc, const tx_pool_options& opts, hf hf_version, + std::shared_ptr ethereum_transaction_review_session, uint64_t* blink_rollback_height) { // this should already be called with that lock, but let's make it explicit for clarity std::unique_lock lock{m_transactions_lock}; @@ -403,12 +435,15 @@ bool tx_memory_pool::add_tx( bool inputs_okay = check_tx_inputs( [&tx]() -> cryptonote::transaction& { return tx; }, id, + ethereum_transaction_review_session, max_used_block_height, max_used_block_id, tvc, opts.kept_by_block, opts.approved_blink ? blink_rollback_height : nullptr); const bool non_standard_tx = !tx.is_transfer(); + + oxen::log::info(logcat, "TODO sean remove this tx blob: {}, size: {}", blob, blob.size()); if (!inputs_okay) { // if the transaction was valid before (kept_by_block), then it // may become valid again, so ignore the failed inputs check. @@ -520,14 +555,14 @@ bool tx_memory_pool::add_tx( } //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx( - transaction& tx, tx_verification_context& tvc, const tx_pool_options& opts, hf version) { + transaction& tx, tx_verification_context& tvc, const tx_pool_options& opts, hf version, std::shared_ptr ethereum_transaction_review_session) { crypto::hash h{}; size_t blob_size = 0; std::string bl; t_serializable_object_to_blob(tx, bl); if (bl.size() == 0 || !get_transaction_hash(tx, h)) return false; - return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, opts, version); + return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, opts, version, ethereum_transaction_review_session); } //--------------------------------------------------------------------------------- bool tx_memory_pool::add_new_blink( @@ -549,7 +584,8 @@ bool tx_memory_pool::add_new_blink( bool approved = blink.approved(); auto hf_version = m_blockchain.get_network_version(blink.height); - bool result = add_tx(tx, tvc, tx_pool_options::new_blink(approved, hf_version), hf_version); + std::shared_ptr ethereum_transaction_review_session = m_blockchain.m_l2_tracker->initialize_mempool_review(); + bool result = add_tx(tx, tvc, tx_pool_options::new_blink(approved, hf_version), hf_version, ethereum_transaction_review_session); if (result && approved) { auto lock = blink_unique_lock(); m_blinks[txhash] = blink_ptr; @@ -1085,8 +1121,10 @@ bool tx_memory_pool::get_relayable_transactions( const uint64_t now = time(NULL); txs.reserve(m_blockchain.get_txpool_tx_count()); + oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); + std::shared_ptr ethereum_transaction_review_session = m_blockchain.m_l2_tracker->initialize_mempool_review(); m_blockchain.for_all_txpool_txes( - [this, now, &txs]( + [this, now, &txs, ðereum_transaction_review_session]( const crypto::hash& txid, const txpool_tx_meta_t& meta, const std::string*) { if (!meta.do_not_relay && (!meta.relayed || @@ -1119,10 +1157,10 @@ bool tx_memory_pool::get_relayable_transactions( crypto::hash max_used_block_id{}; if (!m_blockchain.check_tx_inputs( tx, - max_used_block_height, - max_used_block_id, tvc, - /*kept_by_block*/ false)) { + ethereum_transaction_review_session, + max_used_block_id, + max_used_block_height)) { log::info( logcat, "TX type: {} considered for relaying failed tx inputs " @@ -1509,6 +1547,7 @@ bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) co bool tx_memory_pool::check_tx_inputs( const std::function& get_tx, const crypto::hash& txid, + std::shared_ptr ethereum_transaction_review_session, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context& tvc, @@ -1527,13 +1566,16 @@ bool tx_memory_pool::check_tx_inputs( } } std::unordered_set key_image_conflicts; + + oxen::log::info(logcat, "TODO sean remove this: {}", "check_tx_inputs"); bool ret = m_blockchain.check_tx_inputs( get_tx(), - max_used_block_height, - max_used_block_id, tvc, - kept_by_block, - blink_rollback_height ? &key_image_conflicts : nullptr); + ethereum_transaction_review_session, + max_used_block_id, + max_used_block_height, + blink_rollback_height ? &key_image_conflicts : nullptr, + kept_by_block); if (ret && !key_image_conflicts.empty()) { // There are some key image conflicts, but since we have blink_rollback_height this is an @@ -1625,6 +1667,7 @@ bool tx_memory_pool::check_tx_inputs( bool tx_memory_pool::is_transaction_ready_to_go( txpool_tx_meta_t& txd, const crypto::hash& txid, + std::shared_ptr ethereum_transaction_review_session, const std::string& txblob, transaction& tx) const { struct transction_parser { @@ -1656,7 +1699,7 @@ bool tx_memory_pool::is_transaction_ready_to_go( tx_verification_context tvc; if (!check_tx_inputs( - lazy_tx, txid, txd.max_used_block_height, txd.max_used_block_id, tvc)) { + lazy_tx, txid, ethereum_transaction_review_session, txd.max_used_block_height, txd.max_used_block_id, tvc)) { txd.last_failed_height = m_blockchain.get_current_blockchain_height() - 1; txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); return false; @@ -1672,7 +1715,7 @@ bool tx_memory_pool::is_transaction_ready_to_go( // transaction become again valid tx_verification_context tvc; if (!check_tx_inputs( - lazy_tx, txid, txd.max_used_block_height, txd.max_used_block_id, tvc)) { + lazy_tx, txid, ethereum_transaction_review_session, txd.max_used_block_height, txd.max_used_block_id, tvc)) { txd.last_failed_height = m_blockchain.get_current_blockchain_height() - 1; txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); return false; @@ -1816,6 +1859,12 @@ bool tx_memory_pool::fill_block_template( uint64_t next_reward = 0; uint64_t net_fee = 0; + std::shared_ptr ethereum_transaction_review_session; + if (version >= cryptonote::feature::ETH_BLS) { + oxen::log::info(logcat, "TODO sean remove this initializing mempool with"); + ethereum_transaction_review_session = m_blockchain.m_l2_tracker->initialize_mempool_review(); + } + for (auto sorted_it : m_txs_by_fee_and_receive_time) { txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(sorted_it.second, meta)) { @@ -1879,7 +1928,7 @@ bool tx_memory_pool::fill_block_template( const cryptonote::txpool_tx_meta_t original_meta = meta; bool ready = false; try { - ready = is_transaction_ready_to_go(meta, sorted_it.second, txblob, tx); + ready = is_transaction_ready_to_go(meta, sorted_it.second, ethereum_transaction_review_session, txblob, tx); // TODO oxen delete this after HF20 has occurred // after here if (ready) diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index b8c7c28682..217a6eda59 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -45,6 +45,7 @@ #include "epee/string_tools.h" #include "oxen_economy.h" #include "tx_blink.h" +#include "l2_tracker/l2_tracker.h" namespace cryptonote { class Blockchain; @@ -166,6 +167,7 @@ class tx_memory_pool { tx_verification_context& tvc, const tx_pool_options& opts, hf hf_version, + std::shared_ptr ethereum_transaction_review_session, uint64_t* blink_rollback_height = nullptr); /** @@ -187,7 +189,8 @@ class tx_memory_pool { transaction& tx, tx_verification_context& tvc, const tx_pool_options& opts, - hf hf_version); + hf hf_version, + std::shared_ptr ethereum_transaction_review_session); /** * @brief attempts to add a blink transaction to the transaction pool. @@ -712,6 +715,7 @@ class tx_memory_pool { bool is_transaction_ready_to_go( txpool_tx_meta_t& txd, const crypto::hash& txid, + std::shared_ptr ethereum_transaction_review_session, const std::string& txblob, transaction& tx) const; @@ -815,6 +819,7 @@ class tx_memory_pool { bool check_tx_inputs( const std::function& get_tx, const crypto::hash& txid, + std::shared_ptr ethereum_transaction_review_session, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context& tvc, diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index ae9b3c9746..56851e4ad7 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -13,7 +13,7 @@ L2Tracker::L2Tracker() { L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) : rewards_contract(std::make_shared(get_contract_address(nettype), _provider)), - stop_thread(false), review_block_height(0) { + stop_thread(false) { update_thread = std::thread(&L2Tracker::update_state_thread, this); } @@ -81,13 +81,16 @@ void L2Tracker::update_state() { } std::pair L2Tracker::latest_state() { - if (!service_node) + if (!service_node) { + oxen::log::error(logcat, "L2 tracker doesnt have a provider and cant query state"); throw std::runtime_error("Non Service node doesn't keep track of state"); + } if(state_history.empty()) { + oxen::log::error(logcat, "L2 tracker doesnt have any state history to query"); throw std::runtime_error("Internal error getting latest state from l2 tracker"); } crypto::hash return_hash; - auto& latest_state = state_history.back(); + auto& latest_state = state_history.front(); tools::hex_to_type(latest_state.state, return_hash); return std::make_pair(latest_state.height, return_hash); } @@ -107,29 +110,84 @@ bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state return it != state_history.end(); } -void L2Tracker::initialize_transaction_review(uint64_t ethereum_height) { - if (review_block_height != 0) { - throw std::runtime_error( - "Review not finalized from last block, block height currently reviewing: " - + std::to_string(review_block_height) - + " new review height: " - + std::to_string(ethereum_height) - ); +std::shared_ptr L2Tracker::initialize_transaction_review(uint64_t ethereum_height) { + auto session = std::make_shared(oxen_to_ethereum_block_heights[latest_oxen_block], ethereum_height); + if (!service_node) + session->service_node = false; + populate_review_transactions(session); + return session; +} + +std::shared_ptr L2Tracker::initialize_mempool_review() { + auto session = std::make_shared(oxen_to_ethereum_block_heights[latest_oxen_block], std::numeric_limits::max()); + if (!service_node) + session->service_node = false; + populate_review_transactions(session); + return session; +} + +std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { + return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); +} + +void L2Tracker::populate_review_transactions(std::shared_ptr session) { + for (const auto& state : state_history) { + if (state.height >= session->review_block_height_min && state.height <= session->review_block_height_max) { + for (const auto& transactionVariant : state.state_changes) { + std::visit([&session](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + session->new_service_nodes.push_back(arg); + } else if constexpr (std::is_same_v) { + session->leave_requests.push_back(arg); + } else if constexpr (std::is_same_v) { + session->deregs.push_back(arg); + } + }, transactionVariant); + } + } + if (state.height < session->review_block_height_min) { + // State history should be ordered, if we go below our desired height then we can exit + break; + } + } +} + +std::vector L2Tracker::get_block_transactions(uint64_t begin_height, uint64_t end_height) { + if (!service_node) + throw std::runtime_error("Non Service node doesn't keep track of state"); + std::vector all_transactions; + for (const auto& state : state_history) { + if (state.height >= begin_height && state.height <= end_height) { + for (const auto& transactionVariant : state.state_changes) { + all_transactions.push_back(transactionVariant); + } + } + if (state.height < begin_height) { + // If we go below our desired begin height then throw, as state history should be ordered + throw std::runtime_error("Begin height not found in state history"); + } } - review_block_height = ethereum_height; - get_review_transactions(); // Fills new_service_nodes, leave_requests, deregs + return all_transactions; +} + +void L2Tracker::record_block_height_mapping(uint64_t oxen_block_height, uint64_t ethereum_block_height) { + oxen_to_ethereum_block_heights[oxen_block_height] = ethereum_block_height; + latest_oxen_block = oxen_block_height; } -bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { +bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { if (!service_node) return true; - if (review_block_height == 0) { + if (review_block_height_max == 0) { fail_reason = "Review not initialized"; - oxen::log::error(logcat, "Failed to process new service node tx height {}", review_block_height); + oxen::log::error(logcat, "Failed to process new service node tx height {}", review_block_height_max); return false; } + oxen::log::info(logcat, "Searching for new_service_node bls_key: " + bls_key + " eth_address: " + eth_address + " service_node_pubkey: " + service_node_pubkey); for (auto it = new_service_nodes.begin(); it != new_service_nodes.end(); ++it) { + oxen::log::info(logcat, "new_service_node bls_key: " + it->bls_key + " eth_address: " + it->eth_address + " service_node_pubkey: " + it->service_node_pubkey); if (it->bls_key == bls_key && it->eth_address == eth_address && it->service_node_pubkey == service_node_pubkey) { new_service_nodes.erase(it); return true; @@ -140,12 +198,12 @@ bool L2Tracker::processNewServiceNodeTx(const std::string& bls_key, const std::s return false; } -bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason) { +bool TransactionReviewSession::processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason) { if (!service_node) return true; - if (review_block_height == 0) { + if (review_block_height_max == 0) { fail_reason = "Review not initialized"; - oxen::log::error(logcat, "Failed to process service node leave request tx height {}", review_block_height); + oxen::log::error(logcat, "Failed to process service node leave request tx height {}", review_block_height_max); return false; } @@ -160,12 +218,12 @@ bool L2Tracker::processServiceNodeLeaveRequestTx(const std::string& bls_key, std return false; } -bool L2Tracker::processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { +bool TransactionReviewSession::processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { if (!service_node) return true; - if (review_block_height == 0) { + if (review_block_height_max == 0) { fail_reason = "Review not initialized"; - oxen::log::error(logcat, "Failed to process deregister tx height {}", review_block_height); + oxen::log::error(logcat, "Failed to process deregister tx height {}", review_block_height_max); return false; } @@ -180,67 +238,15 @@ bool L2Tracker::processServiceNodeDeregisterTx(const std::string& bls_key, bool return false; } -bool L2Tracker::finalize_transaction_review() { +bool TransactionReviewSession::finalize_review() { if (!service_node) return true; if (new_service_nodes.empty() && leave_requests.empty() && deregs.empty()) { - review_block_height = 0; + review_block_height_min = review_block_height_max + 1; + review_block_height_max = 0; return true; } return false; } -std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { - return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); -} - -void L2Tracker::get_review_transactions() { - new_service_nodes.clear(); - leave_requests.clear(); - deregs.clear(); - if (review_block_height == 0) { - oxen::log::warning(logcat, "get_review_transactions called with 0 block height"); - return; - } - for (const auto& state : state_history) { - if (state.height == review_block_height) { - for (const auto& transactionVariant : state.state_changes) { - std::visit([this](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - new_service_nodes.push_back(arg); - } else if constexpr (std::is_same_v) { - leave_requests.push_back(arg); - } else if constexpr (std::is_same_v) { - deregs.push_back(arg); - } - }, transactionVariant); - } - break; // Exit the loop once the matching state is processed - } - if (state.height < review_block_height) { - // State history should be ordered, if we go below our desired height then its not there so throw - throw std::runtime_error("Did not find review height in state history"); - } - } -} - -std::vector L2Tracker::get_block_transactions(uint64_t begin_height, uint64_t end_height) { - if (!service_node) - throw std::runtime_error("Non Service node doesn't keep track of state"); - std::vector all_transactions; - for (const auto& state : state_history) { - if (state.height >= begin_height && state.height <= end_height) { - for (const auto& transactionVariant : state.state_changes) { - all_transactions.push_back(transactionVariant); - } - } - if (state.height < begin_height) { - // If we go below our desired begin height then throw, as state history should be ordered - throw std::runtime_error("Begin height not found in state history"); - } - } - return all_transactions; -} - diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 0615b1dea1..32670514ca 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -18,19 +18,35 @@ struct State { : height(_state_response.height), state(_state_response.state) {} }; +struct TransactionReviewSession { + bool service_node = true; + uint64_t review_block_height_min; + uint64_t review_block_height_max; + std::vector new_service_nodes; + std::vector leave_requests; + std::vector deregs; + + TransactionReviewSession(uint64_t min_height, uint64_t max_height) + : review_block_height_min(min_height), review_block_height_max(max_height) {} + + bool processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); + bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); + bool processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); + + bool finalize_review(); +}; + + class L2Tracker { private: std::shared_ptr rewards_contract; std::vector state_history; + std::unordered_map oxen_to_ethereum_block_heights; // Maps Oxen block height to Ethereum block height + uint64_t latest_oxen_block; std::atomic stop_thread; std::thread update_thread; - uint64_t review_block_height = 0; - std::vector new_service_nodes; - std::vector leave_requests; - std::vector deregs; - public: L2Tracker(); L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& client); @@ -50,12 +66,10 @@ class L2Tracker { // and the tracker will make sure that it should actually be on the oxen blockchain // at that height. When done looping call the finalize function which will // then check that all transactions have been accounted for. - void initialize_transaction_review(uint64_t ethereum_height); - bool processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); - bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); - bool processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); + std::shared_ptr initialize_transaction_review(uint64_t ethereum_height); + std::shared_ptr initialize_mempool_review(); - bool finalize_transaction_review(); + void record_block_height_mapping(uint64_t oxen_block_height, uint64_t ethereum_block_height); std::pair latest_state(); std::vector get_block_transactions(uint64_t begin_height, uint64_t end_height); @@ -63,6 +77,7 @@ class L2Tracker { private: static std::string get_contract_address(const cryptonote::network_type nettype); void get_review_transactions(); + void populate_review_transactions(std::shared_ptr session); bool service_node = true; // END }; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index e79ae273cc..c64d8f728c 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -5,18 +5,22 @@ #include #pragma GCC diagnostic pop +#include "logging/oxen_logger.h" + +static auto logcat = oxen::log::Cat("l2_tracker"); + TransactionType RewardsLogEntry::getLogType() const { if (topics.empty()) { throw std::runtime_error("No topics in log entry"); } - // keccak256('NewServiceNode(uint64,address,BN256G1.G1Point,uint256,uint256)') - if (topics[0] == "da543ad9a040217dd88f378dc7fb7759316d2cf046a7eb1106294e6a30761458") { + // keccak256('NewServiceNode(uint64,address,(uint256,uint256),uint256,uint256)') + if (topics[0] == "0x33f8d30fa2c44ed0b2147e4aa1e1d14e3ee4f96d7586e1fea20c3cfe67b40083") { return TransactionType::NewServiceNode; - // keccak256('ServiceNodeRemovalRequest(uint64,address,BN256G1.G1Point)') - } else if (topics[0] == "cea31df077839a5b6d4f079cb9d9e37a75fd2e0494232d8b3b90c3b77eb2f08d") { + // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') + } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { return TransactionType::ServiceNodeLeaveRequest; - // keccak256('ServiceNodeLiquidated(uint64,address,BN256G1.G1Point)') - } else if (topics[0] == "5d7e17cd2edcc6334f540934c0f7150c32f6655120e51ab941b585014b28679a") { + // keccak256('ServiceNodeLiquidated(uint64,address,(uint256,uint256))') + } else if (topics[0] == "0x0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c") { return TransactionType::ServiceNodeDeregister; } return TransactionType::Other; @@ -72,6 +76,10 @@ StateResponse RewardsContract::State() { StateResponse RewardsContract::State(uint64_t height) { std::string blockHash = provider->getContractStorageRoot(contractAddress, height); + // Check if blockHash starts with "0x" and remove it + if (blockHash.size() >= 2 && blockHash[0] == '0' && blockHash[1] == 'x') { + blockHash = blockHash.substr(2); // Skip the first two characters + } return StateResponse{height, blockHash}; } diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index 1c7141af6f..c1cd8bd50a 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -163,6 +163,7 @@ def __init__(self, *, '--p2p-bind-port={}'.format(self.p2p_port), '--rpc-admin={}:{}'.format(self.listen_ip, self.rpc_port), '--quorumnet-port={}'.format(self.qnet_port), + '--ethereum-provider={}'.format("http://127.0.0.1:8545"), ) for d in peers: @@ -173,7 +174,6 @@ def __init__(self, *, '--service-node', '--service-node-public-ip={}'.format(self.listen_ip), '--storage-server-port={}'.format(self.ss_port), - '--ethereum-provider="{}"'.format("127.0.0.1:8545"), ) From 18f0498b73b9067c26046e20884939a053cb000f Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 15 Feb 2024 09:16:36 +1100 Subject: [PATCH 42/97] transaction gets included in a block --- contrib/epee/src/memwipe.c | 5 ++++ src/blockchain_db/lmdb/db_lmdb.cpp | 4 ++- src/bls/bls_aggregator.h | 4 +-- src/common/compat/glibc_compat.cpp | 3 +++ .../cryptonote_format_utils.cpp | 1 - src/cryptonote_core/blockchain.cpp | 27 +++++++------------ src/cryptonote_core/blockchain.h | 3 ++- src/cryptonote_core/cryptonote_core.cpp | 7 ++--- src/cryptonote_core/pulse.cpp | 11 ++++---- src/cryptonote_core/service_node_list.cpp | 2 +- src/cryptonote_core/tx_pool.cpp | 15 +++++++---- src/daemon/rpc_command_executor.cpp | 2 +- src/l2_tracker/l2_tracker.cpp | 16 ++++++----- src/l2_tracker/l2_tracker.h | 2 +- src/ringct/rctTypes.h | 6 +++++ 15 files changed, 63 insertions(+), 45 deletions(-) diff --git a/contrib/epee/src/memwipe.c b/contrib/epee/src/memwipe.c index 4ae1daaaa0..3c4a67aba6 100644 --- a/contrib/epee/src/memwipe.c +++ b/contrib/epee/src/memwipe.c @@ -38,6 +38,9 @@ #endif #include "epee/memwipe.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" + #if defined(_MSC_VER) #define SCARECROW \ __asm; @@ -112,4 +115,6 @@ void *memwipe(void *ptr, size_t n) return ptr; } +#pragma GCC diagnostic pop + #endif diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 452a1f126a..f1eeddfbcd 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1985,6 +1985,9 @@ void BlockchainLMDB::unlock() { void BlockchainLMDB::add_txpool_tx( const crypto::hash& txid, const std::string& blob, const txpool_tx_meta_t& meta) { log::trace(logcat, "BlockchainLMDB::{}", __func__); + if (blob.size() == 0) + throw1(DB_ERROR("Attempting to add txpool tx with empty blob")); + check_open(); mdb_txn_cursors* m_cursors = &m_wcursors; @@ -2002,7 +2005,6 @@ void BlockchainLMDB::add_txpool_tx( .c_str())); } MDB_val_sized(blob_val, blob); - //TODO sean remove this if (blob_val.mv_size == 0) throw1(DB_ERROR("error adding txpool tx blob: tx is present, but data is empty")); if (auto result = mdb_cursor_put(m_cur_txpool_blob, &k, &blob_val, MDB_NODUPDATA)) { diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index d82bb538da..043b7918b1 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -81,8 +81,8 @@ class BLSAggregator { auto it = service_node_list.get_first_pubkey_iterator(); auto end_it = service_node_list.get_end_pubkey_iterator(); crypto::x25519_public_key x_pkey{0}; - uint32_t ip; - uint16_t port; + uint32_t ip = 0; + uint16_t port = 0; while (it != end_it) { service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { diff --git a/src/common/compat/glibc_compat.cpp b/src/common/compat/glibc_compat.cpp index 4349212961..0e902b5b9e 100644 --- a/src/common/compat/glibc_compat.cpp +++ b/src/common/compat/glibc_compat.cpp @@ -48,6 +48,8 @@ extern "C" int64_t __wrap___divmoddi4(int64_t u, int64_t v, int64_t* rp) { } #endif +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" /* glibc-internal users use __explicit_bzero_chk, and explicit_bzero redirects to that. */ #undef explicit_bzero @@ -68,6 +70,7 @@ void __explicit_bzero_chk(void* dst, size_t len, size_t dstlen) { /* Compiler barrier. */ asm volatile("" ::: "memory"); } +#pragma GCC diagnostic pop /* libc-internal references use the hidden __explicit_bzero_chk_internal symbol. This is necessary if __explicit_bzero_chk is implemented as an IFUNC because some diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index c324f3e144..9c1362c742 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -228,7 +228,6 @@ bool parse_and_validate_tx_from_blob(const std::string_view tx_blob, transaction } //--------------------------------------------------------------- bool parse_and_validate_tx_base_from_blob(const std::string_view tx_blob, transaction& tx) { - oxen::log::info(logcat, "TODO sean remove this transaction blob size: {}", tx_blob.size()); serialization::binary_string_unarchiver ba{tx_blob}; try { tx.serialize_base(ba); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index aeeccb725f..1020dfeb2d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1770,13 +1770,8 @@ bool Blockchain::create_block_template_internal( uint64_t fee; //TODO sean - if (hf_version >= cryptonote::feature::ETH_BLS) { + if (hf_version >= cryptonote::feature::ETH_BLS) std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); - // Fill tx_pool with ethereum transactions before we build the block - // TODO sean get the previous height from somewhere - std::vector eth_transactions = m_l2_tracker->get_block_transactions(b.l2_height -1, b.l2_height); - add_ethereum_transactions_to_tx_pool(eth_transactions); - } // Add transactions in mempool to block if (!m_tx_pool.fill_block_template( @@ -1953,11 +1948,14 @@ bool Blockchain::create_next_pulse_block_template( return result; } -void Blockchain::add_ethereum_transactions_to_tx_pool(const std::vector& transactions) { +void Blockchain::add_ethereum_transactions_to_tx_pool() { auto hf_version = get_network_version(); + if (hf_version < feature::ETH_BLS) + return; + std::vector eth_transactions = m_l2_tracker->get_block_transactions(); tx_extra_field field; - for (const auto& tx_variant : transactions) { + for (const auto& tx_variant : eth_transactions) { transaction tx; tx.version = transaction_prefix::get_max_version_for_hf(hf_version); @@ -1965,9 +1963,10 @@ void Blockchain::add_ethereum_transactions_to_tx_pool(const std::vector; if constexpr (std::is_same_v) { tx.type = txtype::ethereum_new_service_node; - crypto::public_key service_node_pubkey; + crypto::public_key service_node_pubkey = {}; tools::hex_to_type(arg.service_node_pubkey, service_node_pubkey); - crypto::signature signature; + // TODO sean need to actually get the signature + crypto::signature signature = {}; tools::hex_to_type(arg.signature, signature); tx_extra_ethereum_new_service_node new_service_node = { 0, arg.bls_key, arg.eth_address, service_node_pubkey, signature }; cryptonote::add_new_service_node_to_tx_extra(tx.extra, new_service_node); @@ -1991,6 +1990,7 @@ void Blockchain::add_ethereum_transactions_to_tx_pool(const std::vector ethereum_transaction_review_session; if (version >= cryptonote::feature::ETH_BLS) { - oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); ethereum_transaction_review_session = m_l2_tracker->initialize_mempool_review(); } for (auto& tx : txs) { @@ -4962,7 +4960,6 @@ bool Blockchain::handle_block_to_main_chain( auto hf_version = bl.major_version; std::shared_ptr ethereum_transaction_review_session; if (hf_version >= cryptonote::feature::ETH_BLS) { - oxen::log::info(logcat, "TODO sean remove this initializing with l2 height: {}", bl.l2_height); ethereum_transaction_review_session = m_l2_tracker->initialize_transaction_review(bl.l2_height); } @@ -5043,7 +5040,6 @@ bool Blockchain::handle_block_to_main_chain( { // validate that transaction inputs and the keys spending them are correct. tx_verification_context tvc{}; - oxen::log::info(logcat, "TODO sean remove this: {}", "calling check tx inputs"); if (!check_tx_inputs(tx, tvc, ethereum_transaction_review_session)) { log::info( logcat, @@ -6037,9 +6033,6 @@ bool Blockchain::prepare_handle_incoming_blocks( if (!parse_and_validate_tx_base_from_blob(tx_blob, tx)) { - oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB TX hash: {}", tx_prefix_hash); - oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex could not parse: {}", oxenc::to_hex(tx_blob)); - oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex size : {}", tx_blob.size()); log::error(log::Cat("verify"), "Could not parse tx from incoming blocks"); m_scan_table.clear(); return false; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 5b34139c42..5968a0eee1 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1163,6 +1163,8 @@ class Blockchain { */ void flush_invalid_blocks(); + void add_ethereum_transactions_to_tx_pool(); + std::shared_ptr m_l2_tracker; #ifndef IN_UNIT_TESTS @@ -1640,7 +1642,6 @@ class Blockchain { */ uint64_t get_adjusted_time() const; - void add_ethereum_transactions_to_tx_pool(const std::vector& transactions); /** * @brief finish an alternate chain's timestamp window from the main chain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index b6a934c693..b0a0e94b1e 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1380,7 +1380,6 @@ bool core::handle_parsed_txs( tx_pool_options tx_opts; std::shared_ptr ethereum_transaction_review_session; if (version >= cryptonote::feature::ETH_BLS) { - oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); ethereum_transaction_review_session = m_blockchain_storage.m_l2_tracker->initialize_mempool_review(); } for (size_t i = 0; i < parsed_txs.size(); i++) { @@ -2216,8 +2215,10 @@ block_complete_entry get_block_complete_entry(block& b, tx_memory_pool& pool) { bce.block = cryptonote::block_to_blob(b); for (const auto& tx_hash : b.tx_hashes) { std::string txblob; - CHECK_AND_ASSERT_THROW_MES( - pool.get_transaction(tx_hash, txblob), "Transaction not found in pool"); + if (!pool.get_transaction(tx_hash, txblob) || txblob.size() == 0) { + oxen::log::error(logcat, "Transaction {} not found in pool", tx_hash); + throw std::runtime_error("Transaction not found in pool"); + } bce.txs.push_back(txblob); } return bce; diff --git a/src/cryptonote_core/pulse.cpp b/src/cryptonote_core/pulse.cpp index 3a165f647f..46b003e663 100644 --- a/src/cryptonote_core/pulse.cpp +++ b/src/cryptonote_core/pulse.cpp @@ -256,7 +256,6 @@ namespace { // hash to be generated correctly. crypto::hash msg_signature_hash(crypto::hash const& top_block_hash, pulse::message const& msg) { crypto::hash result = {}; - oxen::log::info(logcat, "TODO sean remove this msg type pulse: {}", msg.type); switch (msg.type) { case pulse::message_type::invalid: assert("Invalid Code Path" == nullptr); break; @@ -275,7 +274,6 @@ namespace { } break; case pulse::message_type::block_template: { - oxen::log::info(logcat, "TODO sean remove this block template message in pulse: {}", msg.block_template.blob.data()); crypto::hash block_hash = blake2b_hash( msg.block_template.blob.data(), msg.block_template.blob.size()); auto buf = tools::memcpy_le(msg.round, block_hash); @@ -298,7 +296,6 @@ namespace { } break; case pulse::message_type::signed_block: { - oxen::log::info(logcat, "TODO sean remove this signed block in pulse: {}", top_block_hash); crypto::signature const& final_signature = msg.signed_block.signature_of_final_block_hash; auto buf = tools::memcpy_le( @@ -1472,7 +1469,8 @@ namespace { round_context& context, service_nodes::service_node_list& node_list, void* quorumnet_state, - service_nodes::service_node_keys const& key) { + service_nodes::service_node_keys const& key, + cryptonote::Blockchain& blockchain) { handle_messages_received_early_for( context.transient.wait_for_handshake_bitsets.stage, quorumnet_state); pulse_wait_stage const& stage = context.transient.wait_for_handshake_bitsets.stage; @@ -1557,6 +1555,9 @@ namespace { "block template " "from block " "producer")); + //TODO sean put this back and use a max block + // Fill tx_pool with ethereum transactions before we build the block + blockchain.add_ethereum_transactions_to_tx_pool(); if (context.prepare_for_round.participant == sn_type::producer) return round_state::send_block_template; @@ -2003,7 +2004,7 @@ void main(void* quorumnet_state, cryptonote::core& core) { case round_state::wait_for_handshake_bitsets: context.state = - wait_for_handshake_bitsets(context, node_list, quorumnet_state, key); + wait_for_handshake_bitsets(context, node_list, quorumnet_state, key, blockchain); break; case round_state::wait_for_block_template: diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 6642e505fc..988e077c28 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -1192,7 +1192,7 @@ bool service_node_list::state_t::process_ethereum_unlock_tx( } //TODO sean get this from somewhere using bls key instead - crypto::public_key snode_key; + crypto::public_key snode_key = {}; //if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key)) //return false; diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index b407530f9a..7b0fcf6302 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -302,6 +302,11 @@ bool tx_memory_pool::add_tx( uint64_t* blink_rollback_height) { // this should already be called with that lock, but let's make it explicit for clarity std::unique_lock lock{m_transactions_lock}; + if (blob.size() == 0) { + oxen::log::error(logcat, "Could not add to txpool, blob is empty of tx: {}", id); + throw std::runtime_error("Could not add to txpool, blob empty"); + } + if (tx.version == txversion::v0) { // v0 never accepted @@ -443,7 +448,6 @@ bool tx_memory_pool::add_tx( opts.approved_blink ? blink_rollback_height : nullptr); const bool non_standard_tx = !tx.is_transfer(); - oxen::log::info(logcat, "TODO sean remove this tx blob: {}, size: {}", blob, blob.size()); if (!inputs_okay) { // if the transaction was valid before (kept_by_block), then it // may become valid again, so ignore the failed inputs check. @@ -514,8 +518,10 @@ bool tx_memory_pool::add_tx( LockedTXN lock(m_blockchain); m_blockchain.remove_txpool_tx(id); m_blockchain.add_txpool_tx(id, blob, meta); - if (!insert_key_images(tx, id, opts.kept_by_block)) + if (!insert_key_images(tx, id, opts.kept_by_block)) { + oxen::log::error(logcat, "Failed to insert key images for tx: ", id); return false; + } m_txs_by_fee_and_receive_time.emplace( std::tuple( non_standard_tx, @@ -1121,7 +1127,6 @@ bool tx_memory_pool::get_relayable_transactions( const uint64_t now = time(NULL); txs.reserve(m_blockchain.get_txpool_tx_count()); - oxen::log::info(logcat, "TODO sean remove this initializing with mempool"); std::shared_ptr ethereum_transaction_review_session = m_blockchain.m_l2_tracker->initialize_mempool_review(); m_blockchain.for_all_txpool_txes( [this, now, &txs, ðereum_transaction_review_session]( @@ -1567,7 +1572,6 @@ bool tx_memory_pool::check_tx_inputs( } std::unordered_set key_image_conflicts; - oxen::log::info(logcat, "TODO sean remove this: {}", "check_tx_inputs"); bool ret = m_blockchain.check_tx_inputs( get_tx(), tvc, @@ -1722,6 +1726,8 @@ bool tx_memory_pool::is_transaction_ready_to_go( } } } + //TODO sean make sure the ethereum transactions are ready to go into the block + // if we here, transaction seems valid, but, anyway, check for key_images collisions with // blockchain, just to be sure if (m_blockchain.have_tx_keyimges_as_spent(lazy_tx())) { @@ -1861,7 +1867,6 @@ bool tx_memory_pool::fill_block_template( std::shared_ptr ethereum_transaction_review_session; if (version >= cryptonote::feature::ETH_BLS) { - oxen::log::info(logcat, "TODO sean remove this initializing mempool with"); ethereum_transaction_review_session = m_blockchain.m_l2_tracker->initialize_mempool_review(); } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 5df9297796..7b7fcf6873 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -297,7 +297,7 @@ json rpc_command_executor::invoke( bool rpc_command_executor::print_checkpoints( std::optional start_height, std::optional end_height, bool print_json) { - uint32_t count; + uint32_t count = 0; if (!start_height && !end_height) count = GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT; else if (!start_height || !end_height) diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 56851e4ad7..09c24115fb 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -132,7 +132,7 @@ std::string L2Tracker::get_contract_address(const cryptonote::network_type netty void L2Tracker::populate_review_transactions(std::shared_ptr session) { for (const auto& state : state_history) { - if (state.height >= session->review_block_height_min && state.height <= session->review_block_height_max) { + if (state.height > session->review_block_height_min && state.height <= session->review_block_height_max) { for (const auto& transactionVariant : state.state_changes) { std::visit([&session](auto&& arg) { using T = std::decay_t; @@ -146,26 +146,27 @@ void L2Tracker::populate_review_transactions(std::shared_ptrreview_block_height_min) { + if (state.height <= session->review_block_height_min) { // State history should be ordered, if we go below our desired height then we can exit break; } } } -std::vector L2Tracker::get_block_transactions(uint64_t begin_height, uint64_t end_height) { +std::vector L2Tracker::get_block_transactions() { if (!service_node) throw std::runtime_error("Non Service node doesn't keep track of state"); std::vector all_transactions; + const auto begin_height = oxen_to_ethereum_block_heights[latest_oxen_block]; for (const auto& state : state_history) { - if (state.height >= begin_height && state.height <= end_height) { + if (state.height > begin_height) { for (const auto& transactionVariant : state.state_changes) { all_transactions.push_back(transactionVariant); } } - if (state.height < begin_height) { - // If we go below our desired begin height then throw, as state history should be ordered - throw std::runtime_error("Begin height not found in state history"); + if (state.height <= begin_height) { + // If we go below our desired begin height then break, as state history should be ordered + break; } } return all_transactions; @@ -246,6 +247,7 @@ bool TransactionReviewSession::finalize_review() { review_block_height_max = 0; return true; } + return false; } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 32670514ca..05b069c5c7 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -72,7 +72,7 @@ class L2Tracker { void record_block_height_mapping(uint64_t oxen_block_height, uint64_t ethereum_block_height); std::pair latest_state(); - std::vector get_block_transactions(uint64_t begin_height, uint64_t end_height); + std::vector get_block_transactions(); private: static std::string get_contract_address(const cryptonote::network_type nettype); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index dec15d81b8..6bc32673d0 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -30,6 +30,10 @@ #pragma once +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" + #include #include @@ -803,3 +807,5 @@ VARIANT_TAG(rct::Bulletproof, "rct_bulletproof", 0x9c); VARIANT_TAG(rct::multisig_kLRki, "rct_multisig_kLR", 0x9d); VARIANT_TAG(rct::multisig_out, "rct_multisig_out", 0x9e); VARIANT_TAG(rct::clsag, "rct_clsag", 0x9f); + +#pragma GCC diagnostic pop From 710d44175c7b678976efe1789137f38c47b2bc49 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 22 Feb 2024 08:48:41 +1100 Subject: [PATCH 43/97] node gets added to sn list from eth transaction --- src/cryptonote_basic/hardfork.cpp | 2 +- src/cryptonote_core/blockchain.cpp | 5 +- src/cryptonote_core/service_node_list.cpp | 57 ++++++++++++++-------- src/cryptonote_core/service_node_list.h | 10 +++- src/cryptonote_core/service_node_rules.cpp | 14 +++--- src/cryptonote_core/service_node_rules.h | 2 +- utils/local-devnet/service_node_network.py | 1 + 7 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index af58956266..4901235056 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -155,7 +155,7 @@ static constexpr std::array devnet_hard_forks = { hard_fork{hf::hf17, 0, 251, 1653500577}, hard_fork{hf::hf18, 0, 252, 1653500577}, hard_fork{hf::hf19_reward_batching, 0, 253, 1653500577}, - hard_fork{hf::hf20, 0, 254, 1653500577}, + hard_fork{hf::hf20, 0, 379, 1653500577}, }; template diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 1020dfeb2d..1bd36cf911 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1986,11 +1986,8 @@ void Blockchain::add_ethereum_transactions_to_tx_pool() { tx_verification_context tvc = {}; std::shared_ptr ethereum_transaction_review_session = m_l2_tracker->initialize_mempool_review(); - // Add transaction to memory pool - - oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB TX hash created: {}", tx_hash); - oxen::log::info(logcat, "TODO sean remove this BBBBBBBBB blob hex: {}", oxenc::to_hex(cryptonote::tx_to_blob(tx))); on_new_tx_from_block(tx); + // Add transaction to memory pool if (!m_tx_pool.add_tx(tx, tx_hash, cryptonote::tx_to_blob(tx), tx_weight, tvc, tx_pool_options::new_tx(), hf_version, ethereum_transaction_review_session)) { if (tvc.m_verifivation_failed) diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 988e077c28..86d931e2d3 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -367,7 +367,7 @@ std::optional reg_tx_extract_fields(const cryptonote::tran return reg; } -std::optional eth_reg_tx_extract_fields(const cryptonote::transaction& tx) { +std::optional eth_reg_tx_extract_fields(hf hf_version, const cryptonote::transaction& tx) { cryptonote::tx_extra_ethereum_new_service_node registration; if (!get_field_from_tx_extra(tx.extra, registration)) return std::nullopt; @@ -376,11 +376,13 @@ std::optional eth_reg_tx_extract_fields(const cryptonote:: reg.service_node_pubkey = registration.service_node_pubkey; // TODO sean this needs to be thought out - //auto& [addr, amount] = reg.reserved.emplace_back(); - //addr.m_spend_public_key = registration.public_spend_keys[i]; - //amount = registration.amounts[i]; + auto& [addr, amount] = reg.eth_contributions.emplace_back(); + addr = registration.eth_address; + // TODO sean SOMETHING BETTER HERE + amount = 100'000'000'000; + //amount = registration.amount; - //reg.hf = registration.hf_or_expiration; + reg.hf = static_cast(hf_version); reg.uses_portions = false; reg.fee = 0; @@ -422,10 +424,25 @@ void validate_registration( ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1; - if (reg.reserved.empty()) - throw invalid_registration{"No operator contribution given"}; - if (reg.reserved.size() > max_contributors) - throw invalid_registration{"Too many contributors"}; + std::vector extracted_amounts; + if (hf_version >= hf::hf20) { + if (reg.eth_contributions.empty()) + throw invalid_registration{"No operator contribution given"}; + if (!reg.reserved.empty()) + throw invalid_registration{"Operator contributions through oxen no longer an option"}; + if (reg.eth_contributions.size() > max_contributors) + throw invalid_registration{"Too many contributors"}; + std::transform(reg.eth_contributions.begin(), reg.eth_contributions.end(), std::back_inserter(extracted_amounts), + [](const std::pair& pair) { return pair.second; }); + } else { + if (reg.reserved.empty()) + throw invalid_registration{"No operator contribution given"}; + if (reg.reserved.size() > max_contributors) + throw invalid_registration{"Too many contributors"}; + std::transform(reg.reserved.begin(), reg.reserved.end(), std::back_inserter(extracted_amounts), + [](const std::pair& pair) { return pair.second; }); + } + bool valid_stakes, valid_fee; if (reg.uses_portions) { @@ -433,8 +450,7 @@ void validate_registration( valid_stakes = check_service_node_portions(hf_version, reg.reserved); valid_fee = reg.fee <= cryptonote::old::STAKING_PORTIONS; } else { - valid_stakes = - check_service_node_stakes(hf_version, nettype, staking_requirement, reg.reserved); + valid_stakes = check_service_node_stakes(hf_version, nettype, staking_requirement, extracted_amounts); valid_fee = reg.fee <= cryptonote::STAKING_FEE_BASIS; } @@ -446,11 +462,11 @@ void validate_registration( if (!valid_stakes) { std::string amount_dump; - amount_dump.reserve(22 * reg.reserved.size()); - for (size_t i = 0; i < reg.reserved.size(); i++) { + amount_dump.reserve(22 * extracted_amounts.size()); + for (size_t i = 0; i < extracted_amounts.size(); i++) { if (i) amount_dump += ", "; - amount_dump += std::to_string(reg.reserved[i].second); + amount_dump += std::to_string(extracted_amounts[i]); } throw invalid_registration{ "Invalid "s + (reg.uses_portions ? "portions" : "amounts") + ": {" + amount_dump + @@ -1436,17 +1452,18 @@ std::pair> validate_and_g { auto info = std::make_shared(); - auto maybe_reg = eth_reg_tx_extract_fields(tx); + auto maybe_reg = eth_reg_tx_extract_fields(hf_version, tx); if (!maybe_reg) throw std::runtime_error("Could not extract registration details from transaction"); auto& reg = *maybe_reg; uint64_t staking_requirement = get_staking_requirement(nettype, block_height); validate_registration(hf_version, nettype, staking_requirement, block_timestamp, reg); - validate_registration_signature(reg); + // TODO sean bring this signature verification back + //validate_registration_signature(reg); info->staking_requirement = staking_requirement; - info->operator_address = reg.reserved[0].first; + info->operator_ethereum_address = reg.eth_contributions[0].first; info->portions_for_operator = staking_requirement; info->registration_height = block_height; info->registration_hf_version = hf_version; @@ -1571,15 +1588,15 @@ bool service_node_list::state_t::process_ethereum_registration_tx( log::info( logcat, fg(fmt::terminal_color::green), - "Service node registered (yours): {} on height: {}", + "Service node registered (yours) from ethereum: {} on height: {}", key, block_height); else - log::info(logcat, "New service node registered: {} on height: {}", key, block_height); + log::info(logcat, "New service node registered from ethereum: {} on height: {}", key, block_height); service_nodes_infos[key] = std::move(service_node_info); return true; } catch (const std::exception& e) { - log::info(logcat, "Failed to register node from ethereum transaction: {}", e.what()); + log::error(logcat, "Failed to register node from ethereum transaction: {}", e.what()); } return false; } diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 82d9958be2..9be37c8248 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -210,6 +210,7 @@ struct service_node_info // registration information v5_pulse_recomm_credit, v6_reassign_sort_keys, v7_decommission_reason, + v8_ethereum_address, _count }; @@ -297,6 +298,7 @@ struct service_node_info // registration information uint64_t portions_for_operator = 0; swarm_id_t swarm_id = 0; cryptonote::account_public_address operator_address{}; + std::string operator_ethereum_address{}; uint64_t last_ip_change_height = 0; // The height of the last quorum penalty for changing IPs version_t version = tools::enum_top; cryptonote::hf registration_hf_version = cryptonote::hf::none; @@ -358,6 +360,10 @@ struct service_node_info // registration information VARINT_FIELD(last_decommission_reason_consensus_all) VARINT_FIELD(last_decommission_reason_consensus_any) } + //TODO sean serialize this + //if (version >= version_t::v8_ethereum_address) { + //FIELD(operator_ethereum_address) + //} END_SERIALIZE() }; @@ -920,6 +926,7 @@ bool tx_get_staking_components_and_amounts( staking_components* contribution); using contribution = std::pair; +using eth_contribution = std::pair; struct registration_details { crypto::public_key service_node_pubkey; std::vector reserved; @@ -927,6 +934,7 @@ struct registration_details { uint64_t hf; // expiration timestamp before HF19 bool uses_portions; // if true then `hf` is a timestamp crypto::signature signature; + std::vector eth_contributions; }; bool is_registration_tx( @@ -948,7 +956,7 @@ std::pair> validate_and_g uint32_t index); std::optional reg_tx_extract_fields(const cryptonote::transaction& tx); -std::optional eth_reg_tx_extract_fields(const cryptonote::transaction& tx); +std::optional eth_reg_tx_extract_fields(cryptonote::hf hf_version, const cryptonote::transaction& tx); uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height); diff --git a/src/cryptonote_core/service_node_rules.cpp b/src/cryptonote_core/service_node_rules.cpp index c8ba0928ed..95f48599c2 100644 --- a/src/cryptonote_core/service_node_rules.cpp +++ b/src/cryptonote_core/service_node_rules.cpp @@ -151,7 +151,7 @@ bool check_service_node_stakes( hf hf_version, cryptonote::network_type nettype, uint64_t staking_requirement, - const std::vector>& stakes) { + const std::vector& stakes) { if (hf_version < hf::hf19_reward_batching) { log::info( logcat, @@ -178,28 +178,28 @@ bool check_service_node_stakes( i == 0 ? operator_requirement : get_min_node_contribution(hf_version, staking_requirement, reserved, i); - if (stakes[i].second < min_stake) { + if (stakes[i] < min_stake) { log::info( logcat, "Registration tx rejected: stake {} too small ({} < {})", i, - stakes[i].second, + stakes[i], min_stake); return false; } - if (stakes[i].second > remaining) { + if (stakes[i] > remaining) { log::info( logcat, "Registration tx rejected: stake {} ({}) exceeds available remaining stake " "({})", i, - stakes[i].second, + stakes[i], remaining); return false; } - reserved += stakes[i].second; - remaining -= stakes[i].second; + reserved += stakes[i]; + remaining -= stakes[i]; } return true; diff --git a/src/cryptonote_core/service_node_rules.h b/src/cryptonote_core/service_node_rules.h index 2027065c87..95893bbf31 100644 --- a/src/cryptonote_core/service_node_rules.h +++ b/src/cryptonote_core/service_node_rules.h @@ -355,7 +355,7 @@ bool check_service_node_stakes( cryptonote::hf hf_version, cryptonote::network_type nettype, uint64_t staking_requirement, - const std::vector>& stakes); + const std::vector& stakes); crypto::hash generate_request_stake_unlock_hash(uint32_t nonce); uint64_t get_locked_key_image_unlock_height( diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index d272a2a114..94e2011f63 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -215,6 +215,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): sn.send_uptime_proof() ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) + vprint("Submitted registration on ethereum for service node with pubkey: {}".format(self.ethsns[0].sn_key())) result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) vprint("Done.") From 99bbf20c025e34e01c9726e5a4fe81cf63445dc1 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 26 Feb 2024 13:14:45 +1100 Subject: [PATCH 44/97] Ethereum service node rewards recorded to an ethereum address --- src/CMakeLists.txt | 3 +- src/blockchain_db/sqlite/db_sqlite.cpp | 24 +- src/blockchain_db/sqlite/db_sqlite.h | 4 +- src/crypto/base.h | 4 +- src/crypto/crypto.h | 5 + src/cryptonote_basic/cryptonote_basic_impl.h | 4 +- src/cryptonote_basic/tx_extra.h | 4 +- src/cryptonote_core/blockchain.cpp | 8 - src/cryptonote_core/service_node_list.cpp | 30 +- src/cryptonote_core/service_node_list.h | 22 +- src/l2_tracker/l2_tracker.cpp | 8 +- src/l2_tracker/l2_tracker.h | 2 +- src/l2_tracker/rewards_contract.cpp | 6 +- src/l2_tracker/rewards_contract.h | 6 +- src/rpc/core_rpc_server.cpp | 10 +- src/serialization/crypto.h | 1 + utils/local-devnet/commands/hello | 1044 ++++++++++++++++++ 17 files changed, 1130 insertions(+), 55 deletions(-) create mode 100644 utils/local-devnet/commands/hello diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ace8a4d761..e5ffc908ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,8 @@ # warnings are cleared only for GCC on Linux if (NOT (MINGW OR APPLE OR FREEBSD OR OPENBSD OR DRAGONFLY)) - add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow + message(STATUS "Setting warnings as errors") + #add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow endif() set_property(GLOBAL PROPERTY oxen_executable_targets "") diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index 85fd2b38cd..a4c6b66ace 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -309,21 +309,22 @@ void BlockchainSQLite::blockchain_detached(uint64_t new_height) { } // Must be called with the address_str_cache_mutex held! -const std::string& BlockchainSQLite::get_address_str(const cryptonote::batch_sn_payment& addr) { - if (addr.eth_address.has_value()) - return *addr.eth_address; +std::string BlockchainSQLite::get_address_str(const cryptonote::batch_sn_payment& addr) { + if (addr.eth_address.has_value()) { + return tools::type_to_hex(addr.eth_address.value()); + } auto& address_str = address_str_cache[addr.address_info.address]; if (address_str.empty()) address_str = cryptonote::get_account_address_as_str(m_nettype, 0, addr.address_info.address); return address_str; } -bool BlockchainSQLite::update_sn_rewards_address(const std::string& oxen_address, const std::string& eth_address) { +bool BlockchainSQLite::update_sn_rewards_address(const std::string& oxen_address, const crypto::eth_address& eth_address) { auto update_address = prepared_st( "UPDATE batched_payments_accrued SET address = ? WHERE address = ?" " ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount" ); - db::exec_query(update_address, eth_address, oxen_address); + db::exec_query(update_address, tools::type_to_hex(eth_address), oxen_address); return true; } @@ -461,9 +462,12 @@ void BlockchainSQLite::calculate_rewards( payments.clear(); // Pay the operator fee to the operator - if (operator_fee > 0) + if (operator_fee > 0) { + if (sn_info.operator_ethereum_address) + payments.emplace_back(sn_info.operator_ethereum_address, operator_fee); + else payments.emplace_back(sn_info.operator_address, operator_fee); - + } // Pay the balance to all the contributors (including the operator again) uint64_t total_contributed_to_sn = std::accumulate( sn_info.contributors.begin(), @@ -477,7 +481,10 @@ void BlockchainSQLite::calculate_rewards( uint64_t c_reward = mul128_div64( contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn); if (c_reward > 0) - payments.emplace_back(*contributor.address, c_reward); + if (contributor.ethereum_address) + payments.emplace_back(contributor.ethereum_address, operator_fee); + else + payments.emplace_back(contributor.address, c_reward); } } @@ -548,6 +555,7 @@ bool BlockchainSQLite::reward_handler( } // Step 3: Add Governance reward to the list + // TODO sean governance ethereum address if (m_nettype != cryptonote::network_type::FAKECHAIN) { if (parsed_governance_addr.first != block.major_version) { cryptonote::get_account_address_from_str( diff --git a/src/blockchain_db/sqlite/db_sqlite.h b/src/blockchain_db/sqlite/db_sqlite.h index 4e78c6b150..5b75273c3c 100644 --- a/src/blockchain_db/sqlite/db_sqlite.h +++ b/src/blockchain_db/sqlite/db_sqlite.h @@ -65,7 +65,7 @@ class BlockchainSQLite : public db::Database { // exist it will be created. bool add_sn_rewards(const std::vector& payments); bool subtract_sn_rewards(const std::vector& payments); - bool update_sn_rewards_address(const std::string& oxen_address, const std::string& eth_address); + bool update_sn_rewards_address(const std::string& oxen_address, const crypto::eth_address& eth_address); private: bool reward_handler( @@ -75,7 +75,7 @@ class BlockchainSQLite : public db::Database { std::unordered_map address_str_cache; std::pair parsed_governance_addr = {hf::none, {}}; - const std::string& get_address_str(const cryptonote::batch_sn_payment& addr); + std::string get_address_str(const cryptonote::batch_sn_payment& addr); std::mutex address_str_cache_mutex; public: diff --git a/src/crypto/base.h b/src/crypto/base.h index 4a399c0806..bac29adaad 100644 --- a/src/crypto/base.h +++ b/src/crypto/base.h @@ -18,8 +18,8 @@ constexpr T null{}; // Base type for fixed-byte quantities (points, scalars, signatures, hashes). The bool controls // whether the type should have ==, !=, std::hash, and to_hex_string. -template -struct alignas(size_t) bytes { +template +struct alignas(AlignAs) bytes { std::array data_; unsigned char* data() { return data_.data(); } diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index f2afb9f16f..0e2be1c04a 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -103,6 +103,11 @@ struct x25519_public_key : ec_point {}; struct x25519_secret_key_ : bytes<32> {}; using x25519_secret_key = epee::mlocked>; +struct eth_address : bytes<20, true, uint32_t> { + // Returns true if non-null, i.e. not all 0. + explicit operator bool() const { return data_ != null.data_; } +}; + void hash_to_scalar(const void* data, size_t length, ec_scalar& res); ec_scalar hash_to_scalar(const void* data, size_t length); void random_scalar(unsigned char* bytes); diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index c32b2f2704..d6f61670d3 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -76,13 +76,15 @@ struct address_parse_info { struct batch_sn_payment { cryptonote::address_parse_info address_info; - std::optional eth_address; + std::optional eth_address; uint64_t amount; batch_sn_payment() = default; batch_sn_payment(const cryptonote::address_parse_info& addr_info, uint64_t amt) : address_info{addr_info}, amount{amt} {} batch_sn_payment(const cryptonote::account_public_address& addr, uint64_t amt) : address_info{addr, 0}, amount{amt} {} + batch_sn_payment(const crypto::eth_address& addr, uint64_t amt) : + eth_address(addr), amount(amt), address_info() {} }; #pragma pack(push, 1) diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index ef88d55b49..d057b7fdf5 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -625,7 +625,7 @@ struct tx_extra_oxen_name_system { struct tx_extra_ethereum_address_notification { uint8_t version = 0; - std::string eth_address; + crypto::eth_address eth_address; std::string oxen_address; crypto::signature signature; @@ -640,7 +640,7 @@ struct tx_extra_ethereum_address_notification { struct tx_extra_ethereum_new_service_node { uint8_t version = 0; std::string bls_key; - std::string eth_address; + crypto::eth_address eth_address; crypto::public_key service_node_pubkey; crypto::signature signature; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 1bd36cf911..b021a0a1ff 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5223,20 +5223,17 @@ bool Blockchain::handle_block_to_main_chain( bvc.m_verifivation_failed = true; return false; } - if (is_hard_fork_at_least(m_nettype, feature::ETH_BLS, bl.height) && !m_l2_tracker->check_state_in_history(bl.l2_height, bl.l2_state)) { log::error(logcat, fg(fmt::terminal_color::red), "Failed to find state in l2 tracker."); bvc.m_verifivation_failed = true; return false; } - if (!m_ons_db.add_block(bl, only_txs)) { log::info(logcat, fg(fmt::terminal_color::red), "Failed to add block to ONS DB."); bvc.m_verifivation_failed = true; return false; } - if (m_sqlite_db) { // This takes the block that is already validated and records the rewards that should be paid to the service nodes into the batching database if (!m_service_node_list.process_batching_rewards(bl)) { @@ -5254,7 +5251,6 @@ bool Blockchain::handle_block_to_main_chain( throw std::logic_error("Blockchain missing SQLite Database"); } - m_l2_tracker->record_block_height_mapping(bl.height, bl.l2_height); block_add_info hook_data{bl, only_txs, checkpoint}; for (const auto& hook : m_block_add_hooks) { @@ -5270,7 +5266,6 @@ bool Blockchain::handle_block_to_main_chain( return false; } } - auto addblock_elapsed = std::chrono::steady_clock::now() - addblock; // do this after updating the hard fork state since the weight limit may change due to fork @@ -5321,7 +5316,6 @@ bool Blockchain::handle_block_to_main_chain( tools::friendly_duration(block_processing_time), tools::friendly_duration(miner.verify_pow_time)); } - if (m_show_time_stats) { log::info( logcat, @@ -5342,7 +5336,6 @@ bool Blockchain::handle_block_to_main_chain( bvc.m_added_to_main_chain = true; ++m_sync_counter; - m_tx_pool.on_blockchain_inc(bl); invalidate_block_template_cache(); @@ -5351,7 +5344,6 @@ bool Blockchain::handle_block_to_main_chain( for (const auto& hook : m_block_post_add_hooks) hook(hook_data); } - return true; } //------------------------------------------------------------------ diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 86d931e2d3..a1bfb0c523 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -433,7 +433,7 @@ void validate_registration( if (reg.eth_contributions.size() > max_contributors) throw invalid_registration{"Too many contributors"}; std::transform(reg.eth_contributions.begin(), reg.eth_contributions.end(), std::back_inserter(extracted_amounts), - [](const std::pair& pair) { return pair.second; }); + [](const std::pair& pair) { return pair.second; }); } else { if (reg.reserved.empty()) throw invalid_registration{"No operator contribution given"}; @@ -1472,6 +1472,28 @@ std::pair> validate_and_g info->swarm_id = UNASSIGNED_SWARM_ID; info->last_ip_change_height = block_height; + for (auto it = reg.eth_contributions.begin(); it != reg.eth_contributions.end(); ++it) { + auto& [addr, amount] = *it; + bool dupe = false; + for (auto it2 = std::next(it); it2 != reg.eth_contributions.end(); ++it2) { + if (it2->first == addr) { + log::info( + logcat, + "Invalid registration: duplicate reserved address in registration (tx {})", + cryptonote::get_transaction_hash(tx)); + throw std::runtime_error("duplicate reserved address in registration"); + } + } + + auto& contributor = info->contributors.emplace_back(); + contributor.reserved = amount; + contributor.amount = amount; + + contributor.ethereum_address = addr; + info->total_reserved += contributor.reserved; + info->total_contributed += contributor.reserved; + } + return {reg.service_node_pubkey, info}; } @@ -2227,7 +2249,6 @@ void service_node_list::block_add( // in old-data. uint64_t const block_height = cryptonote::get_block_height(block); bool newest_block = m_blockchain.get_current_blockchain_height() == (block_height + 1); - auto now = pulse::clock::now().time_since_epoch(); auto earliest_time = std::chrono::seconds(block.timestamp) - cryptonote::TARGET_BLOCK_TIME; auto latest_time = std::chrono::seconds(block.timestamp) + cryptonote::TARGET_BLOCK_TIME; @@ -2239,7 +2260,6 @@ void service_node_list::block_add( throw std::runtime_error{"Unexpected Pulse error: quorum was not generated"}; if (quorum->validators.empty()) throw std::runtime_error{"Unexpected Pulse error: quorum was empty"}; - for (size_t validator_index = 0; validator_index < service_nodes::PULSE_QUORUM_NUM_VALIDATORS; validator_index++) { @@ -2822,7 +2842,6 @@ void service_node_list::state_t::update_from_block( // decommissioned). std::vector active_snode_list = sort_and_filter( service_nodes_infos, [](const service_node_info& info) { return info.is_active(); }); - if (need_swarm_update) { crypto::hash const block_hash = cryptonote::get_block_hash(block); uint64_t seed = 0; @@ -2845,7 +2864,6 @@ void service_node_list::state_t::update_from_block( } } } - generate_other_quorums(*this, active_snode_list, nettype, hf_version); } @@ -4937,7 +4955,7 @@ payout service_node_payout_portions(const crypto::public_key& key, const service if (contributor.address == info.operator_address) portion += info.portions_for_operator; - result.payouts.push_back({*contributor.address, portion}); + result.payouts.push_back({contributor.address, portion}); } return result; diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 9be37c8248..2b932ac814 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -246,8 +246,8 @@ struct service_node_info // registration information uint8_t version = 1; uint64_t amount = 0; uint64_t reserved = 0; - std::optional address{}; - std::optional ethereum_address{}; + cryptonote::account_public_address address{}; + crypto::eth_address ethereum_address{}; std::vector locked_contributions; contributor_t() = default; @@ -262,11 +262,10 @@ struct service_node_info // registration information VARINT_FIELD(version) VARINT_FIELD(amount) VARINT_FIELD(reserved) - FIELD(*address) + FIELD(address) FIELD(locked_contributions) - //TODO sean serialize this - //if (version >= 1) - //FIELD(*ethereum_address); + if (version >= 1) + FIELD(ethereum_address); END_SERIALIZE() }; @@ -298,7 +297,7 @@ struct service_node_info // registration information uint64_t portions_for_operator = 0; swarm_id_t swarm_id = 0; cryptonote::account_public_address operator_address{}; - std::string operator_ethereum_address{}; + crypto::eth_address operator_ethereum_address{}; uint64_t last_ip_change_height = 0; // The height of the last quorum penalty for changing IPs version_t version = tools::enum_top; cryptonote::hf registration_hf_version = cryptonote::hf::none; @@ -360,10 +359,9 @@ struct service_node_info // registration information VARINT_FIELD(last_decommission_reason_consensus_all) VARINT_FIELD(last_decommission_reason_consensus_any) } - //TODO sean serialize this - //if (version >= version_t::v8_ethereum_address) { - //FIELD(operator_ethereum_address) - //} + if (version >= version_t::v8_ethereum_address) { + FIELD(operator_ethereum_address) + } END_SERIALIZE() }; @@ -926,7 +924,7 @@ bool tx_get_staking_components_and_amounts( staking_components* contribution); using contribution = std::pair; -using eth_contribution = std::pair; +using eth_contribution = std::pair; struct registration_details { crypto::public_key service_node_pubkey; std::vector reserved; diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 09c24115fb..9ab125167a 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -177,7 +177,7 @@ void L2Tracker::record_block_height_mapping(uint64_t oxen_block_height, uint64_t latest_oxen_block = oxen_block_height; } -bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { +bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { if (!service_node) return true; if (review_block_height_max == 0) { @@ -186,16 +186,16 @@ bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_ke return false; } - oxen::log::info(logcat, "Searching for new_service_node bls_key: " + bls_key + " eth_address: " + eth_address + " service_node_pubkey: " + service_node_pubkey); + oxen::log::info(logcat, "Searching for new_service_node bls_key: " + bls_key + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey); for (auto it = new_service_nodes.begin(); it != new_service_nodes.end(); ++it) { - oxen::log::info(logcat, "new_service_node bls_key: " + it->bls_key + " eth_address: " + it->eth_address + " service_node_pubkey: " + it->service_node_pubkey); + oxen::log::info(logcat, "new_service_node bls_key: " + it->bls_key + " eth_address: " + tools::type_to_hex(it->eth_address) + " service_node_pubkey: " + it->service_node_pubkey); if (it->bls_key == bls_key && it->eth_address == eth_address && it->service_node_pubkey == service_node_pubkey) { new_service_nodes.erase(it); return true; } } - fail_reason = "New Service Node Transaction not found bls_key: " + bls_key + " eth_address: " + eth_address + " service_node_pubkey: " + service_node_pubkey; + fail_reason = "New Service Node Transaction not found bls_key: " + bls_key + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey; return false; } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 05b069c5c7..5b06361621 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -29,7 +29,7 @@ struct TransactionReviewSession { TransactionReviewSession(uint64_t min_height, uint64_t max_height) : review_block_height_min(min_height), review_block_height_max(max_height) {} - bool processNewServiceNodeTx(const std::string& bls_key, const std::string& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); + bool processNewServiceNodeTx(const std::string& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); bool processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index c64d8f728c..80b66c88ea 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -34,14 +34,16 @@ std::optional RewardsLogEntry::getLogTransaction( // service node id is a topic so only address and pubkey are in data // address is 32 bytes , pubkey is 64 bytes and serviceNodePubkey is 32 bytes // - // pull 32 bytes from start - std::string eth_address = data.substr(2, 64); + // The address is in 32 bytes, but actually only uses 20 bytes and the first 12 are padding + std::string eth_address_str = data.substr(2 + 24, 40); // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) std::string bls_key = data.substr(64 + 2, 128); // pull 32 bytes (64 characters) std::string service_node_pubkey = data.substr(128 + 64 + 2, 64); // pull 32 bytes (64 characters) std::string signature = data.substr(128 + 64 + 64 + 2, 64); + crypto::eth_address eth_address; + tools::hex_to_type(eth_address_str, eth_address); return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey, signature); } case TransactionType::ServiceNodeLeaveRequest: { diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index b68afe0f56..8caa4e5069 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -16,11 +18,11 @@ enum class TransactionType { class NewServiceNodeTx { public: std::string bls_key; - std::string eth_address; + crypto::eth_address eth_address; std::string service_node_pubkey; std::string signature; - NewServiceNodeTx(const std::string& _bls_key, const std::string& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) + NewServiceNodeTx(const std::string& _bls_key, const crypto::eth_address& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey), signature(_signature) {} }; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7730f455a3..d642ddb99b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -746,12 +746,12 @@ namespace { _load_owner(ons, "backup_owner", x.backup_owner); } void operator()(const tx_extra_ethereum_address_notification& x) { - set("eth_address", x.eth_address); + set("eth_address", tools::type_to_hex(x.eth_address)); set("signature", tools::view_guts(x.signature)); } void operator()(const tx_extra_ethereum_new_service_node& x) { set("bls_key", x.bls_key); - set("eth_address", x.eth_address); + set("eth_address", tools::type_to_hex(x.eth_address)); set("service_node_pubkey", tools::view_guts(x.service_node_pubkey)); set("signature", tools::view_guts(x.signature)); } @@ -2674,6 +2674,7 @@ void core_rpc_server::fill_sn_response_entry( "operator_fee", microportion(info.portions_for_operator), "operator_address", + info.operator_ethereum_address ? tools::type_to_hex(info.operator_ethereum_address) : cryptonote::get_account_address_as_str( m_core.get_nettype(), false /*subaddress*/, info.operator_address), "swarm_id", @@ -2847,8 +2848,9 @@ void core_rpc_server::fill_sn_response_entry( auto& c = contributors.emplace_back(json{ {"amount", contributor.amount}, {"address", - cryptonote::get_account_address_as_str( - m_core.get_nettype(), false /*subaddress*/, *contributor.address)}}); + contributor.ethereum_address ? tools::type_to_hex(contributor.ethereum_address) : + cryptonote::get_account_address_as_str( + m_core.get_nettype(), false /*subaddress*/, contributor.address)}}); if (contributor.reserved != contributor.amount) c["reserved"] = contributor.reserved; if (want_locked_c) { diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 3ab1a8b19a..047887b137 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -63,3 +63,4 @@ BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); BLOB_SERIALIZER(crypto::ed25519_public_key); BLOB_SERIALIZER(crypto::ed25519_signature); +BLOB_SERIALIZER(crypto::eth_address); diff --git a/utils/local-devnet/commands/hello b/utils/local-devnet/commands/hello new file mode 100644 index 0000000000..d18eb34558 --- /dev/null +++ b/utils/local-devnet/commands/hello @@ -0,0 +1,1044 @@ +{"method": "get_service_nodes", "params": []} +{ + "id": null, + "jsonrpc": "2.0", + "result": { + "block_hash": "ba3569ec4f58d8e0c9ec5a7d08c03f25e1778a12438010d7baee8eb0d4c94f98", + "hardfork": 20, + "height": 380, + "service_node_states": [ + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "f5f5a868e6960f27aa45e8c17022f92a02ac268391dba0ab0dae801562810cf5", + "key_image_pub_key": "e0caec5a349afb336db81ad11b2e5d152e73ace86bf56ed33e29607690ca8185" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 372, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178252, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "4d7ce6dcc274cad3fd9b21c9104ada0544410debd53a913d6285957b0c1c306a", + "pubkey_x25519": "78e1fb27b9fc2b1f910a801ff91f4a96ed5b85de95e2e88926ce24fca47a973e", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1158, + "quorumnet_tests": [ + 4, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "4d7ce6dcc274cad3fd9b21c9104ada0544410debd53a913d6285957b0c1c306a", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 4, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "80e16799f9b24c814742fd6ede3723c098f259028713bc42ff08cce6f56bab86", + "key_image_pub_key": "bc6eabf4fbb8bdd408c08cc03bdef1c304326f6477dc19e31291afb37cf34f70" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 371, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178251, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "56b543411b7197ea58dae628c39d0f3fd5314b7532f21ed87f647e3610e96b12", + "pubkey_x25519": "a586e3be7aada16fd23cd3ddc497f4ee3779442dbade4f3a37f6414f0a906c0d", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1138, + "quorumnet_tests": [ + 4, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "56b543411b7197ea58dae628c39d0f3fd5314b7532f21ed87f647e3610e96b12", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 4, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "43660a41bf1b6f0d6edf4481d05763ff8fff23830457328e2f0adace29786b9b", + "key_image_pub_key": "823a15669e08932e629311df9a948365eb8cd1cb974134173ea3fda7c46c9997" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 370, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178251, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "d29c88084386d7a5e1d043c9cf27dff29b93377bc135ea07ca8bc9f8c207713f", + "pubkey_x25519": "4f7c5af5ab72b66c68552e75a2746f2a76b83255092790f92d79e4f65bfc2a6d", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1153, + "quorumnet_tests": [ + 3, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "d29c88084386d7a5e1d043c9cf27dff29b93377bc135ea07ca8bc9f8c207713f", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 3, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "cbc86cb39b8eee4fc677181faa845dc1308bac4dabb2c537d81302fc5720737d", + "key_image_pub_key": "a4a0a91db4996d6151bb2970fdf801d65d20811e7c7d0a3bc9af48e28d2d18fa" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 379, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178251, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "b9a499e0660d61423792dd5612ee06ae236ef4e4584771e45e27913caff323d1", + "pubkey_x25519": "9846b3396a0058c739b2e4d8d5989ee2dced70bbcb4013e33596ebea7d0aa85d", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1133, + "quorumnet_tests": [ + 1, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "b9a499e0660d61423792dd5612ee06ae236ef4e4584771e45e27913caff323d1", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 1, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "6c3cbe383986bf51f9ee11781b7c16e2c51555f975e73ee30d653f48b1e752b1", + "key_image_pub_key": "85be08b598cc6e99dbb37900b6fbc1abdc3c2e5987936f149c3f094bcbd76dfa" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 378, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178251, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "00c5d7e3a4c9e06503b4d9f55f19d5551bea497ea9f3238609ca4122e2983840", + "pubkey_x25519": "57097ee294963e7889f052292318861dbe34cd7d2bbc479f50c77ac090a20e12", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1148, + "quorumnet_tests": [ + 5, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "00c5d7e3a4c9e06503b4d9f55f19d5551bea497ea9f3238609ca4122e2983840", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 5, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "fbb9b1401b1973fc21943c121a56662fed4518b6865085b34e0fe4cb8950fb53", + "key_image_pub_key": "8a8b49e4543707f24864aeb98d27f5604c3650f48065895d424da4e9286be6b7" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 64, + "funded": true, + "last_reward_block_height": 375, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178243, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "2e494288766551cf536aaa4543cee080f87e4c7e8d99637339d0a23b36a44c42", + "pubkey_x25519": "d2dfd20c0dc24a0b8bf87f71b8ac62b1da633f19079c1804861350a5b8a2823b", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1128, + "quorumnet_tests": [ + 2, + 0 + ], + "registration_height": 257, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "2e494288766551cf536aaa4543cee080f87e4c7e8d99637339d0a23b36a44c42", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 257, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 2, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "amount": 100000000000, + "locked_contributions": [] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 72, + "funded": true, + "last_reward_block_height": 380, + "last_reward_transaction_index": 0, + "last_uptime_proof": 1709178412, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "operator_fee": 0, + "portions_for_operator": 100000000000, + "pubkey_ed25519": "78509bc57b54d5c59047692fa10fe87052f6b8c435f2d603a2dac8de47fb51b0", + "pubkey_x25519": "684ac0f60d69be72e8a57220f2590cfdeae5efba3cd50d803331c2bdb066a41d", + "public_ip": "127.112.220.45", + "quorumnet_port": 1103, + "registration_height": 380, + "registration_hf_version": 20, + "requested_unlock_height": 0, + "service_node_pubkey": "78509bc57b54d5c59047692fa10fe87052f6b8c435f2d603a2dac8de47fb51b0", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 0, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "ffffffffffffffff", + "swarm_id": 18446744073709551615, + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2GCC9fgJjYY9XE6wKWJ9CBat2T3wsVuPVZagMFVxQMKZGABd1CTQo1UHKHYodCGxRakm7QLZBm4LffH9XATLyD1xiMXBBpz", + "amount": 28000000000, + "locked_contributions": [ + { + "amount": 28000000000, + "key_image": "0ed0fd2afe473db6c5ab3efecb2a2b277570a6692743f5cf18db4015263ceed9", + "key_image_pub_key": "b6e016f2bb50172ce53bcb830223962432aaa7e723f5e40ed65083f4d4053a33" + } + ] + }, + { + "address": "dV2NhSUhfRKEstcedh7AxUhKYAQWWbD4VQNQKKit8riGTQotWtWHvDKJcFNYcAzAzv3xUxB9RE6uqHSfhmcLuVMe25VWNjnB2", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "8703389a6c70991e45c5e0874ddfdf481f7ecd80d5a420275e7e5e7d9917a732", + "key_image_pub_key": "8ef0bf73079ee4c7f8379a8d2541519e4f11efa6d93efe75acafaa0b1ccc9bb6" + } + ] + }, + { + "address": "dV3k1TG4x3nPW1fdHeCJsM9tcNe8hjVFv4vjNUUFCJ1icVePTVcaJ5CU7NE5QXcQk23hu4PnHhYnQQ5VeEwdiFB92wXBqzv53", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "bcebd358e1331d962920fcb84e27650cc2650852013a14ac477d703552e8fe51", + "key_image_pub_key": "8694a2d07ae94ffbf082a9469b2aeb63818225d308aad2f0dfc2fbc3a99ef28f" + } + ] + }, + { + "address": "dV27X6cCvDyRwxt3vvhwMnZnYAZ1UKrNue5JMhgyEhT4XbcLTLxv2486CSHYgz9PCwENt2qjfrhCePhh7QBCe54C2G9UyQA6T", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "19c8dce986e84607f33ea2d8618acd5b42f019667ef62a8b62f4032d29b56dd4", + "key_image_pub_key": "7d2b8634248d5f78031ce280e6261afc42d6b60dd102cbfa71e5b20667a74be4" + } + ] + }, + { + "address": "dV2na9bu6FgVpsPZ7ygD5xKjtAnsr3tkreUqD2SWj26mB5bAWxiidGmRaFdYNFw89iRjNJi93ZdyrgaMvxrhe3HV1mPsu16p8", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "41efc55faf8cf8c0389ab490ee467fa8641ab2fd3d3fd33b8a14fedea30e6428", + "key_image_pub_key": "d08b86999db25e0fddcf8e2595d7c7cb7741700a9ebf7700690dbe93093f2aff" + } + ] + }, + { + "address": "dV37R1RcDwSWxqJKv24YzYadb52dJjtmtFpDQqiAHatDVutb6Pb2rF2S4P5TWdjwL27RAdB4NNZja5uHtiRqrnwD2F12Wv6Yu", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "014796b3d8b4fb9efde824f56e3a86a0f60d198db3a4fd42063aa7c367223657", + "key_image_pub_key": "04265b6210858b157c12cd94c65bf5535c231a705bb09a7ffc3d54cc6cb35b6b" + } + ] + }, + { + "address": "dV2QmjPRZjDQp7D7VUg8Rk5HCsVN8b1mTV8fvZgE7VJnfaeZMsHKLkbaiWqUP7dCUaKBKi1maiNJCQf7e8LCJXv31ggQFUhDA", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "e0b2136a6c1da5274e02f621d8f498fec76b2a6131213127937ce05113dc8188", + "key_image_pub_key": "dad4d643f7ff7f7a4c594b30ab84358547e5d680abf4b04a1e8821e1a3c23b80" + } + ] + }, + { + "address": "dV2QQnYxLXfcWGftstaxmK5MaYAnjkpQdES5shMXTwy7AdoDCgNcCuB3fS5nBsBSX4VWsoHY5ytVniwZnmCfbSN126TQUzqDS", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "e7f5d416630c61aa983e262e7d86a77b28eb4934909dbad9de60ece8680ca978", + "key_image_pub_key": "cc4056696acb2f9f470af2e54ef460eefcf5e3811ee96c9155d59235df36ad91" + } + ] + }, + { + "address": "dV2sZf5aYsUQqMhggvLRgV2venWRXCkJVAoavRU3iGi5hxhVrL8e1eVSEHqEutYN1HWQsGKwEP7jFchhSBEDbRJb2WT7wRmop", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "a2a0294c8ddd1b4304096bfd9966546012d9e6579310ed3c1d021d24704d1de8", + "key_image_pub_key": "4a0fae3ba8bc29a86121af3ed06f30bbac8fcc01b75a84f7910b8df4be9a6a48" + } + ] + }, + { + "address": "dV3jSDSedYnFnyPJckdnQR71tAhT932L4ZxPQjb7nrjUUn9XuvEdaQ34D2gxYiDy2L7BpvCMagxtu5T1saBEpaD521YGDQ61s", + "amount": 8000000000, + "locked_contributions": [ + { + "amount": 8000000000, + "key_image": "04d668ac23124639b8d9f607f9472755bc2dd88ed16344a5e3ffd17a0dea10b0", + "key_image_pub_key": "14d27c9b0eaf8370b78c03c69339186611b9f4580cdc6f679a2bab35d3253f52" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 60, + "funded": true, + "last_reward_block_height": 379, + "last_reward_transaction_index": 8, + "last_uptime_proof": 1709178256, + "lokinet_reachable": true, + "lokinet_version": [ + 9, + 9, + 9 + ], + "operator_address": "dV2GCC9fgJjYY9XE6wKWJ9CBat2T3wsVuPVZagMFVxQMKZGABd1CTQo1UHKHYodCGxRakm7QLZBm4LffH9XATLyD1xiMXBBpz", + "operator_fee": 100000, + "portions_for_operator": 1844674407370955161, + "pubkey_ed25519": "dc6f1a7f14b4de1419cb3e668fb2e08c748a49c8266eb4b12857e001c36d5b47", + "pubkey_x25519": "74a4ff0513c9e574f14cd54cccc6aff93ea680e4efdb2857cc2469be196c9820", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1163, + "quorumnet_tests": [ + 1, + 0 + ], + "registration_height": 371, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "dc6f1a7f14b4de1419cb3e668fb2e08c748a49c8266eb4b12857e001c36d5b47", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 379, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 1, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "7711ae45305021a4c5738d6096c4e6c5c385b531cc0702345bcc586c30e1c41d", + "key_image_pub_key": "44eb2716f7e6c3561b1055100765196064b00fd022a46af5d14ae3f5c751e233" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 64, + "funded": true, + "last_reward_block_height": 374, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178243, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "1df2d6e606a7ff4204bcf885c012d7217d3f37bffddb656204511c9f8473ec55", + "pubkey_x25519": "f5c41daff1e5056bd3de3df3a912c5c6466be67cfcbf130993633aa8010f2831", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1118, + "quorumnet_tests": [ + 3, + 0 + ], + "registration_height": 257, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "1df2d6e606a7ff4204bcf885c012d7217d3f37bffddb656204511c9f8473ec55", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 257, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 3, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "33de4e957e6d34411e41a7235789dc058d8855e0dd057b05fc9520ada54530ef", + "key_image_pub_key": "8c8a84be21e08c679600be30dcdaa80aadf81d885f5a22e5f72dd1d6cbbc51a3" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 64, + "funded": true, + "last_reward_block_height": 373, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178243, + "lokinet_reachable": true, + "lokinet_version": [ + 9, + 9, + 9 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "12fd18478d61bae9c65303d95f2c8e391ee3f31d5eccda6c17aabe33daf83356", + "pubkey_x25519": "7b12a658b4e7d9ca7693c3b0dc57ac9faac859ba66aa6f92857eb26f2c897c06", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1108, + "quorumnet_tests": [ + 4, + 0 + ], + "registration_height": 257, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "12fd18478d61bae9c65303d95f2c8e391ee3f31d5eccda6c17aabe33daf83356", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 257, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 4, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "9212fd8ce14930a81807cc89ef6d57de67f376b3610b6d4b51d683d56e979494", + "key_image_pub_key": "717c44d5584cf886590ecf3697608956f695a5bdde0475bf2c2f2a558a5ba5e5" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 61, + "funded": true, + "last_reward_block_height": 380, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178251, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "c341533a1c43b4ab68b92e4ec0ba5edb291023dc76daf6d4d39b6fef56b7b1e2", + "pubkey_x25519": "0bf7f01d5e115cd1dcb0deed5919c8534607a9358257fc200aa95e0945a98f21", + "public_ip": "127.112.220.45", + "quorumnet_port": 1143, + "quorumnet_tests": [ + 2, + 0 + ], + "registration_height": 329, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "c341533a1c43b4ab68b92e4ec0ba5edb291023dc76daf6d4d39b6fef56b7b1e2", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 329, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 2, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "dc4c0c8b35cf28b9437ecf706b253b902ceeb395ffdaf7fc1cefbd2286dd8e83", + "key_image_pub_key": "0bb3522b2fe0f551d3668e8c6d1e6160e84e358cfa9bac38156941aa11305436" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 64, + "funded": true, + "last_reward_block_height": 376, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178243, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "f6df367bb4592290981fc7e93d53da0242597cd20ee6c2bd2a3eb4e327427f35", + "pubkey_x25519": "8ca5898f03093bc887b6740d913d9929cf3668ba914a0026677cb8f5ffab8b76", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1123, + "quorumnet_tests": [ + 4, + 0 + ], + "registration_height": 257, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "f6df367bb4592290981fc7e93d53da0242597cd20ee6c2bd2a3eb4e327427f35", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 257, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 4, + 0 + ], + "total_contributed": 100000000000 + }, + { + "active": true, + "contributors": [ + { + "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "amount": 100000000000, + "locked_contributions": [ + { + "amount": 100000000000, + "key_image": "4df6fa737d7b7d21064dfa0d41790fca842295c062a9212b4d0cee66aa1aeae1", + "key_image_pub_key": "377af1a7767c9b1ae98d19b5ade440352bfa1de64d3e48009252042c82b48777" + } + ] + } + ], + "decommission_count": 0, + "earned_downtime_blocks": 64, + "funded": true, + "last_reward_block_height": 377, + "last_reward_transaction_index": 4294967295, + "last_uptime_proof": 1709178243, + "lokinet_reachable": true, + "lokinet_version": [ + 0, + 0, + 0 + ], + "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", + "operator_fee": 1000000, + "portions_for_operator": 18446744073709551612, + "pubkey_ed25519": "a7560ecdcd80c479d5814f5d31704ff71a51a47f2e8060f2977eb2f17135a680", + "pubkey_x25519": "056e59a7c3beae01fd8e2f7b4fc65be6411636d5c098cffe5b91e0c4fcb2fb5e", + "public_ip": "127.112.220.45", + "pulse_votes": { + "missed": [], + "voted": [ + [ + 380, + 0 + ] + ] + }, + "quorumnet_port": 1113, + "quorumnet_tests": [ + 3, + 0 + ], + "registration_height": 257, + "registration_hf_version": 19, + "requested_unlock_height": 0, + "service_node_pubkey": "a7560ecdcd80c479d5814f5d31704ff71a51a47f2e8060f2977eb2f17135a680", + "service_node_version": [ + 11, + 0, + 0 + ], + "staking_requirement": 100000000000, + "state_height": 257, + "storage_lmq_port": 0, + "storage_port": 0, + "storage_server_reachable": true, + "storage_server_version": [ + 0, + 0, + 0 + ], + "swarm": "0", + "swarm_id": 0, + "timesync_tests": [ + 3, + 0 + ], + "total_contributed": 100000000000 + } + ], + "snode_revision": 0, + "status": "OK", + "target_height": 0 + } +} From ec459550a41eb320597a16cd8168006f6d490de2 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Sun, 3 Mar 2024 12:36:00 +1100 Subject: [PATCH 45/97] Unlock Service Node - Ethereum Transaction --- external/ethyl | 2 +- src/crypto/crypto.h | 5 + src/cryptonote_basic/tx_extra.h | 8 +- src/cryptonote_core/blockchain.cpp | 4 +- src/cryptonote_core/service_node_list.cpp | 54 +- src/cryptonote_core/service_node_list.h | 5 + src/l2_tracker/l2_tracker.cpp | 16 +- src/l2_tracker/l2_tracker.h | 6 +- src/l2_tracker/rewards_contract.cpp | 16 +- src/l2_tracker/rewards_contract.h | 12 +- src/rpc/core_rpc_server.cpp | 8 +- src/serialization/crypto.h | 1 + utils/local-devnet/commands/hello | 1044 -------------------- utils/local-devnet/ethereum.py | 29 + utils/local-devnet/service_node_network.py | 3 + 15 files changed, 118 insertions(+), 1095 deletions(-) delete mode 100644 utils/local-devnet/commands/hello diff --git a/external/ethyl b/external/ethyl index 724e2a87b4..bd2a2a7da4 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit 724e2a87b4e58a4b027386341bdf99806af1bd3b +Subproject commit bd2a2a7da4e656e578407eed8b0ddb93c51381ff diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 0e2be1c04a..6c67415e1e 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -108,6 +108,11 @@ struct eth_address : bytes<20, true, uint32_t> { explicit operator bool() const { return data_ != null.data_; } }; +struct bls_public_key : bytes<64, true, uint32_t> { + // Returns true if non-null, i.e. not all 0. + explicit operator bool() const { return data_ != null.data_; } +}; + void hash_to_scalar(const void* data, size_t length, ec_scalar& res); ec_scalar hash_to_scalar(const void* data, size_t length); void random_scalar(unsigned char* bytes); diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index d057b7fdf5..676298ea58 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -639,7 +639,7 @@ struct tx_extra_ethereum_address_notification { struct tx_extra_ethereum_new_service_node { uint8_t version = 0; - std::string bls_key; + crypto::bls_public_key bls_key; crypto::eth_address eth_address; crypto::public_key service_node_pubkey; crypto::signature signature; @@ -655,7 +655,7 @@ struct tx_extra_ethereum_new_service_node { struct tx_extra_ethereum_service_node_leave_request { uint8_t version = 0; - std::string bls_key; + crypto::bls_public_key bls_key; BEGIN_SERIALIZE() FIELD(version) @@ -665,13 +665,11 @@ struct tx_extra_ethereum_service_node_leave_request { struct tx_extra_ethereum_service_node_deregister { uint8_t version = 0; - std::string bls_key; - bool refund_stake; + crypto::bls_public_key bls_key; BEGIN_SERIALIZE() FIELD(version) FIELD(bls_key) - FIELD(refund_stake) END_SERIALIZE() }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index b021a0a1ff..9f9663b953 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1977,7 +1977,7 @@ void Blockchain::add_ethereum_transactions_to_tx_pool() { cryptonote::add_service_node_leave_request_to_tx_extra(tx.extra, leave_request); } else if constexpr (std::is_same_v) { tx.type = txtype::ethereum_service_node_deregister; - tx_extra_ethereum_service_node_deregister deregister = { 0, arg.bls_key, true }; + tx_extra_ethereum_service_node_deregister deregister = { 0, arg.bls_key }; cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister); } }, tx_variant); @@ -4114,7 +4114,7 @@ bool Blockchain::check_tx_inputs( cryptonote::tx_extra_ethereum_service_node_deregister entry = {}; std::string fail_reason; if (!ethereum::validate_ethereum_service_node_deregister_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || - !ethereum_transaction_review_session->processServiceNodeDeregisterTx(entry.bls_key, entry.refund_stake, fail_reason)) { + !ethereum_transaction_review_session->processServiceNodeDeregisterTx(entry.bls_key, fail_reason)) { log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Deregister TX reason: {}", fail_reason); tvc.m_verbose_error = std::move(fail_reason); return false; diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index a1bfb0c523..f4cfcde5bf 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -374,6 +374,7 @@ std::optional eth_reg_tx_extract_fields(hf hf_version, con registration_details reg{}; reg.service_node_pubkey = registration.service_node_pubkey; + reg.bls_key = registration.bls_key; // TODO sean this needs to be thought out auto& [addr, amount] = reg.eth_contributions.emplace_back(); @@ -1063,28 +1064,26 @@ bool service_node_list::state_t::process_ethereum_deregister_tx( } - crypto::public_key key; - // TODO sean get this public key from the bls key somehow - - auto iter = service_nodes_infos.find(key); + crypto::public_key snode_key = sn_list->bls_public_key_lookup(dereg.bls_key); + auto iter = service_nodes_infos.find(snode_key); if (iter == service_nodes_infos.end()) { log::debug( logcat, "Received state change tx for non-registered service node {} (perhaps a delayed " "tx?)", - key); + snode_key); return false; } - bool is_me = my_keys && my_keys->pub == key; + bool is_me = my_keys && my_keys->pub == snode_key; if (is_me) log::info( logcat, fg(fmt::terminal_color::red), "Deregistration for service node (yours): {}", - key); + snode_key); else - log::info(logcat, "Deregistration for service node: {}", key); + log::info(logcat, "Deregistration for service node: {}", snode_key); service_nodes_infos.erase(iter); return true; @@ -1207,11 +1206,7 @@ bool service_node_list::state_t::process_ethereum_unlock_tx( return false; } - //TODO sean get this from somewhere using bls key instead - crypto::public_key snode_key = {}; - //if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key)) - //return false; - + crypto::public_key snode_key = sn_list->bls_public_key_lookup(unlock.bls_key); auto it = service_nodes_infos.find(snode_key); if (it == service_nodes_infos.end()) return false; @@ -1232,11 +1227,8 @@ bool service_node_list::state_t::process_ethereum_unlock_tx( uint64_t unlock_height = get_locked_key_image_unlock_height(nettype, node_info.registration_height, block_height); - for (const auto& contributor : node_info.contributors) { - // TODO sean, why did only the key image unlocker get this set? - // now im looping over all of them cause there is no more key images - duplicate_info(it->second).requested_unlock_height = unlock_height; - } + duplicate_info(it->second).requested_unlock_height = unlock_height; + return true; } //------------------------------------------------------------------ @@ -1464,6 +1456,7 @@ std::pair> validate_and_g info->staking_requirement = staking_requirement; info->operator_ethereum_address = reg.eth_contributions[0].first; + info->bls_public_key = reg.bls_key; info->portions_for_operator = staking_requirement; info->registration_height = block_height; info->registration_hf_version = hf_version; @@ -4262,6 +4255,31 @@ std::string service_node_list::remote_lookup(std::string_view xpk) { return "tcp://" + epee::string_tools::get_ip_string_from_int32(ip) + ":" + std::to_string(port); } +crypto::public_key service_node_list::bls_public_key_lookup(crypto::bls_public_key bls_key) { + std::vector sn_pks; + auto sns = get_service_node_list_state(); + sn_pks.reserve(sns.size()); + for (const auto& sni : sns) + sn_pks.push_back(sni.pubkey); + bool found = false; + crypto::public_key found_pk; + for_each_service_node_info_and_proof( + sn_pks.begin(), sn_pks.end(), [&bls_key, &found, &found_pk](auto& pk, auto& sni, auto& proof) { + if (tools::view_guts(sni.bls_public_key) == tools::view_guts(bls_key)) { + found = true; + found_pk = pk; + } + } + ); + + if (!found) { + log::error(logcat, "Could not find bls key: {}", tools::type_to_hex(bls_key)); + throw std::runtime_error("Could not find bls key"); + } + + return found_pk; +} + void service_node_list::record_checkpoint_participation( crypto::public_key const& pubkey, uint64_t height, bool participated) { std::lock_guard lock(m_sn_mutex); diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 2b932ac814..45529be013 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -298,6 +298,7 @@ struct service_node_info // registration information swarm_id_t swarm_id = 0; cryptonote::account_public_address operator_address{}; crypto::eth_address operator_ethereum_address{}; + crypto::bls_public_key bls_public_key{}; uint64_t last_ip_change_height = 0; // The height of the last quorum penalty for changing IPs version_t version = tools::enum_top; cryptonote::hf registration_hf_version = cryptonote::hf::none; @@ -360,6 +361,7 @@ struct service_node_info // registration information VARINT_FIELD(last_decommission_reason_consensus_any) } if (version >= version_t::v8_ethereum_address) { + FIELD(bls_public_key) FIELD(operator_ethereum_address) } END_SERIALIZE() @@ -611,6 +613,8 @@ class service_node_list { bool& my_uptime_proof_confirmation, crypto::x25519_public_key& x25519_pkey); + crypto::public_key bls_public_key_lookup(crypto::bls_public_key bls_key); + void record_checkpoint_participation( crypto::public_key const& pubkey, uint64_t height, bool participated); @@ -933,6 +937,7 @@ struct registration_details { bool uses_portions; // if true then `hf` is a timestamp crypto::signature signature; std::vector eth_contributions; + crypto::bls_public_key bls_key; }; bool is_registration_tx( diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 9ab125167a..5cc1882f66 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -177,7 +177,7 @@ void L2Tracker::record_block_height_mapping(uint64_t oxen_block_height, uint64_t latest_oxen_block = oxen_block_height; } -bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { +bool TransactionReviewSession::processNewServiceNodeTx(const crypto::bls_public_key& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason) { if (!service_node) return true; if (review_block_height_max == 0) { @@ -186,20 +186,20 @@ bool TransactionReviewSession::processNewServiceNodeTx(const std::string& bls_ke return false; } - oxen::log::info(logcat, "Searching for new_service_node bls_key: " + bls_key + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey); + oxen::log::info(logcat, "Searching for new_service_node bls_key: " + tools::type_to_hex(bls_key) + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey); for (auto it = new_service_nodes.begin(); it != new_service_nodes.end(); ++it) { - oxen::log::info(logcat, "new_service_node bls_key: " + it->bls_key + " eth_address: " + tools::type_to_hex(it->eth_address) + " service_node_pubkey: " + it->service_node_pubkey); + oxen::log::info(logcat, "new_service_node bls_key: " + tools::type_to_hex(it->bls_key) + " eth_address: " + tools::type_to_hex(it->eth_address) + " service_node_pubkey: " + it->service_node_pubkey); if (it->bls_key == bls_key && it->eth_address == eth_address && it->service_node_pubkey == service_node_pubkey) { new_service_nodes.erase(it); return true; } } - fail_reason = "New Service Node Transaction not found bls_key: " + bls_key + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey; + fail_reason = "New Service Node Transaction not found bls_key: " + tools::type_to_hex(bls_key) + " eth_address: " + tools::type_to_hex(eth_address) + " service_node_pubkey: " + service_node_pubkey; return false; } -bool TransactionReviewSession::processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason) { +bool TransactionReviewSession::processServiceNodeLeaveRequestTx(const crypto::bls_public_key& bls_key, std::string& fail_reason) { if (!service_node) return true; if (review_block_height_max == 0) { @@ -215,11 +215,11 @@ bool TransactionReviewSession::processServiceNodeLeaveRequestTx(const std::strin } } - fail_reason = "Leave Request Transaction not found bls_key: " + bls_key; + fail_reason = "Leave Request Transaction not found bls_key: " + tools::type_to_hex(bls_key); return false; } -bool TransactionReviewSession::processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason) { +bool TransactionReviewSession::processServiceNodeDeregisterTx(const crypto::bls_public_key& bls_key, std::string& fail_reason) { if (!service_node) return true; if (review_block_height_max == 0) { @@ -235,7 +235,7 @@ bool TransactionReviewSession::processServiceNodeDeregisterTx(const std::string& } } - fail_reason = "Deregister Transaction not found bls_key: " + bls_key; + fail_reason = "Deregister Transaction not found bls_key: " + tools::type_to_hex(bls_key); return false; } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 5b06361621..a88ef91fd5 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -29,9 +29,9 @@ struct TransactionReviewSession { TransactionReviewSession(uint64_t min_height, uint64_t max_height) : review_block_height_min(min_height), review_block_height_max(max_height) {} - bool processNewServiceNodeTx(const std::string& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); - bool processServiceNodeLeaveRequestTx(const std::string& bls_key, std::string& fail_reason); - bool processServiceNodeDeregisterTx(const std::string& bls_key, bool refund_stake, std::string& fail_reason); + bool processNewServiceNodeTx(const crypto::bls_public_key& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); + bool processServiceNodeLeaveRequestTx(const crypto::bls_public_key& bls_key, std::string& fail_reason); + bool processServiceNodeDeregisterTx(const crypto::bls_public_key& bls_key, std::string& fail_reason); bool finalize_review(); }; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 80b66c88ea..8a9fff7657 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -36,14 +36,16 @@ std::optional RewardsLogEntry::getLogTransaction( // // The address is in 32 bytes, but actually only uses 20 bytes and the first 12 are padding std::string eth_address_str = data.substr(2 + 24, 40); + crypto::eth_address eth_address; + tools::hex_to_type(eth_address_str, eth_address); // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) - std::string bls_key = data.substr(64 + 2, 128); + std::string bls_key_str = data.substr(64 + 2, 128); + crypto::bls_public_key bls_key; + tools::hex_to_type(bls_key_str, bls_key); // pull 32 bytes (64 characters) std::string service_node_pubkey = data.substr(128 + 64 + 2, 64); // pull 32 bytes (64 characters) std::string signature = data.substr(128 + 64 + 64 + 2, 64); - crypto::eth_address eth_address; - tools::hex_to_type(eth_address_str, eth_address); return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey, signature); } case TransactionType::ServiceNodeLeaveRequest: { @@ -52,7 +54,9 @@ std::optional RewardsLogEntry::getLogTransaction( // address is 32 bytes and pubkey is 64 bytes, // // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) - std::string bls_key = data.substr(64 + 2, 128); + std::string bls_key_str = data.substr(64 + 2, 128); + crypto::bls_public_key bls_key; + tools::hex_to_type(bls_key_str, bls_key); return ServiceNodeLeaveRequestTx(bls_key); } case TransactionType::ServiceNodeDeregister: { @@ -61,7 +65,9 @@ std::optional RewardsLogEntry::getLogTransaction( // address is 32 bytes and pubkey is 64 bytes, // // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) - std::string bls_key = data.substr(64 + 2, 128); + std::string bls_key_str = data.substr(64 + 2, 128); + crypto::bls_public_key bls_key; + tools::hex_to_type(bls_key_str, bls_key); return ServiceNodeDeregisterTx(bls_key); } default: diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index 8caa4e5069..b610d6fd0f 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -17,28 +17,28 @@ enum class TransactionType { class NewServiceNodeTx { public: - std::string bls_key; + crypto::bls_public_key bls_key; crypto::eth_address eth_address; std::string service_node_pubkey; std::string signature; - NewServiceNodeTx(const std::string& _bls_key, const crypto::eth_address& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) + NewServiceNodeTx(const crypto::bls_public_key& _bls_key, const crypto::eth_address& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey), signature(_signature) {} }; class ServiceNodeLeaveRequestTx { public: - std::string bls_key; + crypto::bls_public_key bls_key; - ServiceNodeLeaveRequestTx(const std::string& _bls_key) + ServiceNodeLeaveRequestTx(const crypto::bls_public_key& _bls_key) : bls_key(_bls_key) {} }; class ServiceNodeDeregisterTx { public: - std::string bls_key; + crypto::bls_public_key bls_key; - ServiceNodeDeregisterTx(const std::string& _bls_key) + ServiceNodeDeregisterTx(const crypto::bls_public_key& _bls_key) : bls_key(_bls_key) {} }; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d642ddb99b..d9f2019721 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -750,16 +750,16 @@ namespace { set("signature", tools::view_guts(x.signature)); } void operator()(const tx_extra_ethereum_new_service_node& x) { - set("bls_key", x.bls_key); + set("bls_key", tools::type_to_hex(x.bls_key)); set("eth_address", tools::type_to_hex(x.eth_address)); set("service_node_pubkey", tools::view_guts(x.service_node_pubkey)); set("signature", tools::view_guts(x.signature)); } void operator()(const tx_extra_ethereum_service_node_leave_request& x) { - set("bls_key", x.bls_key); + set("bls_key", tools::type_to_hex(x.bls_key)); } void operator()(const tx_extra_ethereum_service_node_deregister& x) { - set("bls_key", x.bls_key); + set("bls_key", tools::type_to_hex(x.bls_key)); } @@ -2681,6 +2681,8 @@ void core_rpc_server::fill_sn_response_entry( info.swarm_id, "swarm", "{:x}"_format(info.swarm_id), + "bls_key", + info.bls_public_key ? tools::type_to_hex(info.bls_public_key) : "", "registration_hf_version", info.registration_hf_version); diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 047887b137..a92d12aba8 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -64,3 +64,4 @@ BLOB_SERIALIZER(crypto::signature); BLOB_SERIALIZER(crypto::ed25519_public_key); BLOB_SERIALIZER(crypto::ed25519_signature); BLOB_SERIALIZER(crypto::eth_address); +BLOB_SERIALIZER(crypto::bls_public_key); diff --git a/utils/local-devnet/commands/hello b/utils/local-devnet/commands/hello deleted file mode 100644 index d18eb34558..0000000000 --- a/utils/local-devnet/commands/hello +++ /dev/null @@ -1,1044 +0,0 @@ -{"method": "get_service_nodes", "params": []} -{ - "id": null, - "jsonrpc": "2.0", - "result": { - "block_hash": "ba3569ec4f58d8e0c9ec5a7d08c03f25e1778a12438010d7baee8eb0d4c94f98", - "hardfork": 20, - "height": 380, - "service_node_states": [ - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "f5f5a868e6960f27aa45e8c17022f92a02ac268391dba0ab0dae801562810cf5", - "key_image_pub_key": "e0caec5a349afb336db81ad11b2e5d152e73ace86bf56ed33e29607690ca8185" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 372, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178252, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "4d7ce6dcc274cad3fd9b21c9104ada0544410debd53a913d6285957b0c1c306a", - "pubkey_x25519": "78e1fb27b9fc2b1f910a801ff91f4a96ed5b85de95e2e88926ce24fca47a973e", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1158, - "quorumnet_tests": [ - 4, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "4d7ce6dcc274cad3fd9b21c9104ada0544410debd53a913d6285957b0c1c306a", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 4, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "80e16799f9b24c814742fd6ede3723c098f259028713bc42ff08cce6f56bab86", - "key_image_pub_key": "bc6eabf4fbb8bdd408c08cc03bdef1c304326f6477dc19e31291afb37cf34f70" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 371, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178251, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "56b543411b7197ea58dae628c39d0f3fd5314b7532f21ed87f647e3610e96b12", - "pubkey_x25519": "a586e3be7aada16fd23cd3ddc497f4ee3779442dbade4f3a37f6414f0a906c0d", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1138, - "quorumnet_tests": [ - 4, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "56b543411b7197ea58dae628c39d0f3fd5314b7532f21ed87f647e3610e96b12", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 4, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "43660a41bf1b6f0d6edf4481d05763ff8fff23830457328e2f0adace29786b9b", - "key_image_pub_key": "823a15669e08932e629311df9a948365eb8cd1cb974134173ea3fda7c46c9997" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 370, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178251, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "d29c88084386d7a5e1d043c9cf27dff29b93377bc135ea07ca8bc9f8c207713f", - "pubkey_x25519": "4f7c5af5ab72b66c68552e75a2746f2a76b83255092790f92d79e4f65bfc2a6d", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1153, - "quorumnet_tests": [ - 3, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "d29c88084386d7a5e1d043c9cf27dff29b93377bc135ea07ca8bc9f8c207713f", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 3, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "cbc86cb39b8eee4fc677181faa845dc1308bac4dabb2c537d81302fc5720737d", - "key_image_pub_key": "a4a0a91db4996d6151bb2970fdf801d65d20811e7c7d0a3bc9af48e28d2d18fa" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 379, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178251, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "b9a499e0660d61423792dd5612ee06ae236ef4e4584771e45e27913caff323d1", - "pubkey_x25519": "9846b3396a0058c739b2e4d8d5989ee2dced70bbcb4013e33596ebea7d0aa85d", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1133, - "quorumnet_tests": [ - 1, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "b9a499e0660d61423792dd5612ee06ae236ef4e4584771e45e27913caff323d1", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 1, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "6c3cbe383986bf51f9ee11781b7c16e2c51555f975e73ee30d653f48b1e752b1", - "key_image_pub_key": "85be08b598cc6e99dbb37900b6fbc1abdc3c2e5987936f149c3f094bcbd76dfa" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 378, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178251, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "00c5d7e3a4c9e06503b4d9f55f19d5551bea497ea9f3238609ca4122e2983840", - "pubkey_x25519": "57097ee294963e7889f052292318861dbe34cd7d2bbc479f50c77ac090a20e12", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1148, - "quorumnet_tests": [ - 5, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "00c5d7e3a4c9e06503b4d9f55f19d5551bea497ea9f3238609ca4122e2983840", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 5, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "fbb9b1401b1973fc21943c121a56662fed4518b6865085b34e0fe4cb8950fb53", - "key_image_pub_key": "8a8b49e4543707f24864aeb98d27f5604c3650f48065895d424da4e9286be6b7" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 64, - "funded": true, - "last_reward_block_height": 375, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178243, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "2e494288766551cf536aaa4543cee080f87e4c7e8d99637339d0a23b36a44c42", - "pubkey_x25519": "d2dfd20c0dc24a0b8bf87f71b8ac62b1da633f19079c1804861350a5b8a2823b", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1128, - "quorumnet_tests": [ - 2, - 0 - ], - "registration_height": 257, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "2e494288766551cf536aaa4543cee080f87e4c7e8d99637339d0a23b36a44c42", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 257, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 2, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "f39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "amount": 100000000000, - "locked_contributions": [] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 72, - "funded": true, - "last_reward_block_height": 380, - "last_reward_transaction_index": 0, - "last_uptime_proof": 1709178412, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "f39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "operator_fee": 0, - "portions_for_operator": 100000000000, - "pubkey_ed25519": "78509bc57b54d5c59047692fa10fe87052f6b8c435f2d603a2dac8de47fb51b0", - "pubkey_x25519": "684ac0f60d69be72e8a57220f2590cfdeae5efba3cd50d803331c2bdb066a41d", - "public_ip": "127.112.220.45", - "quorumnet_port": 1103, - "registration_height": 380, - "registration_hf_version": 20, - "requested_unlock_height": 0, - "service_node_pubkey": "78509bc57b54d5c59047692fa10fe87052f6b8c435f2d603a2dac8de47fb51b0", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 0, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "ffffffffffffffff", - "swarm_id": 18446744073709551615, - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2GCC9fgJjYY9XE6wKWJ9CBat2T3wsVuPVZagMFVxQMKZGABd1CTQo1UHKHYodCGxRakm7QLZBm4LffH9XATLyD1xiMXBBpz", - "amount": 28000000000, - "locked_contributions": [ - { - "amount": 28000000000, - "key_image": "0ed0fd2afe473db6c5ab3efecb2a2b277570a6692743f5cf18db4015263ceed9", - "key_image_pub_key": "b6e016f2bb50172ce53bcb830223962432aaa7e723f5e40ed65083f4d4053a33" - } - ] - }, - { - "address": "dV2NhSUhfRKEstcedh7AxUhKYAQWWbD4VQNQKKit8riGTQotWtWHvDKJcFNYcAzAzv3xUxB9RE6uqHSfhmcLuVMe25VWNjnB2", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "8703389a6c70991e45c5e0874ddfdf481f7ecd80d5a420275e7e5e7d9917a732", - "key_image_pub_key": "8ef0bf73079ee4c7f8379a8d2541519e4f11efa6d93efe75acafaa0b1ccc9bb6" - } - ] - }, - { - "address": "dV3k1TG4x3nPW1fdHeCJsM9tcNe8hjVFv4vjNUUFCJ1icVePTVcaJ5CU7NE5QXcQk23hu4PnHhYnQQ5VeEwdiFB92wXBqzv53", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "bcebd358e1331d962920fcb84e27650cc2650852013a14ac477d703552e8fe51", - "key_image_pub_key": "8694a2d07ae94ffbf082a9469b2aeb63818225d308aad2f0dfc2fbc3a99ef28f" - } - ] - }, - { - "address": "dV27X6cCvDyRwxt3vvhwMnZnYAZ1UKrNue5JMhgyEhT4XbcLTLxv2486CSHYgz9PCwENt2qjfrhCePhh7QBCe54C2G9UyQA6T", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "19c8dce986e84607f33ea2d8618acd5b42f019667ef62a8b62f4032d29b56dd4", - "key_image_pub_key": "7d2b8634248d5f78031ce280e6261afc42d6b60dd102cbfa71e5b20667a74be4" - } - ] - }, - { - "address": "dV2na9bu6FgVpsPZ7ygD5xKjtAnsr3tkreUqD2SWj26mB5bAWxiidGmRaFdYNFw89iRjNJi93ZdyrgaMvxrhe3HV1mPsu16p8", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "41efc55faf8cf8c0389ab490ee467fa8641ab2fd3d3fd33b8a14fedea30e6428", - "key_image_pub_key": "d08b86999db25e0fddcf8e2595d7c7cb7741700a9ebf7700690dbe93093f2aff" - } - ] - }, - { - "address": "dV37R1RcDwSWxqJKv24YzYadb52dJjtmtFpDQqiAHatDVutb6Pb2rF2S4P5TWdjwL27RAdB4NNZja5uHtiRqrnwD2F12Wv6Yu", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "014796b3d8b4fb9efde824f56e3a86a0f60d198db3a4fd42063aa7c367223657", - "key_image_pub_key": "04265b6210858b157c12cd94c65bf5535c231a705bb09a7ffc3d54cc6cb35b6b" - } - ] - }, - { - "address": "dV2QmjPRZjDQp7D7VUg8Rk5HCsVN8b1mTV8fvZgE7VJnfaeZMsHKLkbaiWqUP7dCUaKBKi1maiNJCQf7e8LCJXv31ggQFUhDA", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "e0b2136a6c1da5274e02f621d8f498fec76b2a6131213127937ce05113dc8188", - "key_image_pub_key": "dad4d643f7ff7f7a4c594b30ab84358547e5d680abf4b04a1e8821e1a3c23b80" - } - ] - }, - { - "address": "dV2QQnYxLXfcWGftstaxmK5MaYAnjkpQdES5shMXTwy7AdoDCgNcCuB3fS5nBsBSX4VWsoHY5ytVniwZnmCfbSN126TQUzqDS", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "e7f5d416630c61aa983e262e7d86a77b28eb4934909dbad9de60ece8680ca978", - "key_image_pub_key": "cc4056696acb2f9f470af2e54ef460eefcf5e3811ee96c9155d59235df36ad91" - } - ] - }, - { - "address": "dV2sZf5aYsUQqMhggvLRgV2venWRXCkJVAoavRU3iGi5hxhVrL8e1eVSEHqEutYN1HWQsGKwEP7jFchhSBEDbRJb2WT7wRmop", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "a2a0294c8ddd1b4304096bfd9966546012d9e6579310ed3c1d021d24704d1de8", - "key_image_pub_key": "4a0fae3ba8bc29a86121af3ed06f30bbac8fcc01b75a84f7910b8df4be9a6a48" - } - ] - }, - { - "address": "dV3jSDSedYnFnyPJckdnQR71tAhT932L4ZxPQjb7nrjUUn9XuvEdaQ34D2gxYiDy2L7BpvCMagxtu5T1saBEpaD521YGDQ61s", - "amount": 8000000000, - "locked_contributions": [ - { - "amount": 8000000000, - "key_image": "04d668ac23124639b8d9f607f9472755bc2dd88ed16344a5e3ffd17a0dea10b0", - "key_image_pub_key": "14d27c9b0eaf8370b78c03c69339186611b9f4580cdc6f679a2bab35d3253f52" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 60, - "funded": true, - "last_reward_block_height": 379, - "last_reward_transaction_index": 8, - "last_uptime_proof": 1709178256, - "lokinet_reachable": true, - "lokinet_version": [ - 9, - 9, - 9 - ], - "operator_address": "dV2GCC9fgJjYY9XE6wKWJ9CBat2T3wsVuPVZagMFVxQMKZGABd1CTQo1UHKHYodCGxRakm7QLZBm4LffH9XATLyD1xiMXBBpz", - "operator_fee": 100000, - "portions_for_operator": 1844674407370955161, - "pubkey_ed25519": "dc6f1a7f14b4de1419cb3e668fb2e08c748a49c8266eb4b12857e001c36d5b47", - "pubkey_x25519": "74a4ff0513c9e574f14cd54cccc6aff93ea680e4efdb2857cc2469be196c9820", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1163, - "quorumnet_tests": [ - 1, - 0 - ], - "registration_height": 371, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "dc6f1a7f14b4de1419cb3e668fb2e08c748a49c8266eb4b12857e001c36d5b47", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 379, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 1, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "7711ae45305021a4c5738d6096c4e6c5c385b531cc0702345bcc586c30e1c41d", - "key_image_pub_key": "44eb2716f7e6c3561b1055100765196064b00fd022a46af5d14ae3f5c751e233" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 64, - "funded": true, - "last_reward_block_height": 374, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178243, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "1df2d6e606a7ff4204bcf885c012d7217d3f37bffddb656204511c9f8473ec55", - "pubkey_x25519": "f5c41daff1e5056bd3de3df3a912c5c6466be67cfcbf130993633aa8010f2831", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1118, - "quorumnet_tests": [ - 3, - 0 - ], - "registration_height": 257, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "1df2d6e606a7ff4204bcf885c012d7217d3f37bffddb656204511c9f8473ec55", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 257, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 3, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "33de4e957e6d34411e41a7235789dc058d8855e0dd057b05fc9520ada54530ef", - "key_image_pub_key": "8c8a84be21e08c679600be30dcdaa80aadf81d885f5a22e5f72dd1d6cbbc51a3" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 64, - "funded": true, - "last_reward_block_height": 373, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178243, - "lokinet_reachable": true, - "lokinet_version": [ - 9, - 9, - 9 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "12fd18478d61bae9c65303d95f2c8e391ee3f31d5eccda6c17aabe33daf83356", - "pubkey_x25519": "7b12a658b4e7d9ca7693c3b0dc57ac9faac859ba66aa6f92857eb26f2c897c06", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1108, - "quorumnet_tests": [ - 4, - 0 - ], - "registration_height": 257, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "12fd18478d61bae9c65303d95f2c8e391ee3f31d5eccda6c17aabe33daf83356", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 257, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 4, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "9212fd8ce14930a81807cc89ef6d57de67f376b3610b6d4b51d683d56e979494", - "key_image_pub_key": "717c44d5584cf886590ecf3697608956f695a5bdde0475bf2c2f2a558a5ba5e5" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 61, - "funded": true, - "last_reward_block_height": 380, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178251, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "c341533a1c43b4ab68b92e4ec0ba5edb291023dc76daf6d4d39b6fef56b7b1e2", - "pubkey_x25519": "0bf7f01d5e115cd1dcb0deed5919c8534607a9358257fc200aa95e0945a98f21", - "public_ip": "127.112.220.45", - "quorumnet_port": 1143, - "quorumnet_tests": [ - 2, - 0 - ], - "registration_height": 329, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "c341533a1c43b4ab68b92e4ec0ba5edb291023dc76daf6d4d39b6fef56b7b1e2", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 329, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 2, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "dc4c0c8b35cf28b9437ecf706b253b902ceeb395ffdaf7fc1cefbd2286dd8e83", - "key_image_pub_key": "0bb3522b2fe0f551d3668e8c6d1e6160e84e358cfa9bac38156941aa11305436" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 64, - "funded": true, - "last_reward_block_height": 376, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178243, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "f6df367bb4592290981fc7e93d53da0242597cd20ee6c2bd2a3eb4e327427f35", - "pubkey_x25519": "8ca5898f03093bc887b6740d913d9929cf3668ba914a0026677cb8f5ffab8b76", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1123, - "quorumnet_tests": [ - 4, - 0 - ], - "registration_height": 257, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "f6df367bb4592290981fc7e93d53da0242597cd20ee6c2bd2a3eb4e327427f35", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 257, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 4, - 0 - ], - "total_contributed": 100000000000 - }, - { - "active": true, - "contributors": [ - { - "address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "amount": 100000000000, - "locked_contributions": [ - { - "amount": 100000000000, - "key_image": "4df6fa737d7b7d21064dfa0d41790fca842295c062a9212b4d0cee66aa1aeae1", - "key_image_pub_key": "377af1a7767c9b1ae98d19b5ade440352bfa1de64d3e48009252042c82b48777" - } - ] - } - ], - "decommission_count": 0, - "earned_downtime_blocks": 64, - "funded": true, - "last_reward_block_height": 377, - "last_reward_transaction_index": 4294967295, - "last_uptime_proof": 1709178243, - "lokinet_reachable": true, - "lokinet_version": [ - 0, - 0, - 0 - ], - "operator_address": "dV2UEB1wFjWHvMVdVzwLfFgPUiUtZNLxGJFafkB5aKkTVaRZWBEyLpJUSjTYSL8ye3V9GY7zhig1g7ZN9twtD74E38uckWhkX", - "operator_fee": 1000000, - "portions_for_operator": 18446744073709551612, - "pubkey_ed25519": "a7560ecdcd80c479d5814f5d31704ff71a51a47f2e8060f2977eb2f17135a680", - "pubkey_x25519": "056e59a7c3beae01fd8e2f7b4fc65be6411636d5c098cffe5b91e0c4fcb2fb5e", - "public_ip": "127.112.220.45", - "pulse_votes": { - "missed": [], - "voted": [ - [ - 380, - 0 - ] - ] - }, - "quorumnet_port": 1113, - "quorumnet_tests": [ - 3, - 0 - ], - "registration_height": 257, - "registration_hf_version": 19, - "requested_unlock_height": 0, - "service_node_pubkey": "a7560ecdcd80c479d5814f5d31704ff71a51a47f2e8060f2977eb2f17135a680", - "service_node_version": [ - 11, - 0, - 0 - ], - "staking_requirement": 100000000000, - "state_height": 257, - "storage_lmq_port": 0, - "storage_port": 0, - "storage_server_reachable": true, - "storage_server_version": [ - 0, - 0, - 0 - ], - "swarm": "0", - "swarm_id": 0, - "timesync_tests": [ - 3, - 0 - ], - "total_contributed": 100000000000 - } - ], - "snode_revision": 0, - "status": "OK", - "target_height": 0 - } -} diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py index 58677c84b9..988b1794dc 100644 --- a/utils/local-devnet/ethereum.py +++ b/utils/local-devnet/ethereum.py @@ -63,6 +63,35 @@ def addBLSPublicKey(self, args): self.web3.eth.wait_for_transaction_receipt(tx_hash) return tx_hash + def initiateRemoveBLSPublicKey(self, service_node_id): + # function initiateRemoveBLSPublicKey(uint64 serviceNodeID) public { + + unsent_tx = self.contract.functions.initiateRemoveBLSPublicKey(service_node_id + ).build_transaction({ + "from": self.acc.address, + 'gas': 2000000, + 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + + def getServiceNodeID(self, bls_public_key): + service_node_end_id = 2**64-1 + service_node_end = self.contract.functions.serviceNodes(service_node_end_id).call() + service_node_id = service_node_end[0] + while True: + service_node = self.contract.functions.serviceNodes(service_node_id).call() + if hex(service_node[3][0])[2:].zfill(64) + hex(service_node[3][1])[2:].zfill(64) == bls_public_key: + return service_node_id + service_node_id = service_node[0] + if service_node_id == service_node_end_id: + raise Exception("Iterated through smart contract list and could not find bls key") + + + + + contract_abi = json.loads(""" [ { "inputs": [ diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 94e2011f63..fd714c7c19 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -217,6 +217,9 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) vprint("Submitted registration on ethereum for service node with pubkey: {}".format(self.ethsns[0].sn_key())) result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) + time.sleep(152) + result = self.servicenodecontract.initiateRemoveBLSPublicKey(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"])) + vprint("Submitted transaction to deregister service node id: {}".format(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"]))) vprint("Done.") vprint("Local Devnet SN network setup complete!") From 5688c689d10ba5c13d93f731f49faa3d8d8deaa3 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Fri, 15 Mar 2024 14:17:16 +1100 Subject: [PATCH 46/97] Claim rewards functioning --- src/blockchain_db/sqlite/db_sqlite.cpp | 36 +++--- src/blockchain_db/sqlite/db_sqlite.h | 2 +- src/bls/bls_aggregator.cpp | 122 ++++++++------------- src/bls/bls_aggregator.h | 16 +-- src/bls/bls_signer.cpp | 27 +++-- src/bls/bls_signer.h | 9 ++ src/cryptonote_core/cryptonote_core.cpp | 70 +++++------- src/cryptonote_core/cryptonote_core.h | 3 +- src/daemon/rpc_command_executor.cpp | 2 +- src/rpc/core_rpc_server.cpp | 22 ++-- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 24 ++-- utils/local-devnet/daemons.py | 16 +-- utils/local-devnet/ethereum.py | 55 ++++++++++ utils/local-devnet/service_node_network.py | 16 ++- 17 files changed, 238 insertions(+), 189 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index a4c6b66ace..0fef927d87 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -311,7 +311,10 @@ void BlockchainSQLite::blockchain_detached(uint64_t new_height) { // Must be called with the address_str_cache_mutex held! std::string BlockchainSQLite::get_address_str(const cryptonote::batch_sn_payment& addr) { if (addr.eth_address.has_value()) { - return tools::type_to_hex(addr.eth_address.value()); + auto eth_address = tools::type_to_hex(addr.eth_address.value()); + std::transform(eth_address.begin(), eth_address.end(), eth_address.begin(), + [](unsigned char c){ return std::tolower(c); }); + return "0x" + eth_address; } auto& address_str = address_str_cache[addr.address_info.address]; if (address_str.empty()) @@ -408,24 +411,29 @@ std::vector BlockchainSQLite::get_sn_payments(uint return payments; } -uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) { +std::pair BlockchainSQLite::get_accrued_earnings(const std::string& address) { log::trace(logcat, "BlockchainDB_SQLITE::{}", __func__); + //auto earnings = prepared_maybe_get(R"( + //WITH RelevantOxenAddress AS ( + //SELECT oxen_address + //FROM eth_mapping + //WHERE eth_address = ? + //HAVING height = MAX(height) + //) + //SELECT amount + //FROM batched_payments_accrued + //WHERE address = ? + //UNION + //SELECT bpa.amount + //FROM batched_payments_accrued bpa + //JOIN RelevantOxenAddress roa ON bpa.address = roa.oxen_address; + //)", address, address); auto earnings = prepared_maybe_get(R"( - WITH RelevantOxenAddress AS ( - SELECT oxen_address - FROM eth_mapping - WHERE eth_address = ? - HAVING height = MAX(height) - ) SELECT amount FROM batched_payments_accrued WHERE address = ? - UNION - SELECT bpa.amount - FROM batched_payments_accrued bpa - JOIN RelevantOxenAddress roa ON bpa.address = roa.oxen_address; - )", address, address); - return static_cast(earnings.value_or(0) / 1000); + )", address); + return std::make_pair(height, static_cast(earnings.value_or(0) / 1000)); } std::pair, std::vector> BlockchainSQLite::get_all_accrued_earnings() { diff --git a/src/blockchain_db/sqlite/db_sqlite.h b/src/blockchain_db/sqlite/db_sqlite.h index 5b75273c3c..283a0ef3df 100644 --- a/src/blockchain_db/sqlite/db_sqlite.h +++ b/src/blockchain_db/sqlite/db_sqlite.h @@ -81,7 +81,7 @@ class BlockchainSQLite : public db::Database { public: // get_accrued_earnings -> queries the database for the amount that has been accrued to // `service_node_address` will return the atomic value in oxen that the service node is owed. - uint64_t get_accrued_earnings(const std::string& address); + std::pair get_accrued_earnings(const std::string& address); // get_all_accrued_earnings -> queries the database for all the amount that has been accrued to // service nodes will return 2 vectors corresponding to the addresses and the atomic value in // oxen that the service nodes are owed. diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 10823923ea..9a73067f8b 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -11,18 +11,8 @@ BLSAggregator::BLSAggregator(service_nodes::service_node_list& _snl, std::shared BLSAggregator::~BLSAggregator() { } -std::string BLSAggregator::aggregatePubkeyHex() { - //bls::PublicKey aggregate_pubkey; - //aggregate_pubkey.clear(); - //for(auto& node : nodes) { - //aggregate_pubkey.add(node.getPublicKey()); - //} - //return bls_utils::PublicKeyToHex(aggregate_pubkey); - return ""; -} - -std::vector BLSAggregator::getPubkeys() { - std::vector pubkeys; +std::vector> BLSAggregator::getPubkeys() { + std::vector> pubkeys; std::mutex pubkeys_mutex; processNodes( @@ -30,7 +20,7 @@ std::vector BLSAggregator::getPubkeys() { [this, &pubkeys, &pubkeys_mutex](bool success, std::vector data) { if (success) { std::lock_guard lock(pubkeys_mutex); - pubkeys.emplace_back(data[0]); + pubkeys.emplace_back(data[0], data[1]); } }, [](){} @@ -39,84 +29,62 @@ std::vector BLSAggregator::getPubkeys() { return pubkeys; } -aggregateResponse BLSAggregator::aggregateSignatures(const std::string& message) { - const std::array hash = BLSSigner::hash(message); - bls::Signature aggSig; - aggSig.clear(); - std::vector signers; - std::mutex signers_mutex; - int64_t signers_index = 0; - - processNodes( - "bls.signature_request", - [this, &aggSig, &signers, &signers_mutex, &signers_index](bool success, std::vector data) { - if (success) { - bls::Signature external_signature; - external_signature.setStr(data[0]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); - } - signers_index++; - }, - [this, &aggSig, &hash] { - const auto my_signature = bls_signer->signHash(hash); - aggSig.add(my_signature); - }, - message - ); - - const auto non_signers = findNonSigners(signers); - const auto sig_str = bls_utils::SignatureToHex(aggSig); - return aggregateResponse{non_signers, sig_str}; -} - blsRegistrationResponse BLSAggregator::registration() const { return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(), "","",""}; } aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& address) { - //TODO sean hash something different - const std::array hash = BLSSigner::hash(address); bls::Signature aggSig; aggSig.clear(); - std::vector signers; + std::vector signers; std::mutex signers_mutex; - int64_t signers_index = 0; + uint64_t amount = 0; + uint64_t height = 0; + std::string signed_message = ""; + bool initial_data_set = false; + std::string lower_eth_address = address; + if (lower_eth_address.substr(0, 2) != "0x") { + lower_eth_address = "0x" + lower_eth_address; + } + std::transform(lower_eth_address.begin(), lower_eth_address.end(), lower_eth_address.begin(), + [](unsigned char c){ return std::tolower(c); }); processNodes( "bls.get_reward_balance", - [this, &aggSig, &signers, &signers_mutex, &signers_index](bool success, std::vector data) { + [this, &aggSig, &signers, &signers_mutex, &lower_eth_address, &amount, &height, &signed_message, &initial_data_set](bool success, std::vector data) { if (success) { - bls::Signature external_signature; - external_signature.setStr(data[1]); - std::lock_guard lock(signers_mutex); - aggSig.add(external_signature); - signers.push_back(signers_index); + if (data[0] == "200") { + + // Data contains -> status, address, amount, height, bls_pubkey, signed message, signature + uint64_t current_amount = std::stoull(data[2]); + uint64_t current_height = std::stoull(data[3]); + if (!initial_data_set) { + amount = current_amount; + height = current_height; + signed_message = data[5]; + initial_data_set = true; + } + if (data[1] != lower_eth_address || current_amount != amount || current_height != height || data[5] != signed_message) { + // Log if the current data doesn't match the first set + oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected address: {}, amount: {}, height: {} signed message: {}. Received address: {} amount: {}, height: {} signed_message: {}.", data[4], lower_eth_address, amount, height, signed_message, data[1], current_amount, current_height, data[5]); + } else { + bls::Signature external_signature; + external_signature.setStr(data[6]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(data[4]); + + } + } else { + oxen::log::warning(logcat, "Error message received when getting reward balance {} : {}", data[0], data[1]); + } + } else { + oxen::log::warning(logcat, "OMQ not successful when getting reward balance"); } - signers_index++; }, - [] { - // TODO sean does this need to happen? if we are making the request to all nodes then ive made a request to myself already? - //const auto my_signature = bls_signer->signHash(hash); - //aggSig.add(my_signature); - } + []{}, // No post processing for this call + lower_eth_address ); - - const auto non_signers = findNonSigners(signers); const auto sig_str = bls_utils::SignatureToHex(aggSig); - // TODO sean fill this properly - return aggregateWithdrawalResponse{address, 0, 0, "", non_signers, sig_str}; -} - - - -std::vector BLSAggregator::findNonSigners(const std::vector& indices) { - std::vector nonSignerIndices = {}; - for (int64_t i = 0; i < static_cast(service_node_list.get_service_node_count()); ++i) { - if (std::find(indices.begin(), indices.end(), i) == indices.end()) { - nonSignerIndices.push_back(i); - } - } - return nonSignerIndices; + return aggregateWithdrawalResponse{lower_eth_address, amount, height, signed_message, signers, sig_str}; } diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 043b7918b1..c2603d3a7d 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -26,17 +26,17 @@ #include #include "common/string_util.h" -struct aggregateResponse { - std::vector non_signers; - std::string signature; -}; +//struct aggregateResponse { + //std::vector non_signers; + //std::string signature; +//}; struct aggregateWithdrawalResponse { std::string address; uint64_t amount; uint64_t height; std::string signed_message; - std::vector non_signers; + std::vector signers_bls_pubkeys; std::string signature; }; @@ -58,14 +58,10 @@ class BLSAggregator { BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer); ~BLSAggregator(); - std::string aggregatePubkeyHex(); - std::vector getPubkeys(); - aggregateResponse aggregateSignatures(const std::string& message); + std::vector> getPubkeys(); aggregateWithdrawalResponse aggregateRewards(const std::string& address); blsRegistrationResponse registration() const; - std::vector findNonSigners(const std::vector& indices); - private: // Goes out to the nodes on the network and makes oxenmq requests to all of them, when getting the reply // `callback` will be called to process their reply and after everyone has been received it will then call diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 4bd060882b..4bf3cefad7 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -7,20 +7,6 @@ static auto logcat = oxen::log::Cat("bls_signer"); -const std::string proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"; -const std::string rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"; -const std::string removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"; -const std::string liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"; - -std::string buildTag(const std::string& baseTag, uint32_t chainID, const std::string& contractAddress) { - // Check if contractAddress starts with "0x" prefix - std::string contractAddressOutput = contractAddress; - if (contractAddressOutput.substr(0, 2) == "0x") - contractAddressOutput = contractAddressOutput.substr(2); // remove "0x" - std::string concatenatedTag = "0x" + utils::toHexString(baseTag) + utils::padTo32Bytes(utils::decimalToHex(chainID), utils::PaddingDirection::LEFT) + contractAddressOutput; - return utils::toHexString(utils::hash(concatenatedTag)); -} - BLSSigner::BLSSigner(const cryptonote::network_type nettype) { initCurve(); // This init function generates a secret key calling blsSecretKeySetByCSPRNG @@ -56,6 +42,19 @@ void BLSSigner::initCurve() { blsSetGeneratorOfPublicKey(&publicKey); } +std::string BLSSigner::buildTag(const std::string_view& baseTag, uint32_t chainID, const std::string& contractAddress) { + // Check if contractAddress starts with "0x" prefix + std::string contractAddressOutput = contractAddress; + if (contractAddressOutput.substr(0, 2) == "0x") + contractAddressOutput = contractAddressOutput.substr(2); // remove "0x" + std::string concatenatedTag = "0x" + utils::toHexString(baseTag) + utils::padTo32Bytes(utils::decimalToHex(chainID), utils::PaddingDirection::LEFT) + contractAddressOutput; + return utils::toHexString(utils::hash(concatenatedTag)); +} + +std::string BLSSigner::buildTag(const std::string_view& baseTag) { + return buildTag(baseTag, chainID, contractAddress); +} + bls::Signature BLSSigner::signHash(const std::array& hash) { bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 4eec7980ff..3baa964d42 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -37,11 +37,20 @@ class BLSSigner { std::string getPublicKeyHex(); bls::PublicKey getPublicKey(); + static std::string buildTag(const std::string_view& baseTag, uint32_t chainID, const std::string& contractAddress); + std::string buildTag(const std::string_view& baseTag); + static std::array hash(std::string in); static std::array hashModulus(std::string message); + static constexpr std::string_view proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"sv; + static constexpr std::string_view rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"sv; + static constexpr std::string_view removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"sv; + static constexpr std::string_view liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"sv; + private: // END }; + diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index b0a0e94b1e..e41c427a17 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -75,6 +75,8 @@ extern "C" { #include "uptime_proof.h" #include "version.h" +#include "ethyl/utils.hpp" + DISABLE_VS_WARNINGS(4355) #define BAD_SEMANTICS_TXES_MAX_SIZE 100 @@ -1093,17 +1095,6 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { m_quorumnet_state = quorumnet_new(*this); m_omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) - .add_request_command("signature_request", [&](oxenmq::Message& m) { - oxen::log::debug(logcat, "Received omq signature request"); - if (m.data.size() != 1) - m.send_reply( - "400", - "Bad request: BLS commands must have only one data part " - "(received " + - std::to_string(m.data.size()) + ")"); - const auto h = m_bls_signer->hash(std::string(m.data[0])); - m.send_reply(m_bls_signer->signHash(h).getStr()); - }) .add_request_command("get_reward_balance", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq signature request"); if (m.data.size() != 1) @@ -1112,11 +1103,20 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { "Bad request: BLS rewards command have one data part containing the address" "(received " + std::to_string(m.data.size()) + ")"); - uint64_t amount = get_blockchain_storage().sqlite_db()->get_accrued_earnings(std::string(m.data[0])); - //TODO sean this should concat a bunch of things instead of amount - std::string concatenated_information_for_signing = std::to_string(amount); - const auto h = m_bls_signer->hash(concatenated_information_for_signing); - m.send_reply(concatenated_information_for_signing, m_bls_signer->signHash(h).getStr()); + std::string eth_address = std::string(m.data[0]); + if (eth_address.substr(0, 2) != "0x") { + eth_address = "0x" + eth_address; + } + std::transform(eth_address.begin(), eth_address.end(), eth_address.begin(), + [](unsigned char c){ return std::tolower(c); }); + auto [batchdb_height, amount] = get_blockchain_storage().sqlite_db()->get_accrued_earnings(eth_address); + if (amount == 0) + m.send_reply("400", "Address has a zero balance in the database"); + //bytes memory encodedMessage = abi.encodePacked(rewardTag, recipientAddress, recipientAmount); + std::string encoded_message = "0x" + m_bls_signer->buildTag(m_bls_signer->rewardTag) + utils::padToNBytes(eth_address.substr(2), 20, utils::PaddingDirection::LEFT) + utils::padTo32Bytes(utils::decimalToHex(amount), utils::PaddingDirection::LEFT); + const auto h = m_bls_signer->hash(encoded_message); + // Returns status, address, amount, height, bls_pubkey, signed message, signature + m.send_reply("200", m.data[0], std::to_string(amount), std::to_string(batchdb_height), m_bls_signer->getPublicKeyHex(), encoded_message, m_bls_signer->signHash(h).getStr()); }) .add_request_command("pubkey_request", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq bls pubkey request"); @@ -1126,8 +1126,7 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { "Bad request: BLS pubkey request must have no data parts" "(received " + std::to_string(m.data.size()) + ")"); - const auto h = m_bls_signer->hash(std::string(m.data[0])); - m.send_reply(m_bls_signer->getPublicKeyHex()); + m.send_reply(tools::type_to_hex(m_service_keys.pub), m_bls_signer->getPublicKeyHex()); }); } @@ -2734,33 +2733,22 @@ core::get_service_node_blacklisted_key_images() const { return m_service_node_list.get_blacklisted_key_images(); } //----------------------------------------------------------------------------------------------- -//TODO sean this whole function needs to disappear before release, otherwise people can sign arbitrary messages -aggregateResponse core::bls_request() const { - //TODO sean remove this, just generating random string - const auto length = 20; - srand(static_cast(time(nullptr))); // Seed the random number generator - const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - const int64_t max_index = sizeof(charset) - 1; - - std::string randomString; - - for (size_t i = 0; i < length; ++i) { - randomString += charset[static_cast(rand() % max_index)]; - } - - const auto resp = m_bls_aggregator->aggregateSignatures(randomString); - return resp; -} -//----------------------------------------------------------------------------------------------- aggregateWithdrawalResponse core::aggregate_withdrawal_request(const std::string& ethereum_address) { - uint64_t rewards = m_blockchain_storage.sqlite_db()->get_accrued_earnings(ethereum_address); - //TODO sean something about combining the rewards and address, needs to be standard message format - const auto resp = m_bls_aggregator->aggregateRewards(std::to_string(rewards)); + const auto resp = m_bls_aggregator->aggregateRewards(ethereum_address); return resp; } //----------------------------------------------------------------------------------------------- -std::vector core::get_bls_pubkeys() const { - return m_bls_aggregator->getPubkeys(); +std::vector> core::get_bls_pubkeys() const { + std::vector> bls_pubkeys_and_amounts; + const auto pubkeys = m_bls_aggregator->getPubkeys(); + for (const auto& node : pubkeys) { + std::vector service_node_pubkeys; + auto& key = service_node_pubkeys.emplace_back(); + tools::hex_to_type(node.first, key); + const auto infos = m_service_node_list.get_service_node_list_state(service_node_pubkeys); + bls_pubkeys_and_amounts.emplace_back(node.second, infos[0].info->total_contributed); + } + return bls_pubkeys_and_amounts; } //----------------------------------------------------------------------------------------------- blsRegistrationResponse core::bls_registration(const std::string& ethereum_address) const { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 53ecf594b6..d4f9faf8a4 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -951,9 +951,8 @@ class core : public i_miner_handler { const std::vector& get_service_node_blacklisted_key_images() const; - aggregateResponse bls_request() const; aggregateWithdrawalResponse aggregate_withdrawal_request(const std::string& ethereum_address); - std::vector get_bls_pubkeys() const; + std::vector> get_bls_pubkeys() const; blsRegistrationResponse bls_registration(const std::string& ethereum_address) const; /** diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 7b7fcf6873..b55a8ea04d 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2130,7 +2130,7 @@ bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { bool rpc_command_executor::claim_rewards(const std::string& address) { auto maybe_withdrawal_response = try_running( [this, address] { - return invoke(json{{"address", address}}); + return invoke(json{{"address", address}}); }, "Failed to get withdrawal rewards"); if (!maybe_withdrawal_response) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d9f2019721..d7bf4d8cd0 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2540,11 +2540,15 @@ void core_rpc_server::invoke( return; } //------------------------------------------------------------------------------------------------------------------------------ -void core_rpc_server::invoke( BLS_REQUEST& bls_request, rpc_context context) { - const aggregateResponse bls_signature_response = m_core.bls_request(); - bls_request.response["status"] = STATUS_OK; - bls_request.response["signature"] = bls_signature_response.signature; - bls_request.response["non_signers"] = bls_signature_response.non_signers; +void core_rpc_server::invoke(BLS_REWARDS_REQUEST& bls_rewards_request, rpc_context context) { + const aggregateWithdrawalResponse bls_withdrawal_signature_response = m_core.aggregate_withdrawal_request(bls_rewards_request.request.address); + bls_rewards_request.response["status"] = STATUS_OK; + bls_rewards_request.response["address"] = bls_withdrawal_signature_response.address; + bls_rewards_request.response["height"] = bls_withdrawal_signature_response.height; + bls_rewards_request.response["amount"] = bls_withdrawal_signature_response.amount; + bls_rewards_request.response["signed_message"] = bls_withdrawal_signature_response.signed_message; + bls_rewards_request.response["signature"] = bls_withdrawal_signature_response.signature; + bls_rewards_request.response["signers_bls_pubkeys"] = bls_withdrawal_signature_response.signers_bls_pubkeys; return; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2556,14 +2560,14 @@ void core_rpc_server::invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc bls_withdrawal_request.response["amount"] = bls_withdrawal_signature_response.amount; bls_withdrawal_request.response["signed_message"] = bls_withdrawal_signature_response.signed_message; bls_withdrawal_request.response["signature"] = bls_withdrawal_signature_response.signature; - bls_withdrawal_request.response["non_signers"] = bls_withdrawal_signature_response.non_signers; + bls_withdrawal_request.response["signers_bls_pubkeys"] = bls_withdrawal_signature_response.signers_bls_pubkeys; return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context) { - const std::vector bls_pubkeys = m_core.get_bls_pubkeys(); + const std::vector> nodes = m_core.get_bls_pubkeys(); bls_pubkey_request.response["status"] = STATUS_OK; - bls_pubkey_request.response["pubkeys"] = bls_pubkeys; + bls_pubkey_request.response["nodes"] = nodes; return; } //------------------------------------------------------------------------------------------------------------------------------ @@ -3385,7 +3389,7 @@ void core_rpc_server::invoke( for (const auto& address : req.addresses) { uint64_t amount = 0; if (cryptonote::is_valid_address(address, nettype())) { - amount = blockchain.sqlite_db()->get_accrued_earnings(address); + const auto [_, amount] = blockchain.sqlite_db()->get_accrued_earnings(address); at_least_one_succeeded = true; } balances[address] = amount; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8d8f97f014..28a352dd41 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -183,7 +183,7 @@ class core_rpc_server { void invoke( GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES& get_service_node_blacklisted_key_images, rpc_context context); - void invoke(BLS_REQUEST& bls_request, rpc_context context); + void invoke(BLS_REWARDS_REQUEST& bls_rewards_request, rpc_context context); void invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context); void invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context); void invoke(BLS_REGISTRATION& bls_registration_request, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 05c3dbee7a..2ae43823d1 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -488,6 +488,10 @@ void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in) { required{cmd.request.staking_requirement}); } +void parse_request(BLS_REWARDS_REQUEST& cmd, rpc_input in) { + get_values( in, "address", required{cmd.request.address}); +} + void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in) { get_values( in, "address", required{cmd.request.address}); } diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e848c8eb36..034f162e36 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -54,6 +54,7 @@ void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); +void parse_request(BLS_REWARDS_REQUEST& cmd, rpc_input in); void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in); void parse_request(BLS_REGISTRATION& cmd, rpc_input in); } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 2f2ccc951e..d0fe9389bd 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2284,21 +2284,29 @@ struct GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_service_node_blacklisted_key_images"); } }; - -/// RPC: bls request +/// RPC: bls rewards request /// -/// Sends a request out for all nodes to sign a BLS signature +/// Sends a request out for all nodes to sign a BLS signature of the rewards amount that an address is allowed to withdraw /// -/// Inputs: None +/// Inputs: +/// +/// - `address` -- this address will be looked up in the batching database at the latest height to see how much they can withdraw /// /// Outputs: /// /// - `status` -- generic RPC error code; "OK" means the request was successful. -/// - `merkle_root` -- The Root that has been signed by the network +/// - `address` -- The requested address +/// - `amount` -- The amount that the address can claim +/// - `height` -- The oxen blockchain height that the rewards have been calculated at +/// - `signed_message` -- The Root that has been signed by the network /// - `signature` -- BLS signature of the merkle root /// - `non_signers` -- array of indices of the nodes that did not sign -struct BLS_REQUEST : PUBLIC, NO_ARGS { - static constexpr auto names() { return NAMES("bls_request"); } +struct BLS_REWARDS_REQUEST : PUBLIC { + static constexpr auto names() { return NAMES("bls_rewards_request"); } + + struct request_parameters { + std::string address; + } request; }; /// RPC: bls request @@ -2756,7 +2764,7 @@ using core_rpc_types = tools::type_list< GET_SERVICE_KEYS, GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, - BLS_REQUEST, + BLS_REWARDS_REQUEST, BLS_WITHDRAWAL_REQUEST, BLS_PUBKEYS, BLS_REGISTRATION, diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index c1cd8bd50a..87d08f244e 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -248,14 +248,14 @@ def sn_status(self): return self.json_rpc("get_service_node_status").json()["result"] def get_ethereum_registration_args(self, address): - registration_args = self.json_rpc("bls_registration_request", {"address": address}).json()["result"] - return { - "address" : registration_args["address"], - "bls_pubkey" : registration_args["bls_pubkey"], - "proof_of_possession" : registration_args["proof_of_possession"], - "service_node_pubkey" : registration_args["service_node_pubkey"], - "service_node_signature" : registration_args["service_node_signature"] - } + return self.json_rpc("bls_registration_request", {"address": address}).json()["result"] + + def get_bls_pubkeys(self): + return self.json_rpc("bls_pubkey_request", {}).json()["result"]["nodes"] + + def get_bls_rewards(self, address): + return self.json_rpc("bls_rewards_request", {"address": address}).json() + # return self.json_rpc("bls_rewards_request", {"address": address}).json()["result"] diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py index 988b1794dc..c9ae8d3659 100644 --- a/utils/local-devnet/ethereum.py +++ b/utils/local-devnet/ethereum.py @@ -43,6 +43,9 @@ def getContractDeployedInLatestBlock(self): def hardhatAccountAddress(self): return self.acc.address + def erc20balance(self, address): + return self.erc20_contract.functions.balanceOf(Web3.to_checksum_address(address)).call() + def addBLSPublicKey(self, args): # function addBLSPublicKey(uint256 pkX, uint256 pkY, uint256 sigs0, uint256 sigs1, uint256 sigs2, uint256 sigs3, uint256 serviceNodePubkey, uint256 serviceNodeSignature) public { unsent_tx = self.contract.functions.addBLSPublicKey(int(args["bls_pubkey"][:64], 16), @@ -76,6 +79,58 @@ def initiateRemoveBLSPublicKey(self, service_node_id): self.web3.eth.wait_for_transaction_receipt(tx_hash) return tx_hash + def seedPublicKeyList(self, args): + pkX = [] + pkY = [] + amounts = [] + for item in args: + pkX.append(int(item[0][:64], 16)) # First 32 bytes as pkX + pkY.append(int(item[0][64:], 16)) # Last 32 bytes as pkY + amounts.append(item[1]) # Corresponding amount + + unsent_tx = self.contract.functions.seedPublicKeyList(pkX, pkY, amounts).build_transaction({ + "from": self.acc.address, + 'gas': 3000000, # Adjust gas limit as necessary + 'nonce': self.web3.eth.get_transaction_count(self.acc.address) + }) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + + def numberServiceNodes(self): + return self.contract.functions.serviceNodesLength().call() + + def updateRewardsBalance(self, recipientAddress, recipientAmount, blsSig, ids): + unsent_tx = self.contract.functions.updateRewardsBalance( + Web3.to_checksum_address(recipientAddress), + recipientAmount, + int(blsSig[:64], 16), + int(blsSig[64:128], 16), + int(blsSig[128:192], 16), + int(blsSig[192:256], 16), + ids + ).build_transaction({ + "from": self.acc.address, + 'gas': 3000000, # Adjust gas limit as necessary + 'nonce': self.web3.eth.get_transaction_count(self.acc.address) + }) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + + def claimRewards(self): + unsent_tx = self.contract.functions.claimRewards().build_transaction({ + "from": self.acc.address, + 'gas': 2000000, # Adjust gas limit as necessary + 'nonce': self.web3.eth.get_transaction_count(self.acc.address) + }) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + def getServiceNodeID(self, bls_public_key): service_node_end_id = 2**64-1 service_node_end = self.contract.functions.serviceNodes(service_node_end_id).call() diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index fd714c7c19..b04769d292 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -214,12 +214,22 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): for sn in self.sns: sn.send_uptime_proof() + bls_pubkeys = self.ethsns[0].get_bls_pubkeys() + self.servicenodecontract.seedPublicKeyList(bls_pubkeys) + vprint("Seeded public key list: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) vprint("Submitted registration on ethereum for service node with pubkey: {}".format(self.ethsns[0].sn_key())) result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) - time.sleep(152) - result = self.servicenodecontract.initiateRemoveBLSPublicKey(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"])) - vprint("Submitted transaction to deregister service node id: {}".format(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"]))) + time.sleep(155) + rewards = self.ethsns[0].get_bls_rewards(self.servicenodecontract.hardhatAccountAddress()) + vprint(rewards) + result = self.servicenodecontract.updateRewardsBalance(rewards["result"]["address"], rewards["result"]["amount"], rewards["result"]["signature"], []) + vprint("ERC20 balance: {}".format(self.servicenodecontract.erc20balance(rewards["result"]["address"]))) + result = self.servicenodecontract.claimRewards() + + vprint("ERC20 balance: {}".format(self.servicenodecontract.erc20balance(rewards["result"]["address"]))) + # result = self.servicenodecontract.initiateRemoveBLSPublicKey(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"])) + # vprint("Submitted transaction to deregister service node id: {}".format(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"]))) vprint("Done.") vprint("Local Devnet SN network setup complete!") From 2769dbdeae5c80832e5a82ca81e2664f478c5e4c Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 20 Mar 2024 09:52:53 +1100 Subject: [PATCH 47/97] bls rpc calls for exiting and liquidation --- src/bls/bls_aggregator.cpp | 88 + src/bls/bls_aggregator.h | 12 +- src/cryptonote_core/cryptonote_core.cpp | 46 +- src/cryptonote_core/cryptonote_core.h | 2 + src/rpc/core_rpc_server.cpp | 18 +- src/rpc/core_rpc_server.h | 3 +- src/rpc/core_rpc_server_command_parser.cpp | 8 +- src/rpc/core_rpc_server_command_parser.h | 3 +- src/rpc/core_rpc_server_commands_defs.h | 56 +- utils/local-devnet/daemons.py | 7 +- utils/local-devnet/ethereum.py | 1899 +++++++++++--------- utils/local-devnet/service_node_network.py | 38 +- 12 files changed, 1241 insertions(+), 939 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 9a73067f8b..791d004088 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -88,3 +88,91 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a const auto sig_str = bls_utils::SignatureToHex(aggSig); return aggregateWithdrawalResponse{lower_eth_address, amount, height, signed_message, signers, sig_str}; } + +aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { + bls::Signature aggSig; + aggSig.clear(); + std::vector signers; + std::mutex signers_mutex; + std::string signed_message = ""; + bool initial_data_set = false; + + processNodes( + "bls.get_exit", + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, std::vector data) { + if (success) { + if (data[0] == "200") { + + // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature + if (!initial_data_set) { + signed_message = data[3]; + initial_data_set = true; + } + if (data[1] != bls_key || data[3] != signed_message) { + // Log if the current data doesn't match the first set + oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected bls_key: {}, signed message: {}. Received bls_key: {}, signed_message: {}.", data[2], bls_key, signed_message, data[1], data[3]); + } else { + bls::Signature external_signature; + external_signature.setStr(data[4]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(data[2]); + + } + } else { + oxen::log::warning(logcat, "Error message received when requesting exit {} : {}", data[0], data[1]); + } + } else { + oxen::log::warning(logcat, "OMQ not successful when requesting exit"); + } + }, + []{}, // No post processing for this call + bls_key + ); + const auto sig_str = bls_utils::SignatureToHex(aggSig); + return aggregateExitResponse{bls_key, signed_message, signers, sig_str}; +} + +aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls_key) { + bls::Signature aggSig; + aggSig.clear(); + std::vector signers; + std::mutex signers_mutex; + std::string signed_message = ""; + bool initial_data_set = false; + + processNodes( + "bls.get_liquidation", + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, std::vector data) { + if (success) { + if (data[0] == "200") { + + // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature + if (!initial_data_set) { + signed_message = data[3]; + initial_data_set = true; + } + if (data[1] != bls_key || data[3] != signed_message) { + // Log if the current data doesn't match the first set + oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected bls_key: {}, signed message: {}. Received bls_key: {}, signed_message: {}.", data[2], bls_key, signed_message, data[1], data[3]); + } else { + bls::Signature external_signature; + external_signature.setStr(data[4]); + std::lock_guard lock(signers_mutex); + aggSig.add(external_signature); + signers.push_back(data[2]); + + } + } else { + oxen::log::warning(logcat, "Error message received when requesting liquidation {} : {}", data[0], data[1]); + } + } else { + oxen::log::warning(logcat, "OMQ not successful when requesting liquidation"); + } + }, + []{}, // No post processing for this call + bls_key + ); + const auto sig_str = bls_utils::SignatureToHex(aggSig); + return aggregateExitResponse{bls_key, signed_message, signers, sig_str}; +} diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index c2603d3a7d..0a2c5d5605 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -26,10 +26,12 @@ #include #include "common/string_util.h" -//struct aggregateResponse { - //std::vector non_signers; - //std::string signature; -//}; +struct aggregateExitResponse { + std::string bls_key; + std::string signed_message; + std::vector signers_bls_pubkeys; + std::string signature; +}; struct aggregateWithdrawalResponse { std::string address; @@ -60,6 +62,8 @@ class BLSAggregator { std::vector> getPubkeys(); aggregateWithdrawalResponse aggregateRewards(const std::string& address); + aggregateExitResponse aggregateExit(const std::string& bls_key); + aggregateExitResponse aggregateLiquidation(const std::string& bls_key); blsRegistrationResponse registration() const; private: diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index e41c427a17..5b63aba426 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1096,11 +1096,11 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { m_omq->add_category("bls", oxenmq::Access{oxenmq::AuthLevel::none}) .add_request_command("get_reward_balance", [&](oxenmq::Message& m) { - oxen::log::debug(logcat, "Received omq signature request"); + oxen::log::debug(logcat, "Received omq rewards signature request"); if (m.data.size() != 1) m.send_reply( "400", - "Bad request: BLS rewards command have one data part containing the address" + "Bad request: BLS rewards command should have one data part containing the address" "(received " + std::to_string(m.data.size()) + ")"); std::string eth_address = std::string(m.data[0]); @@ -1118,6 +1118,38 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { // Returns status, address, amount, height, bls_pubkey, signed message, signature m.send_reply("200", m.data[0], std::to_string(amount), std::to_string(batchdb_height), m_bls_signer->getPublicKeyHex(), encoded_message, m_bls_signer->signHash(h).getStr()); }) + .add_request_command("get_exit", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq exit signature request"); + if (m.data.size() != 1) + m.send_reply( + "400", + "Bad request: BLS exit command should have one data part containing the bls_key" + "(received " + + std::to_string(m.data.size()) + ")"); + // TODO sean this should actually check here if the bls key can exit, right not its approving everything + std::string bls_key_requesting_exit(m.data[0]); + //bytes memory encodedMessage = abi.encodePacked(removalTag, blsKey); + std::string encoded_message = "0x" + m_bls_signer->buildTag(m_bls_signer->removalTag) + bls_key_requesting_exit; + const auto h = m_bls_signer->hash(encoded_message); + // Data contains -> status, bls_key, bls_pubkey, signed message, signature + m.send_reply("200", m.data[0], m_bls_signer->getPublicKeyHex(), encoded_message, m_bls_signer->signHash(h).getStr()); + }) + .add_request_command("get_liquidation", [&](oxenmq::Message& m) { + oxen::log::debug(logcat, "Received omq liquidation signature request"); + if (m.data.size() != 1) + m.send_reply( + "400", + "Bad request: BLS liquidation command should have one data part containing the bls_key" + "(received " + + std::to_string(m.data.size()) + ")"); + // TODO sean this should actually check here if the bls key can exit, right not its approving everything + std::string bls_key_requesting_exit(m.data[0]); + //bytes memory encodedMessage = abi.encodePacked(liquidateTag, blsKey); + std::string encoded_message = "0x" + m_bls_signer->buildTag(m_bls_signer->liquidateTag) + utils::padToNBytes(bls_key_requesting_exit, 64, utils::PaddingDirection::LEFT); + const auto h = m_bls_signer->hash(encoded_message); + // Data contains -> status, bls_key, bls_pubkey, signed message, signature + m.send_reply("200", m.data[0], m_bls_signer->getPublicKeyHex(), encoded_message, m_bls_signer->signHash(h).getStr()); + }) .add_request_command("pubkey_request", [&](oxenmq::Message& m) { oxen::log::debug(logcat, "Received omq bls pubkey request"); if (m.data.size() != 0) @@ -2738,6 +2770,16 @@ aggregateWithdrawalResponse core::aggregate_withdrawal_request(const std::string return resp; } //----------------------------------------------------------------------------------------------- +aggregateExitResponse core::aggregate_exit_request(const std::string& bls_key) { + const auto resp = m_bls_aggregator->aggregateExit(bls_key); + return resp; +} +//----------------------------------------------------------------------------------------------- +aggregateExitResponse core::aggregate_liquidation_request(const std::string& bls_key) { + const auto resp = m_bls_aggregator->aggregateLiquidation(bls_key); + return resp; +} +//----------------------------------------------------------------------------------------------- std::vector> core::get_bls_pubkeys() const { std::vector> bls_pubkeys_and_amounts; const auto pubkeys = m_bls_aggregator->getPubkeys(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index d4f9faf8a4..f0850c5faf 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -952,6 +952,8 @@ class core : public i_miner_handler { get_service_node_blacklisted_key_images() const; aggregateWithdrawalResponse aggregate_withdrawal_request(const std::string& ethereum_address); + aggregateExitResponse aggregate_exit_request(const std::string& bls_key); + aggregateExitResponse aggregate_liquidation_request(const std::string& bls_key); std::vector> get_bls_pubkeys() const; blsRegistrationResponse bls_registration(const std::string& ethereum_address) const; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d7bf4d8cd0..fbaed64349 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2552,12 +2552,20 @@ void core_rpc_server::invoke(BLS_REWARDS_REQUEST& bls_rewards_request, rpc_conte return; } //------------------------------------------------------------------------------------------------------------------------------ -void core_rpc_server::invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context) { - const aggregateWithdrawalResponse bls_withdrawal_signature_response = m_core.aggregate_withdrawal_request(bls_withdrawal_request.request.address); +void core_rpc_server::invoke(BLS_EXIT_REQUEST& bls_withdrawal_request, rpc_context context) { + const aggregateExitResponse bls_withdrawal_signature_response = m_core.aggregate_exit_request(bls_withdrawal_request.request.bls_key); bls_withdrawal_request.response["status"] = STATUS_OK; - bls_withdrawal_request.response["address"] = bls_withdrawal_signature_response.address; - bls_withdrawal_request.response["height"] = bls_withdrawal_signature_response.height; - bls_withdrawal_request.response["amount"] = bls_withdrawal_signature_response.amount; + bls_withdrawal_request.response["bls_key"] = bls_withdrawal_signature_response.bls_key; + bls_withdrawal_request.response["signed_message"] = bls_withdrawal_signature_response.signed_message; + bls_withdrawal_request.response["signature"] = bls_withdrawal_signature_response.signature; + bls_withdrawal_request.response["signers_bls_pubkeys"] = bls_withdrawal_signature_response.signers_bls_pubkeys; + return; +} +//------------------------------------------------------------------------------------------------------------------------------ +void core_rpc_server::invoke(BLS_LIQUIDATION_REQUEST& bls_withdrawal_request, rpc_context context) { + const aggregateExitResponse bls_withdrawal_signature_response = m_core.aggregate_liquidation_request(bls_withdrawal_request.request.bls_key); + bls_withdrawal_request.response["status"] = STATUS_OK; + bls_withdrawal_request.response["bls_key"] = bls_withdrawal_signature_response.bls_key; bls_withdrawal_request.response["signed_message"] = bls_withdrawal_signature_response.signed_message; bls_withdrawal_request.response["signature"] = bls_withdrawal_signature_response.signature; bls_withdrawal_request.response["signers_bls_pubkeys"] = bls_withdrawal_signature_response.signers_bls_pubkeys; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 28a352dd41..9231ea94fb 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -184,7 +184,8 @@ class core_rpc_server { GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES& get_service_node_blacklisted_key_images, rpc_context context); void invoke(BLS_REWARDS_REQUEST& bls_rewards_request, rpc_context context); - void invoke(BLS_WITHDRAWAL_REQUEST& bls_withdrawal_request, rpc_context context); + void invoke(BLS_EXIT_REQUEST& bls_withdrawal_request, rpc_context context); + void invoke(BLS_LIQUIDATION_REQUEST& bls_withdrawal_request, rpc_context context); void invoke(BLS_PUBKEYS& bls_pubkey_request, rpc_context context); void invoke(BLS_REGISTRATION& bls_registration_request, rpc_context context); void invoke(RELAY_TX& relay_tx, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 2ae43823d1..e24d193556 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -492,8 +492,12 @@ void parse_request(BLS_REWARDS_REQUEST& cmd, rpc_input in) { get_values( in, "address", required{cmd.request.address}); } -void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in) { - get_values( in, "address", required{cmd.request.address}); +void parse_request(BLS_EXIT_REQUEST& cmd, rpc_input in) { + get_values( in, "bls_key", required{cmd.request.bls_key}); +} + +void parse_request(BLS_LIQUIDATION_REQUEST& cmd, rpc_input in) { + get_values( in, "bls_key", required{cmd.request.bls_key}); } void parse_request(BLS_REGISTRATION& cmd, rpc_input in) { diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 034f162e36..286cc4d94f 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -55,6 +55,7 @@ void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); void parse_request(BLS_REWARDS_REQUEST& cmd, rpc_input in); -void parse_request(BLS_WITHDRAWAL_REQUEST& cmd, rpc_input in); +void parse_request(BLS_EXIT_REQUEST& cmd, rpc_input in); +void parse_request(BLS_LIQUIDATION_REQUEST& cmd, rpc_input in); void parse_request(BLS_REGISTRATION& cmd, rpc_input in); } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d0fe9389bd..d38219cb4b 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2298,9 +2298,9 @@ struct GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS { /// - `address` -- The requested address /// - `amount` -- The amount that the address can claim /// - `height` -- The oxen blockchain height that the rewards have been calculated at -/// - `signed_message` -- The Root that has been signed by the network -/// - `signature` -- BLS signature of the merkle root -/// - `non_signers` -- array of indices of the nodes that did not sign +/// - `signed_message` -- The message that has been signed by the network +/// - `signature` -- BLS signature of the message +/// - `signers_bls_pubkeys` -- array of bls pubkeys of the nodes that signed struct BLS_REWARDS_REQUEST : PUBLIC { static constexpr auto names() { return NAMES("bls_rewards_request"); } @@ -2309,28 +2309,49 @@ struct BLS_REWARDS_REQUEST : PUBLIC { } request; }; -/// RPC: bls request +/// RPC: bls exit request /// -/// Sends a request out for all nodes to sign a BLS signature of the amount that an address is allowed to withdraw +/// Sends a request out for all nodes to sign a BLS signature that the node with the requested bls pubkey can exit /// /// Inputs: /// -/// - `address` -- this address will be looked up in the batching database at the latest height to see how much they can withdraw +/// - `bls_key` -- this bls_key will be searched to see if the node can exit /// /// Outputs: /// /// - `status` -- generic RPC error code; "OK" means the request was successful. -/// - `address` -- The requested address -/// - `amount` -- The amount that the address can claim -/// - `height` -- The oxen blockchain height that the rewards have been calculated at -/// - `signed_message` -- The Root that has been signed by the network -/// - `signature` -- BLS signature of the merkle root -/// - `non_signers` -- array of indices of the nodes that did not sign -struct BLS_WITHDRAWAL_REQUEST : PUBLIC { - static constexpr auto names() { return NAMES("bls_withdrawal_request"); } +/// - `bls_key` -- The bls pubkey of the node requesting to exit +/// - `signed_message` -- The message that has been signed by the network +/// - `signature` -- BLS signature of the message +/// - `signers_bls_pubkeys` -- array of bls pubkeys of the nodes that signed +struct BLS_EXIT_REQUEST : PUBLIC { + static constexpr auto names() { return NAMES("bls_exit_request"); } struct request_parameters { - std::string address; + std::string bls_key; + } request; +}; + +/// RPC: bls liquidation request +/// +/// Sends a request out for all nodes to sign a BLS signature that the node with the requested bls pubkey can be liquidated +/// +/// Inputs: +/// +/// - `bls_key` -- this bls_key will be searched to see if the node can be liquidated +/// +/// Outputs: +/// +/// - `status` -- generic RPC error code; "OK" means the request was successful. +/// - `bls_key` -- The bls pubkey of the node requested to be liquidated +/// - `signed_message` -- The message that has been signed by the network +/// - `signature` -- BLS signature of the message +/// - `signers_bls_pubkeys` -- array of bls pubkeys of the nodes that signed +struct BLS_LIQUIDATION_REQUEST : PUBLIC { + static constexpr auto names() { return NAMES("bls_liquidation_request"); } + + struct request_parameters { + std::string bls_key; } request; }; @@ -2364,6 +2385,8 @@ struct BLS_PUBKEYS: PUBLIC, NO_ARGS { /// - `proof_of_possession` -- A signature over the bls pubkey to prove ownership of the private key /// - `service_node_pubkey` -- The oxen nodes service node pubkey /// - `service_node_signature` -- A signature over the registration parameters +/// +/// TODO sean - This should not be public struct BLS_REGISTRATION: PUBLIC { static constexpr auto names() { return NAMES("bls_registration_request"); } @@ -2765,7 +2788,8 @@ using core_rpc_types = tools::type_list< GET_SERVICE_NODES, GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES, BLS_REWARDS_REQUEST, - BLS_WITHDRAWAL_REQUEST, + BLS_EXIT_REQUEST, + BLS_LIQUIDATION_REQUEST, BLS_PUBKEYS, BLS_REGISTRATION, GET_SERVICE_NODE_REGISTRATION_CMD, diff --git a/utils/local-devnet/daemons.py b/utils/local-devnet/daemons.py index 87d08f244e..5745fc2cad 100644 --- a/utils/local-devnet/daemons.py +++ b/utils/local-devnet/daemons.py @@ -255,7 +255,12 @@ def get_bls_pubkeys(self): def get_bls_rewards(self, address): return self.json_rpc("bls_rewards_request", {"address": address}).json() - # return self.json_rpc("bls_rewards_request", {"address": address}).json()["result"] + + def get_exit_request(self, bls_key): + return self.json_rpc("bls_exit_request", {"bls_key": bls_key}).json() + + def get_liquidation_request(self, bls_key): + return self.json_rpc("bls_liquidation_request", {"bls_key": bls_key}).json() diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py index c9ae8d3659..f2750ea7b5 100644 --- a/utils/local-devnet/ethereum.py +++ b/utils/local-devnet/ethereum.py @@ -79,6 +79,46 @@ def initiateRemoveBLSPublicKey(self, service_node_id): self.web3.eth.wait_for_transaction_receipt(tx_hash) return tx_hash + def removeBLSPublicKeyWithSignature(self, blsKey, blsSig, ids): + unsent_tx = self.contract.functions.removeBLSPublicKeyWithSignature( + self.getServiceNodeID(blsKey), + int(blsKey[:64], 16), + int(blsKey[64:128], 16), + int(blsSig[:64], 16), + int(blsSig[64:128], 16), + int(blsSig[128:192], 16), + int(blsSig[192:256], 16), + ids + ).build_transaction({ + "from": self.acc.address, + 'gas': 3000000, # Adjust gas limit as necessary + 'nonce': self.web3.eth.get_transaction_count(self.acc.address) + }) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + + def liquidateBLSPublicKeyWithSignature(self, blsKey, blsSig, ids): + unsent_tx = self.contract.functions.liquidateBLSPublicKeyWithSignature( + self.getServiceNodeID(blsKey), + int(blsKey[:64], 16), + int(blsKey[64:128], 16), + int(blsSig[:64], 16), + int(blsSig[64:128], 16), + int(blsSig[128:192], 16), + int(blsSig[192:256], 16), + ids + ).build_transaction({ + "from": self.acc.address, + 'gas': 3000000, # Adjust gas limit as necessary + 'nonce': self.web3.eth.get_transaction_count(self.acc.address) + }) + signed_tx = self.web3.eth.account.sign_transaction(unsent_tx, private_key=self.acc.key) + tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.web3.eth.wait_for_transaction_receipt(tx_hash) + return tx_hash + def seedPublicKeyList(self, args): pkX = [] pkY = [] @@ -143,1073 +183,1134 @@ def getServiceNodeID(self, bls_public_key): if service_node_id == service_node_end_id: raise Exception("Iterated through smart contract list and could not find bls key") + def getNonSigners(self, bls_public_keys): + service_node_end_id = 2**64-1 + service_node_end = self.contract.functions.serviceNodes(service_node_end_id).call() + service_node_id = service_node_end[0] + non_signers = [] + while service_node_id != service_node_end_id: + service_node = self.contract.functions.serviceNodes(service_node_id).call() + bls_key = hex(service_node[3][0])[2:].zfill(64) + hex(service_node[3][1])[2:].zfill(64) + if bls_key not in bls_public_keys: + non_signers.append(service_node_id) + service_node_id = service_node[0] + return non_signers; + contract_abi = json.loads(""" -[ { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_foundationPool", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_stakingRequirement", - "type": "uint256" - }, +[ { - "internalType": "uint256", - "name": "_liquidatorRewardRatio", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_foundationPool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_stakingRequirement", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidatorRewardRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_poolShareOfLiquidationRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_recipientRatio", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" }, { - "internalType": "uint256", - "name": "_poolShareOfLiquidationRatio", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" }, { - "internalType": "uint256", - "name": "_recipientRatio", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "AddressEmptyCode", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddressInsufficientBalance", - "type": "error" -}, -{ - "inputs": [], - "name": "ArrayLengthMismatch", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "BLSPubkeyAlreadyExists", - "type": "error" -}, -{ - "inputs": [], - "name": "ContractNotActive", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" }, { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "EarlierLeaveRequestMade", - "type": "error" -}, -{ - "inputs": [], - "name": "FailedInnerCall", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint256", - "name": "numSigners", - "type": "uint256" + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" }, { - "internalType": "uint256", - "name": "requiredSigners", - "type": "uint256" - } - ], - "name": "InsufficientBLSSignatures", - "type": "error" -}, -{ - "inputs": [], - "name": "InvalidBLSProofOfPossession", - "type": "error" -}, -{ - "inputs": [], - "name": "InvalidBLSSignature", - "type": "error" -}, -{ - "inputs": [], - "name": "InvalidParameter", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "BLSPubkeyAlreadyExists", + "type": "error" }, { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "pkX", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pkY", + "type": "uint256" + } + ], + "name": "BLSPubkeyDoesNotMatch", + "type": "error" }, { - "internalType": "uint256", - "name": "currenttime", - "type": "uint256" - } - ], - "name": "LeaveRequestTooEarly", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "expectedRecipient", - "type": "address" + "inputs": [], + "name": "ContractNotActive", + "type": "error" }, { - "internalType": "address", - "name": "providedRecipient", - "type": "address" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "EarlierLeaveRequestMade", + "type": "error" }, { - "internalType": "uint256", - "name": "serviceNodeID", - "type": "uint256" - } - ], - "name": "RecipientAddressDoesNotMatch", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "RecipientAddressNotProvided", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "SafeERC20FailedOperation", - "type": "error" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "ServiceNodeDoesntExist", - "type": "error" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [], + "name": "FailedInnerCall", + "type": "error" }, { - "components": [ + "inputs": [ { "internalType": "uint256", - "name": "X", + "name": "numSigners", "type": "uint256" }, { "internalType": "uint256", - "name": "Y", + "name": "requiredSigners", "type": "uint256" } ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "NewSeededServiceNode", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ + "name": "InsufficientBLSSignatures", + "type": "error" + }, { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [], + "name": "InvalidBLSProofOfPossession", + "type": "error" }, { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" + "inputs": [], + "name": "InvalidBLSSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidParameter", + "type": "error" }, { - "components": [ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, { "internalType": "uint256", - "name": "X", + "name": "timestamp", "type": "uint256" }, { "internalType": "uint256", - "name": "Y", + "name": "currenttime", "type": "uint256" } ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" + "name": "LeaveRequestTooEarly", + "type": "error" }, { - "indexed": false, - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "serviceNodeSignature", - "type": "uint256" - } - ], - "name": "NewServiceNode", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" }, { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" }, { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "expectedRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "providedRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serviceNodeID", + "type": "uint256" + } + ], + "name": "RecipientAddressDoesNotMatch", + "type": "error" }, { - "indexed": false, - "internalType": "uint256", - "name": "previousBalance", - "type": "uint256" - } - ], - "name": "RewardsBalanceUpdated", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipientAddress", - "type": "address" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "RecipientAddressNotProvided", + "type": "error" }, { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "RewardsClaimed", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "ServiceNodeDoesntExist", + "type": "error" }, { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "NewSeededServiceNode", + "type": "event" }, { - "components": [ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, { + "indexed": false, "internalType": "uint256", - "name": "X", + "name": "serviceNodePubkey", "type": "uint256" }, { + "indexed": false, "internalType": "uint256", - "name": "Y", + "name": "serviceNodeSignature", "type": "uint256" } ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeLiquidated", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "name": "NewServiceNode", + "type": "event" }, { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" }, { - "components": [ + "anonymous": false, + "inputs": [ { + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "indexed": false, "internalType": "uint256", - "name": "X", + "name": "amount", "type": "uint256" }, { + "indexed": false, "internalType": "uint256", - "name": "Y", + "name": "previousBalance", "type": "uint256" } ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeRemoval", - "type": "event" -}, -{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "address", - "name": "recipient", - "type": "address" + "name": "RewardsBalanceUpdated", + "type": "event" }, { - "components": [ + "anonymous": false, + "inputs": [ { - "internalType": "uint256", - "name": "X", - "type": "uint256" + "indexed": true, + "internalType": "address", + "name": "recipientAddress", + "type": "address" }, { + "indexed": false, "internalType": "uint256", - "name": "Y", + "name": "amount", "type": "uint256" } ], - "indexed": false, - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" - } - ], - "name": "ServiceNodeRemovalRequest", - "type": "event" -}, -{ - "inputs": [], - "name": "IsActive", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "LIST_END", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "MAX_SERVICE_NODE_REMOVAL_WAIT_TIME", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint256", - "name": "pkX", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "pkY", - "type": "uint256" + "name": "RewardsClaimed", + "type": "event" }, { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeLiquidated", + "type": "event" }, { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "returnedAmount", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemoval", + "type": "event" }, { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + } + ], + "name": "ServiceNodeRemovalRequest", + "type": "event" }, { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" + "inputs": [], + "name": "IsActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" + "inputs": [], + "name": "LIST_END", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "serviceNodeSignature", - "type": "uint256" - } - ], - "name": "addBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "aggregate_pubkey", - "outputs": [ - { - "internalType": "uint256", - "name": "X", - "type": "uint256" + "inputs": [], + "name": "MAX_SERVICE_NODE_REMOVAL_WAIT_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "Y", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "blsNonSignerThreshold", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "recipientAddress", - "type": "address" + "inputs": [ + { + "internalType": "uint256", + "name": "pkX", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pkY", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature", + "type": "uint256" + } + ], + "name": "addBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "name": "buildRecipientMessage", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" -}, -{ - "inputs": [], - "name": "claimRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "designatedToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "foundationPool", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "initiateRemoveBLSPublicKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [], + "name": "aggregate_pubkey", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" + "inputs": [], + "name": "blsNonSignerThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "buildRecipientMessage", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" - }, - { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "liquidateBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "liquidateTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "nextServiceNodeID", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "proofOfPossessionTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "recipients", - "outputs": [ - { - "internalType": "uint256", - "name": "rewards", - "type": "uint256" + "inputs": [], + "name": "designatedToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "claimed", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "removalTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "removeBLSPublicKeyAfterWaitTime", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" + "inputs": [], + "name": "foundationPool", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "initiateRemoveBLSPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "pkX", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pkY", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "liquidateBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" + "inputs": [], + "name": "liquidateTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" + "inputs": [], + "name": "nextServiceNodeID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" - } - ], - "name": "removeBLSPublicKeyWithSignature", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "rewardTag", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint256[]", - "name": "pkX", - "type": "uint256[]" + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256[]", - "name": "pkY", - "type": "uint256[]" + "inputs": [], + "name": "proofOfPossessionTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - } - ], - "name": "seedPublicKeyList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "serviceNodeIDs", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "name": "serviceNodes", - "outputs": [ - { - "internalType": "uint64", - "name": "next", - "type": "uint64" + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "recipients", + "outputs": [ + { + "internalType": "uint256", + "name": "rewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "claimed", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint64", - "name": "previous", - "type": "uint64" + "inputs": [], + "name": "removalTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "address", - "name": "recipient", - "type": "address" + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + } + ], + "name": "removeBLSPublicKeyAfterWaitTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "components": [ + "inputs": [ + { + "internalType": "uint64", + "name": "serviceNodeID", + "type": "uint64" + }, { "internalType": "uint256", - "name": "X", + "name": "pkX", "type": "uint256" }, { "internalType": "uint256", - "name": "Y", + "name": "pkY", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" } ], - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", - "type": "tuple" + "name": "removeBLSPublicKeyWithSignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "leaveRequestTimestamp", - "type": "uint256" + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "deposit", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "serviceNodesLength", - "outputs": [ - { - "internalType": "uint256", - "name": "count", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [], - "name": "start", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "totalNodes", - "outputs": [ + "inputs": [], + "name": "rewardTag", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" -}, -{ - "inputs": [ + "inputs": [ + { + "internalType": "uint256[]", + "name": "pkX", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "pkY", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "seedPublicKeyList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [ + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "serviceNodeIDs", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { - "internalType": "address", - "name": "recipientAddress", - "type": "address" + "inputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "name": "serviceNodes", + "outputs": [ + { + "internalType": "uint64", + "name": "next", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "previous", + "type": "uint64" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "leaveRequestTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deposit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "recipientAmount", - "type": "uint256" + "inputs": [], + "name": "serviceNodesLength", + "outputs": [ + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs0", - "type": "uint256" + "inputs": [], + "name": "start", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs1", - "type": "uint256" + "inputs": [], + "name": "totalNodes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" + "inputs": [ + { + "internalType": "address", + "name": "recipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "recipientAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + }, + { + "internalType": "uint64[]", + "name": "ids", + "type": "uint64[]" + } + ], + "name": "updateRewardsBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "internalType": "uint64[]", - "name": "ids", - "type": "uint64[]" + "inputs": [], + "name": "updateServiceNodesLength", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } - ], - "name": "updateRewardsBalance", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -}, -{ - "inputs": [], - "name": "updateServiceNodesLength", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" -} ] """) erc20_contract_abi = json.loads(""" diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index b04769d292..dbbc06442d 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -220,14 +220,36 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): ethereum_add_bls_args = self.ethsns[0].get_ethereum_registration_args(self.servicenodecontract.hardhatAccountAddress()) vprint("Submitted registration on ethereum for service node with pubkey: {}".format(self.ethsns[0].sn_key())) result = self.servicenodecontract.addBLSPublicKey(ethereum_add_bls_args) - time.sleep(155) - rewards = self.ethsns[0].get_bls_rewards(self.servicenodecontract.hardhatAccountAddress()) - vprint(rewards) - result = self.servicenodecontract.updateRewardsBalance(rewards["result"]["address"], rewards["result"]["amount"], rewards["result"]["signature"], []) - vprint("ERC20 balance: {}".format(self.servicenodecontract.erc20balance(rewards["result"]["address"]))) - result = self.servicenodecontract.claimRewards() - - vprint("ERC20 balance: {}".format(self.servicenodecontract.erc20balance(rewards["result"]["address"]))) + vprint("added node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) + # time.sleep(155) + + # Exit Node + exit = self.ethsns[0].get_exit_request(ethereum_add_bls_args["bls_pubkey"]) + result = self.servicenodecontract.removeBLSPublicKeyWithSignature( + exit["result"]["bls_key"], + exit["result"]["signature"], + self.servicenodecontract.getNonSigners(exit["result"]["signers_bls_pubkeys"])) + vprint(result) + vprint("Submitted transaction to exit service node : {}".format(ethereum_add_bls_args["bls_pubkey"])) + vprint("exited node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) + + # Liquidate Node + # exit = self.ethsns[0].get_liquidation_request(ethereum_add_bls_args["bls_pubkey"]) + # result = self.servicenodecontract.liquidateBLSPublicKeyWithSignature( + # exit["result"]["bls_key"], + # exit["result"]["signature"], + # self.servicenodecontract.getNonSigners(exit["result"]["signers_bls_pubkeys"])) + # vprint(result) + # vprint("Submitted transaction to liquidate service node : {}".format(ethereum_add_bls_args["bls_pubkey"])) + # vprint("liquidated node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) + + # Claim rewards for Address + # rewards = self.ethsns[0].get_bls_rewards(self.servicenodecontract.hardhatAccountAddress()) + # vprint(rewards) + # result = self.servicenodecontract.updateRewardsBalance(rewards["result"]["address"], rewards["result"]["amount"], rewards["result"]["signature"], []) + # result = self.servicenodecontract.claimRewards() + + # Initiate Removeal of BLS Key # result = self.servicenodecontract.initiateRemoveBLSPublicKey(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"])) # vprint("Submitted transaction to deregister service node id: {}".format(self.servicenodecontract.getServiceNodeID(ethereum_add_bls_args["bls_pubkey"]))) vprint("Done.") From f116d3fbc4afe1f1143925b8a94cd87c0409bd72 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 20 Mar 2024 16:52:41 +1100 Subject: [PATCH 48/97] exiting adds to the batching database --- src/blockchain_db/sqlite/db_sqlite.cpp | 29 +++++++++++++++++ src/blockchain_db/sqlite/db_sqlite.h | 2 ++ .../cryptonote_format_utils.cpp | 9 ++++++ .../cryptonote_format_utils.h | 1 + src/cryptonote_basic/tx_extra.h | 18 +++++++++++ src/cryptonote_basic/txtypes.h | 2 ++ src/cryptonote_core/blockchain.cpp | 13 ++++++++ src/cryptonote_core/ethereum_transactions.cpp | 31 +++++++++++++++++++ src/cryptonote_core/ethereum_transactions.h | 7 +++++ src/cryptonote_core/service_node_list.cpp | 22 +++++++++++++ src/cryptonote_core/service_node_list.h | 5 +++ src/cryptonote_core/tx_pool.cpp | 10 ++++++ src/l2_tracker/l2_tracker.cpp | 24 +++++++++++++- src/l2_tracker/l2_tracker.h | 2 ++ src/l2_tracker/rewards_contract.cpp | 25 +++++++++++++-- src/l2_tracker/rewards_contract.h | 13 +++++++- src/rpc/core_rpc_server.cpp | 5 +++ utils/local-devnet/service_node_network.py | 1 - 18 files changed, 214 insertions(+), 5 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index 0fef927d87..e33971153f 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -649,6 +649,35 @@ bool BlockchainSQLite::add_block( return true; } +bool BlockchainSQLite::return_staked_amount_to_user(const crypto::eth_address& eth_address, const uint64_t amount) { + log::trace(logcat, "BlockchainDB_SQLITE::{} called", __func__); + + std::vector payments; + + + try { + SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE}; + + + //TODO sean basic checks here + //if (amount > max_staked amount) + //throw std::logic_error{"Invalid payment: staked returned is too large"}; + + std::lock_guard a_s_lock{address_str_cache_mutex}; + + payments.emplace_back(eth_address, amount); + + if (!add_sn_rewards(payments)) + return false; + + transaction.commit(); + } catch (std::exception& e) { + log::error(logcat, "Error returning stakes: {}", e.what()); + return false; + } + return true; +} + bool BlockchainSQLite::pop_block( const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state) { diff --git a/src/blockchain_db/sqlite/db_sqlite.h b/src/blockchain_db/sqlite/db_sqlite.h index 283a0ef3df..c0347259a4 100644 --- a/src/blockchain_db/sqlite/db_sqlite.h +++ b/src/blockchain_db/sqlite/db_sqlite.h @@ -117,6 +117,8 @@ class BlockchainSQLite : public db::Database { const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state); + bool return_staked_amount_to_user(const crypto::eth_address& eth_address, const uint64_t amount); + // validate_batch_payment -> used to make sure that list of miner_tx_vouts is correct. Compares // the miner_tx_vouts with a list previously extracted payments to make sure that the correct // persons are being paid. diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 9c1362c742..403b231911 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -979,6 +979,15 @@ bool add_service_node_leave_request_to_tx_extra(std::vector& tx_extra, return true; } //--------------------------------------------------------------- +bool add_service_node_exit_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_exit& exit_data) { + tx_extra_field field = exit_data; + if (!add_tx_extra_field_to_tx_extra(tx_extra, field)) { + log::info(logcat, "failed to serialize tx extra for service node exit transaction"); + return false; + } + return true; +} +//--------------------------------------------------------------- bool add_service_node_deregister_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_deregister& deregister) { tx_extra_field field = deregister; if (!add_tx_extra_field_to_tx_extra(tx_extra, field)) { diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 83b2e756f5..099bf1a2a5 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -190,6 +190,7 @@ bool get_encrypted_payment_id_from_tx_extra_nonce( bool add_burned_amount_to_tx_extra(std::vector& tx_extra, uint64_t burn); bool add_new_service_node_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_new_service_node& new_service_node); bool add_service_node_leave_request_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_leave_request& leave_request); +bool add_service_node_exit_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_exit& exit_data); bool add_service_node_deregister_to_tx_extra(std::vector& tx_extra, const tx_extra_ethereum_service_node_deregister& deregister); uint64_t get_burned_amount_from_tx_extra(const std::vector& tx_extra); bool is_out_to_acc( diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 676298ea58..3f9d2cd7ae 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -57,6 +57,7 @@ constexpr uint8_t TX_EXTRA_TAG_PADDING = 0x00, TX_EXTRA_TAG_PUBKEY = 0x01, TX_EX TX_EXTRA_TAG_ETHEREUM_NEW_SERVICE_NODE = 0x7C, TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST= 0x7D, TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DEREGISTER = 0x7E, + TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_EXIT = 0x7F, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG = 0xDE; @@ -663,6 +664,20 @@ struct tx_extra_ethereum_service_node_leave_request { END_SERIALIZE() }; +struct tx_extra_ethereum_service_node_exit { + uint8_t version = 0; + crypto::eth_address eth_address; + uint64_t amount; + crypto::bls_public_key bls_key; + + BEGIN_SERIALIZE() + FIELD(version) + FIELD(eth_address) + FIELD(amount) + FIELD(bls_key) + END_SERIALIZE() +}; + struct tx_extra_ethereum_service_node_deregister { uint8_t version = 0; crypto::bls_public_key bls_key; @@ -701,6 +716,7 @@ using tx_extra_field = std::variant< tx_extra_ethereum_address_notification, tx_extra_ethereum_new_service_node, tx_extra_ethereum_service_node_leave_request, + tx_extra_ethereum_service_node_exit, tx_extra_ethereum_service_node_deregister, tx_extra_padding>; } // namespace cryptonote @@ -745,5 +761,7 @@ BINARY_VARIANT_TAG( cryptonote::tx_extra_ethereum_new_service_node, cryptonote::TX_EXTRA_TAG_ETHEREUM_NEW_SERVICE_NODE); BINARY_VARIANT_TAG( cryptonote::tx_extra_ethereum_service_node_leave_request, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_LEAVE_REQUEST); +BINARY_VARIANT_TAG( + cryptonote::tx_extra_ethereum_service_node_exit, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_EXIT); BINARY_VARIANT_TAG( cryptonote::tx_extra_ethereum_service_node_deregister, cryptonote::TX_EXTRA_TAG_ETHEREUM_SERVICE_NODE_DEREGISTER); diff --git a/src/cryptonote_basic/txtypes.h b/src/cryptonote_basic/txtypes.h index a24d4ead34..598fb5c513 100644 --- a/src/cryptonote_basic/txtypes.h +++ b/src/cryptonote_basic/txtypes.h @@ -27,6 +27,7 @@ enum class txtype : uint16_t { ethereum_address_notification, ethereum_new_service_node, ethereum_service_node_leave_request, + ethereum_service_node_exit, ethereum_service_node_deregister, _count }; @@ -51,6 +52,7 @@ inline constexpr std::string_view to_string(txtype type) { case txtype::ethereum_address_notification: return "ethereum_address_notification"sv; case txtype::ethereum_new_service_node: return "ethereum_new_service_node"sv; case txtype::ethereum_service_node_leave_request: return "ethereum_service_node_leave_request"sv; + case txtype::ethereum_service_node_exit: return "ethereum_service_node_exit"sv; case txtype::ethereum_service_node_deregister: return "ethereum_service_node_deregister"sv; default: assert(false); return "xx_unhandled_type"sv; } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9f9663b953..ef7b1a39f0 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1975,6 +1975,10 @@ void Blockchain::add_ethereum_transactions_to_tx_pool() { tx.type = txtype::ethereum_service_node_leave_request; tx_extra_ethereum_service_node_leave_request leave_request = { 0, arg.bls_key }; cryptonote::add_service_node_leave_request_to_tx_extra(tx.extra, leave_request); + } else if constexpr (std::is_same_v) { + tx.type = txtype::ethereum_service_node_exit; + tx_extra_ethereum_service_node_exit exit_data = { 0, arg.eth_address, arg.amount, arg.bls_key }; + cryptonote::add_service_node_exit_to_tx_extra(tx.extra, exit_data); } else if constexpr (std::is_same_v) { tx.type = txtype::ethereum_service_node_deregister; tx_extra_ethereum_service_node_deregister deregister = { 0, arg.bls_key }; @@ -4110,6 +4114,15 @@ bool Blockchain::check_tx_inputs( tvc.m_verbose_error = std::move(fail_reason); return false; } + } else if (tx.type == txtype::ethereum_service_node_exit) { + cryptonote::tx_extra_ethereum_service_node_exit entry = {}; + std::string fail_reason; + if (!ethereum::validate_ethereum_service_node_exit_tx(hf_version, get_current_blockchain_height(), tx, entry, &fail_reason) || + !ethereum_transaction_review_session->processServiceNodeExitTx(entry.eth_address, entry.amount, entry.bls_key, fail_reason)) { + log::error(log::Cat("verify"), "Failed to validate Ethereum Service Node Exit TX reason: {}", fail_reason); + tvc.m_verbose_error = std::move(fail_reason); + return false; + } } else if (tx.type == txtype::ethereum_service_node_deregister) { cryptonote::tx_extra_ethereum_service_node_deregister entry = {}; std::string fail_reason; diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp index 777cd55d78..92cac3e2a9 100644 --- a/src/cryptonote_core/ethereum_transactions.cpp +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -136,6 +136,37 @@ bool validate_ethereum_service_node_leave_request_tx( return true; } +bool validate_ethereum_service_node_exit_tx( + hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_exit& eth_extra, + std::string* reason) { + + { + if (check_condition( + tx.type != cryptonote::txtype::ethereum_service_node_exit, + reason, + "{} uses wrong tx type, expected={}", + tx, + cryptonote::txtype::ethereum_service_node_exit)) + return false; + + if (check_condition( + !cryptonote::get_field_from_tx_extra(tx.extra, eth_extra), + reason, + "{} didn't have ethereum service node exit data in the tx_extra", + tx)) + return false; + } + + { + // TODO sean: Add specific validation logic for Eth address, Amount and BLS Key + } + + return true; +} + bool validate_ethereum_service_node_deregister_tx( hf hf_version, uint64_t blockchain_height, diff --git a/src/cryptonote_core/ethereum_transactions.h b/src/cryptonote_core/ethereum_transactions.h index 691f1c6f57..5f908a3b2d 100644 --- a/src/cryptonote_core/ethereum_transactions.h +++ b/src/cryptonote_core/ethereum_transactions.h @@ -30,6 +30,13 @@ bool validate_ethereum_service_node_leave_request_tx( cryptonote::tx_extra_ethereum_service_node_leave_request& eth_extra, std::string* reason); +bool validate_ethereum_service_node_exit_tx( + cryptonote::hf hf_version, + uint64_t blockchain_height, + cryptonote::transaction const& tx, + cryptonote::tx_extra_ethereum_service_node_exit& eth_extra, + std::string* reason); + bool validate_ethereum_service_node_deregister_tx( cryptonote::hf hf_version, uint64_t blockchain_height, diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index f4cfcde5bf..2401ee9d50 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -1089,6 +1089,26 @@ bool service_node_list::state_t::process_ethereum_deregister_tx( return true; } +bool service_node_list::state_t::process_ethereum_exit_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx) { + + cryptonote::tx_extra_ethereum_service_node_exit exit_data; + if (!cryptonote::get_field_from_tx_extra(tx.extra, exit_data)) { + log::info( + logcat, + "Unlock TX: couldnt process exit, rejected on height: {} " + "for tx: {}", + block_height, + cryptonote::get_transaction_hash(tx)); + return false; + } + + return sn_list->m_blockchain.sqlite_db()->return_staked_amount_to_user(exit_data.eth_address, exit_data.amount); +} + bool service_node_list::state_t::process_key_image_unlock_tx( cryptonote::network_type nettype, cryptonote::hf hf_version, @@ -2825,6 +2845,8 @@ void service_node_list::state_t::update_from_block( process_ethereum_registration_tx(nettype, block, tx, index, my_keys); } else if (tx.type == cryptonote::txtype::ethereum_service_node_leave_request) { process_ethereum_unlock_tx(nettype, hf_version, block_height, tx); + } else if (tx.type == cryptonote::txtype::ethereum_service_node_exit) { + process_ethereum_exit_tx(nettype, hf_version, block_height, tx); } else if (tx.type == cryptonote::txtype::ethereum_service_node_deregister) { process_ethereum_deregister_tx(nettype, hf_version, block_height, tx, my_keys); diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 45529be013..11e32a34c2 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -826,6 +826,11 @@ class service_node_list { cryptonote::hf hf_version, uint64_t block_height, const cryptonote::transaction& tx); + bool process_ethereum_exit_tx( + cryptonote::network_type nettype, + cryptonote::hf hf_version, + uint64_t block_height, + const cryptonote::transaction& tx); bool process_ethereum_deregister_tx( cryptonote::network_type nettype, cryptonote::hf hf_version, diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 7b0fcf6302..2aeeb1d3a4 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -258,6 +258,16 @@ bool tx_memory_pool::have_duplicated_non_standard_tx( get_transaction_hash(tx)); return true; } + } else if (tx.type == txtype::ethereum_service_node_exit) { + cryptonote::tx_extra_ethereum_service_node_exit data = {}; + if (!cryptonote::get_field_from_tx_extra(tx.extra, data)) { + log::error( + logcat, + "Could not get ethereum service node exit data from tx: {}, tx to add is possibly " + "invalid, rejecting", + get_transaction_hash(tx)); + return true; + } } else if (tx.type == txtype::ethereum_service_node_deregister) { cryptonote::tx_extra_ethereum_service_node_deregister data = {}; if (!cryptonote::get_field_from_tx_extra(tx.extra, data)) { diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 5cc1882f66..bdc4e413c2 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -140,6 +140,8 @@ void L2Tracker::populate_review_transactions(std::shared_ptrnew_service_nodes.push_back(arg); } else if constexpr (std::is_same_v) { session->leave_requests.push_back(arg); + } else if constexpr (std::is_same_v) { + session->exits.push_back(arg); } else if constexpr (std::is_same_v) { session->deregs.push_back(arg); } @@ -219,6 +221,26 @@ bool TransactionReviewSession::processServiceNodeLeaveRequestTx(const crypto::bl return false; } +bool TransactionReviewSession::processServiceNodeExitTx(const crypto::eth_address& eth_address, const uint64_t amount, const crypto::bls_public_key& bls_key, std::string& fail_reason) { + if (!service_node) + return true; + if (review_block_height_max == 0) { + fail_reason = "Review not initialized"; + oxen::log::error(logcat, "Failed to process service node exit tx height {}", review_block_height_max); + return false; + } + + for (auto it = exits.begin(); it != exits.end(); ++it) { + if (it->bls_key == bls_key && it->eth_address == eth_address && it->amount == amount) { + exits.erase(it); + return true; + } + } + + fail_reason = "Exit Transaction not found bls_key: " + tools::type_to_hex(bls_key); + return false; +} + bool TransactionReviewSession::processServiceNodeDeregisterTx(const crypto::bls_public_key& bls_key, std::string& fail_reason) { if (!service_node) return true; @@ -242,7 +264,7 @@ bool TransactionReviewSession::processServiceNodeDeregisterTx(const crypto::bls_ bool TransactionReviewSession::finalize_review() { if (!service_node) return true; - if (new_service_nodes.empty() && leave_requests.empty() && deregs.empty()) { + if (new_service_nodes.empty() && leave_requests.empty() && deregs.empty() && exits.empty()) { review_block_height_min = review_block_height_max + 1; review_block_height_max = 0; return true; diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index a88ef91fd5..95b625be60 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -25,12 +25,14 @@ struct TransactionReviewSession { std::vector new_service_nodes; std::vector leave_requests; std::vector deregs; + std::vector exits; TransactionReviewSession(uint64_t min_height, uint64_t max_height) : review_block_height_min(min_height), review_block_height_max(max_height) {} bool processNewServiceNodeTx(const crypto::bls_public_key& bls_key, const crypto::eth_address& eth_address, const std::string& service_node_pubkey, std::string& fail_reason); bool processServiceNodeLeaveRequestTx(const crypto::bls_public_key& bls_key, std::string& fail_reason); + bool processServiceNodeExitTx(const crypto::eth_address& eth_address, const uint64_t amount, const crypto::bls_public_key& bls_key, std::string& fail_reason); bool processServiceNodeDeregisterTx(const crypto::bls_public_key& bls_key, std::string& fail_reason); bool finalize_review(); diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 8a9fff7657..c37b62a82f 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -19,9 +19,13 @@ TransactionType RewardsLogEntry::getLogType() const { // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { return TransactionType::ServiceNodeLeaveRequest; + // TODO sean the deregister should be something that the l2 tracker makes if it detects rollbacks, liquidations use the normal exit log but have a lower amount paid back to user // keccak256('ServiceNodeLiquidated(uint64,address,(uint256,uint256))') - } else if (topics[0] == "0x0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c") { - return TransactionType::ServiceNodeDeregister; + //} else if (topics[0] == "0x0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c") { + //return TransactionType::ServiceNodeDeregister; + // keccak256('ServiceNodeRemoval(uint64,address,uint256,(uint256,uint256))') + } else if (topics[0] == "0x130a7be04ef1f87b2b436f68f389bf863ee179b95399a3a8444196fab7a4e54c") { + return TransactionType::ServiceNodeExit; } return TransactionType::Other; } @@ -70,6 +74,23 @@ std::optional RewardsLogEntry::getLogTransaction( tools::hex_to_type(bls_key_str, bls_key); return ServiceNodeDeregisterTx(bls_key); } + case TransactionType::ServiceNodeExit: { + // event ServiceNodeRemoval(uint64 indexed serviceNodeID, address recipient, uint256 returnedAmount, BN256G1.G1Point pubkey); + // address is 32 bytes, amount is 32 bytes and pubkey is 64 bytes + // + // The address is in 32 bytes, but actually only uses 20 bytes and the first 12 are padding + std::string eth_address_str = data.substr(2 + 24, 40); + crypto::eth_address eth_address; + tools::hex_to_type(eth_address_str, eth_address); + // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 32 bytes (64 characters) + std::string amount_str = data.substr(64 + 2, 64); + uint64_t amount = utils::fromHexStringToUint64(amount_str); + // pull 64 bytes (128 characters) + std::string bls_key_str = data.substr(64 + 64 + 2, 128); + crypto::bls_public_key bls_key; + tools::hex_to_type(bls_key_str, bls_key); + return ServiceNodeExitTx(eth_address, amount, bls_key); + } default: return std::nullopt; } diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index b610d6fd0f..77e901882f 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -12,6 +12,7 @@ enum class TransactionType { NewServiceNode, ServiceNodeLeaveRequest, ServiceNodeDeregister, + ServiceNodeExit, Other }; @@ -42,7 +43,17 @@ class ServiceNodeDeregisterTx { : bls_key(_bls_key) {} }; -using TransactionStateChangeVariant = std::variant; +class ServiceNodeExitTx { +public: + crypto::eth_address eth_address; + uint64_t amount; + crypto::bls_public_key bls_key; + + ServiceNodeExitTx(const crypto::eth_address& _eth_address, const uint64_t _amount, const crypto::bls_public_key& _bls_key) + : eth_address(_eth_address), amount(_amount), bls_key(_bls_key) {} +}; + +using TransactionStateChangeVariant = std::variant; class RewardsLogEntry : public LogEntry { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index fbaed64349..58f8c7a61b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -758,6 +758,11 @@ namespace { void operator()(const tx_extra_ethereum_service_node_leave_request& x) { set("bls_key", tools::type_to_hex(x.bls_key)); } + void operator()(const tx_extra_ethereum_service_node_exit& x) { + set("eth_address", tools::type_to_hex(x.eth_address)); + set("amount", x.amount); + set("bls_key", tools::type_to_hex(x.bls_key)); + } void operator()(const tx_extra_ethereum_service_node_deregister& x) { set("bls_key", tools::type_to_hex(x.bls_key)); } diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index dbbc06442d..a08fcf41fc 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -229,7 +229,6 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): exit["result"]["bls_key"], exit["result"]["signature"], self.servicenodecontract.getNonSigners(exit["result"]["signers_bls_pubkeys"])) - vprint(result) vprint("Submitted transaction to exit service node : {}".format(ethereum_add_bls_args["bls_pubkey"])) vprint("exited node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) From 7262a9fcee5b31fe30b44ca27c25baacf9f95754 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 18 Apr 2024 08:12:41 +1000 Subject: [PATCH 49/97] multiple contributors data, registration signature validation --- src/bls/bls_aggregator.cpp | 4 +- src/bls/bls_aggregator.h | 2 +- src/bls/bls_signer.cpp | 9 +- src/bls/bls_signer.h | 2 +- src/cryptonote_basic/tx_extra.h | 18 + src/cryptonote_core/blockchain.cpp | 7 +- src/cryptonote_core/cryptonote_core.cpp | 21 +- src/cryptonote_core/cryptonote_core.h | 2 +- src/cryptonote_core/ethereum_transactions.cpp | 1 + src/cryptonote_core/service_node_list.cpp | 17 +- src/l2_tracker/rewards_contract.cpp | 61 +- src/l2_tracker/rewards_contract.h | 14 +- src/rpc/core_rpc_server.cpp | 8 + utils/local-devnet/ethereum.py | 562 +++++++++++++++--- utils/local-devnet/service_node_network.py | 14 +- 15 files changed, 594 insertions(+), 148 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 791d004088..1132d14594 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -29,8 +29,8 @@ std::vector> BLSAggregator::getPubkeys() { return pubkeys; } -blsRegistrationResponse BLSAggregator::registration() const { - return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(), "","",""}; +blsRegistrationResponse BLSAggregator::registration(const std::string& senderEthAddress, const std::string& serviceNodePubkey) const { + return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(senderEthAddress, serviceNodePubkey), senderEthAddress, serviceNodePubkey, ""}; } aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& address) { diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 0a2c5d5605..ac6d10b03c 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -64,7 +64,7 @@ class BLSAggregator { aggregateWithdrawalResponse aggregateRewards(const std::string& address); aggregateExitResponse aggregateExit(const std::string& bls_key); aggregateExitResponse aggregateLiquidation(const std::string& bls_key); - blsRegistrationResponse registration() const; + blsRegistrationResponse registration(const std::string& senderEthAddress, const std::string& serviceNodePubkey) const; private: // Goes out to the nodes on the network and makes oxenmq requests to all of them, when getting the reply diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 4bf3cefad7..6af116f9bd 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -61,11 +61,12 @@ bls::Signature BLSSigner::signHash(const std::array& hash) { return sig; } -std::string BLSSigner::proofOfPossession() { - //TODO sean put constants somewhere, source them - +std::string BLSSigner::proofOfPossession(const std::string& senderEthAddress, const std::string& serviceNodePubkey) { std::string fullTag = buildTag(proofOfPossessionTag, chainID, contractAddress); - std::string message = "0x" + fullTag + getPublicKeyHex(); + std::string senderAddressOutput = senderEthAddress; + if (senderAddressOutput.substr(0, 2) == "0x") + senderAddressOutput = senderAddressOutput.substr(2); // remove "0x" + std::string message = "0x" + fullTag + getPublicKeyHex() + senderAddressOutput + utils::padTo32Bytes(serviceNodePubkey, utils::PaddingDirection::LEFT); const std::array hash = BLSSigner::hash(message); // Get the hash of the publickey bls::Signature sig; diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 3baa964d42..e1f19cf9e5 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -33,7 +33,7 @@ class BLSSigner { ~BLSSigner(); bls::Signature signHash(const std::array& hash); - std::string proofOfPossession(); + std::string proofOfPossession(const std::string& senderEthAddress, const std::string& serviceNodePubkey); std::string getPublicKeyHex(); bls::PublicKey getPublicKey(); diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 3f9d2cd7ae..b26f8ddac4 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -638,12 +638,28 @@ struct tx_extra_ethereum_address_notification { END_SERIALIZE() }; +struct tx_extra_ethereum_contributor { + crypto::eth_address address; + uint64_t amount; + + tx_extra_ethereum_contributor() = default; + tx_extra_ethereum_contributor(const crypto::eth_address& addr, uint64_t amt) + : address(addr), amount(amt) {} + + BEGIN_SERIALIZE() + FIELD(address) + FIELD(amount) + END_SERIALIZE() +}; + struct tx_extra_ethereum_new_service_node { uint8_t version = 0; crypto::bls_public_key bls_key; crypto::eth_address eth_address; crypto::public_key service_node_pubkey; crypto::signature signature; + uint64_t fee; + std::vector contributors; BEGIN_SERIALIZE() FIELD(version) @@ -651,6 +667,8 @@ struct tx_extra_ethereum_new_service_node { FIELD(eth_address) FIELD(service_node_pubkey) FIELD(signature) + FIELD(fee) + FIELD(contributors) END_SERIALIZE() }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index ef7b1a39f0..a2c6416939 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1965,10 +1965,13 @@ void Blockchain::add_ethereum_transactions_to_tx_pool() { tx.type = txtype::ethereum_new_service_node; crypto::public_key service_node_pubkey = {}; tools::hex_to_type(arg.service_node_pubkey, service_node_pubkey); - // TODO sean need to actually get the signature crypto::signature signature = {}; tools::hex_to_type(arg.signature, signature); - tx_extra_ethereum_new_service_node new_service_node = { 0, arg.bls_key, arg.eth_address, service_node_pubkey, signature }; + std::vector contributors; + for (const auto& contributor: arg.contributors) + contributors.emplace_back(contributor.addr, contributor.amount); + + tx_extra_ethereum_new_service_node new_service_node = { 0, arg.bls_key, arg.eth_address, service_node_pubkey, signature, arg.fee, contributors}; cryptonote::add_new_service_node_to_tx_extra(tx.extra, new_service_node); } else if constexpr (std::is_same_v) { diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 5b63aba426..e3b00e65ff 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2793,13 +2793,20 @@ std::vector> core::get_bls_pubkeys() const { return bls_pubkeys_and_amounts; } //----------------------------------------------------------------------------------------------- -blsRegistrationResponse core::bls_registration(const std::string& ethereum_address) const { - auto resp = m_bls_aggregator->registration(); - const auto& pubkey = get_service_keys().pub; - resp.address = ethereum_address; - resp.service_node_pubkey = tools::type_to_hex(pubkey); - // TODO sean sign this somehow - resp.service_node_signature = ""; +blsRegistrationResponse core::bls_registration(const std::string& ethereum_address, const uint64_t fee) const { + const auto& keys = get_service_keys(); + auto resp = m_bls_aggregator->registration(ethereum_address, tools::type_to_hex(keys.pub)); + + auto height = get_current_blockchain_height(); + auto hf_version = get_network_version(m_nettype, height); + service_nodes::registration_details reg{}; + reg.service_node_pubkey = keys.pub; + reg.hf = static_cast(hf_version); + reg.uses_portions = false; + reg.fee = fee; + auto hash = get_registration_hash(reg); + resp.service_node_signature = tools::type_to_hex(crypto::generate_signature(hash, keys.pub, keys.key)); + return resp; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index f0850c5faf..6ece6eac5a 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -955,7 +955,7 @@ class core : public i_miner_handler { aggregateExitResponse aggregate_exit_request(const std::string& bls_key); aggregateExitResponse aggregate_liquidation_request(const std::string& bls_key); std::vector> get_bls_pubkeys() const; - blsRegistrationResponse bls_registration(const std::string& ethereum_address) const; + blsRegistrationResponse bls_registration(const std::string& ethereum_address, const uint64_t fee = 0) const; /** * @brief get a snapshot of the service node list state at the time of the call. diff --git a/src/cryptonote_core/ethereum_transactions.cpp b/src/cryptonote_core/ethereum_transactions.cpp index 92cac3e2a9..660972fa84 100644 --- a/src/cryptonote_core/ethereum_transactions.cpp +++ b/src/cryptonote_core/ethereum_transactions.cpp @@ -100,6 +100,7 @@ bool validate_ethereum_new_service_node_tx( { // TODO sean: Add specific validation logic for BLS Key, Ethereum Address, and Service Node Public Key + // contributor.amount sums to required staking amount } return true; diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 2401ee9d50..4ebba50fda 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -376,17 +376,16 @@ std::optional eth_reg_tx_extract_fields(hf hf_version, con reg.service_node_pubkey = registration.service_node_pubkey; reg.bls_key = registration.bls_key; - // TODO sean this needs to be thought out - auto& [addr, amount] = reg.eth_contributions.emplace_back(); - addr = registration.eth_address; - // TODO sean SOMETHING BETTER HERE - amount = 100'000'000'000; - //amount = registration.amount; + for (const auto& contributor : registration.contributors) { + auto& [addr, amount] = reg.eth_contributions.emplace_back(); + addr = contributor.address; + amount = contributor.amount; + } reg.hf = static_cast(hf_version); reg.uses_portions = false; - reg.fee = 0; + reg.fee = registration.fee; reg.signature = registration.signature; return reg; @@ -1470,9 +1469,9 @@ std::pair> validate_and_g auto& reg = *maybe_reg; uint64_t staking_requirement = get_staking_requirement(nettype, block_height); + validate_registration(hf_version, nettype, staking_requirement, block_timestamp, reg); - // TODO sean bring this signature verification back - //validate_registration_signature(reg); + validate_registration_signature(reg); info->staking_requirement = staking_requirement; info->operator_ethereum_address = reg.eth_contributions[0].first; diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index c37b62a82f..7d07f31862 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -13,16 +13,12 @@ TransactionType RewardsLogEntry::getLogType() const { if (topics.empty()) { throw std::runtime_error("No topics in log entry"); } - // keccak256('NewServiceNode(uint64,address,(uint256,uint256),uint256,uint256)') - if (topics[0] == "0x33f8d30fa2c44ed0b2147e4aa1e1d14e3ee4f96d7586e1fea20c3cfe67b40083") { + // keccak256('NewServiceNode(uint64,address,(uint256,uint256),(uint256,uint256,uint256,uint16),(address,uint256)[])') + if (topics[0] == "0xe82ed1bfc15e6602fba1a19273171c8a63c1d40b0e0117be4598167b8655498f") { return TransactionType::NewServiceNode; // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { return TransactionType::ServiceNodeLeaveRequest; - // TODO sean the deregister should be something that the l2 tracker makes if it detects rollbacks, liquidations use the normal exit log but have a lower amount paid back to user - // keccak256('ServiceNodeLiquidated(uint64,address,(uint256,uint256))') - //} else if (topics[0] == "0x0bfb12191b00293af29126b1c5489f8daeb4a4af82db2960b7f8353c3105cd7c") { - //return TransactionType::ServiceNodeDeregister; // keccak256('ServiceNodeRemoval(uint64,address,uint256,(uint256,uint256))') } else if (topics[0] == "0x130a7be04ef1f87b2b436f68f389bf863ee179b95399a3a8444196fab7a4e54c") { return TransactionType::ServiceNodeExit; @@ -34,23 +30,54 @@ std::optional RewardsLogEntry::getLogTransaction( TransactionType type = getLogType(); switch (type) { case TransactionType::NewServiceNode: { - // event NewServiceNode(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey, uint256 serviceNodePubkey, uint256 serviceNodeSignature); - // service node id is a topic so only address and pubkey are in data - // address is 32 bytes , pubkey is 64 bytes and serviceNodePubkey is 32 bytes + // event NewServiceNode(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey, uint256 serviceNodePubkey, uint256 serviceNodeSignature, uint16 fee, Contributors[] contributors); + // service node id is a topic so only address, pubkeys, signature, fee and contributors are in data + // address is 32 bytes , pubkey is 64 bytes and serviceNodePubkey is 64 bytes // // The address is in 32 bytes, but actually only uses 20 bytes and the first 12 are padding - std::string eth_address_str = data.substr(2 + 24, 40); + int pos = 2; // Start after the 0x prefix + std::string eth_address_str = data.substr(pos + 24, 40); // Skip 24 characters which are always blank crypto::eth_address eth_address; tools::hex_to_type(eth_address_str, eth_address); - // from position 64 (32 bytes -> 64 characters) + 2 for '0x' pull 64 bytes (128 characters) - std::string bls_key_str = data.substr(64 + 2, 128); + pos += 64; + // pull 64 bytes (128 characters) for the BLS pubkey + std::string bls_key_str = data.substr(pos, 128); crypto::bls_public_key bls_key; tools::hex_to_type(bls_key_str, bls_key); - // pull 32 bytes (64 characters) - std::string service_node_pubkey = data.substr(128 + 64 + 2, 64); - // pull 32 bytes (64 characters) - std::string signature = data.substr(128 + 64 + 64 + 2, 64); - return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey, signature); + pos += 128; + // pull 32 bytes (64 characters) ed pubkey + std::string service_node_pubkey = data.substr(pos, 64); + pos += 64; + // pull 64 bytes (128 characters) for ed signature + std::string signature = data.substr(pos, 128); + pos += 128; + // pull 32 bytes (64 characters) for fee + std::string fee_str = data.substr(pos, 64); + uint64_t fee = utils::fromHexStringToUint64(fee_str); + pos += 64; + // There are 32 bytes describing the size of contributors data here, ignore because we always get the same data out of it + pos += 64; + // pull 32 bytes (64 characters) for the number of elements in the array + std::vector contributors; + std::string num_contributors_str = data.substr(pos, 64); + + uint64_t num_contributors = utils::fromHexStringToUint64(num_contributors_str); + pos += 64; + std::string contributor_address_str; + std::string contributor_amount_str; + for (uint64_t i = 0; i < num_contributors; ++i) { + // Each loop iteration processes one contributor + contributor_address_str = data.substr(pos + 24, 40); + crypto::eth_address contributor_address; + tools::hex_to_type(contributor_address_str, contributor_address); + pos += 64; + contributor_amount_str = data.substr(pos, 64); + uint64_t contributor_amount = utils::fromHexStringToUint64(contributor_amount_str); + pos += 64; + contributors.emplace_back(contributor_address, contributor_amount); + } + + return NewServiceNodeTx(bls_key, eth_address, service_node_pubkey, signature, fee, contributors); } case TransactionType::ServiceNodeLeaveRequest: { // event ServiceNodeRemovalRequest(uint64 indexed serviceNodeID, address recipient, BN256G1.G1Point pubkey); diff --git a/src/l2_tracker/rewards_contract.h b/src/l2_tracker/rewards_contract.h index 77e901882f..3322df78c3 100644 --- a/src/l2_tracker/rewards_contract.h +++ b/src/l2_tracker/rewards_contract.h @@ -16,15 +16,25 @@ enum class TransactionType { Other }; +struct Contributor { + crypto::eth_address addr; + uint64_t amount; + + Contributor(const crypto::eth_address& address, uint64_t amt) + : addr(address), amount(amt) {} +}; + class NewServiceNodeTx { public: crypto::bls_public_key bls_key; crypto::eth_address eth_address; std::string service_node_pubkey; std::string signature; + uint64_t fee; + std::vector contributors; - NewServiceNodeTx(const crypto::bls_public_key& _bls_key, const crypto::eth_address& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature) - : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey), signature(_signature) {} + NewServiceNodeTx(const crypto::bls_public_key& _bls_key, const crypto::eth_address& _eth_address, const std::string& _service_node_pubkey, const std::string& _signature, const uint64_t _fee, const std::vector& _contributors) + : bls_key(_bls_key), eth_address(_eth_address), service_node_pubkey(_service_node_pubkey), signature(_signature), fee(_fee), contributors(_contributors) {} }; class ServiceNodeLeaveRequestTx { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 58f8c7a61b..5c25a4e744 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -754,6 +754,14 @@ namespace { set("eth_address", tools::type_to_hex(x.eth_address)); set("service_node_pubkey", tools::view_guts(x.service_node_pubkey)); set("signature", tools::view_guts(x.signature)); + set("fee", tools::view_guts(x.fee)); + auto contributors = json::array(); + for (auto& contributor : x.contributors) { + auto& c = contributors.emplace_back(); + c["address"] = tools::type_to_hex(contributor.address); + c["amount"] = contributor.amount; + } + set("contributors", contributors); } void operator()(const tx_extra_ethereum_service_node_leave_request& x) { set("bls_key", tools::type_to_hex(x.bls_key)); diff --git a/utils/local-devnet/ethereum.py b/utils/local-devnet/ethereum.py index f2750ea7b5..5bfccbdd29 100644 --- a/utils/local-devnet/ethereum.py +++ b/utils/local-devnet/ethereum.py @@ -48,16 +48,24 @@ def erc20balance(self, address): def addBLSPublicKey(self, args): # function addBLSPublicKey(uint256 pkX, uint256 pkY, uint256 sigs0, uint256 sigs1, uint256 sigs2, uint256 sigs3, uint256 serviceNodePubkey, uint256 serviceNodeSignature) public { - unsent_tx = self.contract.functions.addBLSPublicKey(int(args["bls_pubkey"][:64], 16), - int(args["bls_pubkey"][64:128], 16), - int(args["proof_of_possession"][:64], 16), - int(args["proof_of_possession"][64:128], 16), - int(args["proof_of_possession"][128:192], 16), - int(args["proof_of_possession"][192:256], 16), - int(args["service_node_pubkey"], 16), - # int(args["service_node_signature"], 16) - int(0) - ).build_transaction({ + bls_param = { + 'X': int(args["bls_pubkey"][:64], 16), + 'Y': int(args["bls_pubkey"][64:128], 16), + } + sig_param = { + 'sigs0': int(args["proof_of_possession"][:64], 16), + 'sigs1': int(args["proof_of_possession"][64:128], 16), + 'sigs2': int(args["proof_of_possession"][128:192], 16), + 'sigs3': int(args["proof_of_possession"][192:256], 16), + } + service_node_params = { + 'serviceNodePubkey': int(args["service_node_pubkey"], 16), + 'serviceNodeSignature1': int(args["service_node_signature"][:64], 16), + 'serviceNodeSignature2': int(args["service_node_signature"][64:128], 16), + 'fee': int(0), + } + contributors = [] + unsent_tx = self.contract.functions.addBLSPublicKey(bls_param, sig_param, service_node_params, contributors).build_transaction({ "from": self.acc.address, 'gas': 2000000, 'nonce': self.web3.eth.get_transaction_count(self.acc.address)}) @@ -216,22 +224,22 @@ def getNonSigners(self, bls_public_keys): }, { "internalType": "uint256", - "name": "_stakingRequirement", + "name": "__stakingRequirement", "type": "uint256" }, { "internalType": "uint256", - "name": "_liquidatorRewardRatio", + "name": "__liquidatorRewardRatio", "type": "uint256" }, { "internalType": "uint256", - "name": "_poolShareOfLiquidationRatio", + "name": "__poolShareOfLiquidationRatio", "type": "uint256" }, { "internalType": "uint256", - "name": "_recipientRatio", + "name": "__recipientRatio", "type": "uint256" } ], @@ -297,11 +305,32 @@ def getNonSigners(self, bls_public_keys): "name": "BLSPubkeyDoesNotMatch", "type": "error" }, + { + "inputs": [], + "name": "ContractAlreadyActive", + "type": "error" + }, { "inputs": [], "name": "ContractNotActive", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "required", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "provided", + "type": "uint256" + } + ], + "name": "ContributionTotalMismatch", + "type": "error" + }, { "inputs": [ { @@ -318,11 +347,37 @@ def getNonSigners(self, bls_public_keys): "name": "EarlierLeaveRequestMade", "type": "error" }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "contributor", + "type": "address" + } + ], + "name": "FirstContributorMismatch", + "type": "error" + }, { "inputs": [ { @@ -349,11 +404,6 @@ def getNonSigners(self, bls_public_keys): "name": "InvalidBLSSignature", "type": "error" }, - { - "inputs": [], - "name": "InvalidParameter", - "type": "error" - }, { "inputs": [ { @@ -375,6 +425,11 @@ def getNonSigners(self, bls_public_keys): "name": "LeaveRequestTooEarly", "type": "error" }, + { + "inputs": [], + "name": "NullRecipient", + "type": "error" + }, { "inputs": [ { @@ -419,14 +474,8 @@ def getNonSigners(self, bls_public_keys): "type": "error" }, { - "inputs": [ - { - "internalType": "uint64", - "name": "serviceNodeID", - "type": "uint64" - } - ], - "name": "RecipientAddressNotProvided", + "inputs": [], + "name": "RecipientRewardsTooLow", "type": "error" }, { @@ -516,19 +565,85 @@ def getNonSigners(self, bls_public_keys): "type": "tuple" }, { + "components": [ + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature2", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], "indexed": false, - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" + "internalType": "struct IServiceNodeRewards.ServiceNodeParams", + "name": "serviceNode", + "type": "tuple" }, + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" + } + ], + "name": "NewServiceNode", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { "indexed": false, "internalType": "uint256", - "name": "serviceNodeSignature", + "name": "newRequirement", "type": "uint256" } ], - "name": "NewServiceNode", + "name": "NonSignersLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", "type": "event" }, { @@ -550,6 +665,19 @@ def getNonSigners(self, bls_public_keys): "name": "OwnershipTransferred", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -711,6 +839,32 @@ def getNonSigners(self, bls_public_keys): "name": "ServiceNodeRemovalRequest", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "StakingRequirementUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, { "inputs": [], "name": "IsActive", @@ -751,46 +905,119 @@ def getNonSigners(self, bls_public_keys): "type": "function" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "pkX", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "pkY", - "type": "uint256" - }, + "inputs": [], + "name": "_aggregatePubkey", + "outputs": [ { "internalType": "uint256", - "name": "sigs0", + "name": "X", "type": "uint256" }, { "internalType": "uint256", - "name": "sigs1", + "name": "Y", "type": "uint256" - }, + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "uint256", - "name": "sigs2", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "blsPubkey", + "type": "tuple" }, { - "internalType": "uint256", - "name": "sigs3", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "sigs0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs2", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sigs3", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.BLSSignatureParams", + "name": "blsSignature", + "type": "tuple" }, { - "internalType": "uint256", - "name": "serviceNodePubkey", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "serviceNodePubkey", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "serviceNodeSignature2", + "type": "uint256" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], + "internalType": "struct IServiceNodeRewards.ServiceNodeParams", + "name": "serviceNodeParams", + "type": "tuple" }, { - "internalType": "uint256", - "name": "serviceNodeSignature", - "type": "uint256" + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + } + ], + "internalType": "struct IServiceNodeRewards.Contributor[]", + "name": "contributors", + "type": "tuple[]" } ], "name": "addBLSPublicKey", @@ -800,17 +1027,24 @@ def getNonSigners(self, bls_public_keys): }, { "inputs": [], - "name": "aggregate_pubkey", + "name": "aggregatePubkey", "outputs": [ { - "internalType": "uint256", - "name": "X", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "Y", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "", + "type": "tuple" } ], "stateMutability": "view", @@ -960,6 +1194,19 @@ def getNonSigners(self, bls_public_keys): "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "liquidatorRewardRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "nextServiceNodeID", @@ -986,6 +1233,52 @@ def getNonSigners(self, bls_public_keys): "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolShareOfLiquidationRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "proofOfPossessionTag", @@ -999,6 +1292,19 @@ def getNonSigners(self, bls_public_keys): "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "recipientRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1163,65 +1469,111 @@ def getNonSigners(self, bls_public_keys): "inputs": [ { "internalType": "uint64", - "name": "", + "name": "i", "type": "uint64" } ], "name": "serviceNodes", "outputs": [ - { - "internalType": "uint64", - "name": "next", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "previous", - "type": "uint64" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, { "components": [ + { + "internalType": "uint64", + "name": "next", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "previous", + "type": "uint64" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN256G1.G1Point", + "name": "pubkey", + "type": "tuple" + }, { "internalType": "uint256", - "name": "X", + "name": "leaveRequestTimestamp", "type": "uint256" }, { "internalType": "uint256", - "name": "Y", + "name": "deposit", "type": "uint256" } ], - "internalType": "struct BN256G1.G1Point", - "name": "pubkey", + "internalType": "struct IServiceNodeRewards.ServiceNode", + "name": "", "type": "tuple" - }, + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "serviceNodesLength", + "outputs": [ { "internalType": "uint256", - "name": "leaveRequestTimestamp", + "name": "count", "type": "uint256" - }, + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "deposit", + "name": "newRequirement", "type": "uint256" } ], - "stateMutability": "view", + "name": "setStakingRequirement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRequirement", + "type": "uint256" + } + ], + "name": "setUpperLimitNonSigners", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], - "name": "serviceNodesLength", + "name": "stakingRequirement", "outputs": [ { "internalType": "uint256", - "name": "count", + "name": "", "type": "uint256" } ], @@ -1261,6 +1613,13 @@ def getNonSigners(self, bls_public_keys): "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -1270,7 +1629,7 @@ def getNonSigners(self, bls_public_keys): }, { "internalType": "uint256", - "name": "recipientAmount", + "name": "recipientRewards", "type": "uint256" }, { @@ -1310,6 +1669,19 @@ def getNonSigners(self, bls_public_keys): "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "upperLimitNonSigners", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" } ] """) diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index a08fcf41fc..6f75046101 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -224,13 +224,13 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): # time.sleep(155) # Exit Node - exit = self.ethsns[0].get_exit_request(ethereum_add_bls_args["bls_pubkey"]) - result = self.servicenodecontract.removeBLSPublicKeyWithSignature( - exit["result"]["bls_key"], - exit["result"]["signature"], - self.servicenodecontract.getNonSigners(exit["result"]["signers_bls_pubkeys"])) - vprint("Submitted transaction to exit service node : {}".format(ethereum_add_bls_args["bls_pubkey"])) - vprint("exited node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) + # exit = self.ethsns[0].get_exit_request(ethereum_add_bls_args["bls_pubkey"]) + # result = self.servicenodecontract.removeBLSPublicKeyWithSignature( + # exit["result"]["bls_key"], + # exit["result"]["signature"], + # self.servicenodecontract.getNonSigners(exit["result"]["signers_bls_pubkeys"])) + # vprint("Submitted transaction to exit service node : {}".format(ethereum_add_bls_args["bls_pubkey"])) + # vprint("exited node: number of service nodes in contract {}".format(self.servicenodecontract.numberServiceNodes())) # Liquidate Node # exit = self.ethsns[0].get_liquidation_request(ethereum_add_bls_args["bls_pubkey"]) From 07081820cffa97618dd49bcd9354ea9f7cf28249 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 18 Apr 2024 08:53:35 +1000 Subject: [PATCH 50/97] change zero length arrays to nullptr --- tests/unit_tests/ringct.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index 286280a950..96ac2c9a49 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -443,36 +443,30 @@ static bool range_proof_test( TEST(ringct, range_proofs_reject_empty_outs_simple) { const uint64_t inputs[] = {5000}; - const uint64_t outputs[] = {}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs)); + EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, 0, nullptr)); } TEST(ringct, range_proofs_reject_empty_ins_simple) { - const uint64_t inputs[] = {}; const uint64_t outputs[] = {5000}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs)); + EXPECT_FALSE(range_proof_test(0, nullptr, NELTS(outputs), outputs)); } TEST(ringct, range_proofs_reject_all_empty_simple) { - const uint64_t inputs[] = {}; - const uint64_t outputs[] = {}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs)); + EXPECT_FALSE(range_proof_test(0, nullptr, 0, nullptr)); } TEST(ringct, range_proofs_reject_zero_empty_simple) { const uint64_t inputs[] = {0}; - const uint64_t outputs[] = {}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs)); + EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, 0, nullptr)); } TEST(ringct, range_proofs_reject_empty_zero_simple) { - const uint64_t inputs[] = {}; const uint64_t outputs[] = {0}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs)); + EXPECT_FALSE(range_proof_test(0, nullptr, NELTS(outputs), outputs)); } TEST(ringct, range_proofs_accept_zero_zero_simple) @@ -726,8 +720,7 @@ TEST(ringct, fee_burn_valid_one_out_simple) TEST(ringct, fee_burn_invalid_zero_out_simple) { const uint64_t inputs[] = {1000, 1000}; - const uint64_t outputs[] = {}; - EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, NELTS(outputs), outputs, 2000)); + EXPECT_FALSE(range_proof_test(NELTS(inputs), inputs, 0, nullptr, 2000)); } static constexpr std::array base_inputs{1000, 1000}; From 6c85a7a8f039c092a20050d16cfa1c492502b915 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 22 Apr 2024 14:51:40 +1000 Subject: [PATCH 51/97] core tests passing --- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/cryptonote_core/service_node_list.cpp | 2 +- tests/core_tests/chaingen.cpp | 13 ++++++++----- tests/core_tests/oxen_tests.cpp | 22 +++++++++++----------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index e3b00e65ff..8e825dac79 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -258,7 +258,6 @@ core::core() : m_last_storage_server_ping(0), m_last_lokinet_ping(0), m_pad_transactions(false), - m_bls_signer(std::make_shared(m_nettype)), ss_version{0}, lokinet_version{0} { m_checkpoints_updating.clear(); @@ -781,6 +780,7 @@ bool core::init( if (!ons_db) return false; + m_bls_signer = std::make_shared(m_nettype); init_oxenmq(vm); m_bls_aggregator = std::make_unique(m_service_node_list, m_omq, m_bls_signer); diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 4ebba50fda..9d517d6906 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -425,7 +425,7 @@ void validate_registration( : oxen::MAX_CONTRIBUTORS_V1; std::vector extracted_amounts; - if (hf_version >= hf::hf20) { + if (hf_version >= hf::hf20 && nettype != cryptonote::network_type::FAKECHAIN) { if (reg.eth_contributions.empty()) throw invalid_registration{"No operator contribution given"}; if (!reg.reserved.empty()) diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 8a25f94730..75c3c7ae62 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -85,12 +85,12 @@ oxen_generate_hard_fork_table(hf hf_version, uint64_t pos_delay) uint64_t version_height = 1; // HF15 reduces and HF16+ eliminates miner block rewards, so we need to ensure we have enough // HF14 blocks to generate enough LOKI for tests: - if (hf_version > hf::hf14_blink) { - result.push_back({hf::hf14_blink, 0, version_height}); - version_height += pos_delay; + for (uint8_t version = static_cast(std::min(hf_version, hf::hf14_blink)); version <= static_cast(hf_version); version++) + { + result.push_back({static_cast(version), 0, version_height}); + version_height += pos_delay; } - result.push_back({hf_version, 0, version_height}); return result; } @@ -293,6 +293,7 @@ void oxen_chain_generator::add_blocks_until_version(hf hf_version) for (;;) { oxen_blockchain_entry &entry = create_and_add_next_block(); + if (entry.block.major_version == hf_version) return; } } @@ -1062,7 +1063,9 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create } size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); - auto sn_rwds = sqlite_db_->get_sn_payments(height); + std::vector sn_rwds; + if (hf_version_ < hf::hf20) + sn_rwds = sqlite_db_->get_sn_payments(height); if (hf_version_ < hf::hf19_reward_batching) CHECK_AND_ASSERT_MES(sn_rwds.empty(), false, "batch payments should be empty before hf19"); uint64_t block_rewards = 0; diff --git a/tests/core_tests/oxen_tests.cpp b/tests/core_tests/oxen_tests.cpp index 7aa97d09f0..c63efd8c15 100644 --- a/tests/core_tests/oxen_tests.cpp +++ b/tests/core_tests/oxen_tests.cpp @@ -747,7 +747,7 @@ bool oxen_core_block_rewards_lrc6::generate(std::vector& event bool oxen_core_test_deregister_preferred::generate(std::vector &events) { - auto hard_forks = oxen_generate_hard_fork_table(); + auto hard_forks = oxen_generate_hard_fork_table(hf::hf19_reward_batching); oxen_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); @@ -3532,14 +3532,14 @@ bool oxen_pulse_chain_split_with_no_checkpoints::generate(std::vector &events) { constexpr auto& conf = cryptonote::get_config(cryptonote::network_type::FAKECHAIN); - auto hard_forks = oxen_generate_hard_fork_table(); + auto hard_forks = oxen_generate_hard_fork_table(hf::hf19_reward_batching); oxen_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); size_t alice_account_base_event_index = gen.event_index(); auto min_service_nodes = service_nodes::pulse_min_service_nodes(cryptonote::network_type::FAKECHAIN); - gen.add_blocks_until_version(hard_forks.back().version); + gen.add_blocks_until_version(cryptonote::hf::hf19_reward_batching); gen.add_n_blocks(10); gen.add_mined_money_unlock_blocks(); @@ -3643,14 +3643,14 @@ bool oxen_batch_sn_rewards_bad_amount::generate(std::vector &e { constexpr auto& conf = cryptonote::get_config(cryptonote::network_type::FAKECHAIN); - auto hard_forks = oxen_generate_hard_fork_table(); + auto hard_forks = oxen_generate_hard_fork_table(hf::hf19_reward_batching); oxen_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); size_t alice_account_base_event_index = gen.event_index(); auto min_service_nodes = service_nodes::pulse_min_service_nodes(cryptonote::network_type::FAKECHAIN); - gen.add_blocks_until_version(hard_forks.back().version); + gen.add_blocks_until_version(cryptonote::hf::hf19_reward_batching); gen.add_n_blocks(10); gen.add_mined_money_unlock_blocks(); @@ -3690,14 +3690,14 @@ bool oxen_batch_sn_rewards_bad_address::generate(std::vector & { cryptonote::keypair const txkey{hw::get_device("default")}; constexpr auto& conf = cryptonote::get_config(cryptonote::network_type::FAKECHAIN); - auto hard_forks = oxen_generate_hard_fork_table(); + auto hard_forks = oxen_generate_hard_fork_table(hf::hf19_reward_batching); oxen_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); size_t alice_account_base_event_index = gen.event_index(); auto min_service_nodes = service_nodes::pulse_min_service_nodes(cryptonote::network_type::FAKECHAIN); - gen.add_blocks_until_version(hard_forks.back().version); + gen.add_blocks_until_version(cryptonote::hf::hf19_reward_batching); gen.add_n_blocks(10); gen.add_mined_money_unlock_blocks(); @@ -3745,14 +3745,14 @@ bool oxen_batch_sn_rewards_bad_address::generate(std::vector & bool oxen_batch_sn_rewards_pop_blocks::generate(std::vector &events) { constexpr auto& conf = cryptonote::get_config(cryptonote::network_type::FAKECHAIN); - auto hard_forks = oxen_generate_hard_fork_table(); + auto hard_forks = oxen_generate_hard_fork_table(hf::hf19_reward_batching); oxen_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); size_t alice_account_base_event_index = gen.event_index(); auto min_service_nodes = service_nodes::pulse_min_service_nodes(cryptonote::network_type::FAKECHAIN); - gen.add_blocks_until_version(hard_forks.back().version); + gen.add_blocks_until_version(cryptonote::hf::hf19_reward_batching); gen.add_n_blocks(10); gen.add_mined_money_unlock_blocks(); @@ -3830,14 +3830,14 @@ bool oxen_batch_sn_rewards_pop_blocks_after_big_cycle::generate(std::vector Date: Mon, 29 Apr 2024 08:41:39 +1000 Subject: [PATCH 52/97] unit tests passing --- external/ethyl | 2 +- tests/unit_tests/output_distribution.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/ethyl b/external/ethyl index bd2a2a7da4..e24ebbbbe3 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit bd2a2a7da4e656e578407eed8b0ddb93c51381ff +Subproject commit e24ebbbbe34857eb9c88048f5c83faecba327584 diff --git a/tests/unit_tests/output_distribution.cpp b/tests/unit_tests/output_distribution.cpp index c1187d6e5e..c06f2e7a8d 100644 --- a/tests/unit_tests/output_distribution.cpp +++ b/tests/unit_tests/output_distribution.cpp @@ -89,7 +89,7 @@ bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64 }; } opts; cryptonote::Blockchain *blockchain = &bc.m_blockchain; - bool r = blockchain->init(new TestDB(test_distribution_size), nullptr /*ons_db*/, nullptr /*sqlite_db*/, cryptonote::network_type::FAKECHAIN, true, &opts.test_options, 0, NULL); + bool r = blockchain->init(new TestDB(test_distribution_size), nullptr /*ons_db*/, nullptr /*sqlite_db*/, cryptonote::network_type::FAKECHAIN, true, &opts.test_options, 0, "", NULL); return r && blockchain->get_output_distribution(amount, from, to, start_height, distribution, base); } From 953d7bbd6b35df4eb93b8a96dbd70ec81e385bb1 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 08:57:26 +1000 Subject: [PATCH 53/97] bls and ethyl librarys to https instead off ssl --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 5695f91a5d..18ec5391fb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,7 +49,7 @@ branch = stable [submodule "external/ethyl"] path = external/ethyl - url = git@github.com:oxen-io/ethyl.git + url = https://github.com/oxen-io/ethyl.git [submodule "external/bls"] path = external/bls - url = git@github.com:herumi/bls.git + url = https://github.com/herumi/bls.git From a8b8913c9628db73c5d37af13dea5a120393666b Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 09:29:57 +1000 Subject: [PATCH 54/97] update ethyl to latest --- external/ethyl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ethyl b/external/ethyl index e24ebbbbe3..8fee1e5b04 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit e24ebbbbe34857eb9c88048f5c83faecba327584 +Subproject commit 8fee1e5b04d38cbbe85e2a9d0e6fde5e0cbd904e From c68bbc657b1d62b333e0de1d15fa187f13d1a45e Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 09:58:15 +1000 Subject: [PATCH 55/97] update oxen encoding --- external/oxen-encoding | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/oxen-encoding b/external/oxen-encoding index f6c516dce8..201c4ca86a 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit f6c516dce886b93435e28b4211d4e3a61245a33f +Subproject commit 201c4ca86add82eaedf424c5f7c8fb756aa4fc17 From 2f1cf29e5c3ff6179f0ea16e19a7591f66cbd113 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 10:10:59 +1000 Subject: [PATCH 56/97] add logs to ethyl --- external/ethyl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ethyl b/external/ethyl index 8fee1e5b04..c056684518 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit 8fee1e5b04d38cbbe85e2a9d0e6fde5e0cbd904e +Subproject commit c0566845182d9ed23df26420d7daa4f3c23f41b6 From 4d340c8de237c981f3815fd368179f234f3943da Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 10:52:54 +1000 Subject: [PATCH 57/97] update oxen-mq --- external/oxen-mq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/oxen-mq b/external/oxen-mq index 0858dd278b..a27961d787 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit 0858dd278b91a899b69210088622ca6fa3bb0eed +Subproject commit a27961d787c9065f2bf6da9d60d01dca2e125739 From a9932f26dd6814ebca9c3c2253ee98d9f050446d Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 8 Apr 2024 14:05:41 +1000 Subject: [PATCH 58/97] Update static build ZLIB url to Github Archived versions of ZLIB are pruned from the official zlib website. These versions are preserved in the Github which make it more reliable to use a source for static building. --- cmake/StaticBuild.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index d3110a9de2..67a4ee969d 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -89,7 +89,7 @@ set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516 CACHE STRING "libzmq source hash") set(ZLIB_VERSION 1.2.13 CACHE STRING "zlib version") -set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net +set(ZLIB_MIRROR ${LOCAL_MIRROR} https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION} CACHE STRING "zlib mirror(s)") set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) set(ZLIB_HASH SHA256=d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98 From 082cb4b17b381017f36ee1a2a84d1bf65c13b308 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 8 Apr 2024 16:18:56 +1000 Subject: [PATCH 59/97] Add libgmp to requirements for ethyl dependency --- README.md | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d85c0cf01e..b76ba3aea9 100644 --- a/README.md +++ b/README.md @@ -37,27 +37,28 @@ sources are also used for statically-linked builds because distribution packages often include only shared library binaries (`.so`) but not static library archives (`.a`). -| Dep | Min. version | Vendored | Debian/Ubuntu pkg | Arch pkg | Fedora | Optional | Purpose | -| ------------ | ------------- | -------- | ---------------------- | ------------ | ------------------- | -------- | ---------------- | -| GCC | 8.1.0 | NO | `g++`[1] | `base-devel` | `gcc` | NO | | -| CMake | 3.13 | NO | `cmake` | `cmake` | `cmake` | NO | | -| pkg-config | any | NO | `pkg-config` | `base-devel` | `pkgconf` | NO | | -| Boost | 1.65 | NO | `libboost-all-dev`[2] | `boost` | `boost-devel` | NO | C++ libraries | -| libzmq | 4.3.0 | YES | `libzmq3-dev` | `zeromq` | `zeromq-devel` | NO | ZeroMQ library | -| sqlite3 | 3.24.0 | YES | `libsqlite3-dev` | `sqlite` | `sqlite-devel` | NO | ONS, batching | -| libsodium | 1.0.9 | YES | `libsodium-dev` | `libsodium` | `libsodium-devel` | NO | cryptography | -| libcurl | 4.0 | NO | `libcurl4-dev` | `curl` | `curl-devel` | NO | HTTP RPC | -| libuv (Win) | any | NO | (Windows only) | -- | -- | NO | RPC event loop | -| libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | YES | Stack traces | -| liblzma | any | NO | `liblzma-dev` | `xz` | `xz-devel` | YES | For libunwind | -| libreadline | 6.3.0 | NO | `libreadline-dev` | `readline` | `readline-devel` | YES | Input editing | -| Doxygen | any | NO | `doxygen` | `doxygen` | `doxygen` | YES | Documentation | -| Graphviz | any | NO | `graphviz` | `graphviz` | `graphviz` | YES | Documentation | -| Qt tools | 5.x | NO | `qttools5-dev` | `qt5-tools` | `qt5-linguist` | YES | Translations | -| libhidapi | ? | NO | `libhidapi-dev` | `hidapi` | `hidapi-devel` | YES | Hardware wallet | -| libusb | ? | NO | `libusb-dev` | `libusb` | `libusb-devel` | YES | Hardware wallet | -| libprotobuf | ? | NO | `libprotobuf-dev` | `protobuf` | `protobuf-devel` | YES | Hardware wallet | -| protoc | ? | NO | `protobuf-compiler` | `protobuf` | `protobuf-compiler` | YES | Hardware wallet | +| Dep | Min. version | Vendored | Debian/Ubuntu pkg | Arch pkg | Fedora | Optional | Purpose | +| ------------ | ------------- | -------- | ---------------------- | ------------ | ------------------- | -------- | ---------------- | +| GCC | 8.1.0 | NO | `g++`[1] | `base-devel` | `gcc` | NO | | +| CMake | 3.13 | NO | `cmake` | `cmake` | `cmake` | NO | | +| pkg-config | any | NO | `pkg-config` | `base-devel` | `pkgconf` | NO | | +| Boost | 1.65 | NO | `libboost-all-dev`[2] | `boost` | `boost-devel` | NO | C++ libraries | +| libzmq | 4.3.0 | YES | `libzmq3-dev` | `zeromq` | `zeromq-devel` | NO | ZeroMQ library | +| sqlite3 | 3.24.0 | YES | `libsqlite3-dev` | `sqlite` | `sqlite-devel` | NO | ONS, batching | +| libsodium | 1.0.9 | YES | `libsodium-dev` | `libsodium` | `libsodium-devel` | NO | cryptography | +| libcurl | 4.0 | NO | `libcurl4-dev` | `curl` | `curl-devel` | NO | HTTP RPC | +| libuv (Win) | any | NO | (Windows only) | -- | -- | NO | RPC event loop | +| libgmp | any | NO | `libgmp-dev` | -- | -- | NO | BLS precision math | +| libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | YES | Stack traces | +| liblzma | any | NO | `liblzma-dev` | `xz` | `xz-devel` | YES | For libunwind | +| libreadline | 6.3.0 | NO | `libreadline-dev` | `readline` | `readline-devel` | YES | Input editing | +| Doxygen | any | NO | `doxygen` | `doxygen` | `doxygen` | YES | Documentation | +| Graphviz | any | NO | `graphviz` | `graphviz` | `graphviz` | YES | Documentation | +| Qt tools | 5.x | NO | `qttools5-dev` | `qt5-tools` | `qt5-linguist` | YES | Translations | +| libhidapi | ? | NO | `libhidapi-dev` | `hidapi` | `hidapi-devel` | YES | Hardware wallet | +| libusb | ? | NO | `libusb-dev` | `libusb` | `libusb-devel` | YES | Hardware wallet | +| libprotobuf | ? | NO | `libprotobuf-dev` | `protobuf` | `protobuf-devel` | YES | Hardware wallet | +| protoc | ? | NO | `protobuf-compiler` | `protobuf` | `protobuf-compiler` | YES | Hardware wallet | [1] On Ubuntu Bionic you will need the g++-8 package instead of g++ (which is version 7) and will From c5c5ea0713639fed59d0717a99f4a8c6872106b8 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 12:03:47 +1000 Subject: [PATCH 60/97] Remove oxen-encoding, use oxen-mq/external/oxen-encoding We can avoid keeping 2 copies of the same library since oxen-encoding in oxen-mq is exposed as a public library allowing oxen-core to interface with oxen-encoding via oxen-mq. This simplifies the build and avoids any need to synchronise the dependencies together (e.g. oxen-encoding was updated to C++20 but oxen-mq was still on the C++17 branch). We can move up to C++20 in unison when oxen-mq is updated to C++20 (and hence avoid any incompatibility issues). --- .gitmodules | 3 --- external/CMakeLists.txt | 39 +++++++++++++++++---------------------- external/oxen-encoding | 1 - 3 files changed, 17 insertions(+), 26 deletions(-) delete mode 160000 external/oxen-encoding diff --git a/.gitmodules b/.gitmodules index 18ec5391fb..fa25dfb35d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,9 +34,6 @@ [submodule "external/date"] path = external/date url = https://github.com/HowardHinnant/date.git -[submodule "external/oxen-encoding"] - path = external/oxen-encoding - url = https://github.com/oxen-io/oxen-encoding.git [submodule "external/oxen-logging"] path = external/oxen-logging url = https://github.com/oxen-io/oxen-logging diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 3340644646..a9f4206002 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -37,30 +37,25 @@ if(NOT STATIC AND NOT BUILD_STATIC_DEPS) find_package(PkgConfig REQUIRED) endif() -macro(system_or_submodule BIGNAME smallname pkgconf subdir) - option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) - if(NOT STATIC AND NOT FORCE_${BIGNAME}_SUBMODULE) - pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET) - endif() - if(${BIGNAME}_FOUND) - add_library(${smallname} INTERFACE) - if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") - # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) - else() - target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) - endif() - message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") +option(FORCE_OXENC_SUBMODULE "force using oxenmq submodule" OFF) +if(NOT STATIC AND NOT FORCE_OXENC_SUBMODULE) + pkg_check_modules(OXENC liboxenmq>=1.2.13 IMPORTED_TARGET) +endif() +if(OXENC_FOUND) + add_library(oxenmq INTERFACE) + if(NOT TARGET PkgConfig::OXENC AND CMAKE_VERSION VERSION_LESS "3.21") + # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) else() - message(STATUS "using ${smallname} submodule") - add_subdirectory(${subdir}) + target_link_libraries(oxenmq INTERFACE PkgConfig::OXENC) endif() - if(NOT TARGET ${smallname}::${smallname}) - add_library(${smallname}::${smallname} ALIAS ${smallname}) - endif() -endmacro() - -system_or_submodule(OXENC oxenc liboxenc>=1.0.3 oxen-encoding) -system_or_submodule(OXENMQ oxenmq liboxenmq>=1.2.13 oxen-mq) + message(STATUS "Found system oxenmq ${OXENC_VERSION}") +else() + message(STATUS "using oxenmq submodule") + add_subdirectory(oxen-mq) +endif() +if(NOT TARGET oxenmq::oxenmq) + add_library(oxenmq::oxenmq ALIAS oxenmq) +endif() add_subdirectory(db_drivers) add_subdirectory(randomx EXCLUDE_FROM_ALL) diff --git a/external/oxen-encoding b/external/oxen-encoding deleted file mode 160000 index 201c4ca86a..0000000000 --- a/external/oxen-encoding +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 201c4ca86add82eaedf424c5f7c8fb756aa4fc17 From f268f3cf170fb48ff9368e6d3c9328c42fc00561 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Mon, 29 Apr 2024 15:28:03 +1000 Subject: [PATCH 61/97] save bls key to file and load on startup --- src/bls/bls_signer.cpp | 31 ++++++++++++++----------- src/bls/bls_signer.h | 5 ++-- src/cryptonote_core/cryptonote_core.cpp | 4 +++- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 6af116f9bd..0d804039a2 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -4,27 +4,32 @@ #include "crypto/keccak.h" #include #include "ethyl/utils.hpp" +#include +#include static auto logcat = oxen::log::Cat("bls_signer"); -BLSSigner::BLSSigner(const cryptonote::network_type nettype) { +BLSSigner::BLSSigner(const cryptonote::network_type nettype, fs::path key_filepath) { initCurve(); - // This init function generates a secret key calling blsSecretKeySetByCSPRNG - secretKey.init(); const auto config = get_config(nettype); chainID = config.ETHEREUM_CHAIN_ID; contractAddress = config.ETHEREUM_REWARDS_CONTRACT; -} - -BLSSigner::BLSSigner(const cryptonote::network_type nettype, bls::SecretKey _secretKey) { - initCurve(); - secretKey = _secretKey; - const auto config = get_config(nettype); - chainID = config.ETHEREUM_CHAIN_ID; - contractAddress = config.ETHEREUM_REWARDS_CONTRACT; -} + // This init function generates a secret key calling blsSecretKeySetByCSPRNG + secretKey.init(); + if (fs::exists(key_filepath)) { + oxen::log::info(logcat, "Loading bls key from: {}", key_filepath.string()); + fs::ifstream in{key_filepath, std::ios::in}; + if (!in.good()) + throw std::runtime_error(fmt::format("Failed to open input file for bls key {}", key_filepath.string())); + in >> secretKey; + } else { + oxen::log::info(logcat, "No bls key found, saving new key to: {}", key_filepath.string()); + fs::ofstream out{key_filepath, std::ios::out}; + if (!out.good()) + throw std::runtime_error(fmt::format("Failed to open output file for bls key {}", key_filepath.string())); + out << secretKey; + } -BLSSigner::~BLSSigner() { } void BLSSigner::initCurve() { diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index e1f19cf9e5..cfd7d91077 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -17,6 +17,7 @@ #include #include #include "cryptonote_config.h" +#include "common/fs.h" class BLSSigner { private: @@ -28,9 +29,7 @@ class BLSSigner { void initCurve(); public: - BLSSigner(const cryptonote::network_type nettype); - BLSSigner(const cryptonote::network_type nettype, bls::SecretKey _secretKey); - ~BLSSigner(); + BLSSigner(const cryptonote::network_type nettype, fs::path key_filepath); bls::Signature signHash(const std::array& hash); std::string proofOfPossession(const std::string& senderEthAddress, const std::string& serviceNodePubkey); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 8e825dac79..46c57f979c 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -619,6 +619,9 @@ bool core::init( } auto sqliteDB = std::make_shared(m_nettype, sqlite_db_file_path); + auto bls_key_file_path = folder / "bls.key"; + m_bls_signer = std::make_shared(m_nettype, bls_key_file_path); + folder /= db->get_db_name(); log::info(logcat, "Loading blockchain from folder {} ...", folder); @@ -780,7 +783,6 @@ bool core::init( if (!ons_db) return false; - m_bls_signer = std::make_shared(m_nettype); init_oxenmq(vm); m_bls_aggregator = std::make_unique(m_service_node_list, m_omq, m_bls_signer); From 524a54dc5c1f4ec637b63e452ac4022d5a44c12c Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Wed, 1 May 2024 09:57:10 +1000 Subject: [PATCH 62/97] start of following pool --- src/cryptonote_config.h | 6 ++++++ src/l2_tracker/CMakeLists.txt | 1 + src/l2_tracker/l2_tracker.cpp | 9 +++++++-- src/l2_tracker/l2_tracker.h | 5 ++++- src/l2_tracker/pool_contract.cpp | 13 +++++++++++++ src/l2_tracker/pool_contract.h | 26 ++++++++++++++++++++++++++ src/l2_tracker/rewards_contract.cpp | 3 +-- 7 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 src/l2_tracker/pool_contract.cpp create mode 100644 src/l2_tracker/pool_contract.h diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 28dc745154..018a6724e9 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -411,6 +411,7 @@ namespace config { // Details of the ethereum smart contract managing rewards and chain its kept on inline constexpr uint32_t ETHEREUM_CHAIN_ID = 31337; inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; + inline constexpr std::string_view ETHEREUM_POOL_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; @@ -549,6 +550,7 @@ struct network_config { uint32_t ETHEREUM_CHAIN_ID; std::string_view ETHEREUM_REWARDS_CONTRACT; + std::string_view ETHEREUM_POOL_CONTRACT; inline constexpr std::string_view governance_wallet_address(hf hard_fork_version) const { const auto wallet_switch = @@ -587,6 +589,7 @@ inline constexpr network_config mainnet_config{ config::STORE_LONG_TERM_STATE_INTERVAL, config::ETHEREUM_CHAIN_ID, config::ETHEREUM_REWARDS_CONTRACT, + config::ETHEREUM_POOL_CONTRACT, }; inline constexpr network_config testnet_config{ network_type::TESTNET, @@ -617,6 +620,7 @@ inline constexpr network_config testnet_config{ config::STORE_LONG_TERM_STATE_INTERVAL, config::ETHEREUM_CHAIN_ID, config::ETHEREUM_REWARDS_CONTRACT, + config::ETHEREUM_POOL_CONTRACT, }; inline constexpr network_config devnet_config{ network_type::DEVNET, @@ -647,6 +651,7 @@ inline constexpr network_config devnet_config{ config::STORE_LONG_TERM_STATE_INTERVAL, config::ETHEREUM_CHAIN_ID, config::ETHEREUM_REWARDS_CONTRACT, + config::ETHEREUM_POOL_CONTRACT, }; inline constexpr network_config fakenet_config{ network_type::FAKECHAIN, @@ -677,6 +682,7 @@ inline constexpr network_config fakenet_config{ config::STORE_LONG_TERM_STATE_INTERVAL, config::ETHEREUM_CHAIN_ID, config::ETHEREUM_REWARDS_CONTRACT, + config::ETHEREUM_POOL_CONTRACT, }; inline constexpr const network_config& get_config(network_type nettype) { diff --git a/src/l2_tracker/CMakeLists.txt b/src/l2_tracker/CMakeLists.txt index e039cb86fd..bdb3e82460 100644 --- a/src/l2_tracker/CMakeLists.txt +++ b/src/l2_tracker/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(l2_tracker rewards_contract.cpp + pool_contract.cpp l2_tracker.cpp ) diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index bdc4e413c2..99e51a4cad 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -12,7 +12,8 @@ L2Tracker::L2Tracker() { } L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) - : rewards_contract(std::make_shared(get_contract_address(nettype), _provider)), + : rewards_contract(std::make_shared(get_rewards_contract_address(nettype), _provider)), + pool_contract(std::make_shared(get_pool_contract_address(nettype), _provider)), stop_thread(false) { update_thread = std::thread(&L2Tracker::update_state_thread, this); } @@ -126,10 +127,14 @@ std::shared_ptr L2Tracker::initialize_mempool_review() return session; } -std::string L2Tracker::get_contract_address(const cryptonote::network_type nettype) { +std::string L2Tracker::get_rewards_contract_address(const cryptonote::network_type nettype) { return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); } +std::string L2Tracker::get_pool_contract_address(const cryptonote::network_type nettype) { + return std::string(get_config(nettype).ETHEREUM_POOL_CONTRACT); +} + void L2Tracker::populate_review_transactions(std::shared_ptr session) { for (const auto& state : state_history) { if (state.height > session->review_block_height_min && state.height <= session->review_block_height_max) { diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index 95b625be60..af0f22b677 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -1,6 +1,7 @@ #pragma once #include "rewards_contract.h" +#include "pool_contract.h" #include "l2_tracker.h" #include "crypto/hash.h" @@ -43,6 +44,7 @@ struct TransactionReviewSession { class L2Tracker { private: std::shared_ptr rewards_contract; + std::shared_ptr pool_contract; std::vector state_history; std::unordered_map oxen_to_ethereum_block_heights; // Maps Oxen block height to Ethereum block height uint64_t latest_oxen_block; @@ -77,7 +79,8 @@ class L2Tracker { std::vector get_block_transactions(); private: - static std::string get_contract_address(const cryptonote::network_type nettype); + static std::string get_rewards_contract_address(const cryptonote::network_type nettype); + static std::string get_pool_contract_address(const cryptonote::network_type nettype); void get_review_transactions(); void populate_review_transactions(std::shared_ptr session); bool service_node = true; diff --git a/src/l2_tracker/pool_contract.cpp b/src/l2_tracker/pool_contract.cpp new file mode 100644 index 0000000000..2918258d2f --- /dev/null +++ b/src/l2_tracker/pool_contract.cpp @@ -0,0 +1,13 @@ +#include "pool_contract.h" + +PoolContract::PoolContract(const std::string& _contractAddress, std::shared_ptr _provider) + : contractAddress(_contractAddress), provider(std::move(_provider)) {} + +RewardRateResponse PoolContract::RewardRate(uint64_t timestamp) { + //uint256_t reward = provider->callContractFunction(contractAddress, "rewardRate", timestamp); + // TODO sean get this from the contract + // Fetch the reward rate from the smart contract + uint64_t reward = 16500000000; + return RewardRateResponse{timestamp, reward}; +} + diff --git a/src/l2_tracker/pool_contract.h b/src/l2_tracker/pool_contract.h new file mode 100644 index 0000000000..fbfe47062f --- /dev/null +++ b/src/l2_tracker/pool_contract.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +#include + +class RewardRateResponse { +public: + uint64_t timestamp; + uint64_t reward; + RewardRateResponse(uint64_t _timestamp, uint64_t _reward) : timestamp(_timestamp), reward(_reward) {} +}; + +class PoolContract { +public: + PoolContract(const std::string& _contractAddress, std::shared_ptr _provider); + RewardRateResponse RewardRate(uint64_t timestamp); + +private: + std::string contractAddress; + std::shared_ptr provider; +}; + + diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 7d07f31862..7130e97c92 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -16,8 +16,7 @@ TransactionType RewardsLogEntry::getLogType() const { // keccak256('NewServiceNode(uint64,address,(uint256,uint256),(uint256,uint256,uint256,uint16),(address,uint256)[])') if (topics[0] == "0xe82ed1bfc15e6602fba1a19273171c8a63c1d40b0e0117be4598167b8655498f") { return TransactionType::NewServiceNode; - // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') - } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { + // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { return TransactionType::ServiceNodeLeaveRequest; // keccak256('ServiceNodeRemoval(uint64,address,uint256,(uint256,uint256))') } else if (topics[0] == "0x130a7be04ef1f87b2b436f68f389bf863ee179b95399a3a8444196fab7a4e54c") { From 9a989f9addc0d05afbc518fc0d4f53d421dd6094 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:33:17 +1000 Subject: [PATCH 63/97] Undo unneeded churn on error strings to minimise PR changes --- src/blockchain_db/lmdb/db_lmdb.cpp | 104 ++++++++++++++--------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index f1eeddfbcd..f34de706ea 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1020,7 +1020,7 @@ uint64_t BlockchainLMDB::add_transaction_data( .c_str())); } else if (result != MDB_NOTFOUND) { throw1(DB_ERROR(lmdb_error( - "error checking if tx index exists for tx hash " + tools::type_to_hex(tx_hash) + + "Error checking if tx index exists for tx hash " + tools::type_to_hex(tx_hash) + ": ", result))); } @@ -1137,7 +1137,7 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const result = mdb_cursor_del(m_cur_txs_prunable_tip, 0); if (result) throw1(DB_ERROR( - lmdb_error("error adding removal of tx id to db transaction", result).c_str())); + lmdb_error("Error adding removal of tx id to db transaction", result).c_str())); } if (tx.version >= cryptonote::txversion::v2_ringct) { @@ -1310,12 +1310,12 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in throw0(DB_ERROR("Unexpected: global output index not found in m_output_txs")); } else if (result) { throw1(DB_ERROR( - lmdb_error("error adding removal of output tx to db transaction", result).c_str())); + lmdb_error("Error adding removal of output tx to db transaction", result).c_str())); } result = mdb_cursor_del(m_cur_output_txs, 0); if (result) throw0(DB_ERROR(lmdb_error( - std::string("error deleting output index ") + std::string("Error deleting output index ") .append(std::to_string(out_index).append(": ")) .c_str(), result) @@ -1325,7 +1325,7 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in result = mdb_cursor_del(m_cur_output_amounts, 0); if (result) throw0(DB_ERROR(lmdb_error( - std::string("error deleting amount for output index ") + std::string("Error deleting amount for output index ") .append(std::to_string(out_index).append(": ")) .c_str(), result) @@ -1347,7 +1347,7 @@ void BlockchainLMDB::prune_outputs(uint64_t amount) { if (result == MDB_NOTFOUND) return; if (result) - throw0(DB_ERROR(lmdb_error("error looking up outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error looking up outputs: ", result).c_str())); // gather output ids mdb_size_t num_elems; @@ -1363,23 +1363,23 @@ void BlockchainLMDB::prune_outputs(uint64_t amount) { if (result == MDB_NOTFOUND) break; if (result) - throw0(DB_ERROR(lmdb_error("error counting outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error counting outputs: ", result).c_str())); } if (output_ids.size() != num_elems) throw0(DB_ERROR("Unexpected number of outputs")); result = mdb_cursor_del(m_cur_output_amounts, MDB_NODUPDATA); if (result) - throw0(DB_ERROR(lmdb_error("error deleting outputs: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error deleting outputs: ", result).c_str())); for (uint64_t output_id : output_ids) { MDB_val_set(v, output_id); result = mdb_cursor_get(m_cur_output_txs, (MDB_val*)&zerokval, &v, MDB_GET_BOTH); if (result) - throw0(DB_ERROR(lmdb_error("error looking up output: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error looking up output: ", result).c_str())); result = mdb_cursor_del(m_cur_output_txs, 0); if (result) - throw0(DB_ERROR(lmdb_error("error deleting output: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error deleting output: ", result).c_str())); } } @@ -1395,7 +1395,7 @@ void BlockchainLMDB::add_spent_key(const crypto::key_image& k_image) { if (result == MDB_KEYEXIST) throw1(KEY_IMAGE_EXISTS("Attempting to add spent key image that's already in the db")); else - throw1(DB_ERROR(lmdb_error("error adding spent key image to db transaction: ", result) + throw1(DB_ERROR(lmdb_error("Error adding spent key image to db transaction: ", result) .c_str())); } } @@ -1410,12 +1410,12 @@ void BlockchainLMDB::remove_spent_key(const crypto::key_image& k_image) { MDB_val k = {sizeof(k_image), (void*)&k_image}; auto result = mdb_cursor_get(m_cur_spent_keys, (MDB_val*)&zerokval, &k, MDB_GET_BOTH); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("error finding spent key to remove", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding spent key to remove", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_spent_keys, 0); if (result) throw1(DB_ERROR( - lmdb_error("error adding removal of key image to db transaction", result) + lmdb_error("Error adding removal of key image to db transaction", result) .c_str())); } } @@ -2001,18 +2001,18 @@ void BlockchainLMDB::add_txpool_tx( throw1(DB_ERROR("Attempting to add txpool tx metadata that's already in the db")); else throw1(DB_ERROR( - lmdb_error("error adding txpool tx metadata to db transaction: ", result) + lmdb_error("Error adding txpool tx metadata to db transaction: ", result) .c_str())); } MDB_val_sized(blob_val, blob); if (blob_val.mv_size == 0) - throw1(DB_ERROR("error adding txpool tx blob: tx is present, but data is empty")); + throw1(DB_ERROR("Error adding txpool tx blob: tx is present, but data is empty")); if (auto result = mdb_cursor_put(m_cur_txpool_blob, &k, &blob_val, MDB_NODUPDATA)) { if (result == MDB_KEYEXIST) throw1(DB_ERROR("Attempting to add txpool tx blob that's already in the db")); else throw1(DB_ERROR( - lmdb_error("error adding txpool tx blob to db transaction: ", result).c_str())); + lmdb_error("Error adding txpool tx blob to db transaction: ", result).c_str())); } } @@ -2028,11 +2028,11 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash& txid, const txpool_tx_ MDB_val v; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); if (result != 0) - throw1(DB_ERROR(lmdb_error("error finding txpool tx meta to update: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta to update: ", result).c_str())); result = mdb_cursor_del(m_cur_txpool_meta, 0); if (result) throw1(DB_ERROR( - lmdb_error("error adding removal of txpool tx metadata to db transaction: ", result) + lmdb_error("Error adding removal of txpool tx metadata to db transaction: ", result) .c_str())); v = MDB_val({sizeof(meta), (void*)&meta}); if ((result = mdb_cursor_put(m_cur_txpool_meta, &k, &v, MDB_NODUPDATA)) != 0) { @@ -2040,7 +2040,7 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash& txid, const txpool_tx_ throw1(DB_ERROR("Attempting to add txpool tx metadata that's already in the db")); else throw1(DB_ERROR( - lmdb_error("error adding txpool tx metadata to db transaction: ", result) + lmdb_error("Error adding txpool tx metadata to db transaction: ", result) .c_str())); } } @@ -2096,7 +2096,7 @@ bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const { MDB_val k = {sizeof(txid), (void*)&txid}; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("error finding txpool tx meta: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); return result != MDB_NOTFOUND; } @@ -2111,24 +2111,24 @@ void BlockchainLMDB::remove_txpool_tx(const crypto::hash& txid) { MDB_val k = {sizeof(txid), (void*)&txid}; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("error finding txpool tx meta to remove: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta to remove: ", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_txpool_meta, 0); if (result) throw1(DB_ERROR(lmdb_error( - "error adding removal of txpool tx metadata to db " + "Error adding removal of txpool tx metadata to db " "transaction: ", result) .c_str())); } result = mdb_cursor_get(m_cur_txpool_blob, &k, NULL, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) - throw1(DB_ERROR(lmdb_error("error finding txpool tx blob to remove: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx blob to remove: ", result).c_str())); if (!result) { result = mdb_cursor_del(m_cur_txpool_blob, 0); if (result) throw1(DB_ERROR( - lmdb_error("error adding removal of txpool tx blob to db transaction: ", result) + lmdb_error("Error adding removal of txpool tx blob to db transaction: ", result) .c_str())); } } @@ -2146,7 +2146,7 @@ bool BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta if (result == MDB_NOTFOUND) return false; if (result != 0) - throw1(DB_ERROR(lmdb_error("error finding txpool tx meta: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); meta = *(const txpool_tx_meta_t*)v.mv_data; return true; @@ -2165,10 +2165,10 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, std::string& b if (result == MDB_NOTFOUND) return false; if (result != 0) - throw1(DB_ERROR(lmdb_error("error finding txpool tx blob: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx blob: ", result).c_str())); if (v.mv_size == 0) - throw1(DB_ERROR("error finding txpool tx blob: tx is present, but data is empty")); + throw1(DB_ERROR("Error finding txpool tx blob: tx is present, but data is empty")); bd.assign(reinterpret_cast(v.mv_data), v.mv_size); return true; @@ -2395,7 +2395,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable_tip, &kp, &vp, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("error looking for transaction prunable data: ", result) + lmdb_error("Error looking for transaction prunable data: ", result) .c_str())); if (result == MDB_NOTFOUND) log::error( @@ -2409,7 +2409,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_put(c_txs_prunable_tip, &kp, &vp, 0); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("error looking for transaction prunable data: ", result) + lmdb_error("Error looking for transaction prunable data: ", result) .c_str())); } } @@ -2419,7 +2419,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("error looking for transaction prunable data: ", result) + lmdb_error("Error looking for transaction prunable data: ", result) .c_str())); if (mode == prune_mode_check) { if (result != MDB_NOTFOUND) @@ -2457,7 +2457,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { result = mdb_cursor_get(c_txs_prunable, &kp, &v, MDB_SET); if (result && result != MDB_NOTFOUND) throw0(DB_ERROR( - lmdb_error("error looking for transaction prunable data: ", result) + lmdb_error("Error looking for transaction prunable data: ", result) .c_str())); if (result == MDB_NOTFOUND) log::error( @@ -2756,7 +2756,7 @@ T BlockchainLMDB::get_and_convert_block_blob_from_height(uint64_t height) const .append(" failed -- block not in db") .c_str())); else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a block from the db")); + throw0(DB_ERROR("Error attempting to retrieve a block from the db")); std::string_view blob{reinterpret_cast(value.mv_data), value.mv_size}; @@ -2801,7 +2801,7 @@ uint64_t BlockchainLMDB::get_block_height(const crypto::hash& h) const { "Attempted to retrieve non-existent block height from hash " + tools::type_to_hex(h))); else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a block height from the db")); + throw0(DB_ERROR("Error attempting to retrieve a block height from the db")); blk_height* bhp = (blk_height*)key.mv_data; uint64_t ret = bhp->bh_height; @@ -2834,7 +2834,7 @@ uint64_t BlockchainLMDB::get_block_timestamp(const uint64_t& height) const { .append(" failed -- timestamp not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a timestamp from the db")); + throw0(DB_ERROR("Error attempting to retrieve a timestamp from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_timestamp; @@ -2893,7 +2893,7 @@ std::vector BlockchainLMDB::get_block_cumulative_rct_outputs( } if (result) throw0(DB_ERROR(lmdb_error( - "error attempting to retrieve rct distribution from the " + "Error attempting to retrieve rct distribution from the " "db: ", result) .c_str())); @@ -2934,7 +2934,7 @@ size_t BlockchainLMDB::get_block_weight(const uint64_t& height) const { .append(" failed -- block size not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a block size from the db")); + throw0(DB_ERROR("Error attempting to retrieve a block size from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; size_t ret = bi->bi_weight; @@ -2985,7 +2985,7 @@ std::vector BlockchainLMDB::get_block_info_64bit_fields( } if (result) throw0(DB_ERROR( - lmdb_error("error attempting to retrieve block_info from the db: ", result) + lmdb_error("Error attempting to retrieve block_info from the db: ", result) .c_str())); } ret.push_back( @@ -3069,7 +3069,7 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t& .append(" failed -- difficulty not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a cumulative difficulty from the db")); + throw0(DB_ERROR("Error attempting to retrieve a cumulative difficulty from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; difficulty_type ret = bi->bi_diff; @@ -3106,7 +3106,7 @@ uint64_t BlockchainLMDB::get_block_already_generated_coins(const uint64_t& heigh .append(" failed -- block size not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a total generated coins from the db")); + throw0(DB_ERROR("Error attempting to retrieve a total generated coins from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_coins; @@ -3128,7 +3128,7 @@ uint64_t BlockchainLMDB::get_block_long_term_weight(const uint64_t& height) cons .append(" failed -- block info not in db") .c_str())); } else if (get_result) - throw0(DB_ERROR("error attempting to retrieve a long term block weight from the db")); + throw0(DB_ERROR("Error attempting to retrieve a long term block weight from the db")); mdb_block_info* bi = (mdb_block_info*)result.mv_data; uint64_t ret = bi->bi_long_term_block_weight; @@ -3151,7 +3151,7 @@ crypto::hash BlockchainLMDB::get_block_hash_from_height(const uint64_t& height) .c_str())); } else if (get_result) throw0(DB_ERROR( - lmdb_error("error attempting to retrieve a block hash from the db: ", get_result) + lmdb_error("Error attempting to retrieve a block hash from the db: ", get_result) .c_str())); mdb_block_info* bi = (mdb_block_info*)result.mv_data; @@ -3571,7 +3571,7 @@ output_data_t BlockchainLMDB::get_output_key( std::to_string(amount) + ", index " + std::to_string(index)) .c_str())); else if (get_result) - throw0(DB_ERROR("error attempting to retrieve an output pubkey from the db")); + throw0(DB_ERROR("Error attempting to retrieve an output pubkey from the db")); output_data_t ret; if (amount == 0) { @@ -4498,7 +4498,7 @@ void BlockchainLMDB::get_output_key( } else if (get_result) throw0(DB_ERROR( lmdb_error( - "error attempting to retrieve an output pubkey from the db", get_result) + "Error attempting to retrieve an output pubkey from the db", get_result) .c_str())); if (amount == 0) { @@ -4538,7 +4538,7 @@ void BlockchainLMDB::get_output_tx_and_index( throw1(OUTPUT_DNE("Attempting to get output by index, but key does not exist")); else if (get_result) throw0(DB_ERROR( - lmdb_error("error attempting to retrieve an output from the db", get_result) + lmdb_error("Error attempting to retrieve an output from the db", get_result) .c_str())); const outkey* okp = (const outkey*)v.mv_data; @@ -4785,7 +4785,7 @@ void BlockchainLMDB::add_alt_block( if (result == MDB_KEYEXIST) throw1(DB_ERROR("Attempting to add alternate block that's already in the db")); else - throw1(DB_ERROR(lmdb_error("error adding alternate block to db transaction: ", result) + throw1(DB_ERROR(lmdb_error("Error adding alternate block to db transaction: ", result) .c_str())); } } @@ -4809,7 +4809,7 @@ bool BlockchainLMDB::get_alt_block( if (result) throw0(DB_ERROR(lmdb_error( - "error attempting to retrieve alternate block " + + "Error attempting to retrieve alternate block " + tools::type_to_hex(blkid) + " from the db: ", result) .c_str())); @@ -4830,14 +4830,14 @@ void BlockchainLMDB::remove_alt_block(const crypto::hash& blkid) { int result = mdb_cursor_get(m_cur_alt_blocks, &k, &v, MDB_SET); if (result) throw0(DB_ERROR(lmdb_error( - "error locating alternate block " + tools::type_to_hex(blkid) + + "Error locating alternate block " + tools::type_to_hex(blkid) + " in the db: ", result) .c_str())); result = mdb_cursor_del(m_cur_alt_blocks, 0); if (result) throw0(DB_ERROR(lmdb_error( - "error deleting alternate block " + tools::type_to_hex(blkid) + + "Error deleting alternate block " + tools::type_to_hex(blkid) + " from the db: ", result) .c_str())); @@ -4869,7 +4869,7 @@ void BlockchainLMDB::drop_alt_blocks() { auto result = mdb_drop(*txn_ptr, m_alt_blocks, 0); if (result) - throw1(DB_ERROR(lmdb_error("error dropping alternative blocks: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error dropping alternative blocks: ", result).c_str())); TXN_POSTFIX_SUCCESS(); } @@ -4878,7 +4878,7 @@ bool BlockchainLMDB::is_read_only() const { unsigned int flags; auto result = mdb_env_get_flags(m_env, &flags); if (result) - throw0(DB_ERROR(lmdb_error("error getting database environment info: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Error getting database environment info: ", result).c_str())); if (flags & MDB_RDONLY) return true; @@ -6794,10 +6794,10 @@ bool BlockchainLMDB::remove_service_node_proof(const crypto::public_key& pubkey) if (result == MDB_NOTFOUND) return false; if (result != MDB_SUCCESS) - throw0(DB_ERROR(lmdb_error("error finding service node proof to remove", result))); + throw0(DB_ERROR(lmdb_error("Error finding service node proof to remove", result))); result = mdb_cursor_del(m_cursors->service_node_proofs, 0); if (result) - throw0(DB_ERROR(lmdb_error("error remove service node proof", result))); + throw0(DB_ERROR(lmdb_error("Error remove service node proof", result))); return true; } From d3e97588c600315191153a407ec9a2080c1b885f Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:37:36 +1000 Subject: [PATCH 64/97] Fix eth_mapping_table_count typo adding not operator on equality --- src/blockchain_db/sqlite/db_sqlite.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index e33971153f..19f52b139a 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -181,7 +181,7 @@ void BlockchainSQLite::upgrade_schema() { const auto eth_mapping_table_count = prepared_get( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND " "name='eth_mapping';"); - if (!eth_mapping_table_count == 0) { + if (eth_mapping_table_count == 0) { log::info(logcat, "Adding eth mapping table to batching db"); auto& netconf = get_config(m_nettype); SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE}; From 5b503981ae5032b249d74dee0001fba34ea806eb Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:40:41 +1000 Subject: [PATCH 65/97] Avoid fmt::format when raw string literal suffices --- src/blockchain_db/sqlite/db_sqlite.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index 19f52b139a..db5a56520f 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -185,8 +185,7 @@ void BlockchainSQLite::upgrade_schema() { log::info(logcat, "Adding eth mapping table to batching db"); auto& netconf = get_config(m_nettype); SQLite::Transaction transaction{db, SQLite::TransactionBehavior::IMMEDIATE}; - db.exec(fmt::format( - R"( + db.exec(R"( CREATE TABLE eth_mapping( oxen_address VARCHAR NOT NULL, eth_address VARCHAR NOT NULL, @@ -196,7 +195,7 @@ void BlockchainSQLite::upgrade_schema() { ); CREATE INDEX eth_mapping_eth_address_idx ON eth_mapping(eth_address); - )")); + )"); } const auto archive_table_count = prepared_get( From f1b5f119dff07b3ad76c0fbf393c4b87cc27a586 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:42:41 +1000 Subject: [PATCH 66/97] Calculate a return value for BlockchainSQLite::update_sn_rewards_address --- src/blockchain_db/sqlite/db_sqlite.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index db5a56520f..14617ddf7f 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -326,8 +326,8 @@ bool BlockchainSQLite::update_sn_rewards_address(const std::string& oxen_address "UPDATE batched_payments_accrued SET address = ? WHERE address = ?" " ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount" ); - db::exec_query(update_address, tools::type_to_hex(eth_address), oxen_address); - return true; + bool result = db::exec_query(update_address, tools::type_to_hex(eth_address), oxen_address) > 0; + return result; } bool BlockchainSQLite::add_sn_rewards(const std::vector& payments) { From 0e5075a35ff84df4dc4588c572d1bc67b370608a Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:46:18 +1000 Subject: [PATCH 67/97] Fix converting of lvalue to 'const& string' from get_address_str --- src/blockchain_db/sqlite/db_sqlite.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index 14617ddf7f..a164c779f4 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -342,7 +342,7 @@ bool BlockchainSQLite::add_sn_rewards(const std::vector(payment.address_info.address.modulus(netconf.BATCHING_INTERVAL)); auto amt = static_cast(payment.amount); - const auto& address_str = get_address_str(payment); + const auto address_str = get_address_str(payment); log::trace( logcat, "Adding record for SN reward contributor {} to database with amount {}", @@ -362,7 +362,7 @@ bool BlockchainSQLite::subtract_sn_rewards( "UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?"); for (auto& payment : payments) { - const auto& address_str = get_address_str(payment); + const auto address_str = get_address_str(payment); auto result = db::exec_query(update_payment, static_cast(payment.amount), address_str); if (!result) { @@ -794,7 +794,7 @@ bool BlockchainSQLite::save_payments( std::lock_guard a_s_lock{address_str_cache_mutex}; for (const auto& payment : paid_amounts) { - const auto& address_str = get_address_str(payment); + const auto address_str = get_address_str(payment); if (auto maybe_amount = db::exec_and_maybe_get(select_sum, address_str)) { // Truncate the thousanths amount to an atomic OXEN: auto amount = static_cast(*maybe_amount) / BATCH_REWARD_FACTOR * From c54eb8dcccd88b1dab9bff99dd02ba0cfac3b0da Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 14:54:40 +1000 Subject: [PATCH 68/97] Interim fix `initial_data_set` race by locking in BLS agg Also since nodes are untrusted this has a pretty big implication on the current approach. We can't necessarily trust the initial data as the reference. We more likely have to actually aggregate the responses and use the majority. This should be fix in a polish-pass of this BLS integration work. --- src/bls/bls_aggregator.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 1132d14594..35bfd9b69c 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -58,12 +58,16 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a // Data contains -> status, address, amount, height, bls_pubkey, signed message, signature uint64_t current_amount = std::stoull(data[2]); uint64_t current_height = std::stoull(data[3]); + + signers_mutex.lock(); if (!initial_data_set) { amount = current_amount; height = current_height; signed_message = data[5]; initial_data_set = true; } + signers_mutex.unlock(); + if (data[1] != lower_eth_address || current_amount != amount || current_height != height || data[5] != signed_message) { // Log if the current data doesn't match the first set oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected address: {}, amount: {}, height: {} signed message: {}. Received address: {} amount: {}, height: {} signed_message: {}.", data[4], lower_eth_address, amount, height, signed_message, data[1], current_amount, current_height, data[5]); @@ -104,10 +108,12 @@ aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { if (data[0] == "200") { // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature + signers_mutex.lock(); if (!initial_data_set) { signed_message = data[3]; initial_data_set = true; } + signers_mutex.unlock(); if (data[1] != bls_key || data[3] != signed_message) { // Log if the current data doesn't match the first set oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected bls_key: {}, signed message: {}. Received bls_key: {}, signed_message: {}.", data[2], bls_key, signed_message, data[1], data[3]); @@ -148,10 +154,13 @@ aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls if (data[0] == "200") { // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature + signers_mutex.lock(); if (!initial_data_set) { signed_message = data[3]; initial_data_set = true; } + signers_mutex.unlock(); + if (data[1] != bls_key || data[3] != signed_message) { // Log if the current data doesn't match the first set oxen::log::warning(logcat, "Mismatch in data from node with bls pubkey {}. Expected bls_key: {}, signed message: {}. Received bls_key: {}, signed_message: {}.", data[2], bls_key, signed_message, data[1], data[3]); From 5d768f7600b27cd00a831d46321680e09f00c7d8 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 15:00:11 +1000 Subject: [PATCH 69/97] Remove unused BLSaggregator destructor --- src/bls/bls_aggregator.cpp | 3 --- src/bls/bls_aggregator.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 35bfd9b69c..d36b4eced9 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -8,9 +8,6 @@ BLSAggregator::BLSAggregator(service_nodes::service_node_list& _snl, std::shared : service_node_list(_snl), omq(std::move(_omq)), bls_signer(std::move(_bls_signer)) { } -BLSAggregator::~BLSAggregator() { -} - std::vector> BLSAggregator::getPubkeys() { std::vector> pubkeys; std::mutex pubkeys_mutex; diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index ac6d10b03c..5eb2d9d093 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -58,7 +58,6 @@ class BLSAggregator { public: BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer); - ~BLSAggregator(); std::vector> getPubkeys(); aggregateWithdrawalResponse aggregateRewards(const std::string& address); From ba6f231e2d8ac83b77b0c54c126256b666aa0bd6 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 29 Apr 2024 15:03:09 +1000 Subject: [PATCH 70/97] Move BLS headers into impl file for BLSAggregator --- src/bls/bls_aggregator.cpp | 14 ++++++++++++++ src/bls/bls_aggregator.h | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index d36b4eced9..3f9d5498d7 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -2,6 +2,20 @@ #include "logging/oxen_logger.h" +#define BLS_ETH +#define MCLBN_FP_UNIT_SIZE 4 +#define MCLBN_FR_UNIT_SIZE 4 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#include +#include +#undef MCLBN_NO_AUTOLINK +#pragma GCC diagnostic pop + static auto logcat = oxen::log::Cat("bls_aggregator"); BLSAggregator::BLSAggregator(service_nodes::service_node_list& _snl, std::shared_ptr _omq, std::shared_ptr _bls_signer) diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index 5eb2d9d093..d3e32f51e8 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -1,19 +1,5 @@ #pragma once -#define BLS_ETH -#define MCLBN_FP_UNIT_SIZE 4 -#define MCLBN_FR_UNIT_SIZE 4 - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#pragma GCC diagnostic ignored "-Wshadow" -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#include -#include -#undef MCLBN_NO_AUTOLINK -#pragma GCC diagnostic pop - #include #include From 7f2564619a25fbeea0fc10c17a1c5c078e756e8f Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 30 Apr 2024 16:17:38 +1000 Subject: [PATCH 71/97] Move BLSAggregator::processNodes into impl file by removing templated callback --- src/bls/bls_aggregator.cpp | 94 ++++++++++++++++++++++++++++++++++---- src/bls/bls_aggregator.h | 92 +++---------------------------------- 2 files changed, 91 insertions(+), 95 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 3f9d5498d7..5f189bf102 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -28,13 +28,12 @@ std::vector> BLSAggregator::getPubkeys() { processNodes( "bls.pubkey_request", - [this, &pubkeys, &pubkeys_mutex](bool success, std::vector data) { + [this, &pubkeys, &pubkeys_mutex](bool success, const std::vector& data) { if (success) { std::lock_guard lock(pubkeys_mutex); pubkeys.emplace_back(data[0], data[1]); } - }, - [](){} + } ); return pubkeys; @@ -44,6 +43,86 @@ blsRegistrationResponse BLSAggregator::registration(const std::string& senderEth return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(senderEthAddress, serviceNodePubkey), senderEthAddress, serviceNodePubkey, ""}; } +void BLSAggregator::processNodes(std::string_view request_name, std::function&)> callback, const std::optional& message) { + std::mutex connection_mutex; + std::condition_variable cv; + size_t active_connections = 0; + const size_t MAX_CONNECTIONS = 900; + + // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda + auto it = service_node_list.get_first_pubkey_iterator(); + auto end_it = service_node_list.get_end_pubkey_iterator(); + crypto::x25519_public_key x_pkey{0}; + uint32_t ip = 0; + uint16_t port = 0; + + while (it != end_it) { + service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { + x_pkey = proof.pubkey_x25519; + ip = proof.proof->public_ip; + port = proof.proof->qnet_port; + }); + + //{ + //std::unique_lock connection_lock(connection_mutex); + //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); + //} + // TODO sean epee is always little, this will not work on big endian host + boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); + oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + + { + std::lock_guard connection_lock(connection_mutex); + ++active_connections; + } + + auto conn = omq->connect_remote( + addr, + [](oxenmq::ConnectionID c) { + // Successfully connected + }, + [](oxenmq::ConnectionID c, std::string_view err) { + // Failed to connect + }, + oxenmq::AuthLevel::basic + ); + + if (message) { + omq->request( + conn, + request_name, + [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { + callback(success, data); + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + }, + *message + ); + } else { + omq->request( + conn, + request_name, + [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { + callback(success, data); + std::lock_guard connection_lock(connection_mutex); + --active_connections; + cv.notify_all(); + //omq->disconnect(c); + } + ); + } + + it = service_node_list.get_next_pubkey_iterator(it); + } + + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { + return active_connections == 0; + }); +} + aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& address) { bls::Signature aggSig; aggSig.clear(); @@ -62,7 +141,7 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a processNodes( "bls.get_reward_balance", - [this, &aggSig, &signers, &signers_mutex, &lower_eth_address, &amount, &height, &signed_message, &initial_data_set](bool success, std::vector data) { + [this, &aggSig, &signers, &signers_mutex, &lower_eth_address, &amount, &height, &signed_message, &initial_data_set](bool success, const std::vector& data) { if (success) { if (data[0] == "200") { @@ -97,7 +176,6 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a oxen::log::warning(logcat, "OMQ not successful when getting reward balance"); } }, - []{}, // No post processing for this call lower_eth_address ); const auto sig_str = bls_utils::SignatureToHex(aggSig); @@ -114,7 +192,7 @@ aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { processNodes( "bls.get_exit", - [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, std::vector data) { + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, const std::vector& data) { if (success) { if (data[0] == "200") { @@ -143,7 +221,6 @@ aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { oxen::log::warning(logcat, "OMQ not successful when requesting exit"); } }, - []{}, // No post processing for this call bls_key ); const auto sig_str = bls_utils::SignatureToHex(aggSig); @@ -160,7 +237,7 @@ aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls processNodes( "bls.get_liquidation", - [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, std::vector data) { + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, const std::vector& data) { if (success) { if (data[0] == "200") { @@ -190,7 +267,6 @@ aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls oxen::log::warning(logcat, "OMQ not successful when requesting liquidation"); } }, - []{}, // No post processing for this call bls_key ); const auto sig_str = bls_utils::SignatureToHex(aggSig); diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index d3e32f51e8..c702b4d7da 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -52,90 +52,10 @@ class BLSAggregator { blsRegistrationResponse registration(const std::string& senderEthAddress, const std::string& serviceNodePubkey) const; private: - // Goes out to the nodes on the network and makes oxenmq requests to all of them, when getting the reply - // `callback` will be called to process their reply and after everyone has been received it will then call - // `postProcess` - template - void processNodes(const std::string& request_name, Callback callback, PostProcess postProcess, const std::optional& message = std::nullopt) { - std::mutex connection_mutex; - std::condition_variable cv; - size_t active_connections = 0; - const size_t MAX_CONNECTIONS = 900; - - // TODO sean, change this so instead of using an iterator do a for_each_service_node_info_and proof and pass a lambda - auto it = service_node_list.get_first_pubkey_iterator(); - auto end_it = service_node_list.get_end_pubkey_iterator(); - crypto::x25519_public_key x_pkey{0}; - uint32_t ip = 0; - uint16_t port = 0; - - while (it != end_it) { - service_node_list.access_proof(it->first, [&x_pkey, &ip, &port](auto& proof) { - x_pkey = proof.pubkey_x25519; - ip = proof.proof->public_ip; - port = proof.proof->qnet_port; - }); - - //{ - //std::unique_lock connection_lock(connection_mutex); - //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - //} - // TODO sean epee is always little, this will not work on big endian host - boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); - oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; - - { - std::lock_guard connection_lock(connection_mutex); - ++active_connections; - } - - auto conn = omq->connect_remote( - addr, - [](oxenmq::ConnectionID c) { - // Successfully connected - }, - [](oxenmq::ConnectionID c, std::string_view err) { - // Failed to connect - }, - oxenmq::AuthLevel::basic - ); - - if (message) { - omq->request( - conn, - request_name, - [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { - callback(success, data); - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - }, - *message - ); - } else { - omq->request( - conn, - request_name, - [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { - callback(success, data); - std::lock_guard connection_lock(connection_mutex); - --active_connections; - cv.notify_all(); - //omq->disconnect(c); - } - ); - } - - it = service_node_list.get_next_pubkey_iterator(it); - } - - std::unique_lock connection_lock(connection_mutex); - cv.wait(connection_lock, [&active_connections] { - return active_connections == 0; - }); - - postProcess(); - } -// End Service Node List + // Goes out to the nodes on the network and makes oxenmq requests to all of them, when getting the + // reply `callback` will be called to process their reply + void processNodes( + std::string_view request_name, + std::function& data)> callback, + const std::optional& message = std::nullopt); }; From ab7a36eb82bec7d1193f95c2170a6ae3d9a86b34 Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 30 Apr 2024 16:56:14 +1000 Subject: [PATCH 72/97] Add addressing info to processNodes callback for diagnostics --- src/bls/bls_aggregator.cpp | 78 ++++++++++++++++++++++---------------- src/bls/bls_aggregator.h | 16 +++++--- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 5f189bf102..f0380e407b 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -1,6 +1,8 @@ #include "bls_aggregator.h" #include "logging/oxen_logger.h" +#include "common/string_util.h" +#include "bls/bls_utils.h" #define BLS_ETH #define MCLBN_FP_UNIT_SIZE 4 @@ -28,8 +30,8 @@ std::vector> BLSAggregator::getPubkeys() { processNodes( "bls.pubkey_request", - [this, &pubkeys, &pubkeys_mutex](bool success, const std::vector& data) { - if (success) { + [&pubkeys, &pubkeys_mutex](const BLSRequestResult& request_result, const std::vector& data) { + if (request_result.success) { std::lock_guard lock(pubkeys_mutex); pubkeys.emplace_back(data[0], data[1]); } @@ -43,7 +45,18 @@ blsRegistrationResponse BLSAggregator::registration(const std::string& senderEth return blsRegistrationResponse{bls_signer->getPublicKeyHex(), bls_signer->proofOfPossession(senderEthAddress, serviceNodePubkey), senderEthAddress, serviceNodePubkey, ""}; } -void BLSAggregator::processNodes(std::string_view request_name, std::function&)> callback, const std::optional& message) { +static void logNetworkRequestFailedWarning(const BLSRequestResult& result, std::string_view omq_cmd) +{ + std::string ip_string = epee::string_tools::get_ip_string_from_int32(result.ip); + oxen::log::warning( + logcat, + "OMQ network request to {} ({}:{}) failed when executing '{}'", + ip_string, + std::to_string(result.ip), + omq_cmd); +} + +void BLSAggregator::processNodes(std::string_view request_name, std::function&)> callback, const std::optional& message) { std::mutex connection_mutex; std::condition_variable cv; size_t active_connections = 0; @@ -52,24 +65,22 @@ void BLSAggregator::processNodes(std::string_view request_name, std::functionfirst, [&x_pkey, &ip, &port](auto& proof) { - x_pkey = proof.pubkey_x25519; - ip = proof.proof->public_ip; - port = proof.proof->qnet_port; + + BLSRequestResult request_result = {}; + service_node_list.access_proof(it->first, [&request_result](auto& proof) { + request_result.x_pkey = proof.pubkey_x25519; + request_result.ip = proof.proof->public_ip; + request_result.port = proof.proof->qnet_port; }); - + //{ //std::unique_lock connection_lock(connection_mutex); //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); //} // TODO sean epee is always little, this will not work on big endian host - boost::asio::ip::address_v4 address(oxenc::host_to_big(ip)); - oxenmq::address addr{"tcp://{}:{}"_format(address.to_string(), port), tools::view_guts(x_pkey)}; + std::string ip_string = epee::string_tools::get_ip_string_from_int32(request_result.ip); + oxenmq::address addr{"tcp://{}:{}"_format(ip_string, request_result.port), tools::view_guts(request_result.x_pkey)}; { std::lock_guard connection_lock(connection_mutex); @@ -91,8 +102,9 @@ void BLSAggregator::processNodes(std::string_view request_name, std::functionrequest( conn, request_name, - [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { - callback(success, data); + [&connection_mutex, &active_connections, &cv, callback, &request_result](bool success, std::vector data) { + request_result.success = success; + callback(request_result, data); std::lock_guard connection_lock(connection_mutex); --active_connections; cv.notify_all(); @@ -104,8 +116,9 @@ void BLSAggregator::processNodes(std::string_view request_name, std::functionrequest( conn, request_name, - [this, &connection_mutex, &active_connections, &cv, &conn, callback](bool success, std::vector data) { - callback(success, data); + [&connection_mutex, &active_connections, &cv, callback, &request_result](bool success, std::vector data) { + request_result.success = success; + callback(request_result, data); std::lock_guard connection_lock(connection_mutex); --active_connections; cv.notify_all(); @@ -139,10 +152,11 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a std::transform(lower_eth_address.begin(), lower_eth_address.end(), lower_eth_address.begin(), [](unsigned char c){ return std::tolower(c); }); + std::string_view cmd = "bls.get_reward_balance"; processNodes( - "bls.get_reward_balance", - [this, &aggSig, &signers, &signers_mutex, &lower_eth_address, &amount, &height, &signed_message, &initial_data_set](bool success, const std::vector& data) { - if (success) { + cmd, + [this, &aggSig, &signers, &signers_mutex, &lower_eth_address, &amount, &height, &signed_message, &initial_data_set, cmd](const BLSRequestResult& request_result, const std::vector& data) { + if (request_result.success) { if (data[0] == "200") { // Data contains -> status, address, amount, height, bls_pubkey, signed message, signature @@ -167,13 +181,12 @@ aggregateWithdrawalResponse BLSAggregator::aggregateRewards(const std::string& a std::lock_guard lock(signers_mutex); aggSig.add(external_signature); signers.push_back(data[4]); - } } else { oxen::log::warning(logcat, "Error message received when getting reward balance {} : {}", data[0], data[1]); } } else { - oxen::log::warning(logcat, "OMQ not successful when getting reward balance"); + logNetworkRequestFailedWarning(request_result, cmd); } }, lower_eth_address @@ -190,10 +203,11 @@ aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { std::string signed_message = ""; bool initial_data_set = false; + std::string_view cmd = "bls.get_exit"; processNodes( - "bls.get_exit", - [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, const std::vector& data) { - if (success) { + cmd, + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set, cmd](const BLSRequestResult& request_result, const std::vector& data) { + if (request_result.success) { if (data[0] == "200") { // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature @@ -218,7 +232,7 @@ aggregateExitResponse BLSAggregator::aggregateExit(const std::string& bls_key) { oxen::log::warning(logcat, "Error message received when requesting exit {} : {}", data[0], data[1]); } } else { - oxen::log::warning(logcat, "OMQ not successful when requesting exit"); + logNetworkRequestFailedWarning(request_result, cmd); } }, bls_key @@ -235,10 +249,11 @@ aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls std::string signed_message = ""; bool initial_data_set = false; + std::string_view cmd = "bls.get_liquidation"; processNodes( - "bls.get_liquidation", - [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set](bool success, const std::vector& data) { - if (success) { + cmd, + [this, &aggSig, &signers, &signers_mutex, &bls_key, &signed_message, &initial_data_set, cmd](const BLSRequestResult& request_result, const std::vector& data) { + if (request_result.success) { if (data[0] == "200") { // Data contains -> status, bls_pubkey (signer), bls_pubkey (node being removed), signed message, signature @@ -258,13 +273,12 @@ aggregateExitResponse BLSAggregator::aggregateLiquidation(const std::string& bls std::lock_guard lock(signers_mutex); aggSig.add(external_signature); signers.push_back(data[2]); - } } else { oxen::log::warning(logcat, "Error message received when requesting liquidation {} : {}", data[0], data[1]); } } else { - oxen::log::warning(logcat, "OMQ not successful when requesting liquidation"); + logNetworkRequestFailedWarning(request_result, cmd); } }, bls_key diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index c702b4d7da..e18a203d75 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -5,13 +5,8 @@ #include "cryptonote_core/service_node_list.h" #include "bls_signer.h" -#include "bls_utils.h" #include -#include -#include -#include "common/string_util.h" - struct aggregateExitResponse { std::string bls_key; std::string signed_message; @@ -36,6 +31,13 @@ struct blsRegistrationResponse { std::string service_node_signature; }; +struct BLSRequestResult { + crypto::x25519_public_key x_pkey; + uint32_t ip; + uint32_t port; + bool success; +}; + class BLSAggregator { private: std::shared_ptr bls_signer; @@ -56,6 +58,8 @@ class BLSAggregator { // reply `callback` will be called to process their reply void processNodes( std::string_view request_name, - std::function& data)> callback, + std::function& data)> + callback, const std::optional& message = std::nullopt); }; From ab99c5a7b02d37efc16491edd1c887fd831b9370 Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 30 Apr 2024 17:01:16 +1000 Subject: [PATCH 73/97] Use to talk to nodes in BLSAggregator --- src/bls/bls_aggregator.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index f0380e407b..0b8471142f 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -78,26 +78,12 @@ void BLSAggregator::processNodes(std::string_view request_name, std::function connection_lock(connection_mutex); //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); //} - // TODO sean epee is always little, this will not work on big endian host - std::string ip_string = epee::string_tools::get_ip_string_from_int32(request_result.ip); - oxenmq::address addr{"tcp://{}:{}"_format(ip_string, request_result.port), tools::view_guts(request_result.x_pkey)}; - { std::lock_guard connection_lock(connection_mutex); ++active_connections; } - - auto conn = omq->connect_remote( - addr, - [](oxenmq::ConnectionID c) { - // Successfully connected - }, - [](oxenmq::ConnectionID c, std::string_view err) { - // Failed to connect - }, - oxenmq::AuthLevel::basic - ); + auto conn = omq->connect_sn(tools::view_guts(request_result.x_pkey), oxenmq::AuthLevel::basic); if (message) { omq->request( conn, From a55b894e7ba58d50f85de3b9bb085e2dc7a61b7c Mon Sep 17 00:00:00 2001 From: doylet Date: Tue, 30 Apr 2024 17:45:38 +1000 Subject: [PATCH 74/97] Avoid expensive lock/race when iterating the SNs for BLS aggregation --- src/bls/bls_aggregator.cpp | 34 +++++++++-------------- src/bls/bls_aggregator.h | 6 ++-- src/cryptonote_core/service_node_list.cpp | 21 -------------- src/cryptonote_core/service_node_list.h | 32 ++++++++++++++++++--- 4 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/bls/bls_aggregator.cpp b/src/bls/bls_aggregator.cpp index 0b8471142f..4487473c8a 100644 --- a/src/bls/bls_aggregator.cpp +++ b/src/bls/bls_aggregator.cpp @@ -47,12 +47,12 @@ blsRegistrationResponse BLSAggregator::registration(const std::string& senderEth static void logNetworkRequestFailedWarning(const BLSRequestResult& result, std::string_view omq_cmd) { - std::string ip_string = epee::string_tools::get_ip_string_from_int32(result.ip); + std::string ip_string = epee::string_tools::get_ip_string_from_int32(result.sn_address.ip); oxen::log::warning( logcat, "OMQ network request to {} ({}:{}) failed when executing '{}'", ip_string, - std::to_string(result.ip), + std::to_string(result.sn_address.port), omq_cmd); } @@ -62,28 +62,22 @@ void BLSAggregator::processNodes(std::string_view request_name, std::function sn_nodes = {}; + service_node_list.copy_active_service_node_addresses(std::back_inserter(sn_nodes)); - BLSRequestResult request_result = {}; - service_node_list.access_proof(it->first, [&request_result](auto& proof) { - request_result.x_pkey = proof.pubkey_x25519; - request_result.ip = proof.proof->public_ip; - request_result.port = proof.proof->qnet_port; - }); - - //{ - //std::unique_lock connection_lock(connection_mutex); - //cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); - //} - { + for (const service_nodes::service_node_address& sn_address : sn_nodes) { + if (1) { std::lock_guard connection_lock(connection_mutex); ++active_connections; + } else { + // TODO(doyle): Rate limit + std::unique_lock connection_lock(connection_mutex); + cv.wait(connection_lock, [&active_connections] { return active_connections < MAX_CONNECTIONS; }); } - auto conn = omq->connect_sn(tools::view_guts(request_result.x_pkey), oxenmq::AuthLevel::basic); + BLSRequestResult request_result = {}; + request_result.sn_address = sn_address; + auto conn = omq->connect_sn(tools::view_guts(sn_address.x_pkey), oxenmq::AuthLevel::basic); if (message) { omq->request( conn, @@ -112,8 +106,6 @@ void BLSAggregator::processNodes(std::string_view request_name, std::function connection_lock(connection_mutex); diff --git a/src/bls/bls_aggregator.h b/src/bls/bls_aggregator.h index e18a203d75..ff477f3f8c 100644 --- a/src/bls/bls_aggregator.h +++ b/src/bls/bls_aggregator.h @@ -32,10 +32,8 @@ struct blsRegistrationResponse { }; struct BLSRequestResult { - crypto::x25519_public_key x_pkey; - uint32_t ip; - uint32_t port; - bool success; + service_nodes::service_node_address sn_address; + bool success; }; class BLSAggregator { diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index 9d517d6906..be0dd0fba2 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -4203,27 +4203,6 @@ crypto::public_key service_node_list::get_random_pubkey() { } } -// Function to get an iterator for the first service node info -service_nodes::service_nodes_infos_t::iterator service_node_list::get_first_pubkey_iterator() { - std::lock_guard lock{m_sn_mutex}; - return m_state.service_nodes_infos.begin(); -} - -// Function to get the next iterator in the list -service_nodes::service_nodes_infos_t::iterator service_node_list::get_next_pubkey_iterator(service_nodes::service_nodes_infos_t::iterator current_it) { - std::lock_guard lock{m_sn_mutex}; - if (current_it != m_state.service_nodes_infos.end()) { - return ++current_it; - } else { - return current_it; - } -} - -service_nodes::service_nodes_infos_t::iterator service_node_list::get_end_pubkey_iterator() { - std::lock_guard lock{m_sn_mutex}; - return m_state.service_nodes_infos.end(); -} - void service_node_list::initialize_x25519_map() { auto locks = tools::unique_locks(m_sn_mutex, m_x25519_map_mutex); diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 11e32a34c2..4ad9d50996 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -199,6 +199,13 @@ struct pulse_sort_key { END_SERIALIZE() }; +struct service_node_address +{ + crypto::x25519_public_key x_pkey; + uint32_t ip; + uint16_t port; +}; + struct service_node_info // registration information { enum class version_t : uint8_t { @@ -529,10 +536,6 @@ class service_node_list { // Returns a pubkey of a random service node in the service node list crypto::public_key get_random_pubkey(); - service_nodes::service_nodes_infos_t::iterator get_first_pubkey_iterator(); - service_nodes::service_nodes_infos_t::iterator get_next_pubkey_iterator(service_nodes::service_nodes_infos_t::iterator current_it); - service_nodes::service_nodes_infos_t::iterator get_end_pubkey_iterator(); - /// Initializes the x25519 map from current pubkey state; called during initialization void initialize_x25519_map(); @@ -574,6 +577,27 @@ class service_node_list { } } + /// Copies `service_node_addresses` of all currently active SNs into the given output + /// iterator + template + void copy_active_service_node_addresses(OutputIt out) const { + std::lock_guard lock{m_sn_mutex}; + for (const auto& pk_info : m_state.service_nodes_infos) { + if (!pk_info.second->is_active()) + continue; + auto it = proofs.find(pk_info.first); + if (it == proofs.end()) + continue; + if (const auto& x2_pk = it->second.pubkey_x25519) { + service_node_address address = {}; + address.x_pkey = x2_pk; + address.ip = it->second.proof ? it->second.proof->public_ip : 0; + address.port = it->second.proof ? it->second.proof->qnet_port : 0; + *out++ = address; + } + } + } + std::vector active_service_nodes_infos() const { return m_state.active_service_nodes_infos(); } From 741c11c123eedcb8573ebc7baf6cb85d5ee2689b Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 11:39:58 +1000 Subject: [PATCH 75/97] Take in string_view's where possible in bls_signer --- src/bls/bls_signer.cpp | 72 +++++++++++++++++++++++++++--------------- src/bls/bls_signer.h | 21 ++++-------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 0d804039a2..6956686018 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -1,7 +1,6 @@ #include "bls_signer.h" #include "bls_utils.h" #include "logging/oxen_logger.h" -#include "crypto/keccak.h" #include #include "ethyl/utils.hpp" #include @@ -29,7 +28,6 @@ BLSSigner::BLSSigner(const cryptonote::network_type nettype, fs::path key_filepa throw std::runtime_error(fmt::format("Failed to open output file for bls key {}", key_filepath.string())); out << secretKey; } - } void BLSSigner::initCurve() { @@ -41,39 +39,60 @@ void BLSSigner::initCurve() { mcl::bn::G1 gen; bool b; mcl::bn::mapToG1(&b, gen, 1); + blsPublicKey publicKey; - publicKey.v = *reinterpret_cast(&gen); + static_assert(sizeof(publicKey.v) == sizeof(gen), "We memcpy into a C structure hence sizes must be the same"); + std::memcpy(&publicKey.v, &gen, sizeof(gen)); blsSetGeneratorOfPublicKey(&publicKey); } -std::string BLSSigner::buildTag(const std::string_view& baseTag, uint32_t chainID, const std::string& contractAddress) { - // Check if contractAddress starts with "0x" prefix - std::string contractAddressOutput = contractAddress; - if (contractAddressOutput.substr(0, 2) == "0x") - contractAddressOutput = contractAddressOutput.substr(2); // remove "0x" - std::string concatenatedTag = "0x" + utils::toHexString(baseTag) + utils::padTo32Bytes(utils::decimalToHex(chainID), utils::PaddingDirection::LEFT) + contractAddressOutput; - return utils::toHexString(utils::hash(concatenatedTag)); +std::string BLSSigner::buildTag(std::string_view baseTag, uint32_t chainID, std::string_view contractAddress) { + std::string_view hexPrefix = "0x"; + std::string_view contractAddressOutput = utils::trimPrefix(contractAddress, hexPrefix); + std::string baseTagHex = utils::toHexString(baseTag); + std::string chainIDHex = utils::padTo32Bytes(utils::decimalToHex(chainID), utils::PaddingDirection::LEFT); + + std::string concatenatedTag; + concatenatedTag.reserve(hexPrefix.size() + baseTagHex.size() + chainIDHex.size() + contractAddressOutput.size()); + concatenatedTag.append(hexPrefix); + concatenatedTag.append(baseTagHex); + concatenatedTag.append(chainIDHex); + concatenatedTag.append(contractAddressOutput); + + std::array hash = utils::hash(concatenatedTag); + std::string result = utils::toHexString(hash); + return result; } -std::string BLSSigner::buildTag(const std::string_view& baseTag) { +std::string BLSSigner::buildTag(std::string_view baseTag) { return buildTag(baseTag, chainID, contractAddress); } -bls::Signature BLSSigner::signHash(const std::array& hash) { +bls::Signature BLSSigner::signHash(const crypto::bytes<32>& hash) { bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); return sig; } -std::string BLSSigner::proofOfPossession(const std::string& senderEthAddress, const std::string& serviceNodePubkey) { +std::string BLSSigner::proofOfPossession(std::string_view senderEthAddress, std::string_view serviceNodePubkey) { std::string fullTag = buildTag(proofOfPossessionTag, chainID, contractAddress); - std::string senderAddressOutput = senderEthAddress; - if (senderAddressOutput.substr(0, 2) == "0x") - senderAddressOutput = senderAddressOutput.substr(2); // remove "0x" - std::string message = "0x" + fullTag + getPublicKeyHex() + senderAddressOutput + utils::padTo32Bytes(serviceNodePubkey, utils::PaddingDirection::LEFT); + std::string_view hexPrefix = "0x"; + std::string_view senderAddressOutput = utils::trimPrefix(senderEthAddress, hexPrefix); + + // TODO(doyle): padTo32Bytes should take a string_view + std::string publicKeyHex = getPublicKeyHex(); + std::string serviceNodePubkeyHex = utils::padTo32Bytes(std::string(serviceNodePubkey), utils::PaddingDirection::LEFT); + + std::string message; + message.reserve(hexPrefix.size() + fullTag.size() + publicKeyHex.size() + senderAddressOutput.size() + serviceNodePubkeyHex.size()); + message.append(hexPrefix); + message.append(fullTag); + message.append(publicKeyHex); + message.append(senderAddressOutput); + message.append(serviceNodePubkeyHex); - const std::array hash = BLSSigner::hash(message); // Get the hash of the publickey + const crypto::bytes<32> hash = BLSSigner::hash(message); // Get the hash of the publickey bls::Signature sig; secretKey.signHash(sig, hash.data(), hash.size()); return bls_utils::SignatureToHex(sig); @@ -91,19 +110,22 @@ bls::PublicKey BLSSigner::getPublicKey() { return publicKey; } -std::array BLSSigner::hash(std::string in) { - return utils::hash(in); +crypto::bytes<32> BLSSigner::hash(std::string_view in) { + // TODO(doyle): hash should take in a string_view + crypto::bytes<32> result = {}; + result.data_ = utils::hash(std::string(in)); + return result; } -std::array BLSSigner::hashModulus(std::string message) { - std::array hash = BLSSigner::hash(message); +crypto::bytes<32> BLSSigner::hashModulus(std::string_view message) { + // TODO(doyle): hash should take in a string_view + crypto::bytes<32> hash = BLSSigner::hash(std::string(message)); mcl::bn::Fp x; x.clear(); x.setArrayMask(hash.data(), hash.size()); - std::array serialized_hash; + crypto::bytes<32> serialized_hash; uint8_t *hdst = serialized_hash.data(); - mclSize serializedSignatureSize = 32; - if (x.serialize(hdst, serializedSignatureSize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + if (x.serialize(hdst, serialized_hash.data_.max_size(), mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of x is zero"); return serialized_hash; } diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index cfd7d91077..beb9834273 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -14,15 +14,13 @@ #undef MCLBN_NO_AUTOLINK #pragma GCC diagnostic pop -#include -#include +#include "crypto/base.h" #include "cryptonote_config.h" #include "common/fs.h" class BLSSigner { private: bls::SecretKey secretKey; - uint32_t chainID; std::string contractAddress; @@ -31,25 +29,20 @@ class BLSSigner { public: BLSSigner(const cryptonote::network_type nettype, fs::path key_filepath); - bls::Signature signHash(const std::array& hash); - std::string proofOfPossession(const std::string& senderEthAddress, const std::string& serviceNodePubkey); + bls::Signature signHash(const crypto::bytes<32>& hash); + std::string proofOfPossession(std::string_view senderEthAddress, std::string_view serviceNodePubkey); std::string getPublicKeyHex(); bls::PublicKey getPublicKey(); - static std::string buildTag(const std::string_view& baseTag, uint32_t chainID, const std::string& contractAddress); - std::string buildTag(const std::string_view& baseTag); + static std::string buildTag(std::string_view baseTag, uint32_t chainID, std::string_view contractAddress); + std::string buildTag(std::string_view baseTag); - static std::array hash(std::string in); - static std::array hashModulus(std::string message); + static crypto::bytes<32> hash(std::string_view in); + static crypto::bytes<32> hashModulus(std::string_view message); static constexpr std::string_view proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"sv; static constexpr std::string_view rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"sv; static constexpr std::string_view removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"sv; static constexpr std::string_view liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"sv; - - -private: - -// END }; From 3472a6df1c34f1feef0cdbbbaeade350aa7adf4f Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 11:44:24 +1000 Subject: [PATCH 76/97] Make strings inline, remove unneeded sv suffix --- src/bls/bls_signer.cpp | 2 +- src/bls/bls_signer.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index 6956686018..c0401c1622 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -2,9 +2,9 @@ #include "bls_utils.h" #include "logging/oxen_logger.h" #include -#include "ethyl/utils.hpp" #include #include +#include static auto logcat = oxen::log::Cat("bls_signer"); diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index beb9834273..8241dbff59 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -40,9 +40,9 @@ class BLSSigner { static crypto::bytes<32> hash(std::string_view in); static crypto::bytes<32> hashModulus(std::string_view message); - static constexpr std::string_view proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"sv; - static constexpr std::string_view rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"sv; - static constexpr std::string_view removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"sv; - static constexpr std::string_view liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"sv; + static constexpr inline std::string_view proofOfPossessionTag = "BLS_SIG_TRYANDINCREMENT_POP"; + static constexpr inline std::string_view rewardTag = "BLS_SIG_TRYANDINCREMENT_REWARD"; + static constexpr inline std::string_view removalTag = "BLS_SIG_TRYANDINCREMENT_REMOVE"; + static constexpr inline std::string_view liquidateTag = "BLS_SIG_TRYANDINCREMENT_LIQUIDATE"; }; From 88f9df13ace9b4e6a9c0ad8f54cccfecbf5dae0e Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 11:57:46 +1000 Subject: [PATCH 77/97] Use UB safe impl of BLS primitive conversions, simplify header --- src/bls/bls_utils.cpp | 61 ++++++++++++++++++++++++++++++------------- src/bls/bls_utils.h | 27 +++++++------------ 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/bls/bls_utils.cpp b/src/bls/bls_utils.cpp index 14d9253325..5cdad72aa9 100644 --- a/src/bls/bls_utils.cpp +++ b/src/bls/bls_utils.cpp @@ -1,12 +1,26 @@ #include "bls_utils.h" #include -#include "ethyl/utils.hpp" +#include -std::string bls_utils::SignatureToHex(bls::Signature sig) { - mclSize serializedSignatureSize = 32; - std::vector serialized_signature(serializedSignatureSize*4); - uint8_t *dst = serialized_signature.data(); - const blsSignature* blssig = sig.getPtr(); +#define BLS_ETH +#define MCLBN_FP_UNIT_SIZE 4 +#define MCLBN_FR_UNIT_SIZE 4 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#include +#include +#undef MCLBN_NO_AUTOLINK +#pragma GCC diagnostic pop + +std::string bls_utils::SignatureToHex(const bls::Signature& sig) { + const mclSize serializedSignatureSize = 32; + std::array serialized_signature = {}; + char* dst = serialized_signature.data(); + const blsSignature* blssig = sig.getPtr(); const mcl::bn::G2* g2Point = reinterpret_cast(&blssig->v); mcl::bn::G2 g2Point2 = *g2Point; g2Point2.normalize(); @@ -21,19 +35,30 @@ std::string bls_utils::SignatureToHex(bls::Signature sig) { return oxenc::to_hex(serialized_signature.begin(), serialized_signature.end()); } -std::string bls_utils::PublicKeyToHex(bls::PublicKey publicKey) { - mclSize serializedPublicKeySize = 32; - std::vector serialized_pubkey(serializedPublicKeySize*2); - uint8_t *dst = serialized_pubkey.data(); - const blsPublicKey* pub = publicKey.getPtr(); - const mcl::bn::G1* g1Point = reinterpret_cast(&pub->v); - mcl::bn::G1 g1Point2 = *g1Point; - g1Point2.normalize(); - if (g1Point2.x.serialize(dst, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) +std::string bls_utils::PublicKeyToHex(const bls::PublicKey& publicKey) { + const mclSize KEY_SIZE = 32; + std::array serializedKeyHex = {}; + + char* dst = serializedKeyHex.data(); + const blsPublicKey* rawKey = publicKey.getPtr(); + + mcl::bn::G1 g1Point = {}; + g1Point.clear(); + + // NOTE: const_cast is legal because the original g1Point was not declared + // const + static_assert(sizeof(*g1Point.x.getUnit()) * g1Point.x.maxSize == sizeof(rawKey->v.x.d), + "We memcpy the key X,Y,Z component into G1 point's X,Y,Z component, hence, the sizes must match"); + std::memcpy(const_cast(g1Point.x.getUnit()), rawKey->v.x.d, sizeof(rawKey->v.x.d)); + std::memcpy(const_cast(g1Point.y.getUnit()), rawKey->v.y.d, sizeof(rawKey->v.y.d)); + std::memcpy(const_cast(g1Point.z.getUnit()), rawKey->v.z.d, sizeof(rawKey->v.z.d)); + g1Point.normalize(); + + if (g1Point.x.serialize(dst, KEY_SIZE, mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of x is zero"); - if (g1Point2.y.serialize(dst + serializedPublicKeySize, serializedPublicKeySize, mcl::IoSerialize | mcl::IoBigEndian) == 0) + if (g1Point.y.serialize(dst + KEY_SIZE, KEY_SIZE, mcl::IoSerialize | mcl::IoBigEndian) == 0) throw std::runtime_error("size of y is zero"); - return utils::toHexString(serialized_pubkey); + std::string result = oxenc::to_hex(serializedKeyHex.begin(), serializedKeyHex.end()); + return result; } - diff --git a/src/bls/bls_utils.h b/src/bls/bls_utils.h index 92daa3e59e..d761c25c60 100644 --- a/src/bls/bls_utils.h +++ b/src/bls/bls_utils.h @@ -1,22 +1,13 @@ #pragma once -#define BLS_ETH -#define MCLBN_FP_UNIT_SIZE 4 -#define MCLBN_FR_UNIT_SIZE 4 +#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#pragma GCC diagnostic ignored "-Wshadow" -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#include -#include -#undef MCLBN_NO_AUTOLINK -#pragma GCC diagnostic pop +namespace bls { +class PublicKey; +class Signature; +}; // namespace bls -namespace bls_utils -{ - std::string PublicKeyToHex(bls::PublicKey publicKey); - std::string SignatureToHex(bls::Signature sig); -// END -} +namespace bls_utils { +std::string PublicKeyToHex(const bls::PublicKey& publicKey); +std::string SignatureToHex(const bls::Signature& sig); +} // namespace bls_utils From b891d5e9396d989678ecc6fca55bd750468aaeb7 Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 12:34:27 +1000 Subject: [PATCH 78/97] Reuse string helpers in string tools for BLS OMQ input --- src/cryptonote_core/cryptonote_core.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 46c57f979c..59c35d52f0 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1105,12 +1105,11 @@ void core::init_oxenmq(const boost::program_options::variables_map& vm) { "Bad request: BLS rewards command should have one data part containing the address" "(received " + std::to_string(m.data.size()) + ")"); - std::string eth_address = std::string(m.data[0]); - if (eth_address.substr(0, 2) != "0x") { + + std::string eth_address = tools::lowercase_ascii_string(m.data[0]); + if (!tools::starts_with(eth_address, "0x")) { eth_address = "0x" + eth_address; } - std::transform(eth_address.begin(), eth_address.end(), eth_address.begin(), - [](unsigned char c){ return std::tolower(c); }); auto [batchdb_height, amount] = get_blockchain_storage().sqlite_db()->get_accrued_earnings(eth_address); if (amount == 0) m.send_reply("400", "Address has a zero balance in the database"); From 2ab16b04fcf3cbe773891468f3dfa92fb7b25bee Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 12:58:44 +1000 Subject: [PATCH 79/97] Avoid multiple locks and spurious heap allocs in bls_public_key_lookup --- src/cryptonote_core/service_node_list.cpp | 22 ++++++++++------------ src/cryptonote_core/service_node_list.h | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index be0dd0fba2..1abc7c83ce 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -4255,22 +4255,20 @@ std::string service_node_list::remote_lookup(std::string_view xpk) { return "tcp://" + epee::string_tools::get_ip_string_from_int32(ip) + ":" + std::to_string(port); } -crypto::public_key service_node_list::bls_public_key_lookup(crypto::bls_public_key bls_key) { - std::vector sn_pks; - auto sns = get_service_node_list_state(); - sn_pks.reserve(sns.size()); - for (const auto& sni : sns) - sn_pks.push_back(sni.pubkey); +crypto::public_key service_node_list::bls_public_key_lookup(const crypto::bls_public_key& bls_key) const { bool found = false; crypto::public_key found_pk; - for_each_service_node_info_and_proof( - sn_pks.begin(), sn_pks.end(), [&bls_key, &found, &found_pk](auto& pk, auto& sni, auto& proof) { - if (tools::view_guts(sni.bls_public_key) == tools::view_guts(bls_key)) { - found = true; - found_pk = pk; + { + std::lock_guard lock{m_sn_mutex}; + for (auto it : m_state.service_nodes_infos) { + const service_node_info *sn_info = it.second.get(); + if (tools::view_guts(sn_info->bls_public_key) == tools::view_guts(bls_key)) { + found = true; + found_pk = it.first; + break; } } - ); + } if (!found) { log::error(logcat, "Could not find bls key: {}", tools::type_to_hex(bls_key)); diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 4ad9d50996..3c0cec57da 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -637,7 +637,7 @@ class service_node_list { bool& my_uptime_proof_confirmation, crypto::x25519_public_key& x25519_pkey); - crypto::public_key bls_public_key_lookup(crypto::bls_public_key bls_key); + crypto::public_key bls_public_key_lookup(const crypto::bls_public_key& bls_key) const; void record_checkpoint_participation( crypto::public_key const& pubkey, uint64_t height, bool participated); From b9f7e6e84f2be3f62b36d6581029d1648c2de640 Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 13:56:43 +1000 Subject: [PATCH 80/97] Make some targets public due to header usage --- src/bls/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bls/CMakeLists.txt b/src/bls/CMakeLists.txt index 33fb29f437..0bbc9f097f 100644 --- a/src/bls/CMakeLists.txt +++ b/src/bls/CMakeLists.txt @@ -35,10 +35,10 @@ add_library(oxen_bls target_link_libraries(oxen_bls PUBLIC bls::bls256 - PRIVATE cncrypto - common cryptonote_basic + PRIVATE + common logging ethyl extra) From 82d585b194b4c026d4cbf2d6f6eec62dc6a07240 Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 16:35:00 +1000 Subject: [PATCH 81/97] Use the same techniques to load BLS keys as we do Ed25519 Prevents rewriting a bunch of the same code multiple times. More importantly, in our Ed25519 code, when we load keys, we ensure we - Move the buffer with secret data out of the stream - Deserialize into the struct - Secure memory wipe of the buffer Which is a bit more rigorous process for dealing with secrets that is important not to omit in the BLS process as well. --- src/bls/bls_signer.cpp | 30 ++++++++++++++++++------------ src/bls/bls_signer.h | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index c0401c1622..b0320486aa 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -1,32 +1,38 @@ #include "bls_signer.h" #include "bls_utils.h" +#include "common/file.h" +#include "common/string_util.h" #include "logging/oxen_logger.h" #include -#include #include #include +#include static auto logcat = oxen::log::Cat("bls_signer"); -BLSSigner::BLSSigner(const cryptonote::network_type nettype, fs::path key_filepath) { +BLSSigner::BLSSigner(const cryptonote::network_type nettype, const fs::path& key_filepath) { initCurve(); const auto config = get_config(nettype); chainID = config.ETHEREUM_CHAIN_ID; contractAddress = config.ETHEREUM_REWARDS_CONTRACT; - // This init function generates a secret key calling blsSecretKeySetByCSPRNG - secretKey.init(); if (fs::exists(key_filepath)) { oxen::log::info(logcat, "Loading bls key from: {}", key_filepath.string()); - fs::ifstream in{key_filepath, std::ios::in}; - if (!in.good()) - throw std::runtime_error(fmt::format("Failed to open input file for bls key {}", key_filepath.string())); - in >> secretKey; + + std::string key_bytes; + bool r = tools::slurp_file(key_filepath, key_bytes); + secretKey.setStr(key_bytes); + memwipe(key_bytes.data(), key_bytes.size()); + if (!r) + throw std::runtime_error( + fmt::format("Failed to read BLS key at: {}", key_filepath.string())); } else { + // This init function generates a secret key calling blsSecretKeySetByCSPRNG + secretKey.init(); oxen::log::info(logcat, "No bls key found, saving new key to: {}", key_filepath.string()); - fs::ofstream out{key_filepath, std::ios::out}; - if (!out.good()) - throw std::runtime_error(fmt::format("Failed to open output file for bls key {}", key_filepath.string())); - out << secretKey; + bool r = tools::dump_file(key_filepath, tools::view_guts(secretKey)); + if (!r) + throw std::runtime_error( + fmt::format("Failed to write BLS key to: {}", key_filepath.string())); } } diff --git a/src/bls/bls_signer.h b/src/bls/bls_signer.h index 8241dbff59..58a7b7d47d 100644 --- a/src/bls/bls_signer.h +++ b/src/bls/bls_signer.h @@ -27,7 +27,7 @@ class BLSSigner { void initCurve(); public: - BLSSigner(const cryptonote::network_type nettype, fs::path key_filepath); + BLSSigner(const cryptonote::network_type nettype, const fs::path& key_filepath); bls::Signature signHash(const crypto::bytes<32>& hash); std::string proofOfPossession(std::string_view senderEthAddress, std::string_view serviceNodePubkey); From 1fa58ccc452c2458829a62cfc59aeefac9ef8a28 Mon Sep 17 00:00:00 2001 From: doylet Date: Wed, 1 May 2024 17:39:59 +1000 Subject: [PATCH 82/97] Use correct sqlite target in core_tests to defer to static/sys deps accordingly --- tests/core_tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index 44e762a1cb..31cdf9d556 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -48,7 +48,7 @@ add_executable(core_tests target_link_libraries(core_tests PRIVATE - sqlite3 + SQLite::SQLite3 multisig cryptonote_core p2p From 54e9ca06b203546b152876c56d835cd6a24091a1 Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 2 May 2024 07:29:07 +1000 Subject: [PATCH 83/97] oxen-mq to stable --- external/oxen-mq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/oxen-mq b/external/oxen-mq index a27961d787..923a0a80bc 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit a27961d787c9065f2bf6da9d60d01dca2e125739 +Subproject commit 923a0a80bc533e0d63ae32e2f9f5d4f7f53a258e From 8fcacd036683fd0042bbe8ea942d4ba421740fcc Mon Sep 17 00:00:00 2001 From: doylet Date: Thu, 2 May 2024 17:00:26 +1000 Subject: [PATCH 84/97] Bad assertion in json_archive, stack is +=1 for each scope not 2 --- src/serialization/json_archive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index 2018b4b5cd..6215ce7b3f 100644 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -76,7 +76,7 @@ struct json_archiver : public serializer { struct nested_value { json_archiver& ar; ~nested_value() { - assert(ar.stack_.size() >= 2); + assert(ar.stack_.size() >= 1); ar.stack_.pop_back(); } From af973f249038ae0a9ace1e36419ace30d383cb73 Mon Sep 17 00:00:00 2001 From: doylet Date: Thu, 2 May 2024 17:01:44 +1000 Subject: [PATCH 85/97] Create the .oxen folder before we try create keys Creation of keys fail if the folder doesn't exist beforehand. --- src/cryptonote_core/cryptonote_core.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 59c35d52f0..c1cc6c4044 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -582,13 +582,6 @@ bool core::init( bool keep_alt_blocks = command_line::get_arg(vm, arg_keep_alt_blocks); bool keep_fakechain = command_line::get_arg(vm, arg_keep_fakechain); - r = init_service_keys(); - CHECK_AND_ASSERT_MES(r, false, "Failed to create or load service keys"); - if (m_service_node) { - // Only use our service keys for our service node if we are running in SN mode: - m_service_node_list.set_my_service_node_keys(&m_service_keys); - } - auto folder = m_config_folder; if (m_nettype == network_type::FAKECHAIN) folder /= "fake"; @@ -603,6 +596,13 @@ bool core::init( return false; } + r = init_service_keys(); + CHECK_AND_ASSERT_MES(r, false, "Failed to create or load service keys"); + if (m_service_node) { + // Only use our service keys for our service node if we are running in SN mode: + m_service_node_list.set_my_service_node_keys(&m_service_keys); + } + std::unique_ptr db(new_db()); if (!db) { log::error(logcat, "Failed to initialize a database"); From 23f3b44029687cf07a02be88175363ea41867ef1 Mon Sep 17 00:00:00 2001 From: doylet Date: Thu, 2 May 2024 17:02:39 +1000 Subject: [PATCH 86/97] Correctly de/serialise the BLS key as per API --- src/bls/bls_signer.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bls/bls_signer.cpp b/src/bls/bls_signer.cpp index b0320486aa..90c074d263 100644 --- a/src/bls/bls_signer.cpp +++ b/src/bls/bls_signer.cpp @@ -15,12 +15,15 @@ BLSSigner::BLSSigner(const cryptonote::network_type nettype, const fs::path& key const auto config = get_config(nettype); chainID = config.ETHEREUM_CHAIN_ID; contractAddress = config.ETHEREUM_REWARDS_CONTRACT; + + // NOTE: ioMode is taken from bls::SecretKey operator<< implementation + int blsIoMode = 16|bls::IoPrefix; if (fs::exists(key_filepath)) { oxen::log::info(logcat, "Loading bls key from: {}", key_filepath.string()); std::string key_bytes; bool r = tools::slurp_file(key_filepath, key_bytes); - secretKey.setStr(key_bytes); + secretKey.setStr(key_bytes, blsIoMode); memwipe(key_bytes.data(), key_bytes.size()); if (!r) throw std::runtime_error( @@ -29,7 +32,8 @@ BLSSigner::BLSSigner(const cryptonote::network_type nettype, const fs::path& key // This init function generates a secret key calling blsSecretKeySetByCSPRNG secretKey.init(); oxen::log::info(logcat, "No bls key found, saving new key to: {}", key_filepath.string()); - bool r = tools::dump_file(key_filepath, tools::view_guts(secretKey)); + + bool r = tools::dump_file(key_filepath, secretKey.getStr(blsIoMode)); if (!r) throw std::runtime_error( fmt::format("Failed to write BLS key to: {}", key_filepath.string())); From 1e94f0009cb798103c256160ebea969f94aeab78 Mon Sep 17 00:00:00 2001 From: doylet Date: Thu, 2 May 2024 17:06:46 +1000 Subject: [PATCH 87/97] Fix unused else branch of immutable_height check --- src/wallet/node_rpc_proxy.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7393222dd4..f2051df809 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -116,7 +116,6 @@ bool NodeRPCProxy::get_info() const { auto it_immutable_height = res.find("immutable_height"); if (it_immutable_height != res.end()) m_immutable_height = res.at("immutable_height").get(); - else m_get_info_time = now; m_height_time = now; } catch (const std::exception& e) { From ba8a66d0fd272a6deb1e3a1d9d459a641991c60a Mon Sep 17 00:00:00 2001 From: Sean Darcy Date: Thu, 2 May 2024 17:08:48 +1000 Subject: [PATCH 88/97] block rewards pulled from rewards pool smart contract --- src/blockchain_db/sqlite/db_sqlite.cpp | 6 ++-- src/cryptonote_config.h | 4 +-- src/cryptonote_core/blockchain.cpp | 45 ++++++++++++++++++-------- src/l2_tracker/l2_tracker.cpp | 4 +++ src/l2_tracker/l2_tracker.h | 2 ++ src/l2_tracker/pool_contract.cpp | 20 ++++++++---- src/l2_tracker/pool_contract.h | 2 +- 7 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/blockchain_db/sqlite/db_sqlite.cpp b/src/blockchain_db/sqlite/db_sqlite.cpp index e33971153f..478224c859 100644 --- a/src/blockchain_db/sqlite/db_sqlite.cpp +++ b/src/blockchain_db/sqlite/db_sqlite.cpp @@ -513,9 +513,10 @@ bool BlockchainSQLite::reward_handler( throw std::logic_error{"Reward distribution amount is too large"}; uint64_t block_reward = block.reward * BATCH_REWARD_FACTOR; - uint64_t service_node_reward = + uint64_t service_node_reward = block.major_version >= hf::hf20 ? block_reward : cryptonote::service_node_reward_formula(0, block.major_version) * BATCH_REWARD_FACTOR; + std::vector payments; // Step 1: Pay out the block producer their tx fees (note that, unlike the below, this applies @@ -563,8 +564,7 @@ bool BlockchainSQLite::reward_handler( } // Step 3: Add Governance reward to the list - // TODO sean governance ethereum address - if (m_nettype != cryptonote::network_type::FAKECHAIN) { + if (m_nettype != cryptonote::network_type::FAKECHAIN && block.major_version < hf::hf20) { if (parsed_governance_addr.first != block.major_version) { cryptonote::get_account_address_from_str( parsed_governance_addr.second, diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 018a6724e9..5a595b388e 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -410,8 +410,8 @@ namespace config { // Details of the ethereum smart contract managing rewards and chain its kept on inline constexpr uint32_t ETHEREUM_CHAIN_ID = 31337; - inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; - inline constexpr std::string_view ETHEREUM_POOL_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"; + inline constexpr std::string_view ETHEREUM_POOL_CONTRACT = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index a2c6416939..8684a4aebf 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1531,7 +1531,7 @@ bool Blockchain::validate_miner_transaction( if (money_in_use > max_money_in_use) { log::error( - log::Cat("verify"), + logcat, "coinbase transaction spends too much money ({}). Maximum block reward is {} (= {} " "base + {} fees)", print_money(money_in_use), @@ -1549,17 +1549,31 @@ bool Blockchain::validate_miner_transaction( //TODO sean check here that L2 height is reasonable (doesnt go backwards, isn't in future assuming eth blocks issued once every 6 seconds) - if (b.reward > - reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total) { - log::error( - log::Cat("verify"), - "block reward to be batched spends too much money ({}). Maximum block reward is {} " - "(= {} base + {} fees)", - print_money(b.reward), - print_money(max_money_in_use), - print_money(max_base_reward), - print_money(reward_parts.miner_fee)); - return false; + + + if (version <= hf::hf19_reward_batching) { + if (b.reward > + reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total) { + log::error( + logcat, + "block reward to be batched spends too much money ({}). Maximum block reward is {} " + "(= {} base + {} fees)", + print_money(b.reward), + print_money(max_money_in_use), + print_money(max_base_reward), + print_money(reward_parts.miner_fee)); + return false; + } + } else { + const auto pool_block_reward = m_l2_tracker->get_pool_block_reward(b.timestamp, b.l2_height); + if (b.reward != pool_block_reward) { + log::error( + logcat, + "block reward to be batched is incorrect({}). Block reward is {}, should be {}", + print_money(b.reward), + print_money(pool_block_reward)); + return false; + } } @@ -1769,7 +1783,6 @@ bool Blockchain::create_block_template_internal( size_t txs_weight; uint64_t fee; - //TODO sean if (hf_version >= cryptonote::feature::ETH_BLS) std::tie(b.l2_height, b.l2_state) = m_l2_tracker->latest_state(); @@ -1827,7 +1840,7 @@ bool Blockchain::create_block_template_internal( CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx); for (size_t try_count = 0; try_count != 10; ++try_count) { - auto [r, block_rewards] = construct_miner_tx( + std::tie(r, block_rewards) = construct_miner_tx( height, median_weight, already_generated_coins, @@ -1895,6 +1908,10 @@ bool Blockchain::create_block_template_internal( else b.service_node_winner_key = crypto::null; + if (hf_version >= cryptonote::feature::ETH_BLS) { + block_rewards = m_l2_tracker->get_pool_block_reward(b.timestamp, b.l2_height); + } + b.reward = block_rewards; b.height = height; return true; diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 99e51a4cad..9994cf7e19 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -279,3 +279,7 @@ bool TransactionReviewSession::finalize_review() { } +uint64_t L2Tracker::get_pool_block_reward(uint64_t timestamp, uint64_t ethereum_block_height) { + const auto response = pool_contract->RewardRate(timestamp, ethereum_block_height); + return response.reward; +} diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index af0f22b677..b329483a8c 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -78,6 +78,8 @@ class L2Tracker { std::pair latest_state(); std::vector get_block_transactions(); + uint64_t get_pool_block_reward(uint64_t timestamp, uint64_t ethereum_block_height); + private: static std::string get_rewards_contract_address(const cryptonote::network_type nettype); static std::string get_pool_contract_address(const cryptonote::network_type nettype); diff --git a/src/l2_tracker/pool_contract.cpp b/src/l2_tracker/pool_contract.cpp index 2918258d2f..62785857f9 100644 --- a/src/l2_tracker/pool_contract.cpp +++ b/src/l2_tracker/pool_contract.cpp @@ -1,13 +1,21 @@ #include "pool_contract.h" +#include "logging/oxen_logger.h" + +static auto logcat = oxen::log::Cat("l2_tracker"); + PoolContract::PoolContract(const std::string& _contractAddress, std::shared_ptr _provider) : contractAddress(_contractAddress), provider(std::move(_provider)) {} -RewardRateResponse PoolContract::RewardRate(uint64_t timestamp) { - //uint256_t reward = provider->callContractFunction(contractAddress, "rewardRate", timestamp); - // TODO sean get this from the contract - // Fetch the reward rate from the smart contract - uint64_t reward = 16500000000; - return RewardRateResponse{timestamp, reward}; +RewardRateResponse PoolContract::RewardRate(uint64_t timestamp, uint64_t ethereum_block_height) { + ReadCallData callData; + callData.contractAddress = contractAddress; + std::string timestampStr = utils::padTo32Bytes(utils::decimalToHex(timestamp), utils::PaddingDirection::LEFT); + //keccak256("rewardRate(uint256)") + std::string functionABI = "0xcea01962"; + callData.data = functionABI + timestampStr; + std::string result = provider->callReadFunction(callData, ethereum_block_height); + return RewardRateResponse{timestamp, utils::fromHexStringToUint64(result)}; + } diff --git a/src/l2_tracker/pool_contract.h b/src/l2_tracker/pool_contract.h index fbfe47062f..197b35538e 100644 --- a/src/l2_tracker/pool_contract.h +++ b/src/l2_tracker/pool_contract.h @@ -16,7 +16,7 @@ class RewardRateResponse { class PoolContract { public: PoolContract(const std::string& _contractAddress, std::shared_ptr _provider); - RewardRateResponse RewardRate(uint64_t timestamp); + RewardRateResponse RewardRate(uint64_t timestamp, uint64_t ethereum_block_height); private: std::string contractAddress; From 2ccb07be5d61bd8dbc505e7cde664351abcd8a58 Mon Sep 17 00:00:00 2001 From: doylet Date: Fri, 3 May 2024 16:04:02 +1000 Subject: [PATCH 89/97] Allow specifying of binpath from CLI for local devnet sn script --- utils/local-devnet/service_node_network.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/utils/local-devnet/service_node_network.py b/utils/local-devnet/service_node_network.py index 6f75046101..dffca8c277 100755 --- a/utils/local-devnet/service_node_network.py +++ b/utils/local-devnet/service_node_network.py @@ -3,6 +3,8 @@ from daemons import Daemon, Wallet from ethereum import ServiceNodeRewardContract +import pathlib +import argparse import random import time import shutil @@ -47,7 +49,7 @@ def vprint(*args, timestamp=True, **kwargs): class SNNetwork: - def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): + def __init__(self, datadir, *, binpath, sns=12, nodes=3): self.datadir = datadir if not os.path.exists(self.datadir): os.makedirs(self.datadir) @@ -57,7 +59,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): vprint("Using '{}' for data files and logs".format(datadir)) - nodeopts = dict(oxend=self.binpath+'/oxend', datadir=datadir) + nodeopts = dict(oxend=str(self.binpath / 'oxend'), datadir=datadir) self.ethsns = [Daemon(service_node=True, **nodeopts) for _ in range(1)] self.sns = [Daemon(service_node=True, **nodeopts) for _ in range(sns)] @@ -70,7 +72,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): self.wallets.append(Wallet( node=self.nodes[len(self.wallets) % len(self.nodes)], name=name, - rpc_wallet=self.binpath+'/oxen-wallet-rpc', + rpc_wallet=str(self.binpath/'oxen-wallet-rpc'), datadir=datadir)) self.alice, self.bob, self.mike = self.wallets @@ -80,7 +82,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): self.extrawallets.append(Wallet( node=self.nodes[len(self.extrawallets) % len(self.nodes)], name="extrawallet-"+str(name), - rpc_wallet=self.binpath+'/oxen-wallet-rpc', + rpc_wallet=str(self.binpath/'oxen-wallet-rpc'), datadir=datadir)) # Interconnections @@ -151,6 +153,7 @@ def __init__(self, datadir, *, binpath='../../build/bin', sns=12, nodes=3): self.sync_nodes(self.mine(256), timeout=120) vprint("Submitting first round of service node registrations: ", end="", flush=True) # time.sleep(40) + self.mike.refresh() for sn in self.sns[0:5]: self.mike.register_sn(sn) vprint(".", end="", flush=True, timestamp=False) @@ -352,12 +355,19 @@ def __del__(self): snn = None def run(): + arg_parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + arg_parser.add_argument('--bin-path', + help=f'Set the directory where `oxend` is located', + default="../../build/bin", + type=pathlib.Path) + args = arg_parser.parse_args(); + global snn, verbose if not snn: if path.isdir(datadirectory+'/'): shutil.rmtree(datadirectory+'/', ignore_errors=False, onerror=None) vprint("new SNN") - snn = SNNetwork(datadir=datadirectory+'/') + snn = SNNetwork(binpath=args.bin_path, datadir=datadirectory+'/') else: vprint("reusing SNN") snn.alice.new_wallet() From bccc907532c205cd7a67cd66162c80ab3e74045e Mon Sep 17 00:00:00 2001 From: doylet Date: Fri, 3 May 2024 16:04:37 +1000 Subject: [PATCH 90/97] Update the smart contract hashes to match eth-sn-contracts @ 0a223380813 --- src/cryptonote_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 018a6724e9..5d7c2e4e09 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -410,8 +410,8 @@ namespace config { // Details of the ethereum smart contract managing rewards and chain its kept on inline constexpr uint32_t ETHEREUM_CHAIN_ID = 31337; - inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; - inline constexpr std::string_view ETHEREUM_POOL_CONTRACT = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; + inline constexpr std::string_view ETHEREUM_REWARDS_CONTRACT = "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9"; + inline constexpr std::string_view ETHEREUM_POOL_CONTRACT = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767; From 72464abcfb08fe2ea51b6b191d311b56e2533f91 Mon Sep 17 00:00:00 2001 From: doylet Date: Fri, 3 May 2024 16:05:00 +1000 Subject: [PATCH 91/97] Uncomment else if branch prevent service node leave requests from executing --- src/l2_tracker/rewards_contract.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/l2_tracker/rewards_contract.cpp b/src/l2_tracker/rewards_contract.cpp index 7130e97c92..7d07f31862 100644 --- a/src/l2_tracker/rewards_contract.cpp +++ b/src/l2_tracker/rewards_contract.cpp @@ -16,7 +16,8 @@ TransactionType RewardsLogEntry::getLogType() const { // keccak256('NewServiceNode(uint64,address,(uint256,uint256),(uint256,uint256,uint256,uint16),(address,uint256)[])') if (topics[0] == "0xe82ed1bfc15e6602fba1a19273171c8a63c1d40b0e0117be4598167b8655498f") { return TransactionType::NewServiceNode; - // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { + // keccak256('ServiceNodeRemovalRequest(uint64,address,(uint256,uint256))') + } else if (topics[0] == "0x89477e9f4ddcb5eb9f30353ab22c31ef9a91ab33fd1ffef09aadb3458be7775d") { return TransactionType::ServiceNodeLeaveRequest; // keccak256('ServiceNodeRemoval(uint64,address,uint256,(uint256,uint256))') } else if (topics[0] == "0x130a7be04ef1f87b2b436f68f389bf863ee179b95399a3a8444196fab7a4e54c") { From 8434486b5a110c2961b30ec3d904219c8fa1b76e Mon Sep 17 00:00:00 2001 From: doylet Date: Fri, 3 May 2024 16:05:14 +1000 Subject: [PATCH 92/97] Switch oxen-mq to stable branch --- external/oxen-mq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/oxen-mq b/external/oxen-mq index a27961d787..923a0a80bc 160000 --- a/external/oxen-mq +++ b/external/oxen-mq @@ -1 +1 @@ -Subproject commit a27961d787c9065f2bf6da9d60d01dca2e125739 +Subproject commit 923a0a80bc533e0d63ae32e2f9f5d4f7f53a258e From 0d7d8691197e70b0389aae07b098bbbfe1c6aba8 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 6 May 2024 14:36:57 +1000 Subject: [PATCH 93/97] Fix block reward verify fmt log specifying incorrect number of args --- src/cryptonote_core/blockchain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8684a4aebf..a3989b9a75 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1569,7 +1569,7 @@ bool Blockchain::validate_miner_transaction( if (b.reward != pool_block_reward) { log::error( logcat, - "block reward to be batched is incorrect({}). Block reward is {}, should be {}", + "block reward to be batched is incorrect. Block reward is {}, should be {}", print_money(b.reward), print_money(pool_block_reward)); return false; From b7aaf93a9725bcf01a55e30b878796a612425c34 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 6 May 2024 14:51:35 +1000 Subject: [PATCH 94/97] Pre-empt block reward to 0 in chaingen tests when ETH_BLS is enabled --- tests/core_tests/chaingen.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 75c3c7ae62..68b981d134 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1124,6 +1124,18 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create } } + // TODO(doyle): Pre-cursor work I started on to test some BLS features. However this is incorrect, + // the block reward has to be derived from the reward pool contract. This howver requires + // connecting L2 tracker to a Ethereum blockchain (like `hardhat node`). This makes running tests + // a lot more cumbersome. We should either mock the contract into the L2 tracker or startup a + // suitable testing environment for the core tests. + // + // Core tests are more like unit-tests however, so I'd lean more towards implementing a mock + // contract. + if (blk.major_version >= cryptonote::feature::ETH_BLS) { + block_rewards = 0; + } + blk.reward = block_rewards; entry.txs = tx_list; uint64_t block_reward, block_reward_unpenalized; From 4788d52d36076ee0b52ab4df0a5279715c03e1d1 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 6 May 2024 14:52:17 +1000 Subject: [PATCH 95/97] Add some basic guards around L2 tracker that is use concurrently --- src/l2_tracker/l2_tracker.cpp | 37 +++++++++++++++++++------------- src/l2_tracker/l2_tracker.h | 13 ++++++----- src/l2_tracker/pool_contract.cpp | 4 ++-- src/l2_tracker/pool_contract.h | 2 +- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/l2_tracker/l2_tracker.cpp b/src/l2_tracker/l2_tracker.cpp index 9994cf7e19..ef1af41e33 100644 --- a/src/l2_tracker/l2_tracker.cpp +++ b/src/l2_tracker/l2_tracker.cpp @@ -12,25 +12,23 @@ L2Tracker::L2Tracker() { } L2Tracker::L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& _provider) - : rewards_contract(std::make_shared(get_rewards_contract_address(nettype), _provider)), - pool_contract(std::make_shared(get_pool_contract_address(nettype), _provider)), + : rewards_contract(std::make_shared(std::string(get_rewards_contract_address(nettype)), _provider)), + pool_contract(std::make_shared(std::string(get_pool_contract_address(nettype)), _provider)), stop_thread(false) { - update_thread = std::thread(&L2Tracker::update_state_thread, this); + update_thread = std::thread([this] { + while (!stop_thread.load()) { + update_state(); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + }); } - + L2Tracker::~L2Tracker() { stop_thread.store(true); if (update_thread.joinable()) { update_thread.join(); } } -void L2Tracker::update_state_thread() { - while (!stop_thread.load()) { - update_state(); - std::this_thread::sleep_for(std::chrono::seconds(10)); - } -} - void L2Tracker::insert_in_order(State&& new_state) { // Check if the state with the same height already exists auto it = std::find_if(state_history.begin(), state_history.end(), @@ -60,6 +58,7 @@ void L2Tracker::process_logs_for_state(State& state) { } void L2Tracker::update_state() { + std::lock_guard lock{mutex}; try { State new_state(rewards_contract->State()); process_logs_for_state(new_state); @@ -86,6 +85,7 @@ std::pair L2Tracker::latest_state() { oxen::log::error(logcat, "L2 tracker doesnt have a provider and cant query state"); throw std::runtime_error("Non Service node doesn't keep track of state"); } + std::lock_guard lock{mutex}; if(state_history.empty()) { oxen::log::error(logcat, "L2 tracker doesnt have any state history to query"); throw std::runtime_error("Internal error getting latest state from l2 tracker"); @@ -104,6 +104,7 @@ bool L2Tracker::check_state_in_history(uint64_t height, const crypto::hash& stat bool L2Tracker::check_state_in_history(uint64_t height, const std::string& state_root) { if (!service_node) return true; + std::lock_guard lock{mutex}; auto it = std::find_if(state_history.begin(), state_history.end(), [height, &state_root](const State& state) { return state.height == height && state.state == state_root; @@ -115,6 +116,7 @@ std::shared_ptr L2Tracker::initialize_transaction_revi auto session = std::make_shared(oxen_to_ethereum_block_heights[latest_oxen_block], ethereum_height); if (!service_node) session->service_node = false; + std::lock_guard lock{mutex}; populate_review_transactions(session); return session; } @@ -123,16 +125,19 @@ std::shared_ptr L2Tracker::initialize_mempool_review() auto session = std::make_shared(oxen_to_ethereum_block_heights[latest_oxen_block], std::numeric_limits::max()); if (!service_node) session->service_node = false; + std::lock_guard lock{mutex}; populate_review_transactions(session); return session; } -std::string L2Tracker::get_rewards_contract_address(const cryptonote::network_type nettype) { - return std::string(get_config(nettype).ETHEREUM_REWARDS_CONTRACT); +std::string_view L2Tracker::get_rewards_contract_address(const cryptonote::network_type nettype) { + std::string_view result = get_config(nettype).ETHEREUM_REWARDS_CONTRACT; + return result; } -std::string L2Tracker::get_pool_contract_address(const cryptonote::network_type nettype) { - return std::string(get_config(nettype).ETHEREUM_POOL_CONTRACT); +std::string_view L2Tracker::get_pool_contract_address(const cryptonote::network_type nettype) { + std::string_view result = get_config(nettype).ETHEREUM_POOL_CONTRACT; + return result; } void L2Tracker::populate_review_transactions(std::shared_ptr session) { @@ -163,6 +168,7 @@ void L2Tracker::populate_review_transactions(std::shared_ptr L2Tracker::get_block_transactions() { if (!service_node) throw std::runtime_error("Non Service node doesn't keep track of state"); + std::lock_guard lock{mutex}; std::vector all_transactions; const auto begin_height = oxen_to_ethereum_block_heights[latest_oxen_block]; for (const auto& state : state_history) { @@ -180,6 +186,7 @@ std::vector L2Tracker::get_block_transactions() { } void L2Tracker::record_block_height_mapping(uint64_t oxen_block_height, uint64_t ethereum_block_height) { + std::lock_guard lock{mutex}; oxen_to_ethereum_block_heights[oxen_block_height] = ethereum_block_height; latest_oxen_block = oxen_block_height; } diff --git a/src/l2_tracker/l2_tracker.h b/src/l2_tracker/l2_tracker.h index b329483a8c..a1cdcd2d91 100644 --- a/src/l2_tracker/l2_tracker.h +++ b/src/l2_tracker/l2_tracker.h @@ -56,12 +56,7 @@ class L2Tracker { L2Tracker(const cryptonote::network_type nettype, const std::shared_ptr& client); ~L2Tracker(); - void update_state_thread(); void update_state(); - void insert_in_order(State&& new_state); - - void process_logs_for_state(State& state); - bool check_state_in_history(uint64_t height, const crypto::hash& state_root); bool check_state_in_history(uint64_t height, const std::string& state_root); @@ -81,8 +76,12 @@ class L2Tracker { uint64_t get_pool_block_reward(uint64_t timestamp, uint64_t ethereum_block_height); private: - static std::string get_rewards_contract_address(const cryptonote::network_type nettype); - static std::string get_pool_contract_address(const cryptonote::network_type nettype); + void insert_in_order(State&& new_state); + void process_logs_for_state(State& state); + + std::mutex mutex; + static std::string_view get_rewards_contract_address(const cryptonote::network_type nettype); + static std::string_view get_pool_contract_address(const cryptonote::network_type nettype); void get_review_transactions(); void populate_review_transactions(std::shared_ptr session); bool service_node = true; diff --git a/src/l2_tracker/pool_contract.cpp b/src/l2_tracker/pool_contract.cpp index 62785857f9..897c55fb44 100644 --- a/src/l2_tracker/pool_contract.cpp +++ b/src/l2_tracker/pool_contract.cpp @@ -4,8 +4,8 @@ static auto logcat = oxen::log::Cat("l2_tracker"); -PoolContract::PoolContract(const std::string& _contractAddress, std::shared_ptr _provider) - : contractAddress(_contractAddress), provider(std::move(_provider)) {} +PoolContract::PoolContract(std::string _contractAddress, std::shared_ptr _provider) + : contractAddress(std::move(_contractAddress)), provider(std::move(_provider)) {} RewardRateResponse PoolContract::RewardRate(uint64_t timestamp, uint64_t ethereum_block_height) { ReadCallData callData; diff --git a/src/l2_tracker/pool_contract.h b/src/l2_tracker/pool_contract.h index 197b35538e..89779404b4 100644 --- a/src/l2_tracker/pool_contract.h +++ b/src/l2_tracker/pool_contract.h @@ -15,7 +15,7 @@ class RewardRateResponse { class PoolContract { public: - PoolContract(const std::string& _contractAddress, std::shared_ptr _provider); + PoolContract(std::string _contractAddress, std::shared_ptr _provider); RewardRateResponse RewardRate(uint64_t timestamp, uint64_t ethereum_block_height); private: From 0b0baba578a5f9f90298211f08f6c17f8e09c262 Mon Sep 17 00:00:00 2001 From: doylet Date: Mon, 6 May 2024 15:19:09 +1000 Subject: [PATCH 96/97] Get ethyl thread-safe fix & getLogs failing --- external/ethyl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ethyl b/external/ethyl index c056684518..bb3fbf7449 160000 --- a/external/ethyl +++ b/external/ethyl @@ -1 +1 @@ -Subproject commit c0566845182d9ed23df26420d7daa4f3c23f41b6 +Subproject commit bb3fbf744904354452a6b6d856045abb2ca75ccf From 51a5c50e48b8cff07ffe37b362ee6ad7ddeb8f0f Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Mon, 6 May 2024 15:02:53 -0300 Subject: [PATCH 97/97] Unbreak oxen-encoding dependency when oxenmq is installed on the system The change in c5c5ea071 broke compilation when liboxenmq is installed on the system: we find liboxenmq via system dep and so never include the oxen-mq subdirectory, which means the oxenc::oxenc target never gets set up. --- CMakeLists.txt | 2 +- cmake/system_or_submodule.cmake | 22 ++++++++++++++++++++++ external/CMakeLists.txt | 23 ++++------------------- 3 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 cmake/system_or_submodule.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c9cbc1852c..4e7e9bc454 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,7 +298,7 @@ if(NOT MANUAL_SUBMODULES) check_submodule(external/rapidjson) check_submodule(external/trezor-common) check_submodule(external/randomx) - check_submodule(external/oxen-mq cppzmq) + check_submodule(external/oxen-mq cppzmq oxen-encoding) check_submodule(external/SQLiteCpp) if(BUILD_TESTS) check_submodule(external/googletest) diff --git a/cmake/system_or_submodule.cmake b/cmake/system_or_submodule.cmake new file mode 100644 index 0000000000..4096d8ca86 --- /dev/null +++ b/cmake/system_or_submodule.cmake @@ -0,0 +1,22 @@ + +macro(system_or_submodule BIGNAME smallname pkgconf subdir) + option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) + if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) + pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET) + endif() + if(${BIGNAME}_FOUND) + add_library(${smallname} INTERFACE) + if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") + # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) + else() + target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) + endif() + message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") + else() + message(STATUS "using ${smallname} submodule") + add_subdirectory(${subdir} EXCLUDE_FROM_ALL) + endif() + if(NOT TARGET ${smallname}::${smallname}) + add_library(${smallname}::${smallname} ALIAS ${smallname}) + endif() +endmacro() diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a9f4206002..9e2259eb65 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -37,25 +37,10 @@ if(NOT STATIC AND NOT BUILD_STATIC_DEPS) find_package(PkgConfig REQUIRED) endif() -option(FORCE_OXENC_SUBMODULE "force using oxenmq submodule" OFF) -if(NOT STATIC AND NOT FORCE_OXENC_SUBMODULE) - pkg_check_modules(OXENC liboxenmq>=1.2.13 IMPORTED_TARGET) -endif() -if(OXENC_FOUND) - add_library(oxenmq INTERFACE) - if(NOT TARGET PkgConfig::OXENC AND CMAKE_VERSION VERSION_LESS "3.21") - # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) - else() - target_link_libraries(oxenmq INTERFACE PkgConfig::OXENC) - endif() - message(STATUS "Found system oxenmq ${OXENC_VERSION}") -else() - message(STATUS "using oxenmq submodule") - add_subdirectory(oxen-mq) -endif() -if(NOT TARGET oxenmq::oxenmq) - add_library(oxenmq::oxenmq ALIAS oxenmq) -endif() +include(system_or_submodule) + +system_or_submodule(OXENMQ oxenmq liboxenmq>=1.2.13 oxen-mq) +system_or_submodule(OXENC oxenc liboxenc>=1.0.3 oxen-mq/oxen-encoding) add_subdirectory(db_drivers) add_subdirectory(randomx EXCLUDE_FROM_ALL)