From cc0d51078c24f6679e478d28459d2be2af37bf64 Mon Sep 17 00:00:00 2001 From: Doyle Date: Wed, 1 May 2019 16:01:17 +1000 Subject: [PATCH] Merge dev into upstream --- .github/ISSUE_TEMPLATE/bug_report.md | 28 ++ .../blockchain_import.cpp | 1 - src/checkpoints/checkpoints.cpp | 244 ++++++++---- src/checkpoints/checkpoints.h | 67 ++-- src/cryptonote_config.h | 1 + src/cryptonote_core/blockchain.cpp | 111 +++--- src/cryptonote_core/blockchain.h | 33 +- src/cryptonote_core/cryptonote_core.cpp | 183 ++++++--- src/cryptonote_core/cryptonote_core.h | 81 ++-- .../service_node_deregister.cpp | 20 +- src/cryptonote_core/service_node_deregister.h | 23 +- src/cryptonote_core/service_node_list.cpp | 365 ++++++++++-------- src/cryptonote_core/service_node_list.h | 59 ++- .../service_node_quorum_cop.cpp | 88 ++++- src/cryptonote_core/service_node_quorum_cop.h | 38 +- src/cryptonote_core/service_node_rules.h | 4 + .../cryptonote_protocol_defs.h | 16 + .../cryptonote_protocol_handler.h | 23 +- .../cryptonote_protocol_handler.inl | 93 +++-- .../cryptonote_protocol_handler_common.h | 5 + src/daemon/command_parser_executor.h | 2 + src/daemon/command_server.cpp | 5 + src/daemon/rpc_command_executor.h | 2 + src/rpc/core_rpc_server.cpp | 28 +- src/rpc/core_rpc_server.h | 2 + src/rpc/core_rpc_server_commands_defs.h | 7 + src/wallet/wallet2.cpp | 2 +- tests/core_proxy/core_proxy.h | 1 + tests/core_tests/bulletproofs.h | 2 +- tests/core_tests/chaingen.cpp | 18 + tests/core_tests/chaingen.h | 88 +---- tests/core_tests/service_nodes.cpp | 6 +- tests/unit_tests/ban.cpp | 1 + tests/unit_tests/service_nodes.cpp | 4 +- 34 files changed, 1034 insertions(+), 617 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..73567d4b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +**Expected behaviour** +What is the expected behaviour if the steps above are followed: + +**Current Behaviour** +What is the current behaviour if the steps to reproduce are followed: + +**Screenshots or Logs** +If applicable, add screenshots or logs to help explain your problem. + +**Additional Information (please complete the following information):** + - Operating System: [e.g. Ubuntu 16.04, Windows 10] +- Local or Remote node + - Version of Loki daemon, or most recent commit hash. diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 22d00828b..5fc2c9c80 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -768,7 +768,6 @@ int main(int argc, char* argv[]) try { - core.disable_dns_checkpoints(true); #if defined(PER_BLOCK_CHECKPOINT) const GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData; #else diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 2d0fb75ac..ed25b9eba 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -35,7 +35,9 @@ #include "string_tools.h" #include "storages/portable_storage_template_helper.h" // epee json include #include "serialization/keyvalue_serialization.h" +#include "cryptonote_core/service_node_rules.h" #include +#include "syncobj.h" using namespace epee; @@ -69,23 +71,135 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - //--------------------------------------------------------------------------- - checkpoints::checkpoints() - { - } //--------------------------------------------------------------------------- bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str) { crypto::hash h = crypto::null_hash; - bool r = epee::string_tools::hex_to_pod(hash_str, h); + bool r = epee::string_tools::hex_to_pod(hash_str, h); CHECK_AND_ASSERT_MES(r, false, "Failed to parse checkpoint hash string into binary representation!"); - // return false if adding at a height we already have AND the hash is different - if (m_points.count(height)) + CRITICAL_REGION_LOCAL(m_lock); + if (m_points.count(height)) // return false if adding at a height we already have AND the hash is different { - CHECK_AND_ASSERT_MES(h == m_points[height], false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); + checkpoint_t const &checkpoint = m_points[height]; + crypto::hash const &curr_hash = checkpoint.block_hash; + CHECK_AND_ASSERT_MES(h == curr_hash, false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); } - m_points[height] = h; + else + { + checkpoint_t checkpoint = {}; + checkpoint.type = checkpoint_type::predefined_or_dns; + checkpoint.block_hash = h; + m_points[height] = checkpoint; + } + + return true; + } + //--------------------------------------------------------------------------- + static bool add_vote_if_unique(checkpoint_t &checkpoint, service_nodes::checkpoint_vote const &vote) + { + CHECK_AND_ASSERT_MES(checkpoint.block_hash == vote.block_hash, + false, + "Developer error: Add vote if unique should only be called when the vote hash and checkpoint hash match"); + + // TODO(doyle): Factor this out, a similar function exists in service node deregister + CHECK_AND_ASSERT_MES(vote.voters_quorum_index < service_nodes::QUORUM_SIZE, + false, + "Vote is indexing out of bounds"); + + const auto signature_it = std::find_if(checkpoint.signatures.begin(), checkpoint.signatures.end(), [&vote](service_nodes::voter_to_signature const &check) { + return vote.voters_quorum_index == check.quorum_index; + }); + + if (signature_it == checkpoint.signatures.end()) + { + service_nodes::voter_to_signature new_voter_to_signature = {}; + new_voter_to_signature.quorum_index = vote.voters_quorum_index; + new_voter_to_signature.signature = vote.signature; + checkpoint.signatures.push_back(new_voter_to_signature); + return true; + } + + return false; + } + //--------------------------------------------------------------------------- + bool checkpoints::add_checkpoint_vote(service_nodes::checkpoint_vote const &vote) + { + // TODO(doyle): Always accept votes. Later on, we should only accept votes up + // to a certain point, so that eventually once a certain number of blocks + // have transpired, we can go through all our "seen" votes and deregister + // anyone who has not participated in voting by that point. + +#if 0 + uint64_t newest_checkpoint_height = get_max_height(); + if (vote.block_height < newest_checkpoint_height) + return true; +#endif + + CRITICAL_REGION_LOCAL(m_lock); + std::array unique_vote_set = {}; + auto pre_existing_checkpoint_it = m_points.find(vote.block_height); + if (pre_existing_checkpoint_it != m_points.end()) + { + checkpoint_t &checkpoint = pre_existing_checkpoint_it->second; + if (checkpoint.type == checkpoint_type::predefined_or_dns) + return true; + + if (checkpoint.block_hash == vote.block_hash) + { + bool added = add_vote_if_unique(checkpoint, vote); + return added; + } + + for (service_nodes::voter_to_signature const &vote_to_sig : checkpoint.signatures) + { + if (vote_to_sig.quorum_index > unique_vote_set.size()) // TODO(doyle): Sanity check, remove once universal vote verifying function is written + return false; + ++unique_vote_set[vote_to_sig.quorum_index]; + } + + } + + // TODO(doyle): Double work. Factor into a generic vote checker as we already have one in service node deregister + std::vector &candidate_checkpoints = m_staging_points[vote.block_height]; + std::vector::iterator curr_checkpoint = candidate_checkpoints.end(); + for (auto it = candidate_checkpoints.begin(); it != candidate_checkpoints.end(); it++) + { + checkpoint_t const &checkpoint = *it; + if (checkpoint.block_hash == vote.block_hash) + curr_checkpoint = it; + + for (service_nodes::voter_to_signature const &vote_to_sig : checkpoint.signatures) + { + if (vote_to_sig.quorum_index > unique_vote_set.size()) + return false; + + if (++unique_vote_set[vote_to_sig.quorum_index] > 1) + { + // NOTE: Voter is trying to vote twice + return false; + } + } + } + + if (curr_checkpoint == candidate_checkpoints.end()) + { + checkpoint_t new_checkpoint = {}; + new_checkpoint.type = checkpoint_type::service_node; + new_checkpoint.block_hash = vote.block_hash; + candidate_checkpoints.push_back(new_checkpoint); + curr_checkpoint = (candidate_checkpoints.end() - 1); + } + + if (add_vote_if_unique(*curr_checkpoint, vote)) + { + if (curr_checkpoint->signatures.size() > service_nodes::MIN_VOTES_TO_CHECKPOINT) // TODO(doyle): Quorum SuperMajority variable + { + m_points[vote.block_height] = *curr_checkpoint; + candidate_checkpoints.erase(curr_checkpoint); + } + } + return true; } //--------------------------------------------------------------------------- @@ -94,67 +208,80 @@ namespace cryptonote return !m_points.empty() && (height <= (--m_points.end())->first); } //--------------------------------------------------------------------------- - bool checkpoints::check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const + bool checkpoints::check_block(uint64_t height, const crypto::hash& h, bool* is_a_checkpoint) const { auto it = m_points.find(height); - is_a_checkpoint = it != m_points.end(); - if(!is_a_checkpoint) + bool found = (it != m_points.end()); + if (is_a_checkpoint) *is_a_checkpoint = found; + + if(!found) return true; - if(it->second == h) - { - MINFO("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h); - return true; - }else - { - MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h); - return false; - } + checkpoint_t const &checkpoint = it->second; + bool result = checkpoint.block_hash == h; + if (result) MINFO ("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h); + else MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH " << checkpoint.block_hash << "FETCHED HASH: " << h); + return result; } //--------------------------------------------------------------------------- - bool checkpoints::check_block(uint64_t height, const crypto::hash& h) const - { - bool ignored; - return check_block(height, h, ignored); - } - //--------------------------------------------------------------------------- - //FIXME: is this the desired behavior? bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const { if (0 == block_height) return false; - auto it = m_points.upper_bound(blockchain_height); - // Is blockchain_height before the first checkpoint? - if (it == m_points.begin()) + CRITICAL_REGION_LOCAL(m_lock); + std::map::const_iterator it = m_points.upper_bound(blockchain_height); + if (it == m_points.begin()) // Is blockchain_height before the first checkpoint? return true; - --it; - uint64_t checkpoint_height = it->first; - return checkpoint_height < block_height; + --it; // move the iterator to the first checkpoint that is <= my height + uint64_t sentinel_reorg_height = it->first; + if (it->second.type == checkpoint_type::service_node) + { + // NOTE: The current checkpoint is a service node checkpoint. Go back + // 1 checkpoint, which will either be another service node checkpoint or + // a predefined one. + + // If it's a service node checkpoint, this is the 2nd newest checkpoint, + // so we can't reorg past that height. If it's predefined, that's ok as + // well, we can't reorg past that height so irrespective, always accept + // the height of this next checkpoint. + if (it == m_points.begin()) + { + return true; // NOTE: Only one service node checkpoint recorded, we can override this checkpoint. + } + else + { + --it; + sentinel_reorg_height = it->first; + } + } + + bool result = sentinel_reorg_height < block_height; + return result; } //--------------------------------------------------------------------------- uint64_t checkpoints::get_max_height() const { - std::map< uint64_t, crypto::hash >::const_iterator highest = - std::max_element( m_points.begin(), m_points.end(), - ( boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _1) < - boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _2 ) ) ); - return highest->first; + uint64_t result = 0; + if (m_points.size() > 0) + { + auto last_it = m_points.rbegin(); + result = last_it->first; + } + + return result; } //--------------------------------------------------------------------------- - const std::map& checkpoints::get_points() const - { - return m_points; - } - bool checkpoints::check_for_conflicts(const checkpoints& other) const { for (auto& pt : other.get_points()) { if (m_points.count(pt.first)) { - CHECK_AND_ASSERT_MES(pt.second == m_points.at(pt.first), false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); + checkpoint_t const &our_checkpoint = m_points.at(pt.first); + checkpoint_t const &their_checkpoint = pt.second; + CHECK_AND_ASSERT_MES(our_checkpoint.block_hash == their_checkpoint.block_hash, false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); } } return true; @@ -212,33 +339,16 @@ namespace cryptonote uint64_t height; height = it->height; if (height <= prev_max_height) { - LOG_PRINT_L1("ignoring checkpoint height " << height); + LOG_PRINT_L1("ignoring checkpoint height " << height); } else { - std::string blockhash = it->hash; - LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); - ADD_CHECKPOINT(height, blockhash); + std::string blockhash = it->hash; + LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); + ADD_CHECKPOINT(height, blockhash); } ++it; } return true; } - - bool checkpoints::load_checkpoints_from_dns(network_type nettype) - { - return true; - } - - bool checkpoints::load_new_checkpoints(const std::string &json_hashfile_fullpath, network_type nettype, bool dns) - { - bool result; - - result = load_checkpoints_from_json(json_hashfile_fullpath); - if (dns) - { - result &= load_checkpoints_from_dns(nettype); - } - - return result; - } } + diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index a55b94bf0..9222b65cf 100644 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -30,16 +30,32 @@ #pragma once #include +#include + #include "misc_log_ex.h" #include "crypto/hash.h" #include "cryptonote_config.h" +#include "cryptonote_core/service_node_deregister.h" #define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); #define JSON_HASH_FILE_NAME "checkpoints.json" - namespace cryptonote { + struct Blockchain; + enum struct checkpoint_type + { + predefined_or_dns, + service_node, + }; + + struct checkpoint_t + { + checkpoint_type type; + crypto::hash block_hash; + std::vector signatures; // Only service node checkpoints use signatures + }; + /** * @brief A container for blockchain checkpoints * @@ -50,12 +66,6 @@ namespace cryptonote class checkpoints { public: - - /** - * @brief default constructor - */ - checkpoints(); - /** * @brief adds a checkpoint to the container * @@ -68,6 +78,8 @@ namespace cryptonote */ bool add_checkpoint(uint64_t height, const std::string& hash_str); + bool add_checkpoint_vote(service_nodes::checkpoint_vote const &vote); + /** * @brief checks if there is a checkpoint in the future * @@ -90,23 +102,19 @@ namespace cryptonote * * @param height the height to be checked * @param h the hash to be checked - * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height + * @param blockchain the blockchain to query ancestor blocks from the current height + * @param is_a_checkpoint optional return-by-pointer if there is a checkpoint at the given height * * @return true if there is no checkpoint at the given height, * true if the passed parameters match the stored checkpoint, * false otherwise */ - bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; - - /** - * @overload - */ - bool check_block(uint64_t height, const crypto::hash& h) const; + bool check_block(uint64_t height, const crypto::hash& h, bool *is_a_checkpoint = nullptr) const; /** * @brief checks if alternate chain blocks should be kept for a given height * - * this basically says if the blockchain is smaller than the first + *m this basically says if the blockchain is smaller than the first * checkpoint then alternate blocks are allowed. Alternatively, if the * last checkpoint *before* the end of the current chain is also before * the block to be added, then this is fine. @@ -131,7 +139,7 @@ namespace cryptonote * * @return a const reference to the checkpoints container */ - const std::map& get_points() const; + std::map get_points() const { return m_points; }; /** * @brief checks if our checkpoints container conflicts with another @@ -153,20 +161,6 @@ namespace cryptonote */ bool init_default_checkpoints(network_type nettype); - /** - * @brief load new checkpoints - * - * Loads new checkpoints from the specified json file, as well as - * (optionally) from DNS. - * - * @param json_hashfile_fullpath path to the json checkpoints file - * @param nettype network type - * @param dns whether or not to load DNS checkpoints - * - * @return true if loading successful and no conflicts - */ - bool load_new_checkpoints(const std::string &json_hashfile_fullpath, network_type nettype=MAINNET, bool dns=true); - /** * @brief load new checkpoints from json * @@ -176,17 +170,10 @@ namespace cryptonote */ bool load_checkpoints_from_json(const std::string &json_hashfile_fullpath); - /** - * @brief load new checkpoints from DNS - * - * @param nettype network type - * - * @return true if loading successful and no conflicts - */ - bool load_checkpoints_from_dns(network_type nettype = MAINNET); - private: - std::map m_points; //!< the checkpoints container + std::unordered_map> m_staging_points; // Incomplete service node checkpoints being voted on + std::map m_points; //!< the checkpoints container + mutable epee::critical_section m_lock; }; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 31e1a7316..4ea2bf25f 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -265,6 +265,7 @@ namespace cryptonote network_version_9_service_nodes, // Proof Of Stake w/ Service Nodes network_version_10_bulletproofs, // Bulletproofs, Service Node Grace Registration Period, Batched Governance network_version_11_infinite_staking, + network_version_12_checkpointing, network_version_count, }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index be52623f5..038fd41d3 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -126,7 +126,7 @@ static const hard_fork_record stagenet_hard_forks[] = //------------------------------------------------------------------ Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list, service_nodes::deregister_vote_pool& deregister_vote_pool): m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_weight_limit(0), m_current_block_cumul_weight_median(0), - m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), + m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_long_term_effective_median_block_weight(0), m_long_term_block_weights_cache_tip_hash(crypto::null_hash), @@ -136,6 +136,7 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list m_deregister_vote_pool(deregister_vote_pool), m_btc_valid(false) { + m_checkpoint_pool.reserve(service_nodes::QUORUM_SIZE * 4 /*blocks*/); LOG_PRINT_L3("Blockchain::" << __func__); } //------------------------------------------------------------------ @@ -1742,8 +1743,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id return false; } - bool is_a_checkpoint; - if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) + bool is_a_checkpoint = false; + if(!m_checkpoints.check_block(bei.height, id, &is_a_checkpoint)) { LOG_ERROR("CHECKPOINT VALIDATION FAILED"); bvc.m_verifivation_failed = true; @@ -3076,14 +3077,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, return false; } - const std::shared_ptr quorum_state = m_service_node_list.get_quorum_state(deregister.block_height); - if (!quorum_state) + const std::shared_ptr uptime_quorum = m_service_node_list.get_uptime_quorum(deregister.block_height); + if (!uptime_quorum) { MERROR_VER("Deregister TX could not get quorum for height: " << deregister.block_height); return false; } - if (!service_nodes::deregister_vote::verify_deregister(nettype(), deregister, tvc.m_vote_ctx, *quorum_state)) + if (!service_nodes::deregister_vote::verify_deregister(nettype(), deregister, tvc.m_vote_ctx, *uptime_quorum)) { tvc.m_verifivation_failed = true; MERROR_VER("tx " << get_transaction_hash(tx) << ": deregister tx could not be completely verified reason: " << print_vote_verification_context(tvc.m_vote_ctx)); @@ -3146,15 +3147,15 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, continue; } - const std::shared_ptr existing_deregister_quorum_state = m_service_node_list.get_quorum_state(existing_deregister.block_height); - if (!existing_deregister_quorum_state) + const std::shared_ptr existing_uptime_quorum = m_service_node_list.get_uptime_quorum(existing_deregister.block_height); + if (!existing_uptime_quorum) { - MERROR_VER("could not get quorum state for recent deregister tx"); + MERROR_VER("could not get uptime quorum for recent deregister tx"); continue; } - if (existing_deregister_quorum_state->nodes_to_test[existing_deregister.service_node_index] == - quorum_state->nodes_to_test[deregister.service_node_index]) + if (existing_uptime_quorum->nodes_to_test[existing_deregister.service_node_index] == + uptime_quorum->nodes_to_test[deregister.service_node_index]) { MERROR_VER("Already seen this deregister tx (aka double spend)"); tvc.m_double_spend = true; @@ -3672,6 +3673,7 @@ leave: bvc.m_verifivation_failed = true; goto leave; } + } TIME_MEASURE_FINISH(longhash_calculating_time); @@ -4056,6 +4058,7 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti //------------------------------------------------------------------ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc) { + LOG_PRINT_L3("Blockchain::" << __func__); crypto::hash id = get_block_hash(bl); CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process @@ -4090,28 +4093,25 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc) // caller decide course of action. void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) { - const auto& pts = points.get_points(); - bool stop_batch; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - stop_batch = m_db->batch_start(); - const uint64_t blockchain_height = m_db->height(); - for (const auto& pt : pts) - { - // if the checkpoint is for a block we don't have yet, move on - if (pt.first >= blockchain_height) - { - continue; - } + bool stop_batch = m_db->batch_start(); - if (!points.check_block(pt.first, m_db->get_block_hash_from_height(pt.first))) + for (const auto& checkpoint_it : points.get_points()) + { + uint64_t block_height = checkpoint_it.first; + checkpoint_t const &checkpoint = checkpoint_it.second; + + if (block_height >= m_db->height()) // if the checkpoint is for a block we don't have yet, move on + break; + + if (!points.check_block(block_height, m_db->get_block_hash_from_height(block_height), nullptr)) { // if asked to enforce checkpoints, roll back to a couple of blocks before the checkpoint if (enforce) { LOG_ERROR("Local blockchain failed to pass a checkpoint, rolling back!"); std::list empty; - rollback_blockchain_switching(empty, pt.first - 2); + rollback_blockchain_switching(empty, block_height- 2); } else { @@ -4119,6 +4119,7 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor } } } + if (stop_batch) m_db->batch_stop(); } @@ -4126,46 +4127,28 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor // returns false if any of the checkpoints loading returns false. // That should happen only if a checkpoint is added that conflicts // with an existing checkpoint. -bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns) +bool Blockchain::update_checkpoints(const std::string& file_path) { if (!m_checkpoints.load_checkpoints_from_json(file_path)) - { - return false; - } - - // if we're checking both dns and json, load checkpoints from dns. - // if we're not hard-enforcing dns checkpoints, handle accordingly - if (m_enforce_dns_checkpoints && check_dns && !m_offline) - { - if (!m_checkpoints.load_checkpoints_from_dns()) - { - return false; - } - } - else if (check_dns && !m_offline) - { - checkpoints dns_points; - dns_points.load_checkpoints_from_dns(); - if (m_checkpoints.check_for_conflicts(dns_points)) - { - check_against_checkpoints(dns_points, false); - } - else - { - MERROR("One or more checkpoints fetched from DNS conflicted with existing checkpoints!"); - } - } - + return false; check_against_checkpoints(m_checkpoints, true); - return true; } //------------------------------------------------------------------ -void Blockchain::set_enforce_dns_checkpoints(bool enforce_checkpoints) +bool Blockchain::add_checkpoint_vote(service_nodes::checkpoint_vote const &vote) { - m_enforce_dns_checkpoints = enforce_checkpoints; -} + crypto::hash const canonical_block_hash = get_block_id_by_height(vote.block_height); + if (vote.block_hash != canonical_block_hash) + { + // NOTE: Vote is not for a block on the canonical chain, check if it's part + // of an alternative chain + if (m_alternative_chains.find(vote.block_hash) == m_alternative_chains.end()) + return false; + } + m_checkpoints.add_checkpoint_vote(vote); + return true; +} //------------------------------------------------------------------ void Blockchain::block_longhash_worker(uint64_t height, const epee::span &blocks, std::unordered_map &map) const { @@ -4174,7 +4157,7 @@ void Blockchain::block_longhash_worker(uint64_t height, const epee::span> txes(total_txs); #define SCAN_TABLE_QUIT(m) \ - do { \ - MERROR_VER(m) ;\ - m_scan_table.clear(); \ - return false; \ - } while(0); \ + do { \ + MERROR_VER(m) ;\ + m_scan_table.clear(); \ + return false; \ + } while(0); \ // generate sorted tables for all amounts and absolute offsets size_t tx_index = 0, block_index = 0; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index c56144ce5..d1f2c2641 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -92,6 +92,23 @@ namespace cryptonote class Blockchain { public: + void debug__print_checkpoints() + { + const std::map &checkpoint_map = m_checkpoints.get_points(); + if (checkpoint_map.empty()) + { + std::cout << "Checkpoint: None available" << std::endl; + } + else + { + for (auto const &it : checkpoint_map) + { + checkpoint_t const &checkpoint = it.second; + std::cout << "Checkpoint [" << it.first << "]" << ((checkpoint.type == checkpoint_type::service_node) ? "Service Node" : "Predefined") << std::endl; + } + } + } + /** * @brief Now-defunct (TODO: remove) struct from in-memory blockchain */ @@ -747,15 +764,23 @@ namespace cryptonote void set_enforce_dns_checkpoints(bool enforce); /** - * @brief loads new checkpoints from a file and optionally from DNS + * @brief loads new checkpoints from a file * * @param file_path the path of the file to look for and load checkpoints from - * @param check_dns whether or not to check for new DNS-based checkpoints * * @return false if any enforced checkpoint type fails to load, otherwise true */ - bool update_checkpoints(const std::string& file_path, bool check_dns); + bool update_checkpoints(const std::string& file_path); + // TODO(doyle): CHECKPOINTING(doyle): + struct service_node_checkpoint_pool_entry + { + uint64_t height; + std::vector votes; + }; + + std::vector m_checkpoint_pool; + bool add_checkpoint_vote(service_nodes::checkpoint_vote const &vote); // user options, must be called before calling init() @@ -1136,8 +1161,6 @@ namespace cryptonote std::vector m_validate_miner_tx_hooks; checkpoints m_checkpoints; - bool m_enforce_dns_checkpoints; - HardFork *m_hardfork; network_type m_nettype; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 4046b9d26..d43eecd1d 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -111,10 +111,6 @@ namespace cryptonote "offline" , "Do not listen for peers, nor connect to any" }; - const command_line::arg_descriptor arg_disable_dns_checkpoints = { - "disable-dns-checkpoints" - , "Do not retrieve checkpoints from DNS" - }; const command_line::arg_descriptor arg_block_download_max_size = { "block-download-max-size" , "Set maximum size of block download queue in bytes (0 for default)" @@ -135,11 +131,6 @@ namespace cryptonote , "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests." , 0 }; - static const command_line::arg_descriptor arg_dns_checkpoints = { - "enforce-dns-checkpointing" - , "checkpoints from DNS server will be enforced" - , false - }; static const command_line::arg_descriptor arg_fast_block_sync = { "fast-block-sync" , "Sync up most of the way by using embedded, known block hashes." @@ -230,9 +221,7 @@ namespace cryptonote m_starter_message_showed(false), m_target_blockchain_height(0), m_checkpoints_path(""), - m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), - m_disable_dns_checkpoints(false), m_update_download(0), m_nettype(UNDEFINED), m_update_available(false), @@ -248,41 +237,18 @@ namespace cryptonote else m_pprotocol = &m_protocol_stub; } - //----------------------------------------------------------------------------------- - void core::set_checkpoints(checkpoints&& chk_pts) - { - m_blockchain_storage.set_checkpoints(std::move(chk_pts)); - } - //----------------------------------------------------------------------------------- - void core::set_checkpoints_file_path(const std::string& path) - { - m_checkpoints_path = path; - } - //----------------------------------------------------------------------------------- - void core::set_enforce_dns_checkpoints(bool enforce_dns) - { - m_blockchain_storage.set_enforce_dns_checkpoints(enforce_dns); - } //----------------------------------------------------------------------------------------------- bool core::update_checkpoints() { - if (m_nettype != MAINNET || m_disable_dns_checkpoints) return true; - + if (m_nettype != MAINNET) return true; if (m_checkpoints_updating.test_and_set()) return true; bool res = true; - if (time(NULL) - m_last_dns_checkpoints_update >= 3600) + if (time(NULL) - m_last_json_checkpoints_update >= 600) { - res = m_blockchain_storage.update_checkpoints(m_checkpoints_path, true); - m_last_dns_checkpoints_update = time(NULL); + res = m_blockchain_storage.update_checkpoints(m_checkpoints_path); m_last_json_checkpoints_update = time(NULL); } - else if (time(NULL) - m_last_json_checkpoints_update >= 600) - { - res = m_blockchain_storage.update_checkpoints(m_checkpoints_path, false); - m_last_json_checkpoints_update = time(NULL); - } - m_checkpoints_updating.clear(); // if anything fishy happened getting new checkpoints, bring down the house @@ -319,7 +285,6 @@ namespace cryptonote command_line::add_arg(desc, arg_stagenet_on); command_line::add_arg(desc, arg_regtest_on); command_line::add_arg(desc, arg_fixed_difficulty); - command_line::add_arg(desc, arg_dns_checkpoints); command_line::add_arg(desc, arg_prep_blocks_threads); command_line::add_arg(desc, arg_fast_block_sync); command_line::add_arg(desc, arg_show_time_stats); @@ -329,7 +294,6 @@ namespace cryptonote command_line::add_arg(desc, arg_no_fluffy_blocks); command_line::add_arg(desc, arg_test_dbg_lock_sleep); command_line::add_arg(desc, arg_offline); - command_line::add_arg(desc, arg_disable_dns_checkpoints); command_line::add_arg(desc, arg_block_download_max_size); command_line::add_arg(desc, arg_max_txpool_weight); command_line::add_arg(desc, arg_service_node); @@ -362,28 +326,25 @@ namespace cryptonote auto data_dir = boost::filesystem::path(m_config_folder); - if (m_nettype == MAINNET) + // Init Checkpoints { cryptonote::checkpoints checkpoints; if (!checkpoints.init_default_checkpoints(m_nettype)) { throw std::runtime_error("Failed to initialize checkpoints"); } - set_checkpoints(std::move(checkpoints)); + m_blockchain_storage.set_checkpoints(std::move(checkpoints)); boost::filesystem::path json(JSON_HASH_FILE_NAME); boost::filesystem::path checkpoint_json_hashfile_fullpath = data_dir / json; - set_checkpoints_file_path(checkpoint_json_hashfile_fullpath.string()); + m_checkpoints_path = checkpoint_json_hashfile_fullpath.string(); } - - set_enforce_dns_checkpoints(command_line::get_arg(vm, arg_dns_checkpoints)); test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height)); m_fluffy_blocks_enabled = !get_arg(vm, arg_no_fluffy_blocks); m_pad_transactions = get_arg(vm, arg_pad_transactions); m_offline = get_arg(vm, arg_offline); - m_disable_dns_checkpoints = get_arg(vm, arg_disable_dns_checkpoints); if (!command_line::is_arg_defaulted(vm, arg_fluffy_blocks)) MWARNING(arg_fluffy_blocks.name << " is obsolete, it is now default"); @@ -719,9 +680,9 @@ namespace cryptonote MGINFO("Loading checkpoints"); - // load json & DNS checkpoints, and verify them + // load json checkpoints and verify them // with respect to what blocks we already have - CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json conflicted with existing checkpoints."); // DNS versions checking if (check_updates_string == "disabled") @@ -1408,6 +1369,42 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::relay_checkpoint_votes() + { + const time_t now = time(nullptr); + + // Get relayable votes + NOTIFY_NEW_CHECKPOINT_VOTE::request req = {}; + + std::vector relayed_votes; + for (Blockchain::service_node_checkpoint_pool_entry &pool_entry: m_blockchain_storage.m_checkpoint_pool) + { + for (service_nodes::checkpoint_vote &vote : pool_entry.votes) + { + const time_t elapsed = now - vote.time_last_sent_p2p; + const time_t RELAY_THRESHOLD = 60 * 2; + if (elapsed > RELAY_THRESHOLD) + { + relayed_votes.push_back(&vote); + req.votes.push_back(vote); + } + } + } + + // Relay and update timestamp of when we last sent the vote + if (!req.votes.empty()) + { + cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); + if (get_protocol()->relay_checkpoint_votes(req, fake_context)) + { + for (service_nodes::checkpoint_vote *vote : relayed_votes) + vote->time_last_sent_p2p = now; + } + } + + return true; + } + //----------------------------------------------------------------------------------------------- bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) { return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); @@ -1771,6 +1768,7 @@ namespace cryptonote m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this)); m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); m_deregisters_auto_relayer.do_call(boost::bind(&core::relay_deregister_votes, this)); + m_checkpoint_auto_relayer.do_call(boost::bind(&core::relay_checkpoint_votes, this)); // m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this)); @@ -2085,9 +2083,14 @@ namespace cryptonote return si.available; } //----------------------------------------------------------------------------------------------- - const std::shared_ptr core::get_quorum_state(uint64_t height) const + const std::shared_ptr core::get_uptime_quorum(uint64_t height) const { - return m_service_node_list.get_quorum_state(height); + return m_service_node_list.get_uptime_quorum(height); + } + //----------------------------------------------------------------------------------------------- + const std::shared_ptr core::get_checkpointing_quorum(uint64_t height) const + { + return m_service_node_list.get_checkpointing_quorum(height); } //----------------------------------------------------------------------------------------------- bool core::is_service_node(const crypto::public_key& pubkey) const @@ -2135,8 +2138,8 @@ namespace cryptonote return false; } - const auto quorum_state = m_service_node_list.get_quorum_state(vote.block_height); - if (!quorum_state) + const auto uptime_quorum = m_service_node_list.get_uptime_quorum(vote.block_height); + if (!uptime_quorum) { vvc.m_verification_failed = true; vvc.m_invalid_block_height = true; @@ -2146,7 +2149,7 @@ namespace cryptonote cryptonote::transaction deregister_tx; int hf_version = m_blockchain_storage.get_current_hard_fork_version(); - bool result = m_deregister_vote_pool.add_vote(hf_version, vote, vvc, *quorum_state, deregister_tx); + bool result = m_deregister_vote_pool.add_vote(hf_version, vote, vvc, *uptime_quorum, deregister_tx); if (result && vvc.m_full_tx_deregister_made) { tx_verification_context tvc = AUTO_VAL_INIT(tvc); @@ -2165,6 +2168,82 @@ namespace cryptonote return result; } //----------------------------------------------------------------------------------------------- + bool core::add_checkpoint_vote(const service_nodes::checkpoint_vote& vote, vote_verification_context &vvc) + { + // TODO(doyle): Not in this function but, need to add code for culling old + // checkpoints from the "staging" checkpoint pool. + + // TODO(doyle): This is repeated logic for deregister votes and + // checkpointing votes, it is worth considering merging the two into + // a generic voting structure + + // Check Vote Age + { + uint64_t const latest_height = std::max(get_current_blockchain_height(), get_target_blockchain_height()); + if (vote.block_height >= latest_height) + return false; + + uint64_t vote_age = latest_height - vote.block_height; + if (vote_age > ((service_nodes::CHECKPOINT_INTERVAL * 3) - 1)) + return false; + } + + // Validate Vote + { + const std::shared_ptr state = get_uptime_quorum(vote.block_height); + if (!state) + { + // TODO(loki): Fatal error + LOG_ERROR("Quorum state for height: " << vote.block_height << " was not cached in daemon!"); + return false; + } + + if (vote.voters_quorum_index >= state->quorum_nodes.size()) + { + LOG_PRINT_L1("TODO(doyle): CHECKPOINTING(doyle): Writeme"); + return false; + } + + // NOTE(loki): We don't validate that the hash belongs to a valid block + // just yet, just that the signature is valid. + crypto::public_key const &voters_pub_key = state->quorum_nodes[vote.voters_quorum_index]; + if (!crypto::check_signature(vote.block_hash, voters_pub_key, vote.signature)) + { + LOG_PRINT_L1("TODO(doyle): CHECKPOINTING(doyle): Writeme"); + return false; + } + } + + // TODO(doyle): CHECKPOINTING(doyle): We need to check the hash they're voting on matches across votes + // Get Matching Checkpoint + std::vector &checkpoint_pool = m_blockchain_storage.m_checkpoint_pool; + auto it = std::find_if(checkpoint_pool.begin(), checkpoint_pool.end(), [&vote](Blockchain::service_node_checkpoint_pool_entry const &checkpoint) { + return (checkpoint.height == vote.block_height); + }); + + if (it == checkpoint_pool.end()) + { + Blockchain::service_node_checkpoint_pool_entry pool_entry = {}; + pool_entry.height = vote.block_height; + checkpoint_pool.push_back(pool_entry); + it = (checkpoint_pool.end() - 1); + } + + // Add Vote if Unique to Checkpoint + Blockchain::service_node_checkpoint_pool_entry &pool_entry = (*it); + auto vote_it = std::find_if(pool_entry.votes.begin(), pool_entry.votes.end(), [&vote](service_nodes::checkpoint_vote const &preexisting_vote) { + return (preexisting_vote.voters_quorum_index == vote.voters_quorum_index); + }); + + if (vote_it == pool_entry.votes.end()) + { + m_blockchain_storage.add_checkpoint_vote(vote); + pool_entry.votes.push_back(vote); + } + + return true; + } + //----------------------------------------------------------------------------------------------- bool core::get_service_node_keys(crypto::public_key &pub_key, crypto::secret_key &sec_key) const { if (m_service_node) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 797e24417..e27e5ca83 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -82,6 +82,8 @@ namespace cryptonote { public: + void debug__print_checkpoints() { m_blockchain_storage.debug__print_checkpoints(); } + /** * @brief constructor * @@ -411,34 +413,6 @@ namespace cryptonote */ void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); - /** - * @copydoc Blockchain::set_checkpoints - * - * @note see Blockchain::set_checkpoints() - */ - void set_checkpoints(checkpoints&& chk_pts); - - /** - * @brief set the file path to read from when loading checkpoints - * - * @param path the path to set ours as - */ - void set_checkpoints_file_path(const std::string& path); - - /** - * @brief set whether or not we enforce DNS checkpoints - * - * @param enforce_dns enforce DNS checkpoints or not - */ - void set_enforce_dns_checkpoints(bool enforce_dns); - - /** - * @brief set whether or not to enable or disable DNS checkpoints - * - * @param disble whether to disable DNS checkpoints - */ - void disable_dns_checkpoints(bool disable = true) { m_disable_dns_checkpoints = disable; } - /** * @copydoc tx_memory_pool::have_tx * @@ -814,7 +788,16 @@ namespace cryptonote * @return Null shared ptr if quorum has not been determined yet for height */ - const std::shared_ptr get_quorum_state(uint64_t height) const; + const std::shared_ptr get_uptime_quorum(uint64_t height) const; + + /** + * @brief Get the deterministic list of service node's public keys for nodes responsible for checkpointing + * + * @param height Block height to deterministically recreate the quorum list from + + * @return Null shared ptr if quorum has not been determined yet for height + */ + const std::shared_ptr get_checkpointing_quorum(uint64_t height) const; /** * @brief Get a non owning reference to the list of blacklisted key images @@ -830,14 +813,15 @@ namespace cryptonote */ std::vector get_service_node_list_state(const std::vector& service_node_pubkeys) const; - /** - * @brief get whether `pubkey` is known as a service node - * - * @param pubkey the public key to test - * - * @return whether `pubkey` is known as a service node - */ - bool is_service_node(const crypto::public_key& pubkey) const; + /** + * @brief get whether `pubkey` is known as a service node + * + * @param pubkey the public key to test + * + * @return whether `pubkey` is known as a service node + */ + bool is_service_node(const crypto::public_key& pubkey) const; + /** * @brief Add a vote to deregister a service node from network * @@ -847,6 +831,16 @@ namespace cryptonote */ bool add_deregister_vote(const service_nodes::deregister_vote& vote, vote_verification_context &vvc); + /** + * @brief TODO(doyle): CHECKPOINTING(doyle): + * + * @param TODO(doyle): CHECKPOINTING(doyle): + + * @return + */ + bool add_checkpoint_vote(const service_nodes::checkpoint_vote& vote, vote_verification_context &vvc); + + /** * @brief Get the keypair for this service node. @@ -1074,6 +1068,13 @@ namespace cryptonote */ bool relay_txpool_transactions(); + /** + * @brief attempt to relay the pooled checkpoint votes + * + * @return true, necessary for binding this function to a periodic invoker + */ + bool relay_checkpoint_votes(); + /** * @brief checks DNS versions * @@ -1133,7 +1134,6 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions - epee::math_helper::once_a_time_seconds<60*2, false> m_deregisters_auto_relayer; //!< interval for checking re-relaying deregister votes epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space epee::math_helper::once_a_time_seconds m_check_uptime_proof_interval; //!< interval for checking our own uptime proof @@ -1141,6 +1141,9 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate epee::math_helper::once_a_time_seconds<60*60*5, true> m_blockchain_pruning_interval; //!< interval for incremental blockchain pruning + epee::math_helper::once_a_time_seconds<60*2, false> m_deregisters_auto_relayer; + epee::math_helper::once_a_time_seconds<60*2, false> m_checkpoint_auto_relayer; + std::atomic m_starter_message_showed; //!< has the "daemon will sync now" message been shown? uint64_t m_target_blockchain_height; //!< blockchain height target @@ -1150,11 +1153,9 @@ namespace cryptonote std::atomic m_update_available; std::string m_checkpoints_path; //!< path to json checkpoints file - time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once - bool m_disable_dns_checkpoints; bool m_service_node; crypto::secret_key m_service_node_key; diff --git a/src/cryptonote_core/service_node_deregister.cpp b/src/cryptonote_core/service_node_deregister.cpp index e4efbe810..d5ef1b6cf 100644 --- a/src/cryptonote_core/service_node_deregister.cpp +++ b/src/cryptonote_core/service_node_deregister.cpp @@ -90,16 +90,16 @@ namespace service_nodes static bool verify_votes_helper(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister, cryptonote::vote_verification_context &vvc, - const service_nodes::quorum_state &quorum_state) + const service_nodes::quorum_uptime_proof &uptime_quorum) { - if (deregister.service_node_index >= quorum_state.nodes_to_test.size()) + if (deregister.service_node_index >= uptime_quorum.nodes_to_test.size()) { vvc.m_service_node_index_out_of_bounds = true; - LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << quorum_state.nodes_to_test.size() << ")"); + LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << uptime_quorum.nodes_to_test.size() << ")"); return false; } - const std::vector& quorum = quorum_state.quorum_nodes; + const std::vector& quorum = uptime_quorum.quorum_nodes; std::vector quorum_set; std::vector> keys_and_sigs; @@ -135,7 +135,7 @@ namespace service_nodes bool deregister_vote::verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister, cryptonote::vote_verification_context &vvc, - const service_nodes::quorum_state &quorum_state) + const service_nodes::quorum_uptime_proof &uptime_quorum) { if (deregister.votes.size() < service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE) { @@ -144,18 +144,18 @@ namespace service_nodes return false; } - bool result = verify_votes_helper(nettype, deregister, vvc, quorum_state); + bool result = verify_votes_helper(nettype, deregister, vvc, uptime_quorum); return result; } bool deregister_vote::verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc, - const service_nodes::quorum_state &quorum_state) + const service_nodes::quorum_uptime_proof &uptime_quorum) { cryptonote::tx_extra_service_node_deregister deregister; deregister.block_height = v.block_height; deregister.service_node_index = v.service_node_index; deregister.votes.push_back(cryptonote::tx_extra_service_node_deregister::vote{ v.signature, v.voters_quorum_index }); - return verify_votes_helper(nettype, deregister, vvc, quorum_state); + return verify_votes_helper(nettype, deregister, vvc, uptime_quorum); } void deregister_vote_pool::set_relayed(const std::vector& votes) @@ -217,10 +217,10 @@ namespace service_nodes bool deregister_vote_pool::add_vote(const int hf_version, const deregister_vote& new_vote, cryptonote::vote_verification_context& vvc, - const service_nodes::quorum_state &quorum_state, + const service_nodes::quorum_uptime_proof &uptime_quorum, cryptonote::transaction &tx) { - if (!deregister_vote::verify_vote(m_nettype, new_vote, vvc, quorum_state)) + if (!deregister_vote::verify_vote(m_nettype, new_vote, vvc, uptime_quorum)) { LOG_PRINT_L1("Signature verification failed for deregister vote"); return false; diff --git a/src/cryptonote_core/service_node_deregister.h b/src/cryptonote_core/service_node_deregister.h index b6cc0b89b..9a2b7c419 100644 --- a/src/cryptonote_core/service_node_deregister.h +++ b/src/cryptonote_core/service_node_deregister.h @@ -47,7 +47,22 @@ namespace cryptonote namespace service_nodes { - struct quorum_state; + struct quorum_uptime_proof; + + struct checkpoint_vote + { + uint64_t block_height; + crypto::hash block_hash; + uint32_t voters_quorum_index; + crypto::signature signature; + uint64_t time_last_sent_p2p; + }; + + struct voter_to_signature + { + uint16_t quorum_index; + crypto::signature signature; + }; struct deregister_vote { @@ -65,10 +80,10 @@ namespace service_nodes static bool verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister, cryptonote::vote_verification_context& vvc, - const service_nodes::quorum_state &quorum); + const service_nodes::quorum_uptime_proof &quorum); static bool verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc, - const service_nodes::quorum_state &quorum); + const service_nodes::quorum_uptime_proof &quorum); }; class deregister_vote_pool @@ -80,7 +95,7 @@ namespace service_nodes bool add_vote(const int hf_version, const deregister_vote& new_vote, cryptonote::vote_verification_context& vvc, - const service_nodes::quorum_state &quorum_state, + const service_nodes::quorum_uptime_proof &uptime_quorum, cryptonote::transaction &tx); // TODO(loki): Review relay behaviour and all the cases when it should be triggered diff --git a/src/cryptonote_core/service_node_list.cpp b/src/cryptonote_core/service_node_list.cpp index d8a65fe46..ab54467d5 100644 --- a/src/cryptonote_core/service_node_list.cpp +++ b/src/cryptonote_core/service_node_list.cpp @@ -56,12 +56,16 @@ namespace service_nodes if (hf_version == cryptonote::network_version_10_bulletproofs) return service_node_info::version_1_swarms; - return service_node_info::version_2_infinite_staking; + if (hf_version == cryptonote::network_version_11_infinite_staking) + return service_node_info::version_2_infinite_staking; + + return service_node_info::version_3_checkpointing; } service_node_list::service_node_list(cryptonote::Blockchain& blockchain) - : m_blockchain(blockchain), m_hooks_registered(false), m_height(0), m_db(nullptr), m_service_node_pubkey(nullptr) + : m_blockchain(blockchain), m_hooks_registered(false), m_db(nullptr), m_service_node_pubkey(nullptr) { + m_transient_state = {}; } void service_node_list::register_hooks(service_nodes::quorum_cop &quorum_cop) @@ -93,18 +97,18 @@ namespace service_nodes uint64_t current_height = m_blockchain.get_current_blockchain_height(); bool loaded = load(); - if (loaded && m_height == current_height) return; + if (loaded && m_transient_state.height == current_height) return; - if (!loaded || m_height > current_height) clear(true); + if (!loaded || m_transient_state.height > current_height) clear(true); - LOG_PRINT_L0("Recalculating service nodes list, scanning blockchain from height " << m_height); + LOG_PRINT_L0("Recalculating service nodes list, scanning blockchain from height " << m_transient_state.height); LOG_PRINT_L0("This may take some time..."); std::vector> blocks; - while (m_height < current_height) + while (m_transient_state.height < current_height) { blocks.clear(); - if (!m_blockchain.get_blocks(m_height, 1000, blocks)) + if (!m_blockchain.get_blocks(m_transient_state.height, 1000, blocks)) { MERROR("Unable to initialize service nodes list"); return; @@ -132,7 +136,7 @@ namespace service_nodes std::vector service_node_list::get_service_nodes_pubkeys() const { std::vector result; - for (const auto& iter : m_service_nodes_infos) + for (const auto& iter : m_transient_state.service_nodes_infos) if (iter.second.is_fully_funded()) result.push_back(iter.first); @@ -143,15 +147,21 @@ namespace service_nodes return result; } - const std::shared_ptr service_node_list::get_quorum_state(uint64_t height) const + const std::shared_ptr service_node_list::get_uptime_quorum(uint64_t height) const { std::lock_guard lock(m_sn_mutex); - const auto &it = m_quorum_states.find(height); - if (it != m_quorum_states.end()) - { - return it->second; - } + const auto &it = m_transient_state.quorum_states.find(height); + if (it != m_transient_state.quorum_states.end()) + return it->second.uptime_proof; + return nullptr; + } + const std::shared_ptr service_node_list::get_checkpointing_quorum(uint64_t height) const + { + std::lock_guard lock(m_sn_mutex); + const auto &it = m_transient_state.quorum_states.find(height); + if (it != m_transient_state.quorum_states.end()) + return it->second.checkpointing; return nullptr; } @@ -162,9 +172,9 @@ namespace service_nodes if (service_node_pubkeys.empty()) { - result.reserve(m_service_nodes_infos.size()); + result.reserve(m_transient_state.service_nodes_infos.size()); - for (const auto &it : m_service_nodes_infos) + for (const auto &it : m_transient_state.service_nodes_infos) { service_node_pubkey_info entry = {}; entry.pubkey = it.first; @@ -177,8 +187,8 @@ namespace service_nodes result.reserve(service_node_pubkeys.size()); for (const auto &it : service_node_pubkeys) { - const auto &find_it = m_service_nodes_infos.find(it); - if (find_it == m_service_nodes_infos.end()) + const auto &find_it = m_transient_state.service_nodes_infos.find(it); + if (find_it == m_transient_state.service_nodes_infos.end()) continue; service_node_pubkey_info entry = {}; @@ -206,12 +216,12 @@ namespace service_nodes bool service_node_list::is_service_node(const crypto::public_key& pubkey) const { std::lock_guard lock(m_sn_mutex); - return m_service_nodes_infos.find(pubkey) != m_service_nodes_infos.end(); + return m_transient_state.service_nodes_infos.find(pubkey) != m_transient_state.service_nodes_infos.end(); } bool service_node_list::is_key_image_locked(crypto::key_image const &check_image, uint64_t *unlock_height, service_node_info::contribution_t *the_locked_contribution) const { - for (const auto& pubkey_info : m_service_nodes_infos) + for (const auto& pubkey_info : m_transient_state.service_nodes_infos) { const service_node_info &info = pubkey_info.second; for (const service_node_info::contributor_t &contributor : info.contributors) @@ -308,12 +318,12 @@ namespace service_nodes return false; } - const auto state = get_quorum_state(deregister.block_height); + const auto state = get_uptime_quorum(deregister.block_height); if (!state) { // TODO(loki): Not being able to find a quorum is fatal! We want better caching abilities. - MERROR("Quorum state for height: " << deregister.block_height << ", was not stored by the daemon"); + MERROR("Uptime quorum for height: " << deregister.block_height << ", was not stored by the daemon"); return false; } @@ -325,8 +335,8 @@ namespace service_nodes const crypto::public_key& key = state->nodes_to_test[deregister.service_node_index]; - auto iter = m_service_nodes_infos.find(key); - if (iter == m_service_nodes_infos.end()) + auto iter = m_transient_state.service_nodes_infos.find(key); + if (iter == m_transient_state.service_nodes_infos.end()) return false; if (m_service_node_pubkey && *m_service_node_pubkey == key) @@ -338,7 +348,7 @@ namespace service_nodes LOG_PRINT_L1("Deregistration for service node: " << key); } - m_rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, key, iter->second))); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, key, iter->second))); int hard_fork_version = m_blockchain.get_hard_fork_version(block_height); if (hard_fork_version >= cryptonote::network_version_11_infinite_staking) @@ -350,15 +360,15 @@ namespace service_nodes key_image_blacklist_entry entry = {}; entry.key_image = contribution.key_image; entry.unlock_height = block_height + staking_num_lock_blocks(m_blockchain.nettype()); - m_key_image_blacklist.push_back(entry); + m_transient_state.key_image_blacklist.push_back(entry); const bool adding_to_blacklist = true; - m_rollback_events.push_back(std::unique_ptr(new rollback_key_image_blacklist(block_height, entry, adding_to_blacklist))); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_key_image_blacklist(block_height, entry, adding_to_blacklist))); } } } - m_service_nodes_infos.erase(iter); + m_transient_state.service_nodes_infos.erase(iter); return true; } @@ -371,7 +381,7 @@ namespace service_nodes /// Gather existing swarms from infos swarm_snode_map_t existing_swarms; - for (const auto& entry : m_service_nodes_infos) { + for (const auto& entry : m_transient_state.service_nodes_infos) { const auto id = entry.second.swarm_id; existing_swarms[id].push_back(entry.first); } @@ -386,11 +396,11 @@ namespace service_nodes for (const auto snode : snodes) { - auto& sn_info = m_service_nodes_infos.at(snode); + auto& sn_info = m_transient_state.service_nodes_infos.at(snode); if (sn_info.swarm_id == swarm_id) continue; /// nothing changed for this snode /// modify info and record the change - m_rollback_events.push_back(std::unique_ptr(new rollback_change(height, snode, sn_info))); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_change(height, snode, sn_info))); sn_info.swarm_id = swarm_id; } @@ -634,8 +644,8 @@ namespace service_nodes if (hard_fork_version >= cryptonote::network_version_11_infinite_staking) { // NOTE(loki): Grace period is not used anymore with infinite staking. So, if someone somehow reregisters, we just ignore it - const auto iter = m_service_nodes_infos.find(key); - if (iter != m_service_nodes_infos.end()) + const auto iter = m_transient_state.service_nodes_infos.find(key); + if (iter != m_transient_state.service_nodes_infos.end()) return false; if (m_service_node_pubkey && *m_service_node_pubkey == key) MGINFO_GREEN("Service node registered (yours): " << key << " on height: " << block_height); @@ -646,8 +656,8 @@ namespace service_nodes // NOTE: A node doesn't expire until registration_height + lock blocks excess now which acts as the grace period // So it is possible to find the node still in our list. bool registered_during_grace_period = false; - const auto iter = m_service_nodes_infos.find(key); - if (iter != m_service_nodes_infos.end()) + const auto iter = m_transient_state.service_nodes_infos.find(key); + if (iter != m_transient_state.service_nodes_infos.end()) { if (hard_fork_version >= cryptonote::network_version_10_bulletproofs) { @@ -684,8 +694,8 @@ namespace service_nodes } } - m_rollback_events.push_back(std::unique_ptr(new rollback_new(block_height, key))); - m_service_nodes_infos[key] = info; + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_new(block_height, key))); + m_transient_state.service_nodes_infos[key] = info; return true; } @@ -705,8 +715,8 @@ namespace service_nodes } /// Service node must be registered - auto iter = m_service_nodes_infos.find(pubkey); - if (iter == m_service_nodes_infos.end()) + auto iter = m_transient_state.service_nodes_infos.find(pubkey); + if (iter == m_transient_state.service_nodes_infos.end()) { LOG_PRINT_L1("Contribution TX: Contribution received for service node: " << pubkey << ", but could not be found in the service node list on height: " << block_height << @@ -765,7 +775,7 @@ namespace service_nodes // Successfully Validated // - m_rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, pubkey, info))); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, pubkey, info))); if (new_contributor) { service_node_info::contributor_t new_contributor = {}; @@ -838,28 +848,28 @@ namespace service_nodes // Remove old rollback events // { - assert(m_height == block_height); - ++m_height; + assert(m_transient_state.height == block_height); + ++m_transient_state.height; const size_t ROLLBACK_EVENT_EXPIRATION_BLOCKS = 30; uint64_t cull_height = (block_height < ROLLBACK_EVENT_EXPIRATION_BLOCKS) ? block_height : block_height - ROLLBACK_EVENT_EXPIRATION_BLOCKS; - while (!m_rollback_events.empty() && m_rollback_events.front()->m_block_height < cull_height) + while (!m_transient_state.rollback_events.empty() && m_transient_state.rollback_events.front()->m_block_height < cull_height) { - m_rollback_events.pop_front(); + m_transient_state.rollback_events.pop_front(); } - m_rollback_events.push_front(std::unique_ptr(new prevent_rollback(cull_height))); + m_transient_state.rollback_events.push_front(std::unique_ptr(new prevent_rollback(cull_height))); } // // Remove expired blacklisted key images // - for (auto entry = m_key_image_blacklist.begin(); entry != m_key_image_blacklist.end();) + for (auto entry = m_transient_state.key_image_blacklist.begin(); entry != m_transient_state.key_image_blacklist.end();) { if (block_height >= entry->unlock_height) { const bool adding_to_blacklist = false; - m_rollback_events.push_back(std::unique_ptr(new rollback_key_image_blacklist(block_height, (*entry), adding_to_blacklist))); - entry = m_key_image_blacklist.erase(entry); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_key_image_blacklist(block_height, (*entry), adding_to_blacklist))); + entry = m_transient_state.key_image_blacklist.erase(entry); } else entry++; @@ -871,8 +881,8 @@ namespace service_nodes size_t expired_count = 0; for (const crypto::public_key& pubkey : update_and_get_expired_nodes(txs, block_height)) { - auto i = m_service_nodes_infos.find(pubkey); - if (i != m_service_nodes_infos.end()) + auto i = m_transient_state.service_nodes_infos.find(pubkey); + if (i != m_transient_state.service_nodes_infos.end()) { if (m_service_node_pubkey && *m_service_node_pubkey == pubkey) { @@ -883,10 +893,10 @@ namespace service_nodes LOG_PRINT_L1("Service node expired: " << pubkey << " at block height: " << block_height); } - m_rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, pubkey, i->second))); + m_transient_state.rollback_events.push_back(std::unique_ptr(new rollback_change(block_height, pubkey, i->second))); expired_count++; - m_service_nodes_infos.erase(i); + m_transient_state.service_nodes_infos.erase(i); } } @@ -895,16 +905,16 @@ namespace service_nodes // { crypto::public_key winner_pubkey = cryptonote::get_service_node_winner_from_tx_extra(block.miner_tx.extra); - if (m_service_nodes_infos.count(winner_pubkey) == 1) + if (m_transient_state.service_nodes_infos.count(winner_pubkey) == 1) { - m_rollback_events.push_back( + m_transient_state.rollback_events.push_back( std::unique_ptr( - new rollback_change(block_height, winner_pubkey, m_service_nodes_infos[winner_pubkey]) + new rollback_change(block_height, winner_pubkey, m_transient_state.service_nodes_infos[winner_pubkey]) ) ); // set the winner as though it was re-registering at transaction index=UINT32_MAX for this block - m_service_nodes_infos[winner_pubkey].last_reward_block_height = block_height; - m_service_nodes_infos[winner_pubkey].last_reward_transaction_index = UINT32_MAX; + m_transient_state.service_nodes_infos[winner_pubkey].last_reward_block_height = block_height; + m_transient_state.service_nodes_infos[winner_pubkey].last_reward_transaction_index = UINT32_MAX; } } @@ -935,8 +945,8 @@ namespace service_nodes if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key)) continue; - auto it = m_service_nodes_infos.find(snode_key); - if (it == m_service_nodes_infos.end()) + auto it = m_transient_state.service_nodes_infos.find(snode_key); + if (it == m_transient_state.service_nodes_infos.end()) continue; service_node_info &node_info = (*it).second; @@ -990,27 +1000,25 @@ namespace service_nodes // // Update Quorum // + generate_quorums(block); const size_t cache_state_from_height = (block_height < QUORUM_LIFETIME) ? 0 : block_height - QUORUM_LIFETIME; - store_quorum_state_from_rewards_list(block_height); - while (!m_quorum_states.empty() && m_quorum_states.begin()->first < cache_state_from_height) - { - m_quorum_states.erase(m_quorum_states.begin()); - } + while (!m_transient_state.quorum_states.empty() && m_transient_state.quorum_states.begin()->first < cache_state_from_height) + m_transient_state.quorum_states.erase(m_transient_state.quorum_states.begin()); } void service_node_list::blockchain_detached(uint64_t height) { std::lock_guard lock(m_sn_mutex); - while (!m_rollback_events.empty() && m_rollback_events.back()->m_block_height >= height) + while (!m_transient_state.rollback_events.empty() && m_transient_state.rollback_events.back()->m_block_height >= height) { - rollback_event *event = &(*m_rollback_events.back()); + rollback_event *event = &(*m_transient_state.rollback_events.back()); bool rollback_applied = true; switch(event->type) { case rollback_event::change_type: { auto *rollback = reinterpret_cast(event); - m_service_nodes_infos[rollback->m_key] = rollback->m_info; + m_transient_state.service_nodes_infos[rollback->m_key] = rollback->m_info; } break; @@ -1018,15 +1026,15 @@ namespace service_nodes { auto *rollback = reinterpret_cast(event); - auto iter = m_service_nodes_infos.find(rollback->m_key); - if (iter == m_service_nodes_infos.end()) + auto iter = m_transient_state.service_nodes_infos.find(rollback->m_key); + if (iter == m_transient_state.service_nodes_infos.end()) { MERROR("Could not find service node pubkey in rollback new"); rollback_applied = false; break; } - m_service_nodes_infos.erase(iter); + m_transient_state.service_nodes_infos.erase(iter); } break; @@ -1037,23 +1045,23 @@ namespace service_nodes auto *rollback = reinterpret_cast(event); if (rollback->m_was_adding_to_blacklist) { - auto it = std::find_if(m_key_image_blacklist.begin(), m_key_image_blacklist.end(), + auto it = std::find_if(m_transient_state.key_image_blacklist.begin(), m_transient_state.key_image_blacklist.end(), [rollback] (key_image_blacklist_entry const &a) { return (rollback->m_entry.unlock_height == a.unlock_height && rollback->m_entry.key_image == a.key_image); }); - if (it == m_key_image_blacklist.end()) + if (it == m_transient_state.key_image_blacklist.end()) { LOG_PRINT_L1("Could not find blacklisted key image to remove"); rollback_applied = false; break; } - m_key_image_blacklist.erase(it); + m_transient_state.key_image_blacklist.erase(it); } else { - m_key_image_blacklist.push_back(rollback->m_entry); + m_transient_state.key_image_blacklist.push_back(rollback->m_entry); } } break; @@ -1072,13 +1080,13 @@ namespace service_nodes break; } - m_rollback_events.pop_back(); + m_transient_state.rollback_events.pop_back(); } - while (!m_quorum_states.empty() && (--m_quorum_states.end())->first >= height) - m_quorum_states.erase(--m_quorum_states.end()); + while (!m_transient_state.quorum_states.empty() && (--m_transient_state.quorum_states.end())->first >= height) + m_transient_state.quorum_states.erase(--m_transient_state.quorum_states.end()); - m_height = height; + m_transient_state.height = height; store(); } @@ -1125,7 +1133,7 @@ namespace service_nodes } else { - for (auto it = m_service_nodes_infos.begin(); it != m_service_nodes_infos.end(); it++) + for (auto it = m_transient_state.service_nodes_infos.begin(); it != m_transient_state.service_nodes_infos.end(); it++) { crypto::public_key const &snode_key = it->first; service_node_info &info = it->second; @@ -1161,7 +1169,7 @@ namespace service_nodes std::vector> winners; - const service_node_info& info = m_service_nodes_infos.at(key); + const service_node_info& info = m_transient_state.service_nodes_infos.at(key); const uint64_t remaining_portions = STAKING_PORTIONS - info.portions_for_operator; @@ -1185,7 +1193,7 @@ namespace service_nodes std::lock_guard lock(m_sn_mutex); auto oldest_waiting = std::pair(std::numeric_limits::max(), std::numeric_limits::max()); crypto::public_key key = crypto::null_pkey; - for (const auto& info : m_service_nodes_infos) + for (const auto& info : m_transient_state.service_nodes_infos) if (info.second.is_fully_funded()) { auto waiting_since = std::make_pair(info.second.last_reward_block_height, info.second.last_reward_transaction_index); @@ -1260,63 +1268,98 @@ namespace service_nodes return true; } - void service_node_list::store_quorum_state_from_rewards_list(uint64_t height) + std::vector generate_shuffled_service_node_index_list(std::vector const &snode_list, crypto::hash const &block_hash, quorum_type type) { - const crypto::hash block_hash = m_blockchain.get_block_id_by_height(height); - if (block_hash == crypto::null_hash) + std::vector result(snode_list.size()); + size_t index = 0; + for (size_t i = 0; i < snode_list.size(); i++) result[i] = i; + + uint64_t seed = 0; + std::memcpy(&seed, block_hash.data, std::min(sizeof(seed), sizeof(block_hash.data))); + + seed += static_cast(type); + loki_shuffle(result, seed); + return result; + } + + void service_node_list::generate_quorums(cryptonote::block const &block) + { + crypto::hash block_hash; + uint64_t const height = cryptonote::get_block_height(block); + if (!cryptonote::get_block_hash(block, block_hash)) { MERROR("Block height: " << height << " returned null hash"); return; } - std::vector full_node_list = get_service_nodes_pubkeys(); - std::vector pub_keys_indexes(full_node_list.size()); + std::vector const snode_list = get_service_nodes_pubkeys(); + for (int type_int = 0; type_int < static_cast(quorum_type::count); type_int++) { - size_t index = 0; - for (size_t i = 0; i < full_node_list.size(); i++) { pub_keys_indexes[i] = i; } + auto type = static_cast(type_int); + std::vector const pub_keys_indexes = generate_shuffled_service_node_index_list(snode_list, block_hash, type); - // Shuffle indexes - uint64_t seed = 0; - std::memcpy(&seed, block_hash.data, std::min(sizeof(seed), sizeof(block_hash.data))); - - loki_shuffle(pub_keys_indexes, seed); - } - - // Assign indexes from shuffled list into quorum and list of nodes to test - - auto new_state = std::make_shared(); - - { - std::vector& quorum = new_state->quorum_nodes; + switch(type) { - quorum.resize(std::min(full_node_list.size(), QUORUM_SIZE)); - for (size_t i = 0; i < quorum.size(); i++) + case quorum_type::uptime_proof: { - size_t node_index = pub_keys_indexes[i]; - const crypto::public_key &key = full_node_list[node_index]; - quorum[i] = key; + // Assign indexes from shuffled list into quorum and list of nodes to test + auto new_state = std::make_shared(); + std::vector& quorum = new_state->quorum_nodes; + { + quorum.resize(std::min(snode_list.size(), QUORUM_SIZE)); + for (size_t i = 0; i < quorum.size(); i++) + { + size_t node_index = pub_keys_indexes[i]; + const crypto::public_key &key = snode_list[node_index]; + quorum[i] = key; + } + } + + std::vector& nodes_to_test = new_state->nodes_to_test; + { + size_t num_remaining_nodes = pub_keys_indexes.size() - quorum.size(); + size_t num_nodes_to_test = std::max(num_remaining_nodes/NTH_OF_THE_NETWORK_TO_TEST, + std::min(MIN_NODES_TO_TEST, num_remaining_nodes)); + + nodes_to_test.resize(num_nodes_to_test); + + const int pub_keys_offset = quorum.size(); + for (size_t i = 0; i < nodes_to_test.size(); i++) + { + size_t node_index = pub_keys_indexes[pub_keys_offset + i]; + const crypto::public_key &key = snode_list[node_index]; + nodes_to_test[i] = key; + } + } + + m_transient_state.quorum_states[height].uptime_proof = new_state; } + break; + + case quorum_type::checkpointing: + { + auto new_state = std::make_shared(); + std::vector& quorum = new_state->quorum_nodes; + quorum.resize(std::min(snode_list.size(), QUORUM_SIZE)); + for (size_t i = 0; i < quorum.size(); i++) + { + size_t node_index = pub_keys_indexes[i]; + const crypto::public_key &key = snode_list[node_index]; + quorum[i] = key; + } + + m_transient_state.quorum_states[height].checkpointing = new_state; + } + break; + + default: + { + assert("Loki Developer Error: Unhandled enum" == 0); + } + break; } - std::vector& nodes_to_test = new_state->nodes_to_test; - { - size_t num_remaining_nodes = pub_keys_indexes.size() - quorum.size(); - size_t num_nodes_to_test = std::max(num_remaining_nodes/NTH_OF_THE_NETWORK_TO_TEST, - std::min(MIN_NODES_TO_TEST, num_remaining_nodes)); - - nodes_to_test.resize(num_nodes_to_test); - - const int pub_keys_offset = quorum.size(); - for (size_t i = 0; i < nodes_to_test.size(); i++) - { - size_t node_index = pub_keys_indexes[pub_keys_offset + i]; - const crypto::public_key &key = full_node_list[node_index]; - nodes_to_test[i] = key; - } - } } - - m_quorum_states[height] = new_state; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1344,7 +1387,8 @@ namespace service_nodes bool service_node_list::store() { - if (m_blockchain.get_current_hard_fork_version() < cryptonote::network_version_9_service_nodes) + int hf_version = m_blockchain.get_current_hard_fork_version(); + if (hf_version < cryptonote::network_version_9_service_nodes) return true; CHECK_AND_ASSERT_MES(m_db != nullptr, false, "Failed to store service node info, m_db == nullptr"); @@ -1352,23 +1396,34 @@ namespace service_nodes { std::lock_guard lock(m_sn_mutex); - quorum_state_for_serialization quorum; - for(const auto& kv_pair : m_quorum_states) + for(const auto& kv_pair : m_transient_state.quorum_states) { - quorum.height = kv_pair.first; - quorum.state = *kv_pair.second; + quorum_for_serialization quorum = {}; + quorum.version = get_min_service_node_info_version_for_hf(hf_version); + quorum.height = kv_pair.first; + quorum_manager const &manager = kv_pair.second; + + if (manager.uptime_proof) + quorum.uptime_quorum = *manager.uptime_proof; + + if (quorum.version >= service_node_info::version_3_checkpointing) + { + if (manager.checkpointing) + quorum.checkpointing_quorum = *manager.checkpointing; + } + data_to_store.quorum_states.push_back(quorum); } service_node_pubkey_info info; - for (const auto& kv_pair : m_service_nodes_infos) + for (const auto& kv_pair : m_transient_state.service_nodes_infos) { info.pubkey = kv_pair.first; info.info = kv_pair.second; data_to_store.infos.push_back(info); } - for (const auto& event_ptr : m_rollback_events) + for (const auto& event_ptr : m_transient_state.rollback_events) { switch (event_ptr->type) { @@ -1382,11 +1437,10 @@ namespace service_nodes } } - data_to_store.key_image_blacklist = m_key_image_blacklist; + data_to_store.key_image_blacklist = m_transient_state.key_image_blacklist; } - data_to_store.height = m_height; - int hf_version = m_blockchain.get_hard_fork_version(m_height - 1); + data_to_store.height = m_transient_state.height; data_to_store.version = get_min_service_node_info_version_for_hf(hf_version); std::stringstream ss; @@ -1406,12 +1460,12 @@ namespace service_nodes void service_node_list::get_all_service_nodes_public_keys(std::vector& keys, bool fully_funded_nodes_only) const { keys.clear(); - keys.resize(m_service_nodes_infos.size()); + keys.resize(m_transient_state.service_nodes_infos.size()); size_t i = 0; if (fully_funded_nodes_only) { - for (const auto &it : m_service_nodes_infos) + for (const auto &it : m_transient_state.service_nodes_infos) { service_node_info const &info = it.second; if (info.is_fully_funded()) @@ -1420,7 +1474,7 @@ namespace service_nodes } else { - for (const auto &it : m_service_nodes_infos) + for (const auto &it : m_transient_state.service_nodes_infos) keys[i++] = it.first; } } @@ -1452,17 +1506,24 @@ namespace service_nodes bool r = ::serialization::serialize(ba, data_in); CHECK_AND_ASSERT_MES(r, false, "Failed to parse service node data from blob"); - m_height = data_in.height; - m_key_image_blacklist = data_in.key_image_blacklist; + m_transient_state.height = data_in.height; + m_transient_state.key_image_blacklist = data_in.key_image_blacklist; - for (const auto& quorum : data_in.quorum_states) + for (const auto& states : data_in.quorum_states) { - m_quorum_states[quorum.height] = std::make_shared(quorum.state); + if (states.uptime_quorum.quorum_nodes.size() > 0) + m_transient_state.quorum_states[states.height].uptime_proof = std::make_shared(states.uptime_quorum); + + if (states.version >= service_node_info::version_3_checkpointing) + { + if (states.checkpointing_quorum.quorum_nodes.size() > 0) + m_transient_state.quorum_states[states.height].checkpointing = std::make_shared(states.checkpointing_quorum); + } } for (const auto& info : data_in.infos) { - m_service_nodes_infos[info.pubkey] = info.info; + m_transient_state.service_nodes_infos[info.pubkey] = info.info; } for (const auto& event : data_in.events) @@ -1472,28 +1533,28 @@ namespace service_nodes const auto& from = boost::get(event); auto *i = new rollback_change(); *i = from; - m_rollback_events.push_back(std::unique_ptr(i)); + m_transient_state.rollback_events.push_back(std::unique_ptr(i)); } else if (event.type() == typeid(rollback_new)) { const auto& from = boost::get(event); auto *i = new rollback_new(); *i = from; - m_rollback_events.push_back(std::unique_ptr(i)); + m_transient_state.rollback_events.push_back(std::unique_ptr(i)); } else if (event.type() == typeid(prevent_rollback)) { const auto& from = boost::get(event); auto *i = new prevent_rollback(); *i = from; - m_rollback_events.push_back(std::unique_ptr(i)); + m_transient_state.rollback_events.push_back(std::unique_ptr(i)); } else if (event.type() == typeid(rollback_key_image_blacklist)) { const auto& from = boost::get(event); auto *i = new rollback_key_image_blacklist(); *i = from; - m_rollback_events.push_back(std::unique_ptr(i)); + m_transient_state.rollback_events.push_back(std::unique_ptr(i)); } else { @@ -1502,8 +1563,8 @@ namespace service_nodes } } - MGINFO("Service node data loaded successfully, m_height: " << m_height); - MGINFO(m_service_nodes_infos.size() << " nodes and " << m_rollback_events.size() << " rollback events loaded."); + MGINFO("Service node data loaded successfully, height: " << m_transient_state.height); + MGINFO(m_transient_state.service_nodes_infos.size() << " nodes and " << m_transient_state.rollback_events.size() << " rollback events loaded."); LOG_PRINT_L1("service_node_list::load() returning success"); return true; @@ -1511,9 +1572,7 @@ namespace service_nodes void service_node_list::clear(bool delete_db_entry) { - m_service_nodes_infos.clear(); - m_rollback_events.clear(); - + m_transient_state = {}; if (m_db && delete_db_entry) { m_db->block_txn_start(false/*readonly*/); @@ -1521,15 +1580,13 @@ namespace service_nodes m_db->block_txn_stop(); } - m_quorum_states.clear(); - uint64_t hardfork_9_from_height = 0; { uint32_t window, votes, threshold; uint8_t voting; m_blockchain.get_hard_fork_voting_info(9, window, votes, threshold, hardfork_9_from_height, voting); } - m_height = hardfork_9_from_height; + m_transient_state.height = hardfork_9_from_height; } size_t service_node_info::total_num_locked_contributions() const diff --git a/src/cryptonote_core/service_node_list.h b/src/cryptonote_core/service_node_list.h index 48bbd5635..10db0726b 100644 --- a/src/cryptonote_core/service_node_list.h +++ b/src/cryptonote_core/service_node_list.h @@ -33,22 +33,10 @@ #include "serialization/serialization.h" #include "cryptonote_core/service_node_rules.h" #include "cryptonote_core/service_node_deregister.h" +#include "cryptonote_core/service_node_quorum_cop.h" namespace service_nodes { - class quorum_cop; - - struct quorum_state - { - std::vector quorum_nodes; - std::vector nodes_to_test; - - BEGIN_SERIALIZE() - FIELD(quorum_nodes) - FIELD(nodes_to_test) - END_SERIALIZE() - }; - struct service_node_info // registration information { struct contribution_t @@ -71,6 +59,7 @@ namespace service_nodes version_0, version_1_swarms, version_2_infinite_staking, + version_3_checkpointing, }; struct contributor_t @@ -198,9 +187,11 @@ namespace service_nodes void update_swarms(uint64_t height); /// Note(maxim): this should not affect thread-safety as the returned object is const - const std::shared_ptr get_quorum_state(uint64_t height) const; + const std::shared_ptr get_uptime_quorum (uint64_t height) const; + const std::shared_ptr get_checkpointing_quorum(uint64_t height) const; + std::vector get_service_node_list_state(const std::vector &service_node_pubkeys) const; - const std::vector &get_blacklisted_key_images() const { return m_key_image_blacklist; } + const std::vector &get_blacklisted_key_images() const { return m_transient_state.key_image_blacklist; } void set_db_pointer(cryptonote::BlockchainDB* db); void set_my_service_node_keys(crypto::public_key const *pub_key); @@ -283,16 +274,19 @@ namespace service_nodes }; typedef boost::variant rollback_event_variant; - struct quorum_state_for_serialization + struct quorum_for_serialization { uint8_t version; uint64_t height; - quorum_state state; + quorum_uptime_proof uptime_quorum; + quorum_checkpointing checkpointing_quorum; BEGIN_SERIALIZE() FIELD(version) FIELD(height) - FIELD(state) + FIELD(uptime_quorum) + if (version >= service_node_info::version_3_checkpointing) + FIELD(checkpointing_quorum) END_SERIALIZE() }; @@ -300,7 +294,7 @@ namespace service_nodes { uint8_t version; uint64_t height; - std::vector quorum_states; + std::vector quorum_states; std::vector infos; std::vector events; std::vector key_image_blacklist; @@ -327,8 +321,7 @@ namespace service_nodes std::vector get_service_nodes_pubkeys() const; bool contribution_tx_output_has_correct_unlock_time(const cryptonote::transaction& tx, size_t i, uint64_t block_height) const; - - void store_quorum_state_from_rewards_list(uint64_t height); + void generate_quorums(cryptonote::block const &block); bool is_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info) const; std::vector update_and_get_expired_nodes(const std::vector &txs, uint64_t block_height); @@ -337,20 +330,20 @@ namespace service_nodes bool load(); mutable boost::recursive_mutex m_sn_mutex; - std::unordered_map m_service_nodes_infos; - std::list> m_rollback_events; - cryptonote::Blockchain& m_blockchain; - bool m_hooks_registered; + cryptonote::Blockchain& m_blockchain; + bool m_hooks_registered; + crypto::public_key const *m_service_node_pubkey; + cryptonote::BlockchainDB *m_db; using block_height = uint64_t; - block_height m_height; - - crypto::public_key const *m_service_node_pubkey; - cryptonote::BlockchainDB* m_db; - - std::vector m_key_image_blacklist; - std::map> m_quorum_states; - + struct + { + std::unordered_map service_nodes_infos; + std::vector key_image_blacklist; + std::map quorum_states; + std::list> rollback_events; + block_height height; + } m_transient_state; }; bool reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector& addresses, uint64_t& portions_for_operator, std::vector& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key); diff --git a/src/cryptonote_core/service_node_quorum_cop.cpp b/src/cryptonote_core/service_node_quorum_cop.cpp index 3a20a4903..db7dbefd5 100644 --- a/src/cryptonote_core/service_node_quorum_cop.cpp +++ b/src/cryptonote_core/service_node_quorum_cop.cpp @@ -32,6 +32,7 @@ #include "cryptonote_config.h" #include "cryptonote_core.h" #include "version.h" +#include "common/loki.h" #include "common/loki_integration_test_hooks.h" @@ -41,31 +42,36 @@ namespace service_nodes { quorum_cop::quorum_cop(cryptonote::core& core) - : m_core(core), m_last_height(0) + : m_core(core), m_uptime_proof_height(0) { init(); } void quorum_cop::init() { - m_last_height = 0; + m_uptime_proof_height = 0; m_uptime_proof_seen.clear(); } void quorum_cop::blockchain_detached(uint64_t height) { - if (m_last_height >= height) + if (m_uptime_proof_height >= height) { - LOG_ERROR("The blockchain was detached to height: " << height << ", but quorum cop has already processed votes up to " << m_last_height); + LOG_ERROR("The blockchain was detached to height: " << height << ", but quorum cop has already processed votes up to " << m_uptime_proof_height); LOG_ERROR("This implies a reorg occured that was over " << REORG_SAFETY_BUFFER_IN_BLOCKS << ". This should never happen! Please report this to the devs."); - m_last_height = height; + m_uptime_proof_height = height; } } void quorum_cop::block_added(const cryptonote::block& block, const std::vector& txs) { - uint64_t const height = cryptonote::get_block_height(block); + process_uptime_quorum(block); + process_checkpoint_quorum(block); + } + void quorum_cop::process_uptime_quorum(cryptonote::block const &block) + { + uint64_t const height = cryptonote::get_block_height(block); if (m_core.get_hard_fork_version(height) < 9) return; @@ -87,7 +93,6 @@ namespace service_nodes } uint64_t const latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height()); - if (latest_height < service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT) return; @@ -95,20 +100,19 @@ namespace service_nodes if (height < execute_justice_from_height) return; - if (m_last_height < execute_justice_from_height) - m_last_height = execute_justice_from_height; + if (m_uptime_proof_height < execute_justice_from_height) + m_uptime_proof_height = execute_justice_from_height; - - for (;m_last_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_last_height++) + for (;m_uptime_proof_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_uptime_proof_height++) { - if (m_core.get_hard_fork_version(m_last_height) < 9) + if (m_core.get_hard_fork_version(m_uptime_proof_height) < 9) continue; - const std::shared_ptr state = m_core.get_quorum_state(m_last_height); + const std::shared_ptr state = m_core.get_uptime_quorum(m_uptime_proof_height); if (!state) { // TODO(loki): Fatal error - LOG_ERROR("Quorum state for height: " << m_last_height << "was not cached in daemon!"); + LOG_ERROR("Quorum state for height: " << m_uptime_proof_height << " was not cached in daemon!"); continue; } @@ -116,6 +120,9 @@ namespace service_nodes if (it == state->quorum_nodes.end()) continue; + // + // NOTE: I am in the quorum + // size_t my_index_in_quorum = it - state->quorum_nodes.begin(); for (size_t node_index = 0; node_index < state->nodes_to_test.size(); ++node_index) { @@ -128,7 +135,7 @@ namespace service_nodes continue; service_nodes::deregister_vote vote = {}; - vote.block_height = m_last_height; + vote.block_height = m_uptime_proof_height; vote.service_node_index = node_index; vote.voters_quorum_index = my_index_in_quorum; vote.signature = service_nodes::deregister_vote::sign_vote(vote.block_height, vote.service_node_index, my_pubkey, my_seckey); @@ -142,6 +149,57 @@ namespace service_nodes } } + void quorum_cop::process_checkpoint_quorum(cryptonote::block const &block) + { + uint64_t const height = cryptonote::get_block_height(block); + if (m_core.get_hard_fork_version(height) < cryptonote::network_version_12_checkpointing) + return; + + crypto::public_key my_pubkey; + crypto::secret_key my_seckey; + if (!m_core.get_service_node_keys(my_pubkey, my_seckey)) + return; + + if (height % CHECKPOINT_INTERVAL != 0) + return; + + const std::shared_ptr state = m_core.get_checkpointing_quorum(height); + if (!state) + { + // TODO(loki): Fatal error + LOG_ERROR("Quorum state for height: " << height << " was not cached in daemon!"); + return; + } + + auto it = std::find(state->quorum_nodes.begin(), state->quorum_nodes.end(), my_pubkey); + if (it == state->quorum_nodes.end()) + return; + + // + // NOTE: I am in the quorum, handle checkpointing + // + size_t my_index_in_quorum = it - state->quorum_nodes.begin(); + service_nodes::checkpoint_vote vote = {}; + if (!cryptonote::get_block_hash(block, vote.block_hash)) + { + // TODO(loki): Fatal error + LOG_ERROR("Could not get block hash for block on height: " << height); + return; + } + + vote.block_height = height; + vote.voters_quorum_index = my_index_in_quorum; + crypto::generate_signature(vote.block_hash, my_pubkey, my_seckey, vote.signature); + + cryptonote::vote_verification_context vvc = {}; + if (!m_core.add_checkpoint_vote(vote, vvc)) + { + // TODO(doyle): CHECKPOINTING(doyle): + LOG_ERROR("Failed to add checkpoint vote reason: " << print_vote_verification_context(vvc, nullptr)); + } + } + + static crypto::hash make_hash(crypto::public_key const &pubkey, uint64_t timestamp) { char buf[44] = "SUP"; // Meaningless magic bytes diff --git a/src/cryptonote_core/service_node_quorum_cop.h b/src/cryptonote_core/service_node_quorum_cop.h index 702d08397..b31f6e715 100644 --- a/src/cryptonote_core/service_node_quorum_cop.h +++ b/src/cryptonote_core/service_node_quorum_cop.h @@ -29,6 +29,7 @@ #pragma once #include "blockchain.h" +#include "serialization/serialization.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "cryptonote_basic/blobdatatype.h" @@ -45,6 +46,38 @@ namespace service_nodes uint16_t version_major, version_minor, version_patch; }; + struct quorum_checkpointing + { + std::vector quorum_nodes; + BEGIN_SERIALIZE() + FIELD(quorum_nodes) + END_SERIALIZE() + }; + + struct quorum_uptime_proof + { + std::vector quorum_nodes; + std::vector nodes_to_test; + + BEGIN_SERIALIZE() + FIELD(quorum_nodes) + FIELD(nodes_to_test) + END_SERIALIZE() + }; + + struct quorum_manager + { + std::shared_ptr uptime_proof; + std::shared_ptr checkpointing; + }; + + enum struct quorum_type + { + uptime_proof = 0, + checkpointing, + count, + }; + class quorum_cop : public cryptonote::Blockchain::BlockAddedHook, public cryptonote::Blockchain::BlockchainDetachedHook, @@ -57,6 +90,9 @@ namespace service_nodes void block_added(const cryptonote::block& block, const std::vector& txs) override; void blockchain_detached(uint64_t height) override; + void process_uptime_quorum (cryptonote::block const &block); + void process_checkpoint_quorum(cryptonote::block const &block); + bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof); static const uint64_t REORG_SAFETY_BUFFER_IN_BLOCKS = 20; @@ -71,7 +107,7 @@ namespace service_nodes private: cryptonote::core& m_core; - uint64_t m_last_height; + uint64_t m_uptime_proof_height; std::unordered_map m_uptime_proof_seen; mutable epee::critical_section m_lock; diff --git a/src/cryptonote_core/service_node_rules.h b/src/cryptonote_core/service_node_rules.h index e80862693..bbc7d27a2 100644 --- a/src/cryptonote_core/service_node_rules.h +++ b/src/cryptonote_core/service_node_rules.h @@ -10,6 +10,7 @@ namespace service_nodes { constexpr size_t QUORUM_SIZE = 10; constexpr size_t QUORUM_LIFETIME = (6 * deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT); constexpr size_t MIN_VOTES_TO_KICK_SERVICE_NODE = 7; + constexpr size_t MIN_VOTES_TO_CHECKPOINT = MIN_VOTES_TO_KICK_SERVICE_NODE; constexpr size_t NTH_OF_THE_NETWORK_TO_TEST = 100; constexpr size_t MIN_NODES_TO_TEST = 50; constexpr size_t MAX_SWARM_SIZE = 10; @@ -32,9 +33,12 @@ namespace service_nodes { constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1; constexpr uint64_t QUEUE_SWARM_ID = 0; constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0; + constexpr uint64_t CHECKPOINT_INTERVAL = 4; using swarm_id_t = uint64_t; constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX; + static_assert(MIN_VOTES_TO_KICK_SERVICE_NODE <= QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick."); + static_assert(MIN_VOTES_TO_CHECKPOINT <= QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick."); inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 172b66612..25aa5f00e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -347,4 +347,20 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + /************************************************************************/ + /* */ + /************************************************************************/ + struct NOTIFY_NEW_CHECKPOINT_VOTE + { + const static int ID = BC_COMMANDS_POOL_BASE + 12; + + struct request + { + std::vector votes; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(votes) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 56fff4e2b..2593258f9 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -94,6 +94,7 @@ namespace cryptonote HANDLE_NOTIFY_T2(NOTIFY_REQUEST_FLUFFY_MISSING_TX, &cryptonote_protocol_handler::handle_request_fluffy_missing_tx) HANDLE_NOTIFY_T2(NOTIFY_NEW_DEREGISTER_VOTE, &cryptonote_protocol_handler::handle_notify_new_deregister_vote) HANDLE_NOTIFY_T2(NOTIFY_UPTIME_PROOF, &cryptonote_protocol_handler::handle_uptime_proof) + HANDLE_NOTIFY_T2(NOTIFY_NEW_CHECKPOINT_VOTE, &cryptonote_protocol_handler::handle_notify_new_checkpoint_vote) END_INVOKE_MAP2() bool on_idle(); @@ -130,13 +131,33 @@ namespace cryptonote int handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context); int handle_notify_new_deregister_vote(int command, NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& context); int handle_uptime_proof(int command, NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& context); + int handle_notify_new_checkpoint_vote(int command, NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& context); //----------------- i_bc_protocol_layout --------------------------------------- + template + bool relay_on_public_network_generic(typename T::request& arg, cryptonote_connection_context& exclude_context) + { + LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << typeid(T).name() << " -->"); + std::string arg_buff; + epee::serialization::store_t_to_binary(arg, arg_buff); + + std::vector> connections; + m_p2p->for_each_connection([this, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) + { + epee::net_utils::zone zone = context.m_remote_address.get_zone(); + if (peer_id && exclude_context.m_connection_id != context.m_connection_id && zone == epee::net_utils::zone::public_) + connections.push_back({zone, context.m_connection_id}); + return true; + }); + + return m_p2p->relay_notify_to_list(T::ID, epee::strspan(arg_buff), std::move(connections)); + } + virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context); virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context); virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context); - //----------------- uptime proof --------------------------------------- virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context); + virtual bool relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context); //---------------------------------------------------------------------------------- //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context); bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 2ee5c9c04..a6ea6f1af 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -798,6 +798,50 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------ template + int t_cryptonote_protocol_handler::handle_notify_new_checkpoint_vote(int command, NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& context) + { + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_CHECKPOINT_VOTE (" << arg.votes.size() << " txes)"); + + if(context.m_state != cryptonote_connection_context::state_normal) + return 1; + + if(!is_synchronized()) + { + LOG_DEBUG_CC(context, "Received new checkpoint vote while syncing, ignored"); + return 1; + } + + for(auto it = arg.votes.begin(); it != arg.votes.end();) + { + cryptonote::vote_verification_context vvc = {}; + m_core.add_checkpoint_vote(*it, vvc); + + if (vvc.m_verification_failed) + { + LOG_PRINT_CCONTEXT_L1("Checkpoint vote verification failed, dropping connection"); + drop_connection(context, false /*add_fail*/, false /*flush_all_spans i.e. delete cached block data from this peer*/); + return 1; + } + + if (vvc.m_added_to_pool) + { + it++; + } + else + { + it = arg.votes.erase(it); + } + } + + if (arg.votes.size()) + { + relay_checkpoint_votes(arg, context); + } + + return 1; + } + //------------------------------------------------------------------------------------------------------------------------ + template int t_cryptonote_protocol_handler::handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context) { MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_FLUFFY_MISSING_TX (" << arg.missing_tx_indices.size() << " txes), block hash " << arg.block_hash); @@ -2249,20 +2293,22 @@ skip: template bool t_cryptonote_protocol_handler::relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context) { - LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << typeid(NOTIFY_NEW_DEREGISTER_VOTE).name() << " -->"); - std::string arg_buff; - epee::serialization::store_t_to_binary(arg, arg_buff); - - std::vector> connections; - m_p2p->for_each_connection([this, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) - { - epee::net_utils::zone zone = context.m_remote_address.get_zone(); - if (peer_id && exclude_context.m_connection_id != context.m_connection_id && zone == epee::net_utils::zone::public_) - connections.push_back({zone, context.m_connection_id}); - return true; - }); - - return m_p2p->relay_notify_to_list(NOTIFY_NEW_DEREGISTER_VOTE::ID, epee::strspan(arg_buff), std::move(connections)); + bool result = relay_on_public_network_generic(arg, exclude_context); + return result; + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_cryptonote_protocol_handler::relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context) + { + bool result = relay_on_public_network_generic(arg, exclude_context); + return result; + } + //------------------------------------------------------------------------------------------------------------------------ + template + bool t_cryptonote_protocol_handler::relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context) + { + bool result = relay_on_public_network_generic(arg, exclude_context); + return result; } //------------------------------------------------------------------------------------------------------------------------ template @@ -2335,25 +2381,6 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template - bool t_cryptonote_protocol_handler::relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context) - { - LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << typeid(NOTIFY_UPTIME_PROOF).name() << " -->"); - std::string arg_buff; - epee::serialization::store_t_to_binary(arg, arg_buff); - - std::vector> connections; - m_p2p->for_each_connection([this, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) - { - epee::net_utils::zone zone = context.m_remote_address.get_zone(); - if (peer_id && exclude_context.m_connection_id != context.m_connection_id && zone == epee::net_utils::zone::public_) - connections.push_back({zone, context.m_connection_id}); - return true; - }); - - return m_p2p->relay_notify_to_list(NOTIFY_UPTIME_PROOF::ID, epee::strspan(arg_buff), std::move(connections)); - } - //------------------------------------------------------------------------------------------------------------------------ - template std::string t_cryptonote_protocol_handler::get_peers_overview() const { std::stringstream ss; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index e0a721569..efd03b390 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -45,6 +45,7 @@ namespace cryptonote virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context)=0; //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0; virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0; + virtual bool relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0; }; /************************************************************************/ @@ -64,6 +65,10 @@ namespace cryptonote { return false; } + virtual bool relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context) + { + return false; + } virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context) { return false; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 8c701d8ba..b4d4769f3 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -57,6 +57,8 @@ public: , cryptonote::core_rpc_server* rpc_server = NULL ); + bool print_checkpoints(const std::vector& args) { m_executor.print_checkpoints(); return true; } + bool print_peer_list(const std::vector& args); bool print_peer_list_stats(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 898547db2..f87b16ab5 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -353,6 +353,11 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::check_blockchain_pruning, &m_parser, p::_1) , "Check the blockchain pruning." ); + m_command_lookup.set_handler( + "print_checkpoints" + , std::bind(&t_command_parser_executor::print_checkpoints, &m_parser, p::_1) + , "" + ); #if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS) m_command_lookup.set_handler( diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 882fccd7c..10fb8c5a9 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -68,6 +68,8 @@ public: ~t_rpc_command_executor(); + bool print_checkpoints() { m_rpc_server->on_get_checkpoints(); return true; } + bool print_peer_list(bool white = true, bool gray = true, size_t limit = 0); bool print_peer_list_stats(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index fdd5c41b8..2529f1093 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2458,18 +2458,18 @@ namespace cryptonote PERF_TIMER(on_get_quorum_state); bool r; - const auto quorum_state = m_core.get_quorum_state(req.height); - r = (quorum_state != nullptr); + const auto uptime_quorum = m_core.get_uptime_quorum(req.height); + r = (uptime_quorum != nullptr); if (r) { res.status = CORE_RPC_STATUS_OK; - res.quorum_nodes.reserve (quorum_state->quorum_nodes.size()); - res.nodes_to_test.reserve(quorum_state->nodes_to_test.size()); + res.quorum_nodes.reserve (uptime_quorum->quorum_nodes.size()); + res.nodes_to_test.reserve(uptime_quorum->nodes_to_test.size()); - for (const auto &key : quorum_state->quorum_nodes) + for (const auto &key : uptime_quorum->quorum_nodes) res.quorum_nodes.push_back(epee::string_tools::pod_to_hex(key)); - for (const auto &key : quorum_state->nodes_to_test) + for (const auto &key : uptime_quorum->nodes_to_test) res.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key)); } else @@ -2504,9 +2504,9 @@ namespace cryptonote res.quorum_entries.reserve(height_end - height_begin + 1); for (auto h = height_begin; h <= height_end; ++h) { - const auto quorum_state = m_core.get_quorum_state(h); + const auto uptime_quorum = m_core.get_uptime_quorum(h); - if (!quorum_state) { + if (!uptime_quorum) { failed_height = h; break; } @@ -2516,13 +2516,13 @@ namespace cryptonote auto &entry = res.quorum_entries.back(); entry.height = h; - entry.quorum_nodes.reserve(quorum_state->quorum_nodes.size()); - entry.nodes_to_test.reserve(quorum_state->nodes_to_test.size()); + entry.quorum_nodes.reserve(uptime_quorum->quorum_nodes.size()); + entry.nodes_to_test.reserve(uptime_quorum->nodes_to_test.size()); - for (const auto &key : quorum_state->quorum_nodes) + for (const auto &key : uptime_quorum->quorum_nodes) entry.quorum_nodes.push_back(epee::string_tools::pod_to_hex(key)); - for (const auto &key : quorum_state->nodes_to_test) + for (const auto &key : uptime_quorum->nodes_to_test) entry.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key)); } @@ -2700,6 +2700,9 @@ namespace cryptonote res.as_json = cryptonote::obj_to_json_str(pubkey_info_list); } } + + res.height = m_core.get_current_blockchain_height(); + res.block_hash = string_tools::pod_to_hex(m_core.get_block_id_by_height(res.height - 1)); for (auto &pubkey_info : pubkey_info_list) { @@ -2743,6 +2746,7 @@ namespace cryptonote entry.staking_requirement = pubkey_info.info.staking_requirement; entry.portions_for_operator = pubkey_info.info.portions_for_operator; entry.operator_address = cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*is_subaddress*/, pubkey_info.info.operator_address); + entry.swarm_id = pubkey_info.info.swarm_id; res.service_node_states.push_back(entry); } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 38b4d454b..bbf229eaf 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -269,6 +269,8 @@ namespace cryptonote bool on_get_staking_requirement(const COMMAND_RPC_GET_STAKING_REQUIREMENT::request& req, COMMAND_RPC_GET_STAKING_REQUIREMENT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); //----------------------- + void on_get_checkpoints() { m_core.debug__print_checkpoints(); } + #if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS) void on_relay_uptime_and_votes() { diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 6e548fb47..a7a474ca7 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2744,6 +2744,7 @@ namespace cryptonote uint64_t total_reserved; // The total amount of Loki in atomic units reserved in this Service Node. uint64_t staking_requirement; // The staking requirement in atomic units that is required to be contributed to become a Service Node. uint64_t portions_for_operator; // The operator percentage cut to take from each reward expressed in portions, see cryptonote_config.h's STAKING_PORTIONS. + uint64_t swarm_id; // The identifier of the Service Node's current swarm. std::string operator_address; // The wallet address of the operator to which the operator cut of the staking reward is sent to. BEGIN_KV_SERIALIZE_MAP() @@ -2759,16 +2760,22 @@ namespace cryptonote KV_SERIALIZE(total_reserved) KV_SERIALIZE(staking_requirement) KV_SERIALIZE(portions_for_operator) + KV_SERIALIZE(swarm_id) KV_SERIALIZE(operator_address) END_KV_SERIALIZE_MAP() }; std::vector service_node_states; // Array of service node registration information + uint64_t height; // Current block's height. + std::string block_hash; // Current block's hash. std::string status; // Generic RPC error code. "OK" is the success value. std::string as_json; // If `include_json` is set in the request, this contains the json representation of the `entry` data structure + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(service_node_states) + KV_SERIALIZE(height) + KV_SERIALIZE(block_hash) KV_SERIALIZE(status) KV_SERIALIZE(as_json) END_KV_SERIALIZE_MAP() diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4681657b8..faf05cbc9 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2973,7 +2973,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, uint64_t missing_blocks = m_checkpoints.get_max_height() - m_blockchain.size(); while (missing_blocks-- > 0) m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector - m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height)); + m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height).block_hash); m_blockchain.trim(checkpoint_height); short_chain_history.clear(); get_short_chain_history(short_chain_history); diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 515f885e0..d35820869 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -109,6 +109,7 @@ namespace tests uint64_t prevalidate_block_hashes(uint64_t height, const std::vector &hashes) { return 0; } // TODO(loki): Write tests bool add_deregister_vote(const service_nodes::deregister_vote& vote, cryptonote::vote_verification_context &vvc) { return false; } + bool add_checkpoint_vote(const service_nodes::checkpoint_vote& vote, cryptonote::vote_verification_context &vvc) { return false; } bool pad_transactions() const { return false; } uint32_t get_blockchain_pruning_seed() const { return 0; } bool prune_blockchain(uint32_t pruning_seed) const { return true; } diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h index 8208729f2..edf5fa581 100644 --- a/tests/core_tests/bulletproofs.h +++ b/tests/core_tests/bulletproofs.h @@ -50,7 +50,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base return !tvc.m_verifivation_failed && tx_added; } - bool check_tx_verification_context(const std::vector& tvcs, size_t tx_added, size_t event_idx, const std::vector& /*txs*/) + bool check_tx_verification_context_array(const std::vector& tvcs, size_t tx_added, size_t event_idx, const std::vector& /*txs*/) { size_t failed = 0; for (const cryptonote::tx_verification_context &tvc: tvcs) diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 993f7b7c2..ef1976bcb 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1759,3 +1759,21 @@ bool test_chain_unit_base::verify(const std::string& cb_name, cryptonote::core& } return cb_it->second(c, ev_index, events); } + +bool test_chain_unit_base::check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/) +{ + return !bvc.m_verifivation_failed; +} + +bool test_chain_unit_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool /*tx_added*/, size_t /*event_index*/, const cryptonote::transaction& /*tx*/) +{ + return !tvc.m_verifivation_failed; +} + +bool test_chain_unit_base::check_tx_verification_context_array(const std::vector& tvcs, size_t /*tx_added*/, size_t /*event_index*/, const std::vector& /*txs*/) +{ + for (const cryptonote::tx_verification_context &tvc: tvcs) + if (tvc.m_verifivation_failed) + return false; + return true; +} diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index dcacd6dc4..40282ba42 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -179,6 +179,10 @@ public: void register_callback(const std::string& cb_name, verify_callback cb); bool verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector &events); + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*blk*/); + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool /*tx_added*/, size_t /*event_index*/, const cryptonote::transaction& /*tx*/); + bool check_tx_verification_context_array(const std::vector& tvcs, size_t /*tx_added*/, size_t /*event_index*/, const std::vector& /*txs*/); + private: callbacks_map m_callbacks; }; @@ -790,78 +794,6 @@ uint64_t get_balance(const cryptonote::account_base& addr, const std::vector& blockchain, const map_hash2tx_t& mtx); bool extract_hard_forks(const std::vector& events, v_hardforks_t& hard_forks); - -//-------------------------------------------------------------------------- -template -auto do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator, int) - -> decltype(validator.check_tx_verification_context(tvc, tx_added, event_index, tx)) -{ - return validator.check_tx_verification_context(tvc, tx_added, event_index, tx); -} -//-------------------------------------------------------------------------- -template -bool do_check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t /*event_index*/, const cryptonote::transaction& /*tx*/, t_test_class&, long) -{ - // Default block verification context check - if (tvc.m_verifivation_failed) - throw std::runtime_error("Transaction verification failed"); - return true; -} -//-------------------------------------------------------------------------- -template -bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_index, const cryptonote::transaction& tx, t_test_class& validator) -{ - // SFINAE in action - return do_check_tx_verification_context(tvc, tx_added, event_index, tx, validator, 0); -} -//-------------------------------------------------------------------------- -template -auto do_check_tx_verification_context(const std::vector& tvcs, size_t tx_added, size_t event_index, const std::vector& txs, t_test_class& validator, int) - -> decltype(validator.check_tx_verification_context(tvcs, tx_added, event_index, txs)) -{ - return validator.check_tx_verification_context(tvcs, tx_added, event_index, txs); -} -//-------------------------------------------------------------------------- -template -bool do_check_tx_verification_context(const std::vector& tvcs, size_t tx_added, size_t /*event_index*/, const std::vector& /*txs*/, t_test_class&, long) -{ - // Default block verification context check - for (const cryptonote::tx_verification_context &tvc: tvcs) - if (tvc.m_verifivation_failed) - throw std::runtime_error("Transaction verification failed"); - return true; -} -//-------------------------------------------------------------------------- -template -bool check_tx_verification_context(const std::vector& tvcs, size_t tx_added, size_t event_index, const std::vector& txs, t_test_class& validator) -{ - // SFINAE in action - return do_check_tx_verification_context(tvcs, tx_added, event_index, txs, validator, 0); -} -//-------------------------------------------------------------------------- -template -auto do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator, int) - -> decltype(validator.check_block_verification_context(bvc, event_index, blk)) -{ - return validator.check_block_verification_context(bvc, event_index, blk); -} -//-------------------------------------------------------------------------- -template -bool do_check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t /*event_index*/, const cryptonote::block& /*blk*/, t_test_class&, long) -{ - // Default block verification context check - if (bvc.m_verifivation_failed) - throw std::runtime_error("Block verification failed"); - return true; -} -//-------------------------------------------------------------------------- -template -bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_index, const cryptonote::block& blk, t_test_class& validator) -{ - // SFINAE in action - return do_check_block_verification_context(bvc, event_index, blk, validator, 0); -} - /************************************************************************/ /* */ /************************************************************************/ @@ -917,7 +849,7 @@ public: size_t pool_size = m_c.get_pool_transactions_count(); m_c.handle_incoming_tx(t_serializable_object_to_blob(tx), tvc, m_txs_keeped_by_block, false, false); bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); - bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator); + bool r = m_validator.check_tx_verification_context(tvc, tx_added, m_ev_index, tx); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed"); return true; } @@ -937,7 +869,7 @@ public: size_t pool_size = m_c.get_pool_transactions_count(); m_c.handle_incoming_txs(tx_blobs, tvcs, m_txs_keeped_by_block, false, false); size_t tx_added = m_c.get_pool_transactions_count() - pool_size; - bool r = check_tx_verification_context(tvcs, tx_added, m_ev_index, txs, m_validator); + bool r = m_validator.check_tx_verification_context_array(tvcs, tx_added, m_ev_index, txs); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed"); return true; } @@ -948,7 +880,7 @@ public: cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); m_c.handle_incoming_block(t_serializable_object_to_blob(b), &b, bvc); - bool r = check_block_verification_context(bvc, m_ev_index, b, m_validator); + bool r = m_validator.check_block_verification_context(bvc, m_ev_index, b); CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); return r; } @@ -981,8 +913,8 @@ public: { blk = cryptonote::block(); } - bool r = check_block_verification_context(bvc, m_ev_index, blk, m_validator); - CHECK_AND_NO_ASSERT_MES(r, false, "serialized block verification context check failed"); + bool r = m_validator.check_block_verification_context(bvc, m_ev_index, blk); + CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed"); return true; } @@ -1005,7 +937,7 @@ public: tx = cryptonote::transaction(); } - bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator); + bool r = m_validator.check_tx_verification_context(tvc, tx_added, m_ev_index, tx); CHECK_AND_NO_ASSERT_MES(r, false, "transaction verification context check failed"); return true; } diff --git a/tests/core_tests/service_nodes.cpp b/tests/core_tests/service_nodes.cpp index f98603ae6..19b1392e3 100644 --- a/tests/core_tests/service_nodes.cpp +++ b/tests/core_tests/service_nodes.cpp @@ -691,9 +691,9 @@ bool sn_test_rollback::test_registrations(cryptonote::core& c, size_t ev_index, tx_extra_service_node_deregister deregistration; get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration); - const auto quorum_state = c.get_quorum_state(deregistration.block_height); - CHECK_TEST_CONDITION(quorum_state); - const auto pk_a = quorum_state->nodes_to_test.at(deregistration.service_node_index); + const auto uptime_quorum = c.get_uptime_quorum(deregistration.block_height); + CHECK_TEST_CONDITION(uptime_quorum); + const auto pk_a = uptime_quorum->nodes_to_test.at(deregistration.service_node_index); /// Check present const bool found_a = contains(sn_list, pk_a); diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp index 6b7de8d92..98b404479 100644 --- a/tests/unit_tests/ban.cpp +++ b/tests/unit_tests/ban.cpp @@ -91,6 +91,7 @@ public: // TODO(loki): Write tests bool add_deregister_vote(const service_nodes::deregister_vote& vote, cryptonote::vote_verification_context &vvc) { return true; } + bool add_checkpoint_vote(const service_nodes::checkpoint_vote& vote, cryptonote::vote_verification_context &vvc) { return true; } }; typedef nodetool::node_server> Server; diff --git a/tests/unit_tests/service_nodes.cpp b/tests/unit_tests/service_nodes.cpp index f56c859c4..f4abe6595 100644 --- a/tests/unit_tests/service_nodes.cpp +++ b/tests/unit_tests/service_nodes.cpp @@ -123,7 +123,7 @@ TEST(service_nodes, vote_validation) cryptonote::keypair service_node_voter = cryptonote::keypair::generate(hw::get_device("default")); int voter_index = 0; - service_nodes::quorum_state state = {}; + service_nodes::quorum_uptime_proof state = {}; { state.quorum_nodes.resize(10); state.nodes_to_test.resize(state.quorum_nodes.size()); @@ -191,7 +191,7 @@ TEST(service_nodes, tx_extra_deregister_validation) const size_t num_voters = 10; cryptonote::keypair voters[num_voters] = {}; - service_nodes::quorum_state state = {}; + service_nodes::quorum_uptime_proof state = {}; { state.quorum_nodes.resize(num_voters); state.nodes_to_test.resize(num_voters);