Merge dev into upstream

This commit is contained in:
Doyle 2019-05-01 16:01:17 +10:00
parent fbf15ec6ac
commit cc0d51078c
34 changed files with 1034 additions and 617 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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.

View File

@ -768,7 +768,6 @@ int main(int argc, char* argv[])
try try
{ {
core.disable_dns_checkpoints(true);
#if defined(PER_BLOCK_CHECKPOINT) #if defined(PER_BLOCK_CHECKPOINT)
const GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData; const GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData;
#else #else

View File

@ -35,7 +35,9 @@
#include "string_tools.h" #include "string_tools.h"
#include "storages/portable_storage_template_helper.h" // epee json include #include "storages/portable_storage_template_helper.h" // epee json include
#include "serialization/keyvalue_serialization.h" #include "serialization/keyvalue_serialization.h"
#include "cryptonote_core/service_node_rules.h"
#include <vector> #include <vector>
#include "syncobj.h"
using namespace epee; using namespace epee;
@ -69,23 +71,135 @@ namespace cryptonote
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
//---------------------------------------------------------------------------
checkpoints::checkpoints()
{
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str) bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str)
{ {
crypto::hash h = crypto::null_hash; 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!"); 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 CRITICAL_REGION_LOCAL(m_lock);
if (m_points.count(height)) 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<int, service_nodes::QUORUM_SIZE> 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<checkpoint_t> &candidate_checkpoints = m_staging_points[vote.block_height];
std::vector<checkpoint_t>::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; return true;
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -94,67 +208,80 @@ namespace cryptonote
return !m_points.empty() && (height <= (--m_points.end())->first); 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); auto it = m_points.find(height);
is_a_checkpoint = it != m_points.end(); bool found = (it != m_points.end());
if(!is_a_checkpoint) if (is_a_checkpoint) *is_a_checkpoint = found;
if(!found)
return true; return true;
if(it->second == h) checkpoint_t const &checkpoint = it->second;
{ bool result = checkpoint.block_hash == h;
MINFO("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h); if (result) MINFO ("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h);
return true; else MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH " << checkpoint.block_hash << "FETCHED HASH: " << h);
}else return result;
{
MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h);
return false;
}
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
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 bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const
{ {
if (0 == block_height) if (0 == block_height)
return false; return false;
auto it = m_points.upper_bound(blockchain_height); CRITICAL_REGION_LOCAL(m_lock);
// Is blockchain_height before the first checkpoint? std::map<uint64_t, checkpoint_t>::const_iterator it = m_points.upper_bound(blockchain_height);
if (it == m_points.begin()) if (it == m_points.begin()) // Is blockchain_height before the first checkpoint?
return true; return true;
--it; --it; // move the iterator to the first checkpoint that is <= my height
uint64_t checkpoint_height = it->first; uint64_t sentinel_reorg_height = it->first;
return checkpoint_height < block_height; 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 uint64_t checkpoints::get_max_height() const
{ {
std::map< uint64_t, crypto::hash >::const_iterator highest = uint64_t result = 0;
std::max_element( m_points.begin(), m_points.end(), if (m_points.size() > 0)
( boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _1) < {
boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _2 ) ) ); auto last_it = m_points.rbegin();
return highest->first; result = last_it->first;
}
return result;
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
const std::map<uint64_t, crypto::hash>& checkpoints::get_points() const
{
return m_points;
}
bool checkpoints::check_for_conflicts(const checkpoints& other) const bool checkpoints::check_for_conflicts(const checkpoints& other) const
{ {
for (auto& pt : other.get_points()) for (auto& pt : other.get_points())
{ {
if (m_points.count(pt.first)) 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; return true;
@ -212,33 +339,16 @@ namespace cryptonote
uint64_t height; uint64_t height;
height = it->height; height = it->height;
if (height <= prev_max_height) { if (height <= prev_max_height) {
LOG_PRINT_L1("ignoring checkpoint height " << height); LOG_PRINT_L1("ignoring checkpoint height " << height);
} else { } else {
std::string blockhash = it->hash; std::string blockhash = it->hash;
LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash);
ADD_CHECKPOINT(height, blockhash); ADD_CHECKPOINT(height, blockhash);
} }
++it; ++it;
} }
return true; 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;
}
} }

View File

@ -30,16 +30,32 @@
#pragma once #pragma once
#include <map> #include <map>
#include <vector>
#include "misc_log_ex.h" #include "misc_log_ex.h"
#include "crypto/hash.h" #include "crypto/hash.h"
#include "cryptonote_config.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 ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false);
#define JSON_HASH_FILE_NAME "checkpoints.json" #define JSON_HASH_FILE_NAME "checkpoints.json"
namespace cryptonote 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<service_nodes::voter_to_signature> signatures; // Only service node checkpoints use signatures
};
/** /**
* @brief A container for blockchain checkpoints * @brief A container for blockchain checkpoints
* *
@ -50,12 +66,6 @@ namespace cryptonote
class checkpoints class checkpoints
{ {
public: public:
/**
* @brief default constructor
*/
checkpoints();
/** /**
* @brief adds a checkpoint to the container * @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(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 * @brief checks if there is a checkpoint in the future
* *
@ -90,23 +102,19 @@ namespace cryptonote
* *
* @param height the height to be checked * @param height the height to be checked
* @param h the hash 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, * @return true if there is no checkpoint at the given height,
* true if the passed parameters match the stored checkpoint, * true if the passed parameters match the stored checkpoint,
* false otherwise * false otherwise
*/ */
bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; bool check_block(uint64_t height, const crypto::hash& h, bool *is_a_checkpoint = nullptr) const;
/**
* @overload
*/
bool check_block(uint64_t height, const crypto::hash& h) const;
/** /**
* @brief checks if alternate chain blocks should be kept for a given height * @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 * checkpoint then alternate blocks are allowed. Alternatively, if the
* last checkpoint *before* the end of the current chain is also before * last checkpoint *before* the end of the current chain is also before
* the block to be added, then this is fine. * the block to be added, then this is fine.
@ -131,7 +139,7 @@ namespace cryptonote
* *
* @return a const reference to the checkpoints container * @return a const reference to the checkpoints container
*/ */
const std::map<uint64_t, crypto::hash>& get_points() const; std::map<uint64_t, checkpoint_t> get_points() const { return m_points; };
/** /**
* @brief checks if our checkpoints container conflicts with another * @brief checks if our checkpoints container conflicts with another
@ -153,20 +161,6 @@ namespace cryptonote
*/ */
bool init_default_checkpoints(network_type nettype); 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 * @brief load new checkpoints from json
* *
@ -176,17 +170,10 @@ namespace cryptonote
*/ */
bool load_checkpoints_from_json(const std::string &json_hashfile_fullpath); 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: private:
std::map<uint64_t, crypto::hash> m_points; //!< the checkpoints container std::unordered_map<uint64_t, std::vector<checkpoint_t>> m_staging_points; // Incomplete service node checkpoints being voted on
std::map<uint64_t, checkpoint_t> m_points; //!< the checkpoints container
mutable epee::critical_section m_lock;
}; };
} }

View File

@ -265,6 +265,7 @@ namespace cryptonote
network_version_9_service_nodes, // Proof Of Stake w/ Service Nodes 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_10_bulletproofs, // Bulletproofs, Service Node Grace Registration Period, Batched Governance
network_version_11_infinite_staking, network_version_11_infinite_staking,
network_version_12_checkpointing,
network_version_count, network_version_count,
}; };

View File

@ -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): 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_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_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE),
m_long_term_effective_median_block_weight(0), m_long_term_effective_median_block_weight(0),
m_long_term_block_weights_cache_tip_hash(crypto::null_hash), 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_deregister_vote_pool(deregister_vote_pool),
m_btc_valid(false) m_btc_valid(false)
{ {
m_checkpoint_pool.reserve(service_nodes::QUORUM_SIZE * 4 /*blocks*/);
LOG_PRINT_L3("Blockchain::" << __func__); LOG_PRINT_L3("Blockchain::" << __func__);
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -1742,8 +1743,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
return false; return false;
} }
bool is_a_checkpoint; bool is_a_checkpoint = false;
if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) if(!m_checkpoints.check_block(bei.height, id, &is_a_checkpoint))
{ {
LOG_ERROR("CHECKPOINT VALIDATION FAILED"); LOG_ERROR("CHECKPOINT VALIDATION FAILED");
bvc.m_verifivation_failed = true; bvc.m_verifivation_failed = true;
@ -3076,14 +3077,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
return false; return false;
} }
const std::shared_ptr<const service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(deregister.block_height); const std::shared_ptr<const service_nodes::quorum_uptime_proof> uptime_quorum = m_service_node_list.get_uptime_quorum(deregister.block_height);
if (!quorum_state) if (!uptime_quorum)
{ {
MERROR_VER("Deregister TX could not get quorum for height: " << deregister.block_height); MERROR_VER("Deregister TX could not get quorum for height: " << deregister.block_height);
return false; 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; 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)); 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; continue;
} }
const std::shared_ptr<const service_nodes::quorum_state> existing_deregister_quorum_state = m_service_node_list.get_quorum_state(existing_deregister.block_height); const std::shared_ptr<const service_nodes::quorum_uptime_proof> existing_uptime_quorum = m_service_node_list.get_uptime_quorum(existing_deregister.block_height);
if (!existing_deregister_quorum_state) 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; continue;
} }
if (existing_deregister_quorum_state->nodes_to_test[existing_deregister.service_node_index] == if (existing_uptime_quorum->nodes_to_test[existing_deregister.service_node_index] ==
quorum_state->nodes_to_test[deregister.service_node_index]) uptime_quorum->nodes_to_test[deregister.service_node_index])
{ {
MERROR_VER("Already seen this deregister tx (aka double spend)"); MERROR_VER("Already seen this deregister tx (aka double spend)");
tvc.m_double_spend = true; tvc.m_double_spend = true;
@ -3672,6 +3673,7 @@ leave:
bvc.m_verifivation_failed = true; bvc.m_verifivation_failed = true;
goto leave; goto leave;
} }
} }
TIME_MEASURE_FINISH(longhash_calculating_time); 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) bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
{ {
LOG_PRINT_L3("Blockchain::" << __func__); LOG_PRINT_L3("Blockchain::" << __func__);
crypto::hash id = get_block_hash(bl); 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 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. // caller decide course of action.
void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) 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); CRITICAL_REGION_LOCAL(m_blockchain_lock);
stop_batch = m_db->batch_start(); bool 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;
}
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 asked to enforce checkpoints, roll back to a couple of blocks before the checkpoint
if (enforce) if (enforce)
{ {
LOG_ERROR("Local blockchain failed to pass a checkpoint, rolling back!"); LOG_ERROR("Local blockchain failed to pass a checkpoint, rolling back!");
std::list<block> empty; std::list<block> empty;
rollback_blockchain_switching(empty, pt.first - 2); rollback_blockchain_switching(empty, block_height- 2);
} }
else else
{ {
@ -4119,6 +4119,7 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor
} }
} }
} }
if (stop_batch) if (stop_batch)
m_db->batch_stop(); 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. // returns false if any of the checkpoints loading returns false.
// That should happen only if a checkpoint is added that conflicts // That should happen only if a checkpoint is added that conflicts
// with an existing checkpoint. // 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)) if (!m_checkpoints.load_checkpoints_from_json(file_path))
{ return false;
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!");
}
}
check_against_checkpoints(m_checkpoints, true); check_against_checkpoints(m_checkpoints, true);
return 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<const block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const void Blockchain::block_longhash_worker(uint64_t height, const epee::span<const block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const
{ {
@ -4174,7 +4157,7 @@ void Blockchain::block_longhash_worker(uint64_t height, const epee::span<const b
for (const auto & block : blocks) for (const auto & block : blocks)
{ {
if (m_cancel) if (m_cancel)
break; break;
crypto::hash id = get_block_hash(block); crypto::hash id = get_block_hash(block);
crypto::hash pow = get_block_longhash(block, height++); crypto::hash pow = get_block_longhash(block, height++);
map.emplace(id, pow); map.emplace(id, pow);
@ -4546,7 +4529,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
waiter.wait(&tpool); waiter.wait(&tpool);
if (m_cancel) if (m_cancel)
return false; return false;
for (const auto & map : maps) for (const auto & map : maps)
{ {
@ -4587,11 +4570,11 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
std::vector<std::pair<cryptonote::transaction, crypto::hash>> txes(total_txs); std::vector<std::pair<cryptonote::transaction, crypto::hash>> txes(total_txs);
#define SCAN_TABLE_QUIT(m) \ #define SCAN_TABLE_QUIT(m) \
do { \ do { \
MERROR_VER(m) ;\ MERROR_VER(m) ;\
m_scan_table.clear(); \ m_scan_table.clear(); \
return false; \ return false; \
} while(0); \ } while(0); \
// generate sorted tables for all amounts and absolute offsets // generate sorted tables for all amounts and absolute offsets
size_t tx_index = 0, block_index = 0; size_t tx_index = 0, block_index = 0;

View File

@ -92,6 +92,23 @@ namespace cryptonote
class Blockchain class Blockchain
{ {
public: public:
void debug__print_checkpoints()
{
const std::map<uint64_t, checkpoint_t> &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 * @brief Now-defunct (TODO: remove) struct from in-memory blockchain
*/ */
@ -747,15 +764,23 @@ namespace cryptonote
void set_enforce_dns_checkpoints(bool enforce); 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 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 * @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<service_nodes::checkpoint_vote> votes;
};
std::vector<service_node_checkpoint_pool_entry> m_checkpoint_pool;
bool add_checkpoint_vote(service_nodes::checkpoint_vote const &vote);
// user options, must be called before calling init() // user options, must be called before calling init()
@ -1136,8 +1161,6 @@ namespace cryptonote
std::vector<ValidateMinerTxHook*> m_validate_miner_tx_hooks; std::vector<ValidateMinerTxHook*> m_validate_miner_tx_hooks;
checkpoints m_checkpoints; checkpoints m_checkpoints;
bool m_enforce_dns_checkpoints;
HardFork *m_hardfork; HardFork *m_hardfork;
network_type m_nettype; network_type m_nettype;

View File

@ -111,10 +111,6 @@ namespace cryptonote
"offline" "offline"
, "Do not listen for peers, nor connect to any" , "Do not listen for peers, nor connect to any"
}; };
const command_line::arg_descriptor<bool> arg_disable_dns_checkpoints = {
"disable-dns-checkpoints"
, "Do not retrieve checkpoints from DNS"
};
const command_line::arg_descriptor<size_t> arg_block_download_max_size = { const command_line::arg_descriptor<size_t> arg_block_download_max_size = {
"block-download-max-size" "block-download-max-size"
, "Set maximum size of block download queue in bytes (0 for default)" , "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." , "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests."
, 0 , 0
}; };
static const command_line::arg_descriptor<bool> arg_dns_checkpoints = {
"enforce-dns-checkpointing"
, "checkpoints from DNS server will be enforced"
, false
};
static const command_line::arg_descriptor<uint64_t> arg_fast_block_sync = { static const command_line::arg_descriptor<uint64_t> arg_fast_block_sync = {
"fast-block-sync" "fast-block-sync"
, "Sync up most of the way by using embedded, known block hashes." , "Sync up most of the way by using embedded, known block hashes."
@ -230,9 +221,7 @@ namespace cryptonote
m_starter_message_showed(false), m_starter_message_showed(false),
m_target_blockchain_height(0), m_target_blockchain_height(0),
m_checkpoints_path(""), m_checkpoints_path(""),
m_last_dns_checkpoints_update(0),
m_last_json_checkpoints_update(0), m_last_json_checkpoints_update(0),
m_disable_dns_checkpoints(false),
m_update_download(0), m_update_download(0),
m_nettype(UNDEFINED), m_nettype(UNDEFINED),
m_update_available(false), m_update_available(false),
@ -248,41 +237,18 @@ namespace cryptonote
else else
m_pprotocol = &m_protocol_stub; 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() 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; if (m_checkpoints_updating.test_and_set()) return true;
bool res = 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); res = m_blockchain_storage.update_checkpoints(m_checkpoints_path);
m_last_dns_checkpoints_update = time(NULL);
m_last_json_checkpoints_update = time(NULL); 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(); m_checkpoints_updating.clear();
// if anything fishy happened getting new checkpoints, bring down the house // 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_stagenet_on);
command_line::add_arg(desc, arg_regtest_on); command_line::add_arg(desc, arg_regtest_on);
command_line::add_arg(desc, arg_fixed_difficulty); 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_prep_blocks_threads);
command_line::add_arg(desc, arg_fast_block_sync); command_line::add_arg(desc, arg_fast_block_sync);
command_line::add_arg(desc, arg_show_time_stats); 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_no_fluffy_blocks);
command_line::add_arg(desc, arg_test_dbg_lock_sleep); command_line::add_arg(desc, arg_test_dbg_lock_sleep);
command_line::add_arg(desc, arg_offline); 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_block_download_max_size);
command_line::add_arg(desc, arg_max_txpool_weight); command_line::add_arg(desc, arg_max_txpool_weight);
command_line::add_arg(desc, arg_service_node); command_line::add_arg(desc, arg_service_node);
@ -362,28 +326,25 @@ namespace cryptonote
auto data_dir = boost::filesystem::path(m_config_folder); auto data_dir = boost::filesystem::path(m_config_folder);
if (m_nettype == MAINNET) // Init Checkpoints
{ {
cryptonote::checkpoints checkpoints; cryptonote::checkpoints checkpoints;
if (!checkpoints.init_default_checkpoints(m_nettype)) if (!checkpoints.init_default_checkpoints(m_nettype))
{ {
throw std::runtime_error("Failed to initialize checkpoints"); 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 json(JSON_HASH_FILE_NAME);
boost::filesystem::path checkpoint_json_hashfile_fullpath = data_dir / json; 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)); 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_fluffy_blocks_enabled = !get_arg(vm, arg_no_fluffy_blocks);
m_pad_transactions = get_arg(vm, arg_pad_transactions); m_pad_transactions = get_arg(vm, arg_pad_transactions);
m_offline = get_arg(vm, arg_offline); 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)) if (!command_line::is_arg_defaulted(vm, arg_fluffy_blocks))
MWARNING(arg_fluffy_blocks.name << " is obsolete, it is now default"); MWARNING(arg_fluffy_blocks.name << " is obsolete, it is now default");
@ -719,9 +680,9 @@ namespace cryptonote
MGINFO("Loading checkpoints"); MGINFO("Loading checkpoints");
// load json & DNS checkpoints, and verify them // load json checkpoints and verify them
// with respect to what blocks we already have // 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 // DNS versions checking
if (check_updates_string == "disabled") if (check_updates_string == "disabled")
@ -1408,6 +1369,42 @@ namespace cryptonote
return true; return true;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool core::relay_checkpoint_votes()
{
const time_t now = time(nullptr);
// Get relayable votes
NOTIFY_NEW_CHECKPOINT_VOTE::request req = {};
std::vector<service_nodes::checkpoint_vote *> 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) 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); 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_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_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_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_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_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)); m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this));
@ -2085,9 +2083,14 @@ namespace cryptonote
return si.available; return si.available;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
const std::shared_ptr<const service_nodes::quorum_state> core::get_quorum_state(uint64_t height) const const std::shared_ptr<const service_nodes::quorum_uptime_proof> 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<const service_nodes::quorum_checkpointing> 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 bool core::is_service_node(const crypto::public_key& pubkey) const
@ -2135,8 +2138,8 @@ namespace cryptonote
return false; return false;
} }
const auto quorum_state = m_service_node_list.get_quorum_state(vote.block_height); const auto uptime_quorum = m_service_node_list.get_uptime_quorum(vote.block_height);
if (!quorum_state) if (!uptime_quorum)
{ {
vvc.m_verification_failed = true; vvc.m_verification_failed = true;
vvc.m_invalid_block_height = true; vvc.m_invalid_block_height = true;
@ -2146,7 +2149,7 @@ namespace cryptonote
cryptonote::transaction deregister_tx; cryptonote::transaction deregister_tx;
int hf_version = m_blockchain_storage.get_current_hard_fork_version(); 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) if (result && vvc.m_full_tx_deregister_made)
{ {
tx_verification_context tvc = AUTO_VAL_INIT(tvc); tx_verification_context tvc = AUTO_VAL_INIT(tvc);
@ -2165,6 +2168,82 @@ namespace cryptonote
return result; 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<const service_nodes::quorum_uptime_proof> 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<Blockchain::service_node_checkpoint_pool_entry> &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 bool core::get_service_node_keys(crypto::public_key &pub_key, crypto::secret_key &sec_key) const
{ {
if (m_service_node) if (m_service_node)

View File

@ -82,6 +82,8 @@ namespace cryptonote
{ {
public: public:
void debug__print_checkpoints() { m_blockchain_storage.debug__print_checkpoints(); }
/** /**
* @brief constructor * @brief constructor
* *
@ -411,34 +413,6 @@ namespace cryptonote
*/ */
void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); 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 * @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 * @return Null shared ptr if quorum has not been determined yet for height
*/ */
const std::shared_ptr<const service_nodes::quorum_state> get_quorum_state(uint64_t height) const; const std::shared_ptr<const service_nodes::quorum_uptime_proof> 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<const service_nodes::quorum_checkpointing> get_checkpointing_quorum(uint64_t height) const;
/** /**
* @brief Get a non owning reference to the list of blacklisted key images * @brief Get a non owning reference to the list of blacklisted key images
@ -830,14 +813,15 @@ namespace cryptonote
*/ */
std::vector<service_nodes::service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key>& service_node_pubkeys) const; std::vector<service_nodes::service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key>& service_node_pubkeys) const;
/** /**
* @brief get whether `pubkey` is known as a service node * @brief get whether `pubkey` is known as a service node
* *
* @param pubkey the public key to test * @param pubkey the public key to test
* *
* @return whether `pubkey` is known as a service node * @return whether `pubkey` is known as a service node
*/ */
bool is_service_node(const crypto::public_key& pubkey) const; bool is_service_node(const crypto::public_key& pubkey) const;
/** /**
* @brief Add a vote to deregister a service node from network * @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); 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. * @brief Get the keypair for this service node.
@ -1074,6 +1068,13 @@ namespace cryptonote
*/ */
bool relay_txpool_transactions(); 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 * @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*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*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_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*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<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space
epee::math_helper::once_a_time_seconds<UPTIME_PROOF_BUFFER_IN_SECONDS, true> m_check_uptime_proof_interval; //!< interval for checking our own uptime proof epee::math_helper::once_a_time_seconds<UPTIME_PROOF_BUFFER_IN_SECONDS, true> 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<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*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<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown?
uint64_t m_target_blockchain_height; //!< blockchain height target uint64_t m_target_blockchain_height; //!< blockchain height target
@ -1150,11 +1153,9 @@ namespace cryptonote
std::atomic<bool> m_update_available; std::atomic<bool> m_update_available;
std::string m_checkpoints_path; //!< path to json checkpoints file 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 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 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; bool m_service_node;
crypto::secret_key m_service_node_key; crypto::secret_key m_service_node_key;

View File

@ -90,16 +90,16 @@ namespace service_nodes
static bool verify_votes_helper(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister, static bool verify_votes_helper(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context &vvc, 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; 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; return false;
} }
const std::vector<crypto::public_key>& quorum = quorum_state.quorum_nodes; const std::vector<crypto::public_key>& quorum = uptime_quorum.quorum_nodes;
std::vector<int8_t> quorum_set; std::vector<int8_t> quorum_set;
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs; std::vector<std::pair<crypto::public_key, crypto::signature>> 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, bool deregister_vote::verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context &vvc, 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) if (deregister.votes.size() < service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
{ {
@ -144,18 +144,18 @@ namespace service_nodes
return false; return false;
} }
bool result = verify_votes_helper(nettype, deregister, vvc, quorum_state); bool result = verify_votes_helper(nettype, deregister, vvc, uptime_quorum);
return result; return result;
} }
bool deregister_vote::verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc, 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; cryptonote::tx_extra_service_node_deregister deregister;
deregister.block_height = v.block_height; deregister.block_height = v.block_height;
deregister.service_node_index = v.service_node_index; 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 }); 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<deregister_vote>& votes) void deregister_vote_pool::set_relayed(const std::vector<deregister_vote>& votes)
@ -217,10 +217,10 @@ namespace service_nodes
bool deregister_vote_pool::add_vote(const int hf_version, bool deregister_vote_pool::add_vote(const int hf_version,
const deregister_vote& new_vote, const deregister_vote& new_vote,
cryptonote::vote_verification_context& vvc, cryptonote::vote_verification_context& vvc,
const service_nodes::quorum_state &quorum_state, const service_nodes::quorum_uptime_proof &uptime_quorum,
cryptonote::transaction &tx) 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"); LOG_PRINT_L1("Signature verification failed for deregister vote");
return false; return false;

View File

@ -47,7 +47,22 @@ namespace cryptonote
namespace service_nodes 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 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, static bool verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context& vvc, 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, 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 class deregister_vote_pool
@ -80,7 +95,7 @@ namespace service_nodes
bool add_vote(const int hf_version, bool add_vote(const int hf_version,
const deregister_vote& new_vote, const deregister_vote& new_vote,
cryptonote::vote_verification_context& vvc, cryptonote::vote_verification_context& vvc,
const service_nodes::quorum_state &quorum_state, const service_nodes::quorum_uptime_proof &uptime_quorum,
cryptonote::transaction &tx); cryptonote::transaction &tx);
// TODO(loki): Review relay behaviour and all the cases when it should be triggered // TODO(loki): Review relay behaviour and all the cases when it should be triggered

View File

@ -56,12 +56,16 @@ namespace service_nodes
if (hf_version == cryptonote::network_version_10_bulletproofs) if (hf_version == cryptonote::network_version_10_bulletproofs)
return service_node_info::version_1_swarms; 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) 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) 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(); uint64_t current_height = m_blockchain.get_current_blockchain_height();
bool loaded = load(); 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..."); LOG_PRINT_L0("This may take some time...");
std::vector<std::pair<cryptonote::blobdata, cryptonote::block>> blocks; std::vector<std::pair<cryptonote::blobdata, cryptonote::block>> blocks;
while (m_height < current_height) while (m_transient_state.height < current_height)
{ {
blocks.clear(); 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"); MERROR("Unable to initialize service nodes list");
return; return;
@ -132,7 +136,7 @@ namespace service_nodes
std::vector<crypto::public_key> service_node_list::get_service_nodes_pubkeys() const std::vector<crypto::public_key> service_node_list::get_service_nodes_pubkeys() const
{ {
std::vector<crypto::public_key> result; std::vector<crypto::public_key> 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()) if (iter.second.is_fully_funded())
result.push_back(iter.first); result.push_back(iter.first);
@ -143,15 +147,21 @@ namespace service_nodes
return result; return result;
} }
const std::shared_ptr<const quorum_state> service_node_list::get_quorum_state(uint64_t height) const const std::shared_ptr<const quorum_uptime_proof> service_node_list::get_uptime_quorum(uint64_t height) const
{ {
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex); std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
const auto &it = m_quorum_states.find(height); const auto &it = m_transient_state.quorum_states.find(height);
if (it != m_quorum_states.end()) if (it != m_transient_state.quorum_states.end())
{ return it->second.uptime_proof;
return it->second; return nullptr;
} }
const std::shared_ptr<const quorum_checkpointing> service_node_list::get_checkpointing_quorum(uint64_t height) const
{
std::lock_guard<boost::recursive_mutex> 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; return nullptr;
} }
@ -162,9 +172,9 @@ namespace service_nodes
if (service_node_pubkeys.empty()) 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 = {}; service_node_pubkey_info entry = {};
entry.pubkey = it.first; entry.pubkey = it.first;
@ -177,8 +187,8 @@ namespace service_nodes
result.reserve(service_node_pubkeys.size()); result.reserve(service_node_pubkeys.size());
for (const auto &it : service_node_pubkeys) for (const auto &it : service_node_pubkeys)
{ {
const auto &find_it = m_service_nodes_infos.find(it); const auto &find_it = m_transient_state.service_nodes_infos.find(it);
if (find_it == m_service_nodes_infos.end()) if (find_it == m_transient_state.service_nodes_infos.end())
continue; continue;
service_node_pubkey_info entry = {}; 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 bool service_node_list::is_service_node(const crypto::public_key& pubkey) const
{ {
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex); std::lock_guard<boost::recursive_mutex> 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 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; const service_node_info &info = pubkey_info.second;
for (const service_node_info::contributor_t &contributor : info.contributors) for (const service_node_info::contributor_t &contributor : info.contributors)
@ -308,12 +318,12 @@ namespace service_nodes
return false; return false;
} }
const auto state = get_quorum_state(deregister.block_height); const auto state = get_uptime_quorum(deregister.block_height);
if (!state) if (!state)
{ {
// TODO(loki): Not being able to find a quorum is fatal! We want better caching abilities. // 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; return false;
} }
@ -325,8 +335,8 @@ namespace service_nodes
const crypto::public_key& key = state->nodes_to_test[deregister.service_node_index]; const crypto::public_key& key = state->nodes_to_test[deregister.service_node_index];
auto iter = m_service_nodes_infos.find(key); auto iter = m_transient_state.service_nodes_infos.find(key);
if (iter == m_service_nodes_infos.end()) if (iter == m_transient_state.service_nodes_infos.end())
return false; return false;
if (m_service_node_pubkey && *m_service_node_pubkey == key) 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); LOG_PRINT_L1("Deregistration for service node: " << key);
} }
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, key, iter->second))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, key, iter->second)));
int hard_fork_version = m_blockchain.get_hard_fork_version(block_height); int hard_fork_version = m_blockchain.get_hard_fork_version(block_height);
if (hard_fork_version >= cryptonote::network_version_11_infinite_staking) if (hard_fork_version >= cryptonote::network_version_11_infinite_staking)
@ -350,15 +360,15 @@ namespace service_nodes
key_image_blacklist_entry entry = {}; key_image_blacklist_entry entry = {};
entry.key_image = contribution.key_image; entry.key_image = contribution.key_image;
entry.unlock_height = block_height + staking_num_lock_blocks(m_blockchain.nettype()); 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; const bool adding_to_blacklist = true;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_key_image_blacklist(block_height, entry, adding_to_blacklist))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(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; return true;
} }
@ -371,7 +381,7 @@ namespace service_nodes
/// Gather existing swarms from infos /// Gather existing swarms from infos
swarm_snode_map_t existing_swarms; 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; const auto id = entry.second.swarm_id;
existing_swarms[id].push_back(entry.first); existing_swarms[id].push_back(entry.first);
} }
@ -386,11 +396,11 @@ namespace service_nodes
for (const auto snode : snodes) { 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 if (sn_info.swarm_id == swarm_id) continue; /// nothing changed for this snode
/// modify info and record the change /// modify info and record the change
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(height, snode, sn_info))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(height, snode, sn_info)));
sn_info.swarm_id = swarm_id; sn_info.swarm_id = swarm_id;
} }
@ -634,8 +644,8 @@ namespace service_nodes
if (hard_fork_version >= cryptonote::network_version_11_infinite_staking) 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 // 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); const auto iter = m_transient_state.service_nodes_infos.find(key);
if (iter != m_service_nodes_infos.end()) if (iter != m_transient_state.service_nodes_infos.end())
return false; return false;
if (m_service_node_pubkey && *m_service_node_pubkey == key) MGINFO_GREEN("Service node registered (yours): " << key << " on height: " << block_height); 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 // 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. // So it is possible to find the node still in our list.
bool registered_during_grace_period = false; bool registered_during_grace_period = false;
const auto iter = m_service_nodes_infos.find(key); const auto iter = m_transient_state.service_nodes_infos.find(key);
if (iter != m_service_nodes_infos.end()) if (iter != m_transient_state.service_nodes_infos.end())
{ {
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs) if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
{ {
@ -684,8 +694,8 @@ namespace service_nodes
} }
} }
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_new(block_height, key))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_new(block_height, key)));
m_service_nodes_infos[key] = info; m_transient_state.service_nodes_infos[key] = info;
return true; return true;
} }
@ -705,8 +715,8 @@ namespace service_nodes
} }
/// Service node must be registered /// Service node must be registered
auto iter = m_service_nodes_infos.find(pubkey); auto iter = m_transient_state.service_nodes_infos.find(pubkey);
if (iter == m_service_nodes_infos.end()) if (iter == m_transient_state.service_nodes_infos.end())
{ {
LOG_PRINT_L1("Contribution TX: Contribution received for service node: " << pubkey << 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 << ", but could not be found in the service node list on height: " << block_height <<
@ -765,7 +775,7 @@ namespace service_nodes
// Successfully Validated // Successfully Validated
// //
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, pubkey, info))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, pubkey, info)));
if (new_contributor) if (new_contributor)
{ {
service_node_info::contributor_t new_contributor = {}; service_node_info::contributor_t new_contributor = {};
@ -838,28 +848,28 @@ namespace service_nodes
// Remove old rollback events // Remove old rollback events
// //
{ {
assert(m_height == block_height); assert(m_transient_state.height == block_height);
++m_height; ++m_transient_state.height;
const size_t ROLLBACK_EVENT_EXPIRATION_BLOCKS = 30; 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; 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<rollback_event>(new prevent_rollback(cull_height))); m_transient_state.rollback_events.push_front(std::unique_ptr<rollback_event>(new prevent_rollback(cull_height)));
} }
// //
// Remove expired blacklisted key images // 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) if (block_height >= entry->unlock_height)
{ {
const bool adding_to_blacklist = false; const bool adding_to_blacklist = false;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_key_image_blacklist(block_height, (*entry), adding_to_blacklist))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_key_image_blacklist(block_height, (*entry), adding_to_blacklist)));
entry = m_key_image_blacklist.erase(entry); entry = m_transient_state.key_image_blacklist.erase(entry);
} }
else else
entry++; entry++;
@ -871,8 +881,8 @@ namespace service_nodes
size_t expired_count = 0; size_t expired_count = 0;
for (const crypto::public_key& pubkey : update_and_get_expired_nodes(txs, block_height)) for (const crypto::public_key& pubkey : update_and_get_expired_nodes(txs, block_height))
{ {
auto i = m_service_nodes_infos.find(pubkey); auto i = m_transient_state.service_nodes_infos.find(pubkey);
if (i != m_service_nodes_infos.end()) if (i != m_transient_state.service_nodes_infos.end())
{ {
if (m_service_node_pubkey && *m_service_node_pubkey == pubkey) 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); LOG_PRINT_L1("Service node expired: " << pubkey << " at block height: " << block_height);
} }
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, pubkey, i->second))); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, pubkey, i->second)));
expired_count++; 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); 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<rollback_event>( std::unique_ptr<rollback_event>(
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 // 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_transient_state.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_transaction_index = UINT32_MAX;
} }
} }
@ -935,8 +945,8 @@ namespace service_nodes
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key)) if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, snode_key))
continue; continue;
auto it = m_service_nodes_infos.find(snode_key); auto it = m_transient_state.service_nodes_infos.find(snode_key);
if (it == m_service_nodes_infos.end()) if (it == m_transient_state.service_nodes_infos.end())
continue; continue;
service_node_info &node_info = (*it).second; service_node_info &node_info = (*it).second;
@ -990,27 +1000,25 @@ namespace service_nodes
// //
// Update Quorum // Update Quorum
// //
generate_quorums(block);
const size_t cache_state_from_height = (block_height < QUORUM_LIFETIME) ? 0 : block_height - QUORUM_LIFETIME; 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_transient_state.quorum_states.empty() && m_transient_state.quorum_states.begin()->first < cache_state_from_height)
while (!m_quorum_states.empty() && m_quorum_states.begin()->first < cache_state_from_height) m_transient_state.quorum_states.erase(m_transient_state.quorum_states.begin());
{
m_quorum_states.erase(m_quorum_states.begin());
}
} }
void service_node_list::blockchain_detached(uint64_t height) void service_node_list::blockchain_detached(uint64_t height)
{ {
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex); std::lock_guard<boost::recursive_mutex> 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; bool rollback_applied = true;
switch(event->type) switch(event->type)
{ {
case rollback_event::change_type: case rollback_event::change_type:
{ {
auto *rollback = reinterpret_cast<rollback_change *>(event); auto *rollback = reinterpret_cast<rollback_change *>(event);
m_service_nodes_infos[rollback->m_key] = rollback->m_info; m_transient_state.service_nodes_infos[rollback->m_key] = rollback->m_info;
} }
break; break;
@ -1018,15 +1026,15 @@ namespace service_nodes
{ {
auto *rollback = reinterpret_cast<rollback_new *>(event); auto *rollback = reinterpret_cast<rollback_new *>(event);
auto iter = m_service_nodes_infos.find(rollback->m_key); auto iter = m_transient_state.service_nodes_infos.find(rollback->m_key);
if (iter == m_service_nodes_infos.end()) if (iter == m_transient_state.service_nodes_infos.end())
{ {
MERROR("Could not find service node pubkey in rollback new"); MERROR("Could not find service node pubkey in rollback new");
rollback_applied = false; rollback_applied = false;
break; break;
} }
m_service_nodes_infos.erase(iter); m_transient_state.service_nodes_infos.erase(iter);
} }
break; break;
@ -1037,23 +1045,23 @@ namespace service_nodes
auto *rollback = reinterpret_cast<rollback_key_image_blacklist *>(event); auto *rollback = reinterpret_cast<rollback_key_image_blacklist *>(event);
if (rollback->m_was_adding_to_blacklist) 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) { [rollback] (key_image_blacklist_entry const &a) {
return (rollback->m_entry.unlock_height == a.unlock_height && rollback->m_entry.key_image == a.key_image); 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"); LOG_PRINT_L1("Could not find blacklisted key image to remove");
rollback_applied = false; rollback_applied = false;
break; break;
} }
m_key_image_blacklist.erase(it); m_transient_state.key_image_blacklist.erase(it);
} }
else else
{ {
m_key_image_blacklist.push_back(rollback->m_entry); m_transient_state.key_image_blacklist.push_back(rollback->m_entry);
} }
} }
break; break;
@ -1072,13 +1080,13 @@ namespace service_nodes
break; break;
} }
m_rollback_events.pop_back(); m_transient_state.rollback_events.pop_back();
} }
while (!m_quorum_states.empty() && (--m_quorum_states.end())->first >= height) while (!m_transient_state.quorum_states.empty() && (--m_transient_state.quorum_states.end())->first >= height)
m_quorum_states.erase(--m_quorum_states.end()); m_transient_state.quorum_states.erase(--m_transient_state.quorum_states.end());
m_height = height; m_transient_state.height = height;
store(); store();
} }
@ -1125,7 +1133,7 @@ namespace service_nodes
} }
else 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; crypto::public_key const &snode_key = it->first;
service_node_info &info = it->second; service_node_info &info = it->second;
@ -1161,7 +1169,7 @@ namespace service_nodes
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> winners; std::vector<std::pair<cryptonote::account_public_address, uint64_t>> 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; const uint64_t remaining_portions = STAKING_PORTIONS - info.portions_for_operator;
@ -1185,7 +1193,7 @@ namespace service_nodes
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex); std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
auto oldest_waiting = std::pair<uint64_t, uint32_t>(std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint32_t>::max()); auto oldest_waiting = std::pair<uint64_t, uint32_t>(std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint32_t>::max());
crypto::public_key key = crypto::null_pkey; 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()) if (info.second.is_fully_funded())
{ {
auto waiting_since = std::make_pair(info.second.last_reward_block_height, info.second.last_reward_transaction_index); 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; return true;
} }
void service_node_list::store_quorum_state_from_rewards_list(uint64_t height) std::vector<size_t> generate_shuffled_service_node_index_list(std::vector<crypto::public_key> const &snode_list, crypto::hash const &block_hash, quorum_type type)
{ {
const crypto::hash block_hash = m_blockchain.get_block_id_by_height(height); std::vector<size_t> result(snode_list.size());
if (block_hash == crypto::null_hash) 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<uint64_t>(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"); MERROR("Block height: " << height << " returned null hash");
return; return;
} }
std::vector<crypto::public_key> full_node_list = get_service_nodes_pubkeys(); std::vector<crypto::public_key> const snode_list = get_service_nodes_pubkeys();
std::vector<size_t> pub_keys_indexes(full_node_list.size()); for (int type_int = 0; type_int < static_cast<int>(quorum_type::count); type_int++)
{ {
size_t index = 0; auto type = static_cast<quorum_type>(type_int);
for (size_t i = 0; i < full_node_list.size(); i++) { pub_keys_indexes[i] = i; } std::vector<size_t> const pub_keys_indexes = generate_shuffled_service_node_index_list(snode_list, block_hash, type);
// Shuffle indexes switch(type)
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<quorum_state>();
{
std::vector<crypto::public_key>& quorum = new_state->quorum_nodes;
{ {
quorum.resize(std::min(full_node_list.size(), QUORUM_SIZE)); case quorum_type::uptime_proof:
for (size_t i = 0; i < quorum.size(); i++)
{ {
size_t node_index = pub_keys_indexes[i]; // Assign indexes from shuffled list into quorum and list of nodes to test
const crypto::public_key &key = full_node_list[node_index]; auto new_state = std::make_shared<quorum_uptime_proof>();
quorum[i] = key; std::vector<crypto::public_key>& 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<crypto::public_key>& 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<quorum_checkpointing>();
std::vector<crypto::public_key>& 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<crypto::public_key>& 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() 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; return true;
CHECK_AND_ASSERT_MES(m_db != nullptr, false, "Failed to store service node info, m_db == nullptr"); 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<boost::recursive_mutex> lock(m_sn_mutex); std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
quorum_state_for_serialization quorum; for(const auto& kv_pair : m_transient_state.quorum_states)
for(const auto& kv_pair : m_quorum_states)
{ {
quorum.height = kv_pair.first; quorum_for_serialization quorum = {};
quorum.state = *kv_pair.second; 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); data_to_store.quorum_states.push_back(quorum);
} }
service_node_pubkey_info info; 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.pubkey = kv_pair.first;
info.info = kv_pair.second; info.info = kv_pair.second;
data_to_store.infos.push_back(info); 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) 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; data_to_store.height = m_transient_state.height;
int hf_version = m_blockchain.get_hard_fork_version(m_height - 1);
data_to_store.version = get_min_service_node_info_version_for_hf(hf_version); data_to_store.version = get_min_service_node_info_version_for_hf(hf_version);
std::stringstream ss; std::stringstream ss;
@ -1406,12 +1460,12 @@ namespace service_nodes
void service_node_list::get_all_service_nodes_public_keys(std::vector<crypto::public_key>& keys, bool fully_funded_nodes_only) const void service_node_list::get_all_service_nodes_public_keys(std::vector<crypto::public_key>& keys, bool fully_funded_nodes_only) const
{ {
keys.clear(); keys.clear();
keys.resize(m_service_nodes_infos.size()); keys.resize(m_transient_state.service_nodes_infos.size());
size_t i = 0; size_t i = 0;
if (fully_funded_nodes_only) 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; service_node_info const &info = it.second;
if (info.is_fully_funded()) if (info.is_fully_funded())
@ -1420,7 +1474,7 @@ namespace service_nodes
} }
else else
{ {
for (const auto &it : m_service_nodes_infos) for (const auto &it : m_transient_state.service_nodes_infos)
keys[i++] = it.first; keys[i++] = it.first;
} }
} }
@ -1452,17 +1506,24 @@ namespace service_nodes
bool r = ::serialization::serialize(ba, data_in); bool r = ::serialization::serialize(ba, data_in);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse service node data from blob"); CHECK_AND_ASSERT_MES(r, false, "Failed to parse service node data from blob");
m_height = data_in.height; m_transient_state.height = data_in.height;
m_key_image_blacklist = data_in.key_image_blacklist; 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>(quorum.state); if (states.uptime_quorum.quorum_nodes.size() > 0)
m_transient_state.quorum_states[states.height].uptime_proof = std::make_shared<quorum_uptime_proof>(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<quorum_checkpointing>(states.checkpointing_quorum);
}
} }
for (const auto& info : data_in.infos) 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) for (const auto& event : data_in.events)
@ -1472,28 +1533,28 @@ namespace service_nodes
const auto& from = boost::get<rollback_change>(event); const auto& from = boost::get<rollback_change>(event);
auto *i = new rollback_change(); auto *i = new rollback_change();
*i = from; *i = from;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i)); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(i));
} }
else if (event.type() == typeid(rollback_new)) else if (event.type() == typeid(rollback_new))
{ {
const auto& from = boost::get<rollback_new>(event); const auto& from = boost::get<rollback_new>(event);
auto *i = new rollback_new(); auto *i = new rollback_new();
*i = from; *i = from;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i)); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(i));
} }
else if (event.type() == typeid(prevent_rollback)) else if (event.type() == typeid(prevent_rollback))
{ {
const auto& from = boost::get<prevent_rollback>(event); const auto& from = boost::get<prevent_rollback>(event);
auto *i = new prevent_rollback(); auto *i = new prevent_rollback();
*i = from; *i = from;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i)); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(i));
} }
else if (event.type() == typeid(rollback_key_image_blacklist)) else if (event.type() == typeid(rollback_key_image_blacklist))
{ {
const auto& from = boost::get<rollback_key_image_blacklist>(event); const auto& from = boost::get<rollback_key_image_blacklist>(event);
auto *i = new rollback_key_image_blacklist(); auto *i = new rollback_key_image_blacklist();
*i = from; *i = from;
m_rollback_events.push_back(std::unique_ptr<rollback_event>(i)); m_transient_state.rollback_events.push_back(std::unique_ptr<rollback_event>(i));
} }
else else
{ {
@ -1502,8 +1563,8 @@ namespace service_nodes
} }
} }
MGINFO("Service node data loaded successfully, m_height: " << m_height); MGINFO("Service node data loaded successfully, height: " << m_transient_state.height);
MGINFO(m_service_nodes_infos.size() << " nodes and " << m_rollback_events.size() << " rollback events loaded."); 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"); LOG_PRINT_L1("service_node_list::load() returning success");
return true; return true;
@ -1511,9 +1572,7 @@ namespace service_nodes
void service_node_list::clear(bool delete_db_entry) void service_node_list::clear(bool delete_db_entry)
{ {
m_service_nodes_infos.clear(); m_transient_state = {};
m_rollback_events.clear();
if (m_db && delete_db_entry) if (m_db && delete_db_entry)
{ {
m_db->block_txn_start(false/*readonly*/); m_db->block_txn_start(false/*readonly*/);
@ -1521,15 +1580,13 @@ namespace service_nodes
m_db->block_txn_stop(); m_db->block_txn_stop();
} }
m_quorum_states.clear();
uint64_t hardfork_9_from_height = 0; uint64_t hardfork_9_from_height = 0;
{ {
uint32_t window, votes, threshold; uint32_t window, votes, threshold;
uint8_t voting; uint8_t voting;
m_blockchain.get_hard_fork_voting_info(9, window, votes, threshold, hardfork_9_from_height, 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 size_t service_node_info::total_num_locked_contributions() const

View File

@ -33,22 +33,10 @@
#include "serialization/serialization.h" #include "serialization/serialization.h"
#include "cryptonote_core/service_node_rules.h" #include "cryptonote_core/service_node_rules.h"
#include "cryptonote_core/service_node_deregister.h" #include "cryptonote_core/service_node_deregister.h"
#include "cryptonote_core/service_node_quorum_cop.h"
namespace service_nodes namespace service_nodes
{ {
class quorum_cop;
struct quorum_state
{
std::vector<crypto::public_key> quorum_nodes;
std::vector<crypto::public_key> nodes_to_test;
BEGIN_SERIALIZE()
FIELD(quorum_nodes)
FIELD(nodes_to_test)
END_SERIALIZE()
};
struct service_node_info // registration information struct service_node_info // registration information
{ {
struct contribution_t struct contribution_t
@ -71,6 +59,7 @@ namespace service_nodes
version_0, version_0,
version_1_swarms, version_1_swarms,
version_2_infinite_staking, version_2_infinite_staking,
version_3_checkpointing,
}; };
struct contributor_t struct contributor_t
@ -198,9 +187,11 @@ namespace service_nodes
void update_swarms(uint64_t height); void update_swarms(uint64_t height);
/// Note(maxim): this should not affect thread-safety as the returned object is const /// Note(maxim): this should not affect thread-safety as the returned object is const
const std::shared_ptr<const quorum_state> get_quorum_state(uint64_t height) const; const std::shared_ptr<const quorum_uptime_proof> get_uptime_quorum (uint64_t height) const;
const std::shared_ptr<const quorum_checkpointing> get_checkpointing_quorum(uint64_t height) const;
std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const; std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const;
const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_key_image_blacklist; } const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_transient_state.key_image_blacklist; }
void set_db_pointer(cryptonote::BlockchainDB* db); void set_db_pointer(cryptonote::BlockchainDB* db);
void set_my_service_node_keys(crypto::public_key const *pub_key); void set_my_service_node_keys(crypto::public_key const *pub_key);
@ -283,16 +274,19 @@ namespace service_nodes
}; };
typedef boost::variant<rollback_change, rollback_new, prevent_rollback, rollback_key_image_blacklist> rollback_event_variant; typedef boost::variant<rollback_change, rollback_new, prevent_rollback, rollback_key_image_blacklist> rollback_event_variant;
struct quorum_state_for_serialization struct quorum_for_serialization
{ {
uint8_t version; uint8_t version;
uint64_t height; uint64_t height;
quorum_state state; quorum_uptime_proof uptime_quorum;
quorum_checkpointing checkpointing_quorum;
BEGIN_SERIALIZE() BEGIN_SERIALIZE()
FIELD(version) FIELD(version)
FIELD(height) FIELD(height)
FIELD(state) FIELD(uptime_quorum)
if (version >= service_node_info::version_3_checkpointing)
FIELD(checkpointing_quorum)
END_SERIALIZE() END_SERIALIZE()
}; };
@ -300,7 +294,7 @@ namespace service_nodes
{ {
uint8_t version; uint8_t version;
uint64_t height; uint64_t height;
std::vector<quorum_state_for_serialization> quorum_states; std::vector<quorum_for_serialization> quorum_states;
std::vector<service_node_pubkey_info> infos; std::vector<service_node_pubkey_info> infos;
std::vector<rollback_event_variant> events; std::vector<rollback_event_variant> events;
std::vector<key_image_blacklist_entry> key_image_blacklist; std::vector<key_image_blacklist_entry> key_image_blacklist;
@ -327,8 +321,7 @@ namespace service_nodes
std::vector<crypto::public_key> get_service_nodes_pubkeys() const; std::vector<crypto::public_key> 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; bool contribution_tx_output_has_correct_unlock_time(const cryptonote::transaction& tx, size_t i, uint64_t block_height) const;
void generate_quorums(cryptonote::block const &block);
void store_quorum_state_from_rewards_list(uint64_t height);
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; 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<crypto::public_key> update_and_get_expired_nodes(const std::vector<cryptonote::transaction> &txs, uint64_t block_height); std::vector<crypto::public_key> update_and_get_expired_nodes(const std::vector<cryptonote::transaction> &txs, uint64_t block_height);
@ -337,20 +330,20 @@ namespace service_nodes
bool load(); bool load();
mutable boost::recursive_mutex m_sn_mutex; mutable boost::recursive_mutex m_sn_mutex;
std::unordered_map<crypto::public_key, service_node_info> m_service_nodes_infos; cryptonote::Blockchain& m_blockchain;
std::list<std::unique_ptr<rollback_event>> m_rollback_events; bool m_hooks_registered;
cryptonote::Blockchain& m_blockchain; crypto::public_key const *m_service_node_pubkey;
bool m_hooks_registered; cryptonote::BlockchainDB *m_db;
using block_height = uint64_t; using block_height = uint64_t;
block_height m_height; struct
{
crypto::public_key const *m_service_node_pubkey; std::unordered_map<crypto::public_key, service_node_info> service_nodes_infos;
cryptonote::BlockchainDB* m_db; std::vector<key_image_blacklist_entry> key_image_blacklist;
std::map<block_height, quorum_manager> quorum_states;
std::vector<key_image_blacklist_entry> m_key_image_blacklist; std::list<std::unique_ptr<rollback_event>> rollback_events;
std::map<block_height, std::shared_ptr<const quorum_state>> m_quorum_states; block_height height;
} m_transient_state;
}; };
bool reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector<cryptonote::account_public_address>& addresses, uint64_t& portions_for_operator, std::vector<uint64_t>& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key); bool reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector<cryptonote::account_public_address>& addresses, uint64_t& portions_for_operator, std::vector<uint64_t>& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key);

View File

@ -32,6 +32,7 @@
#include "cryptonote_config.h" #include "cryptonote_config.h"
#include "cryptonote_core.h" #include "cryptonote_core.h"
#include "version.h" #include "version.h"
#include "common/loki.h"
#include "common/loki_integration_test_hooks.h" #include "common/loki_integration_test_hooks.h"
@ -41,31 +42,36 @@
namespace service_nodes namespace service_nodes
{ {
quorum_cop::quorum_cop(cryptonote::core& core) quorum_cop::quorum_cop(cryptonote::core& core)
: m_core(core), m_last_height(0) : m_core(core), m_uptime_proof_height(0)
{ {
init(); init();
} }
void quorum_cop::init() void quorum_cop::init()
{ {
m_last_height = 0; m_uptime_proof_height = 0;
m_uptime_proof_seen.clear(); m_uptime_proof_seen.clear();
} }
void quorum_cop::blockchain_detached(uint64_t height) 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."); 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<cryptonote::transaction>& txs) void quorum_cop::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& 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) if (m_core.get_hard_fork_version(height) < 9)
return; 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()); 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) if (latest_height < service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT)
return; return;
@ -95,20 +100,19 @@ namespace service_nodes
if (height < execute_justice_from_height) if (height < execute_justice_from_height)
return; return;
if (m_last_height < execute_justice_from_height) if (m_uptime_proof_height < execute_justice_from_height)
m_last_height = execute_justice_from_height; m_uptime_proof_height = execute_justice_from_height;
for (;m_uptime_proof_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_uptime_proof_height++)
for (;m_last_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_last_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; continue;
const std::shared_ptr<const quorum_state> state = m_core.get_quorum_state(m_last_height); const std::shared_ptr<const quorum_uptime_proof> state = m_core.get_uptime_quorum(m_uptime_proof_height);
if (!state) if (!state)
{ {
// TODO(loki): Fatal error // 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; continue;
} }
@ -116,6 +120,9 @@ namespace service_nodes
if (it == state->quorum_nodes.end()) if (it == state->quorum_nodes.end())
continue; continue;
//
// NOTE: I am in the quorum
//
size_t my_index_in_quorum = it - state->quorum_nodes.begin(); 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) for (size_t node_index = 0; node_index < state->nodes_to_test.size(); ++node_index)
{ {
@ -128,7 +135,7 @@ namespace service_nodes
continue; continue;
service_nodes::deregister_vote vote = {}; 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.service_node_index = node_index;
vote.voters_quorum_index = my_index_in_quorum; 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); 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<const quorum_checkpointing> 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) static crypto::hash make_hash(crypto::public_key const &pubkey, uint64_t timestamp)
{ {
char buf[44] = "SUP"; // Meaningless magic bytes char buf[44] = "SUP"; // Meaningless magic bytes

View File

@ -29,6 +29,7 @@
#pragma once #pragma once
#include "blockchain.h" #include "blockchain.h"
#include "serialization/serialization.h"
#include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h"
#include "cryptonote_basic/blobdatatype.h" #include "cryptonote_basic/blobdatatype.h"
@ -45,6 +46,38 @@ namespace service_nodes
uint16_t version_major, version_minor, version_patch; uint16_t version_major, version_minor, version_patch;
}; };
struct quorum_checkpointing
{
std::vector<crypto::public_key> quorum_nodes;
BEGIN_SERIALIZE()
FIELD(quorum_nodes)
END_SERIALIZE()
};
struct quorum_uptime_proof
{
std::vector<crypto::public_key> quorum_nodes;
std::vector<crypto::public_key> nodes_to_test;
BEGIN_SERIALIZE()
FIELD(quorum_nodes)
FIELD(nodes_to_test)
END_SERIALIZE()
};
struct quorum_manager
{
std::shared_ptr<const quorum_uptime_proof> uptime_proof;
std::shared_ptr<const quorum_checkpointing> checkpointing;
};
enum struct quorum_type
{
uptime_proof = 0,
checkpointing,
count,
};
class quorum_cop class quorum_cop
: public cryptonote::Blockchain::BlockAddedHook, : public cryptonote::Blockchain::BlockAddedHook,
public cryptonote::Blockchain::BlockchainDetachedHook, public cryptonote::Blockchain::BlockchainDetachedHook,
@ -57,6 +90,9 @@ namespace service_nodes
void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override; void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override;
void blockchain_detached(uint64_t height) 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); bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof);
static const uint64_t REORG_SAFETY_BUFFER_IN_BLOCKS = 20; static const uint64_t REORG_SAFETY_BUFFER_IN_BLOCKS = 20;
@ -71,7 +107,7 @@ namespace service_nodes
private: private:
cryptonote::core& m_core; cryptonote::core& m_core;
uint64_t m_last_height; uint64_t m_uptime_proof_height;
std::unordered_map<crypto::public_key, proof_info> m_uptime_proof_seen; std::unordered_map<crypto::public_key, proof_info> m_uptime_proof_seen;
mutable epee::critical_section m_lock; mutable epee::critical_section m_lock;

View File

@ -10,6 +10,7 @@ namespace service_nodes {
constexpr size_t QUORUM_SIZE = 10; constexpr size_t QUORUM_SIZE = 10;
constexpr size_t QUORUM_LIFETIME = (6 * deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT); 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_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 NTH_OF_THE_NETWORK_TO_TEST = 100;
constexpr size_t MIN_NODES_TO_TEST = 50; constexpr size_t MIN_NODES_TO_TEST = 50;
constexpr size_t MAX_SWARM_SIZE = 10; constexpr size_t MAX_SWARM_SIZE = 10;
@ -32,9 +33,12 @@ namespace service_nodes {
constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1; constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1;
constexpr uint64_t QUEUE_SWARM_ID = 0; constexpr uint64_t QUEUE_SWARM_ID = 0;
constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0; constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
constexpr uint64_t CHECKPOINT_INTERVAL = 4;
using swarm_id_t = uint64_t; using swarm_id_t = uint64_t;
constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX; 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) inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype)
{ {

View File

@ -347,4 +347,20 @@ namespace cryptonote
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
/************************************************************************/
/* */
/************************************************************************/
struct NOTIFY_NEW_CHECKPOINT_VOTE
{
const static int ID = BC_COMMANDS_POOL_BASE + 12;
struct request
{
std::vector<service_nodes::checkpoint_vote> votes;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(votes)
END_KV_SERIALIZE_MAP()
};
};
} }

View File

@ -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_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_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_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() END_INVOKE_MAP2()
bool on_idle(); 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_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_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_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 --------------------------------------- //----------------- i_bc_protocol_layout ---------------------------------------
template<class T>
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<std::pair<epee::net_utils::zone, boost::uuids::uuid>> 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<uint8_t>(arg_buff), std::move(connections));
}
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context); 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_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); 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_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 get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe); bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);

View File

@ -798,6 +798,50 @@ namespace cryptonote
} }
//------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------
template<class t_core> template<class t_core>
int t_cryptonote_protocol_handler<t_core>::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<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context) int t_cryptonote_protocol_handler<t_core>::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); 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<class t_core> template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context) bool t_cryptonote_protocol_handler<t_core>::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() << " -->"); bool result = relay_on_public_network_generic<NOTIFY_NEW_DEREGISTER_VOTE>(arg, exclude_context);
std::string arg_buff; return result;
epee::serialization::store_t_to_binary(arg, arg_buff); }
//------------------------------------------------------------------------------------------------------------------------
std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections; template<class t_core>
m_p2p->for_each_connection([this, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) bool t_cryptonote_protocol_handler<t_core>::relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context)
{ {
epee::net_utils::zone zone = context.m_remote_address.get_zone(); bool result = relay_on_public_network_generic<NOTIFY_UPTIME_PROOF>(arg, exclude_context);
if (peer_id && exclude_context.m_connection_id != context.m_connection_id && zone == epee::net_utils::zone::public_) return result;
connections.push_back({zone, context.m_connection_id}); }
return true; //------------------------------------------------------------------------------------------------------------------------
}); template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context)
return m_p2p->relay_notify_to_list(NOTIFY_NEW_DEREGISTER_VOTE::ID, epee::strspan<uint8_t>(arg_buff), std::move(connections)); {
bool result = relay_on_public_network_generic<NOTIFY_NEW_CHECKPOINT_VOTE>(arg, exclude_context);
return result;
} }
//------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------
template<class t_core> template<class t_core>
@ -2335,25 +2381,6 @@ skip:
} }
//------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------
template<class t_core> template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::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<std::pair<epee::net_utils::zone, boost::uuids::uuid>> 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<uint8_t>(arg_buff), std::move(connections));
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
std::string t_cryptonote_protocol_handler<t_core>::get_peers_overview() const std::string t_cryptonote_protocol_handler<t_core>::get_peers_overview() const
{ {
std::stringstream ss; std::stringstream ss;

View File

@ -45,6 +45,7 @@ namespace cryptonote
virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context)=0; 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 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_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; 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) virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context)
{ {
return false; return false;

View File

@ -57,6 +57,8 @@ public:
, cryptonote::core_rpc_server* rpc_server = NULL , cryptonote::core_rpc_server* rpc_server = NULL
); );
bool print_checkpoints(const std::vector<std::string>& args) { m_executor.print_checkpoints(); return true; }
bool print_peer_list(const std::vector<std::string>& args); bool print_peer_list(const std::vector<std::string>& args);
bool print_peer_list_stats(const std::vector<std::string>& args); bool print_peer_list_stats(const std::vector<std::string>& args);

View File

@ -353,6 +353,11 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::check_blockchain_pruning, &m_parser, p::_1) , std::bind(&t_command_parser_executor::check_blockchain_pruning, &m_parser, p::_1)
, "Check the blockchain pruning." , "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) #if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
m_command_lookup.set_handler( m_command_lookup.set_handler(

View File

@ -68,6 +68,8 @@ public:
~t_rpc_command_executor(); ~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(bool white = true, bool gray = true, size_t limit = 0);
bool print_peer_list_stats(); bool print_peer_list_stats();

View File

@ -2458,18 +2458,18 @@ namespace cryptonote
PERF_TIMER(on_get_quorum_state); PERF_TIMER(on_get_quorum_state);
bool r; bool r;
const auto quorum_state = m_core.get_quorum_state(req.height); const auto uptime_quorum = m_core.get_uptime_quorum(req.height);
r = (quorum_state != nullptr); r = (uptime_quorum != nullptr);
if (r) if (r)
{ {
res.status = CORE_RPC_STATUS_OK; res.status = CORE_RPC_STATUS_OK;
res.quorum_nodes.reserve (quorum_state->quorum_nodes.size()); res.quorum_nodes.reserve (uptime_quorum->quorum_nodes.size());
res.nodes_to_test.reserve(quorum_state->nodes_to_test.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)); 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)); res.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key));
} }
else else
@ -2504,9 +2504,9 @@ namespace cryptonote
res.quorum_entries.reserve(height_end - height_begin + 1); res.quorum_entries.reserve(height_end - height_begin + 1);
for (auto h = height_begin; h <= height_end; ++h) 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; failed_height = h;
break; break;
} }
@ -2516,13 +2516,13 @@ namespace cryptonote
auto &entry = res.quorum_entries.back(); auto &entry = res.quorum_entries.back();
entry.height = h; entry.height = h;
entry.quorum_nodes.reserve(quorum_state->quorum_nodes.size()); entry.quorum_nodes.reserve(uptime_quorum->quorum_nodes.size());
entry.nodes_to_test.reserve(quorum_state->nodes_to_test.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)); 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)); 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.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) for (auto &pubkey_info : pubkey_info_list)
{ {
@ -2743,6 +2746,7 @@ namespace cryptonote
entry.staking_requirement = pubkey_info.info.staking_requirement; entry.staking_requirement = pubkey_info.info.staking_requirement;
entry.portions_for_operator = pubkey_info.info.portions_for_operator; 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.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); res.service_node_states.push_back(entry);
} }

View File

@ -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); 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) #if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
void on_relay_uptime_and_votes() void on_relay_uptime_and_votes()
{ {

View File

@ -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 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 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 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. 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() BEGIN_KV_SERIALIZE_MAP()
@ -2759,16 +2760,22 @@ namespace cryptonote
KV_SERIALIZE(total_reserved) KV_SERIALIZE(total_reserved)
KV_SERIALIZE(staking_requirement) KV_SERIALIZE(staking_requirement)
KV_SERIALIZE(portions_for_operator) KV_SERIALIZE(portions_for_operator)
KV_SERIALIZE(swarm_id)
KV_SERIALIZE(operator_address) KV_SERIALIZE(operator_address)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
std::vector<entry> service_node_states; // Array of service node registration information std::vector<entry> 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 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 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() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(service_node_states) KV_SERIALIZE(service_node_states)
KV_SERIALIZE(height)
KV_SERIALIZE(block_hash)
KV_SERIALIZE(status) KV_SERIALIZE(status)
KV_SERIALIZE(as_json) KV_SERIALIZE(as_json)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()

View File

@ -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(); uint64_t missing_blocks = m_checkpoints.get_max_height() - m_blockchain.size();
while (missing_blocks-- > 0) 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(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); m_blockchain.trim(checkpoint_height);
short_chain_history.clear(); short_chain_history.clear();
get_short_chain_history(short_chain_history); get_short_chain_history(short_chain_history);

View File

@ -109,6 +109,7 @@ namespace tests
uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) { return 0; }
// TODO(loki): Write tests // TODO(loki): Write tests
bool add_deregister_vote(const service_nodes::deregister_vote& vote, cryptonote::vote_verification_context &vvc) { return false; } 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; } bool pad_transactions() const { return false; }
uint32_t get_blockchain_pruning_seed() const { return 0; } uint32_t get_blockchain_pruning_seed() const { return 0; }
bool prune_blockchain(uint32_t pruning_seed) const { return true; } bool prune_blockchain(uint32_t pruning_seed) const { return true; }

View File

@ -50,7 +50,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base
return !tvc.m_verifivation_failed && tx_added; return !tvc.m_verifivation_failed && tx_added;
} }
bool check_tx_verification_context(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/) bool check_tx_verification_context_array(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/)
{ {
size_t failed = 0; size_t failed = 0;
for (const cryptonote::tx_verification_context &tvc: tvcs) for (const cryptonote::tx_verification_context &tvc: tvcs)

View File

@ -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); 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<cryptonote::tx_verification_context>& tvcs, size_t /*tx_added*/, size_t /*event_index*/, const std::vector<cryptonote::transaction>& /*txs*/)
{
for (const cryptonote::tx_verification_context &tvc: tvcs)
if (tvc.m_verifivation_failed)
return false;
return true;
}

View File

@ -179,6 +179,10 @@ public:
void register_callback(const std::string& cb_name, verify_callback cb); 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<test_event_entry> &events); bool verify(const std::string& cb_name, cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry> &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<cryptonote::tx_verification_context>& tvcs, size_t /*tx_added*/, size_t /*event_index*/, const std::vector<cryptonote::transaction>& /*txs*/);
private: private:
callbacks_map m_callbacks; callbacks_map m_callbacks;
}; };
@ -790,78 +794,6 @@ uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cry
uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx); uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx);
bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks); bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks);
//--------------------------------------------------------------------------
template<class t_test_class>
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<class t_test_class>
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<class t_test_class>
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<class t_test_class>
auto do_check_tx_verification_context(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_index, const std::vector<cryptonote::transaction>& 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<class t_test_class>
bool do_check_tx_verification_context(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t /*event_index*/, const std::vector<cryptonote::transaction>& /*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<class t_test_class>
bool check_tx_verification_context(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_index, const std::vector<cryptonote::transaction>& txs, t_test_class& validator)
{
// SFINAE in action
return do_check_tx_verification_context(tvcs, tx_added, event_index, txs, validator, 0);
}
//--------------------------------------------------------------------------
template<class t_test_class>
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<class t_test_class>
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<class t_test_class>
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(); 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); 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 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"); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed");
return true; return true;
} }
@ -937,7 +869,7 @@ public:
size_t pool_size = m_c.get_pool_transactions_count(); 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); 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; 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"); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed");
return true; return true;
} }
@ -948,7 +880,7 @@ public:
cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc); cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc);
m_c.handle_incoming_block(t_serializable_object_to_blob(b), &b, 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"); CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed");
return r; return r;
} }
@ -981,8 +913,8 @@ public:
{ {
blk = cryptonote::block(); blk = cryptonote::block();
} }
bool r = check_block_verification_context(bvc, m_ev_index, blk, m_validator); bool r = m_validator.check_block_verification_context(bvc, m_ev_index, blk);
CHECK_AND_NO_ASSERT_MES(r, false, "serialized block verification context check failed"); CHECK_AND_NO_ASSERT_MES(r, false, "block verification context check failed");
return true; return true;
} }
@ -1005,7 +937,7 @@ public:
tx = cryptonote::transaction(); 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"); CHECK_AND_NO_ASSERT_MES(r, false, "transaction verification context check failed");
return true; return true;
} }

View File

@ -691,9 +691,9 @@ bool sn_test_rollback::test_registrations(cryptonote::core& c, size_t ev_index,
tx_extra_service_node_deregister deregistration; tx_extra_service_node_deregister deregistration;
get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration); get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration);
const auto quorum_state = c.get_quorum_state(deregistration.block_height); const auto uptime_quorum = c.get_uptime_quorum(deregistration.block_height);
CHECK_TEST_CONDITION(quorum_state); CHECK_TEST_CONDITION(uptime_quorum);
const auto pk_a = quorum_state->nodes_to_test.at(deregistration.service_node_index); const auto pk_a = uptime_quorum->nodes_to_test.at(deregistration.service_node_index);
/// Check present /// Check present
const bool found_a = contains(sn_list, pk_a); const bool found_a = contains(sn_list, pk_a);

View File

@ -91,6 +91,7 @@ public:
// TODO(loki): Write tests // TODO(loki): Write tests
bool add_deregister_vote(const service_nodes::deregister_vote& vote, cryptonote::vote_verification_context &vvc) { return true; } 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<cryptonote::t_cryptonote_protocol_handler<test_core>> Server; typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server;

View File

@ -123,7 +123,7 @@ TEST(service_nodes, vote_validation)
cryptonote::keypair service_node_voter = cryptonote::keypair::generate(hw::get_device("default")); cryptonote::keypair service_node_voter = cryptonote::keypair::generate(hw::get_device("default"));
int voter_index = 0; int voter_index = 0;
service_nodes::quorum_state state = {}; service_nodes::quorum_uptime_proof state = {};
{ {
state.quorum_nodes.resize(10); state.quorum_nodes.resize(10);
state.nodes_to_test.resize(state.quorum_nodes.size()); 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; const size_t num_voters = 10;
cryptonote::keypair voters[num_voters] = {}; cryptonote::keypair voters[num_voters] = {};
service_nodes::quorum_state state = {}; service_nodes::quorum_uptime_proof state = {};
{ {
state.quorum_nodes.resize(num_voters); state.quorum_nodes.resize(num_voters);
state.nodes_to_test.resize(num_voters); state.nodes_to_test.resize(num_voters);