mirror of https://github.com/oxen-io/oxen-core.git
Unify and move responsibility of voting to quorum_cop (#615)
* Unify checkpointing and uptime quorums * Begin making checkpoints cull old votes/checkpoints * Begin rehaul of service node code out of core, to assist checkpoints * Begin overhaul of votes to move resposibility into quorum_cop * Update testing suite to work with the new system * Remove vote culling from checkpoints and into voting_pool * Fix bugs making integration deregistration fail * Votes don't always specify an index in the validators * Update tests for validator index member change * Rename deregister to voting, fix subtle hashing bug Update the deregister hash derivation to use uint32_t as originally set not uint64_t otherwise this affects the result and produces different results. * Remove un-needed nettype from vote pool * PR review, use <algorithms> * Rename uptime_deregister/uptime quorums to just deregister quorums * Remove unused add_deregister_vote, move side effect out of macro
This commit is contained in:
parent
efc9e44137
commit
8af377d2b8
|
@ -3747,7 +3747,7 @@ void BlockchainLMDB::update_block_checkpoint(checkpoint_t const &checkpoint)
|
|||
header.block_hash = checkpoint.block_hash;
|
||||
header.num_signatures = checkpoint.signatures.size();
|
||||
|
||||
size_t const MAX_BYTES_REQUIRED = sizeof(header) + (sizeof(*checkpoint.signatures.data()) * service_nodes::QUORUM_SIZE);
|
||||
size_t const MAX_BYTES_REQUIRED = sizeof(header) + (sizeof(*checkpoint.signatures.data()) * service_nodes::CHECKPOINT_QUORUM_SIZE);
|
||||
uint8_t buffer[MAX_BYTES_REQUIRED];
|
||||
|
||||
size_t const bytes_for_signatures = sizeof(*checkpoint.signatures.data()) * header.num_signatures;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "cryptonote_core/blockchain.h"
|
||||
#include "cryptonote_core/tx_pool.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
||||
// NOTE(loki): This is done this way because of the circular constructors.
|
||||
struct blockchain_objects_t
|
||||
|
@ -12,9 +12,8 @@ struct blockchain_objects_t
|
|||
cryptonote::Blockchain m_blockchain;
|
||||
cryptonote::tx_memory_pool m_mempool;
|
||||
service_nodes::service_node_list m_service_node_list;
|
||||
service_nodes::deregister_vote_pool m_deregister_vote_pool;
|
||||
blockchain_objects_t() :
|
||||
m_blockchain(m_mempool, m_service_node_list, m_deregister_vote_pool),
|
||||
m_blockchain(m_mempool, m_service_node_list),
|
||||
m_service_node_list(m_blockchain),
|
||||
m_mempool(m_blockchain) { }
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <vector>
|
||||
#include "syncobj.h"
|
||||
#include "blockchain_db/blockchain_db.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
|
||||
using namespace epee;
|
||||
|
||||
|
@ -100,10 +101,11 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool get_checkpoint_from_db_safe(BlockchainDB const *db, uint64_t height, checkpoint_t &checkpoint)
|
||||
static bool get_checkpoint_from_db_safe(BlockchainDB *db, uint64_t height, checkpoint_t &checkpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto guard = db_rtxn_guard(db);
|
||||
return db->get_block_checkpoint(height, checkpoint);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
|
@ -113,10 +115,11 @@ namespace cryptonote
|
|||
}
|
||||
}
|
||||
|
||||
static bool update_checkpoint_in_db_safe(BlockchainDB *db, checkpoint_t &checkpoint)
|
||||
static bool update_checkpoint_in_db_safe(BlockchainDB *db, checkpoint_t const &checkpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto guard = db_wtxn_guard(db);
|
||||
db->update_block_checkpoint(checkpoint);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
|
@ -127,7 +130,6 @@ namespace cryptonote
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str)
|
||||
{
|
||||
|
@ -143,97 +145,36 @@ namespace cryptonote
|
|||
}
|
||||
else
|
||||
{
|
||||
checkpoint.height = height;
|
||||
checkpoint.type = checkpoint_type::hardcoded;
|
||||
checkpoint.block_hash = h;
|
||||
r = update_checkpoint_in_db_safe(m_db, checkpoint);
|
||||
checkpoint.type = checkpoint_type::hardcoded;
|
||||
checkpoint.height = height;
|
||||
checkpoint.block_hash = h;
|
||||
r = update_checkpoint(checkpoint);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
static bool add_vote_if_unique(checkpoint_t &checkpoint, service_nodes::checkpoint_vote const &vote)
|
||||
bool checkpoints::update_checkpoint(checkpoint_t const &checkpoint)
|
||||
{
|
||||
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())
|
||||
// TODO(doyle): Verify signatures and hash check out
|
||||
std::array<size_t, service_nodes::CHECKPOINT_QUORUM_SIZE> unique_vote_set = {};
|
||||
if (checkpoint.type == checkpoint_type::service_node)
|
||||
{
|
||||
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
|
||||
|
||||
// TODO(doyle): Double work. Factor into a generic vote checker as we already have one in service node deregister
|
||||
std::array<int, service_nodes::QUORUM_SIZE> unique_vote_set = {};
|
||||
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;
|
||||
|
||||
CHECK_AND_ASSERT_MES(checkpoint.signatures.size() >= service_nodes::CHECKPOINT_MIN_VOTES, false, "Checkpoint has insufficient signatures to be considered");
|
||||
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;
|
||||
}
|
||||
++unique_vote_set[vote_to_sig.voter_index];
|
||||
CHECK_AND_ASSERT_MES(vote_to_sig.voter_index < service_nodes::CHECKPOINT_QUORUM_SIZE, false, "Vote is indexing out of bounds");
|
||||
CHECK_AND_ASSERT_MES(unique_vote_set[vote_to_sig.voter_index] == 1, false, "Voter is trying to vote twice");
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_checkpoint == candidate_checkpoints.end())
|
||||
else
|
||||
{
|
||||
checkpoint_t new_checkpoint = {};
|
||||
new_checkpoint.height = vote.block_height;
|
||||
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);
|
||||
CHECK_AND_ASSERT_MES(checkpoint.signatures.size() == 0, false, "Non service-node checkpoints should have no signatures");
|
||||
}
|
||||
|
||||
if (add_vote_if_unique(*curr_checkpoint, vote))
|
||||
{
|
||||
if (curr_checkpoint->signatures.size() > service_nodes::MIN_VOTES_TO_CHECKPOINT)
|
||||
{
|
||||
update_checkpoint_in_db_safe(m_db, *curr_checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
bool result = update_checkpoint_in_db_safe(m_db, checkpoint);
|
||||
return result;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
bool checkpoints::is_in_checkpoint_zone(uint64_t height) const
|
||||
|
@ -315,7 +256,6 @@ namespace cryptonote
|
|||
*this = {};
|
||||
m_db = db;
|
||||
|
||||
db_wtxn_guard txn_guard(m_db);
|
||||
#if !defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
if (nettype == MAINNET)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "misc_log_ex.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
||||
#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false);
|
||||
#define JSON_HASH_FILE_NAME "checkpoints.json"
|
||||
|
@ -102,7 +102,7 @@ namespace cryptonote
|
|||
*/
|
||||
bool add_checkpoint(uint64_t height, const std::string& hash_str);
|
||||
|
||||
bool add_checkpoint_vote(service_nodes::checkpoint_vote const &vote);
|
||||
bool update_checkpoint(checkpoint_t const &checkpoin);
|
||||
|
||||
/**
|
||||
* @brief checks if there is a checkpoint in the future
|
||||
|
@ -167,8 +167,7 @@ namespace cryptonote
|
|||
bool init(network_type nettype, struct BlockchainDB *db);
|
||||
|
||||
private:
|
||||
BlockchainDB *m_db;
|
||||
std::unordered_map<uint64_t, std::vector<checkpoint_t>> m_staging_points; // Incomplete service node checkpoints being voted on
|
||||
BlockchainDB *m_db;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,30 @@
|
|||
|
||||
|
||||
namespace cryptonote {
|
||||
class BlockAddedHook
|
||||
{
|
||||
public:
|
||||
virtual void block_added(const block& block, const std::vector<transaction>& txs) = 0;
|
||||
};
|
||||
|
||||
class BlockchainDetachedHook
|
||||
{
|
||||
public:
|
||||
virtual void blockchain_detached(uint64_t height) = 0;
|
||||
};
|
||||
|
||||
class InitHook
|
||||
{
|
||||
public:
|
||||
virtual void init() = 0;
|
||||
};
|
||||
|
||||
class ValidateMinerTxHook
|
||||
{
|
||||
public:
|
||||
virtual bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, struct block_reward_parts const &reward_parts) const = 0;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
/* */
|
||||
/************************************************************************/
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
#include "crypto/hash.h"
|
||||
#include "ringct/rctSigs.h"
|
||||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
||||
using namespace epee;
|
||||
|
||||
|
@ -1199,21 +1199,27 @@ namespace cryptonote
|
|||
return buf;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
char const *print_vote_verification_context(vote_verification_context const &vvc, service_nodes::deregister_vote const *vote)
|
||||
char const *print_vote_verification_context(vote_verification_context const &vvc, service_nodes::quorum_vote_t const *vote)
|
||||
{
|
||||
static char buf[2048];
|
||||
buf[0] = 0;
|
||||
|
||||
char *bufPtr = buf;
|
||||
char *bufEnd = buf + sizeof(buf);
|
||||
if (vvc.m_invalid_block_height) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid block height: %s, ", vote ? std::to_string(vote->block_height).c_str() : "??");
|
||||
if (vvc.m_duplicate_voters) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Voters quorum index was duplicated: %s, ",vote ? std::to_string(vote->voters_quorum_index).c_str() : "??");
|
||||
if (vvc.m_voters_quorum_index_out_of_bounds) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Voters quorum index out of bounds: %s, ", vote ? std::to_string(vote->voters_quorum_index).c_str() : "??");
|
||||
if (vvc.m_service_node_index_out_of_bounds) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Service node index out of bounds: %s, ", vote ? std::to_string(vote->service_node_index).c_str() : "??");
|
||||
if (vvc.m_signature_not_valid) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Signature not valid, ");
|
||||
if (vvc.m_added_to_pool) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Added to pool, ");
|
||||
if (vvc.m_full_tx_deregister_made) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Full TX deregister made, ");
|
||||
if (vvc.m_not_enough_votes) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Not enough votes, ");
|
||||
if (vvc.m_invalid_block_height) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Invalid block height: %s, ", vote ? std::to_string(vote->block_height).c_str() : "??");
|
||||
if (vvc.m_duplicate_voters) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Index in group was duplicated: %s, ", vote ? std::to_string(vote->index_in_group).c_str() : "??");
|
||||
if (vvc.m_validator_index_out_of_bounds) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Validator index out of bounds");
|
||||
if (vvc.m_worker_index_out_of_bounds) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Worker index out of bounds: %s, ", vote ? std::to_string(vote->deregister.worker_index).c_str() : "??");
|
||||
if (vvc.m_signature_not_valid) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Signature not valid, ");
|
||||
if (vvc.m_added_to_pool) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Added to pool, ");
|
||||
if (vvc.m_not_enough_votes) bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Not enough votes, ");
|
||||
if (vvc.m_incorrect_voting_group)
|
||||
{
|
||||
bufPtr += snprintf(bufPtr, bufEnd - bufPtr, "Incorrect voting group specified");
|
||||
if (vote)
|
||||
bufPtr += snprintf(bufPtr, bufEnd - bufPtr, ": %s", (vote->group == service_nodes::quorum_group::validator) ? "validator" : "worker");
|
||||
bufPtr += snprintf(bufPtr, bufEnd - bufPtr, ", ");
|
||||
}
|
||||
|
||||
if (bufPtr != buf)
|
||||
{
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace epee
|
|||
class wipeable_string;
|
||||
}
|
||||
|
||||
namespace service_nodes { struct deregister_vote; }
|
||||
namespace service_nodes { struct quorum_vote_t; }
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
|
@ -170,7 +170,7 @@ namespace cryptonote
|
|||
std::string print_money(uint64_t amount, unsigned int decimal_point = -1);
|
||||
|
||||
char const *print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr);
|
||||
char const *print_vote_verification_context(vote_verification_context const &vvc, service_nodes::deregister_vote const *vote = nullptr);
|
||||
char const *print_vote_verification_context(vote_verification_context const &vvc, service_nodes::quorum_vote_t const *vote = nullptr);
|
||||
//---------------------------------------------------------------
|
||||
template<class t_object>
|
||||
bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob)
|
||||
|
|
|
@ -239,8 +239,10 @@ namespace cryptonote
|
|||
{
|
||||
struct vote
|
||||
{
|
||||
vote() = default;
|
||||
vote(crypto::signature const &signature, uint32_t validator_index): signature(signature), validator_index(validator_index) { }
|
||||
crypto::signature signature;
|
||||
uint32_t voters_quorum_index;
|
||||
uint32_t validator_index;
|
||||
};
|
||||
|
||||
uint64_t block_height;
|
||||
|
|
|
@ -41,23 +41,23 @@ namespace cryptonote
|
|||
bool m_verification_failed;
|
||||
bool m_invalid_block_height;
|
||||
bool m_duplicate_voters;
|
||||
bool m_voters_quorum_index_out_of_bounds;
|
||||
bool m_service_node_index_out_of_bounds;
|
||||
bool m_validator_index_out_of_bounds;
|
||||
bool m_worker_index_out_of_bounds;
|
||||
bool m_signature_not_valid;
|
||||
bool m_added_to_pool;
|
||||
bool m_full_tx_deregister_made;
|
||||
bool m_not_enough_votes;
|
||||
bool m_incorrect_voting_group;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(m_verification_failed)
|
||||
KV_SERIALIZE(m_invalid_block_height)
|
||||
KV_SERIALIZE(m_duplicate_voters)
|
||||
KV_SERIALIZE(m_voters_quorum_index_out_of_bounds)
|
||||
KV_SERIALIZE(m_service_node_index_out_of_bounds)
|
||||
KV_SERIALIZE(m_validator_index_out_of_bounds)
|
||||
KV_SERIALIZE(m_worker_index_out_of_bounds)
|
||||
KV_SERIALIZE(m_signature_not_valid)
|
||||
KV_SERIALIZE(m_added_to_pool)
|
||||
KV_SERIALIZE(m_full_tx_deregister_made)
|
||||
KV_SERIALIZE(m_not_enough_votes)
|
||||
KV_SERIALIZE(m_incorrect_voting_group)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ set(cryptonote_core_sources
|
|||
cryptonote_core.cpp
|
||||
service_node_rules.cpp
|
||||
service_node_list.cpp
|
||||
service_node_deregister.cpp
|
||||
service_node_voting.cpp
|
||||
service_node_quorum_cop.cpp
|
||||
service_node_swarm.cpp
|
||||
tx_pool.cpp
|
||||
|
@ -49,7 +49,7 @@ set(cryptonote_core_private_headers
|
|||
service_node_quorum_cop.h
|
||||
service_node_swarm.h
|
||||
cryptonote_core.h
|
||||
service_node_deregister.h
|
||||
service_node_voting.h
|
||||
tx_pool.h
|
||||
tx_sanity_check.h
|
||||
cryptonote_tx_utils.h)
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
#include "blockchain.h"
|
||||
#include "blockchain_db/blockchain_db.h"
|
||||
#include "cryptonote_basic/cryptonote_boost_serialization.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_basic/miner.h"
|
||||
#include "misc_language.h"
|
||||
|
@ -56,7 +55,7 @@
|
|||
#include "ringct/rctSigs.h"
|
||||
#include "common/perf_timer.h"
|
||||
#include "common/notify.h"
|
||||
#include "service_node_deregister.h"
|
||||
#include "service_node_voting.h"
|
||||
#include "service_node_list.h"
|
||||
#include "common/varint.h"
|
||||
#include "common/pruning.h"
|
||||
|
@ -124,7 +123,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):
|
||||
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_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),
|
||||
|
@ -133,10 +132,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list
|
|||
m_difficulty_for_next_block_top_hash(crypto::null_hash),
|
||||
m_difficulty_for_next_block(1),
|
||||
m_service_node_list(service_node_list),
|
||||
m_deregister_vote_pool(deregister_vote_pool),
|
||||
m_btc_valid(false)
|
||||
{
|
||||
m_checkpoint_pool.reserve(service_nodes::QUORUM_SIZE * 4 /*blocks*/);
|
||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
|
@ -3083,14 +3080,14 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
return false;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const service_nodes::quorum_uptime_proof> uptime_quorum = m_service_node_list.get_uptime_quorum(deregister.block_height);
|
||||
if (!uptime_quorum)
|
||||
const std::shared_ptr<const service_nodes::testing_quorum> quorum = m_service_node_list.get_testing_quorum(service_nodes::quorum_type::deregister, deregister.block_height);
|
||||
if (!quorum)
|
||||
{
|
||||
MERROR_VER("Deregister TX could not get quorum for height: " << deregister.block_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service_nodes::deregister_vote::verify_deregister(nettype(), deregister, tvc.m_vote_ctx, *uptime_quorum))
|
||||
if (!service_nodes::verify_tx_deregister(deregister, tvc.m_vote_ctx, *quorum))
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("tx " << get_transaction_hash(tx) << ": deregister tx could not be completely verified reason: " << print_vote_verification_context(tvc.m_vote_ctx));
|
||||
|
@ -3112,11 +3109,11 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
}
|
||||
|
||||
uint64_t delta_height = curr_height - deregister.block_height;
|
||||
if (delta_height >= service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT)
|
||||
if (delta_height >= service_nodes::DEREGISTER_TX_LIFETIME_IN_BLOCKS)
|
||||
{
|
||||
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
|
||||
<< " and service node: " << deregister.service_node_index
|
||||
<< ", is older than: " << service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT
|
||||
<< ", is older than: " << service_nodes::DEREGISTER_TX_LIFETIME_IN_BLOCKS
|
||||
<< " blocks and has been rejected. The current height is: " << curr_height);
|
||||
tvc.m_vote_ctx.m_invalid_block_height = true;
|
||||
tvc.m_verifivation_failed = true;
|
||||
|
@ -3125,7 +3122,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
}
|
||||
|
||||
const uint64_t height = deregister.block_height;
|
||||
const size_t num_blocks_to_check = service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT;
|
||||
const size_t num_blocks_to_check = service_nodes::DEREGISTER_TX_LIFETIME_IN_BLOCKS;
|
||||
|
||||
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
|
||||
std::vector<cryptonote::blobdata> txs;
|
||||
|
@ -3153,15 +3150,15 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
continue;
|
||||
}
|
||||
|
||||
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_uptime_quorum)
|
||||
const std::shared_ptr<const service_nodes::testing_quorum> existing_quorum = m_service_node_list.get_testing_quorum(service_nodes::quorum_type::deregister, existing_deregister.block_height);
|
||||
if (!existing_quorum)
|
||||
{
|
||||
MERROR_VER("could not get uptime quorum for recent deregister tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existing_uptime_quorum->nodes_to_test[existing_deregister.service_node_index] ==
|
||||
uptime_quorum->nodes_to_test[deregister.service_node_index])
|
||||
if (existing_quorum->workers[existing_deregister.service_node_index] ==
|
||||
quorum->workers[deregister.service_node_index])
|
||||
{
|
||||
MERROR_VER("Already seen this deregister tx (aka double spend)");
|
||||
tvc.m_double_spend = true;
|
||||
|
@ -3923,15 +3920,6 @@ leave:
|
|||
get_difficulty_for_next_block(); // just to cache it
|
||||
invalidate_block_template_cache();
|
||||
|
||||
// New height is the height of the block we just mined. We want (new_height
|
||||
// + 1) because our age checks for deregister votes is now (age >=
|
||||
// DEREGISTER_VOTE_LIFETIME_BY_HEIGHT) where age is derived from
|
||||
// get_current_blockchain_height() which gives you the height that you are
|
||||
// currently mining for, i.e. (new_height + 1). Otherwise peers will silently
|
||||
// drop connection from each other when they go around P2Ping votes.
|
||||
m_deregister_vote_pool.remove_expired_votes(new_height + 1);
|
||||
m_deregister_vote_pool.remove_used_votes(only_txs);
|
||||
|
||||
std::shared_ptr<tools::Notify> block_notify = m_block_notify;
|
||||
if (block_notify)
|
||||
block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL);
|
||||
|
@ -4164,19 +4152,10 @@ bool Blockchain::update_checkpoints(const std::string& file_path)
|
|||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
bool Blockchain::add_checkpoint_vote(service_nodes::checkpoint_vote const &vote)
|
||||
bool Blockchain::update_checkpoint(cryptonote::checkpoint_t const &checkpoint)
|
||||
{
|
||||
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;
|
||||
bool result = m_checkpoints.update_checkpoint(checkpoint);
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
void Blockchain::block_longhash_worker(uint64_t height, const epee::span<const block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
#include "cryptonote_basic/hardfork.h"
|
||||
#include "blockchain_db/blockchain_db.h"
|
||||
|
||||
namespace service_nodes { class service_node_list; class deregister_vote_pool; };
|
||||
namespace service_nodes { class service_node_list; class voting_pool; };
|
||||
namespace tools { class Notify; }
|
||||
|
||||
namespace cryptonote
|
||||
|
@ -137,36 +137,12 @@ namespace cryptonote
|
|||
uint64_t already_generated_coins; //!< the total coins minted after that block
|
||||
};
|
||||
|
||||
class BlockAddedHook
|
||||
{
|
||||
public:
|
||||
virtual void block_added(const block& block, const std::vector<transaction>& txs) = 0;
|
||||
};
|
||||
|
||||
class BlockchainDetachedHook
|
||||
{
|
||||
public:
|
||||
virtual void blockchain_detached(uint64_t height) = 0;
|
||||
};
|
||||
|
||||
class InitHook
|
||||
{
|
||||
public:
|
||||
virtual void init() = 0;
|
||||
};
|
||||
|
||||
class ValidateMinerTxHook
|
||||
{
|
||||
public:
|
||||
virtual bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, block_reward_parts const &reward_parts) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Blockchain constructor
|
||||
*
|
||||
* @param tx_pool a reference to the transaction pool to be kept by the Blockchain
|
||||
*/
|
||||
Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list, service_nodes::deregister_vote_pool &deregister_vote_pool);
|
||||
Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list);
|
||||
|
||||
/**
|
||||
* @brief Blockchain destructor
|
||||
|
@ -751,15 +727,7 @@ namespace cryptonote
|
|||
*/
|
||||
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);
|
||||
bool update_checkpoint(checkpoint_t const &checkpoint);
|
||||
|
||||
// user options, must be called before calling init()
|
||||
|
||||
|
@ -1080,9 +1048,7 @@ namespace cryptonote
|
|||
BlockchainDB* m_db;
|
||||
|
||||
tx_memory_pool& m_tx_pool;
|
||||
|
||||
service_nodes::service_node_list& m_service_node_list;
|
||||
service_nodes::deregister_vote_pool& m_deregister_vote_pool;
|
||||
service_nodes::service_node_list& m_service_node_list;
|
||||
|
||||
mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ namespace cryptonote
|
|||
core::core(i_cryptonote_protocol* pprotocol):
|
||||
m_mempool(m_blockchain_storage),
|
||||
m_service_node_list(m_blockchain_storage),
|
||||
m_blockchain_storage(m_mempool, m_service_node_list, m_deregister_vote_pool),
|
||||
m_blockchain_storage(m_mempool, m_service_node_list),
|
||||
m_quorum_cop(*this),
|
||||
m_miner(this),
|
||||
m_miner_address(boost::value_initialized<account_public_address>()),
|
||||
|
@ -653,8 +653,16 @@ namespace cryptonote
|
|||
// Service Nodes
|
||||
{
|
||||
m_service_node_list.set_db_pointer(initialized_db);
|
||||
m_service_node_list.register_hooks(m_quorum_cop);
|
||||
m_deregister_vote_pool.m_nettype = m_nettype;
|
||||
|
||||
m_blockchain_storage.hook_block_added(m_service_node_list);
|
||||
m_blockchain_storage.hook_blockchain_detached(m_service_node_list);
|
||||
m_blockchain_storage.hook_init(m_service_node_list);
|
||||
m_blockchain_storage.hook_validate_miner_tx(m_service_node_list);
|
||||
|
||||
// NOTE: There is an implicit dependency on service node lists being hooked first!
|
||||
m_blockchain_storage.hook_init(m_quorum_cop);
|
||||
m_blockchain_storage.hook_block_added(m_quorum_cop);
|
||||
m_blockchain_storage.hook_blockchain_detached(m_quorum_cop);
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
|
@ -1363,50 +1371,41 @@ namespace cryptonote
|
|||
m_mempool.set_relayed(txs);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::relay_deregister_votes()
|
||||
bool core::relay_service_node_votes()
|
||||
{
|
||||
NOTIFY_NEW_DEREGISTER_VOTE::request req;
|
||||
req.votes = m_deregister_vote_pool.get_relayable_votes();
|
||||
if (!req.votes.empty())
|
||||
std::vector<service_nodes::quorum_vote_t> relayable_votes = m_quorum_cop.get_relayable_votes();
|
||||
int hf_version = get_blockchain_storage().get_current_hard_fork_version();
|
||||
if (hf_version < cryptonote::network_version_11_infinite_staking)
|
||||
{
|
||||
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
||||
if (get_protocol()->relay_deregister_votes(req, fake_context))
|
||||
m_deregister_vote_pool.set_relayed(req.votes);
|
||||
}
|
||||
|
||||
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)
|
||||
NOTIFY_NEW_DEREGISTER_VOTE::request req = {};
|
||||
for (service_nodes::quorum_vote_t const &vote : relayable_votes)
|
||||
{
|
||||
const time_t elapsed = now - vote.time_last_sent_p2p;
|
||||
const time_t RELAY_THRESHOLD = 60 * 2;
|
||||
if (elapsed > RELAY_THRESHOLD)
|
||||
service_nodes::legacy_deregister_vote legacy_vote = {};
|
||||
if (service_nodes::convert_deregister_vote_to_legacy(vote, legacy_vote))
|
||||
req.votes.push_back(legacy_vote);
|
||||
}
|
||||
|
||||
if (req.votes.size())
|
||||
{
|
||||
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
||||
if (get_protocol()->relay_deregister_votes(req, fake_context))
|
||||
{
|
||||
relayed_votes.push_back(&vote);
|
||||
req.votes.push_back(vote);
|
||||
m_quorum_cop.set_votes_relayed(relayable_votes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relay and update timestamp of when we last sent the vote
|
||||
if (!req.votes.empty())
|
||||
else
|
||||
{
|
||||
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
||||
if (get_protocol()->relay_checkpoint_votes(req, fake_context))
|
||||
// Get relayable votes
|
||||
NOTIFY_NEW_SERVICE_NODE_VOTE::request req = {};
|
||||
req.votes = std::move(relayable_votes);
|
||||
if (req.votes.size())
|
||||
{
|
||||
for (service_nodes::checkpoint_vote *vote : relayed_votes)
|
||||
vote->time_last_sent_p2p = now;
|
||||
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
||||
if (get_protocol()->relay_service_node_votes(req, fake_context))
|
||||
{
|
||||
m_quorum_cop.set_votes_relayed(relayable_votes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1772,8 +1771,7 @@ namespace cryptonote
|
|||
|
||||
m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this));
|
||||
m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this));
|
||||
m_deregisters_auto_relayer.do_call(boost::bind(&core::relay_deregister_votes, this));
|
||||
m_checkpoint_auto_relayer.do_call(boost::bind(&core::relay_checkpoint_votes, this));
|
||||
m_service_node_vote_relayer.do_call(boost::bind(&core::relay_service_node_votes, this));
|
||||
// m_check_updates_interval.do_call(boost::bind(&core::check_updates, this));
|
||||
m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this));
|
||||
m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this));
|
||||
|
@ -2091,14 +2089,9 @@ namespace cryptonote
|
|||
return si.available;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
const std::shared_ptr<const service_nodes::quorum_uptime_proof> core::get_uptime_quorum(uint64_t height) const
|
||||
std::shared_ptr<const service_nodes::testing_quorum> core::get_testing_quorum(service_nodes::quorum_type type, uint64_t height) const
|
||||
{
|
||||
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);
|
||||
return m_service_node_list.get_testing_quorum(type, height);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::is_service_node(const crypto::public_key& pubkey) const
|
||||
|
@ -2118,140 +2111,12 @@ namespace cryptonote
|
|||
return result;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::add_deregister_vote(const service_nodes::deregister_vote& vote, vote_verification_context &vvc)
|
||||
bool core::add_service_node_vote(const service_nodes::quorum_vote_t& vote, vote_verification_context &vvc)
|
||||
{
|
||||
uint64_t latest_block_height = std::max(get_current_blockchain_height(), get_target_blockchain_height());
|
||||
uint64_t delta_height = latest_block_height - vote.block_height;
|
||||
|
||||
if (vote.block_height < latest_block_height && delta_height >= service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height
|
||||
<< " and service node: " << vote.service_node_index
|
||||
<< ", is older than: " << service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT
|
||||
<< " blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
else if (vote.block_height > latest_block_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height
|
||||
<< " and service node: " << vote.service_node_index
|
||||
<< ", is newer than: " << latest_block_height
|
||||
<< " (latest block height) and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
|
||||
if (vvc.m_invalid_block_height)
|
||||
{
|
||||
vvc.m_verification_failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto uptime_quorum = m_service_node_list.get_uptime_quorum(vote.block_height);
|
||||
if (!uptime_quorum)
|
||||
{
|
||||
vvc.m_verification_failed = true;
|
||||
vvc.m_invalid_block_height = true;
|
||||
LOG_ERROR("Could not get quorum state for height: " << vote.block_height);
|
||||
return false;
|
||||
}
|
||||
|
||||
cryptonote::transaction deregister_tx;
|
||||
int hf_version = m_blockchain_storage.get_current_hard_fork_version();
|
||||
bool result = m_deregister_vote_pool.add_vote(hf_version, vote, vvc, *uptime_quorum, deregister_tx);
|
||||
if (result && vvc.m_full_tx_deregister_made)
|
||||
{
|
||||
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
|
||||
blobdata const tx_blob = tx_to_blob(deregister_tx);
|
||||
|
||||
result = handle_incoming_tx(tx_blob, tvc, false /*keeped_by_block*/, false /*relayed*/, false /*do_not_relay*/);
|
||||
if (!result || tvc.m_verifivation_failed)
|
||||
{
|
||||
LOG_PRINT_L1("A full deregister tx for height: " << vote.block_height <<
|
||||
" and service node: " << vote.service_node_index <<
|
||||
" could not be verified and was not added to the memory pool, reason: " <<
|
||||
print_tx_verification_context(tvc, &deregister_tx));
|
||||
}
|
||||
}
|
||||
|
||||
bool result = m_quorum_cop.handle_vote(vote, vvc);
|
||||
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
|
||||
{
|
||||
if (m_service_node)
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
#include "common/command_line.h"
|
||||
#include "tx_pool.h"
|
||||
#include "blockchain.h"
|
||||
#include "service_node_deregister.h"
|
||||
#include "service_node_voting.h"
|
||||
#include "service_node_list.h"
|
||||
#include "service_node_quorum_cop.h"
|
||||
#include "cryptonote_basic/miner.h"
|
||||
|
@ -783,22 +783,14 @@ namespace cryptonote
|
|||
bool offline() const { return m_offline; }
|
||||
|
||||
/**
|
||||
* @brief Get the deterministic list of service node's public keys for quorum testing
|
||||
* @brief Get the deterministic quorum of service node's public keys responsible for the specified quorum type
|
||||
*
|
||||
* @param type The quorum type to retrieve
|
||||
* @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_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;
|
||||
std::shared_ptr<const service_nodes::testing_quorum> get_testing_quorum(service_nodes::quorum_type type, uint64_t height) const;
|
||||
|
||||
/**
|
||||
* @brief Get a non owning reference to the list of blacklisted key images
|
||||
|
@ -824,23 +816,13 @@ namespace cryptonote
|
|||
bool is_service_node(const crypto::public_key& pubkey) const;
|
||||
|
||||
/**
|
||||
* @brief Add a vote to deregister a service node from network
|
||||
* @brief Add a service node vote
|
||||
*
|
||||
* @param vote The vote for deregistering a service node.
|
||||
|
||||
* @return Whether the vote was added to the partial deregister pool
|
||||
*/
|
||||
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);
|
||||
|
||||
bool add_service_node_vote(const service_nodes::quorum_vote_t& vote, vote_verification_context &vvc);
|
||||
|
||||
/**
|
||||
* @brief Get the keypair for this service node.
|
||||
|
@ -908,11 +890,11 @@ namespace cryptonote
|
|||
bool check_blockchain_pruning();
|
||||
|
||||
/**
|
||||
* @brief attempt to relay the pooled deregister votes
|
||||
* @brief attempt to relay the pooled checkpoint votes
|
||||
*
|
||||
* @return true, necessary for binding this function to a periodic invoker
|
||||
*/
|
||||
bool relay_deregister_votes();
|
||||
bool relay_service_node_votes();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -1069,13 +1051,6 @@ namespace cryptonote
|
|||
*/
|
||||
bool relay_txpool_transactions();
|
||||
|
||||
/**
|
||||
* @brief attempt to relay the pooled checkpoint votes
|
||||
*
|
||||
* @return true, necessary for binding this function to a periodic invoker
|
||||
*/
|
||||
bool relay_checkpoint_votes();
|
||||
|
||||
/**
|
||||
* @brief checks DNS versions
|
||||
*
|
||||
|
@ -1116,9 +1091,8 @@ namespace cryptonote
|
|||
tx_memory_pool m_mempool; //!< transaction pool instance
|
||||
Blockchain m_blockchain_storage; //!< Blockchain instance
|
||||
|
||||
service_nodes::deregister_vote_pool m_deregister_vote_pool;
|
||||
service_nodes::service_node_list m_service_node_list;
|
||||
service_nodes::quorum_cop m_quorum_cop;
|
||||
service_nodes::service_node_list m_service_node_list;
|
||||
service_nodes::quorum_cop m_quorum_cop;
|
||||
|
||||
i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance
|
||||
|
||||
|
@ -1141,9 +1115,7 @@ namespace cryptonote
|
|||
epee::math_helper::once_a_time_seconds<30, true> m_uptime_proof_pruner;
|
||||
epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate
|
||||
epee::math_helper::once_a_time_seconds<60*60*5, true> m_blockchain_pruning_interval; //!< interval for incremental blockchain pruning
|
||||
|
||||
epee::math_helper::once_a_time_seconds<60*2, false> m_deregisters_auto_relayer;
|
||||
epee::math_helper::once_a_time_seconds<60*2, false> m_checkpoint_auto_relayer;
|
||||
epee::math_helper::once_a_time_seconds<60*2, false> m_service_node_vote_relayer;
|
||||
|
||||
std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown?
|
||||
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
// Copyright (c) 2018, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "service_node_deregister.h"
|
||||
#include "service_node_list.h"
|
||||
#include "cryptonote_basic/tx_extra.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_basic/connection_context.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
||||
#include "cryptonote_core/blockchain.h"
|
||||
|
||||
#include "misc_log_ex.h"
|
||||
#include "string_tools.h"
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#undef LOKI_DEFAULT_LOG_CATEGORY
|
||||
#define LOKI_DEFAULT_LOG_CATEGORY "service_nodes"
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
static crypto::hash make_hash_from(uint64_t block_height, uint32_t service_node_index)
|
||||
{
|
||||
const int buf_size = sizeof(block_height) + sizeof(service_node_index);
|
||||
char buf[buf_size];
|
||||
|
||||
memcpy(buf, reinterpret_cast<void *>(&block_height), sizeof(block_height));
|
||||
memcpy(buf + sizeof(block_height), reinterpret_cast<void *>(&service_node_index), sizeof(service_node_index));
|
||||
|
||||
crypto::hash result;
|
||||
crypto::cn_fast_hash(buf, buf_size, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
crypto::signature deregister_vote::sign_vote(uint64_t block_height, uint32_t service_node_index, const crypto::public_key& pub, const crypto::secret_key& sec)
|
||||
{
|
||||
crypto::signature result;
|
||||
crypto::generate_signature(make_hash_from(block_height, service_node_index), pub, sec, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool deregister_vote::verify_vote_signature(uint64_t block_height, uint32_t service_node_index, crypto::public_key const &p, crypto::signature const &s)
|
||||
{
|
||||
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs{ std::make_pair(p, s) };
|
||||
return verify_votes_signature(block_height, service_node_index, keys_and_sigs);
|
||||
}
|
||||
|
||||
bool deregister_vote::verify_votes_signature(uint64_t block_height, uint32_t service_node_index, const std::vector<std::pair<crypto::public_key, crypto::signature>>& keys_and_sigs)
|
||||
{
|
||||
crypto::hash hash = make_hash_from(block_height, service_node_index);
|
||||
for (auto& key_and_sig : keys_and_sigs)
|
||||
{
|
||||
if (!crypto::check_signature(hash, key_and_sig.first, key_and_sig.second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool verify_votes_helper(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
|
||||
cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
||||
{
|
||||
if (deregister.service_node_index >= uptime_quorum.nodes_to_test.size())
|
||||
{
|
||||
vvc.m_service_node_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << uptime_quorum.nodes_to_test.size() << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<crypto::public_key>& quorum = uptime_quorum.quorum_nodes;
|
||||
std::vector<int8_t> quorum_set;
|
||||
|
||||
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs;
|
||||
for (const cryptonote::tx_extra_service_node_deregister::vote& vote : deregister.votes)
|
||||
{
|
||||
if (vote.voters_quorum_index >= quorum.size())
|
||||
{
|
||||
vvc.m_voters_quorum_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Voter's index in deregister vote was out of bounds: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
quorum_set.resize(quorum.size());
|
||||
if (++quorum_set[vote.voters_quorum_index] > 1)
|
||||
{
|
||||
vvc.m_duplicate_voters = true;
|
||||
LOG_PRINT_L1("Voter quorum index is duplicated: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
keys_and_sigs.push_back(std::make_pair(quorum[vote.voters_quorum_index], vote.signature));
|
||||
}
|
||||
|
||||
bool r = deregister_vote::verify_votes_signature(deregister.block_height, deregister.service_node_index, keys_and_sigs);
|
||||
if (!r)
|
||||
{
|
||||
LOG_PRINT_L1("Invalid signatures for votes");
|
||||
vvc.m_verification_failed = true;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
bool deregister_vote::verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
|
||||
cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
||||
{
|
||||
if (deregister.votes.size() < service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
{
|
||||
LOG_PRINT_L1("Not enough votes");
|
||||
vvc.m_not_enough_votes = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = verify_votes_helper(nettype, deregister, vvc, uptime_quorum);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool deregister_vote::verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
||||
{
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
deregister.block_height = v.block_height;
|
||||
deregister.service_node_index = v.service_node_index;
|
||||
deregister.votes.push_back(cryptonote::tx_extra_service_node_deregister::vote{ v.signature, v.voters_quorum_index });
|
||||
return verify_votes_helper(nettype, deregister, vvc, uptime_quorum);
|
||||
}
|
||||
|
||||
void deregister_vote_pool::set_relayed(const std::vector<deregister_vote>& votes)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
const time_t now = time(NULL);
|
||||
|
||||
for (const deregister_vote &find_vote : votes)
|
||||
{
|
||||
deregister_group desired_group = {};
|
||||
desired_group.block_height = find_vote.block_height;
|
||||
desired_group.service_node_index = find_vote.service_node_index;
|
||||
|
||||
auto deregister_entry = m_deregisters.find(desired_group);
|
||||
if (deregister_entry != m_deregisters.end())
|
||||
{
|
||||
std::vector<deregister_pool_entry> &deregister_vector = deregister_entry->second;
|
||||
for (auto &deregister : deregister_vector)
|
||||
{
|
||||
if (deregister.m_vote.voters_quorum_index == find_vote.voters_quorum_index)
|
||||
{
|
||||
deregister.m_time_last_sent_p2p = now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<deregister_vote> deregister_vote_pool::get_relayable_votes() const
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
const cryptonote::cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
||||
|
||||
// TODO(doyle): Rate-limiting: A better threshold value that follows suite with transaction relay time back-off
|
||||
const time_t now = time(NULL);
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
const time_t THRESHOLD = 60 * 2;
|
||||
#else
|
||||
const time_t THRESHOLD = 0;
|
||||
#endif
|
||||
|
||||
std::vector<deregister_vote> result;
|
||||
for (const auto &deregister_entry : m_deregisters)
|
||||
{
|
||||
const std::vector<deregister_pool_entry>& deregister_vector = deregister_entry.second;
|
||||
for (const deregister_pool_entry &entry : deregister_vector)
|
||||
{
|
||||
const time_t last_sent = now - entry.m_time_last_sent_p2p;
|
||||
if (last_sent > THRESHOLD)
|
||||
{
|
||||
result.push_back(entry.m_vote);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool deregister_vote_pool::add_vote(const int hf_version,
|
||||
const deregister_vote& new_vote,
|
||||
cryptonote::vote_verification_context& vvc,
|
||||
const service_nodes::quorum_uptime_proof &uptime_quorum,
|
||||
cryptonote::transaction &tx)
|
||||
{
|
||||
if (!deregister_vote::verify_vote(m_nettype, new_vote, vvc, uptime_quorum))
|
||||
{
|
||||
LOG_PRINT_L1("Signature verification failed for deregister vote");
|
||||
return false;
|
||||
}
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
time_t const now = time(NULL);
|
||||
std::vector<deregister_pool_entry> *deregister_votes;
|
||||
{
|
||||
deregister_group desired_group = {};
|
||||
desired_group.block_height = new_vote.block_height;
|
||||
desired_group.service_node_index = new_vote.service_node_index;
|
||||
deregister_votes = &m_deregisters[desired_group];
|
||||
}
|
||||
|
||||
bool new_deregister_is_unique = true;
|
||||
for (const auto &entry : *deregister_votes)
|
||||
{
|
||||
if (entry.m_vote.voters_quorum_index == new_vote.voters_quorum_index)
|
||||
{
|
||||
new_deregister_is_unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_deregister_is_unique)
|
||||
{
|
||||
vvc.m_added_to_pool = true;
|
||||
deregister_votes->emplace_back(deregister_pool_entry(0 /*time_last_sent_p2p*/, new_vote));
|
||||
|
||||
if (deregister_votes->size() >= service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
{
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
deregister.block_height = new_vote.block_height;
|
||||
deregister.service_node_index = new_vote.service_node_index;
|
||||
deregister.votes.reserve(deregister_votes->size());
|
||||
|
||||
for (const auto& entry : *deregister_votes)
|
||||
{
|
||||
cryptonote::tx_extra_service_node_deregister::vote tx_vote = {};
|
||||
tx_vote.signature = entry.m_vote.signature;
|
||||
tx_vote.voters_quorum_index = entry.m_vote.voters_quorum_index;
|
||||
deregister.votes.push_back(tx_vote);
|
||||
}
|
||||
|
||||
vvc.m_full_tx_deregister_made = cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister);
|
||||
if (vvc.m_full_tx_deregister_made)
|
||||
{
|
||||
tx.version = cryptonote::transaction::get_max_version_for_hf(hf_version, m_nettype);
|
||||
tx.type = cryptonote::transaction::type_deregister;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L1("Could not create deregistration transaction from votes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void deregister_vote_pool::remove_used_votes(std::vector<cryptonote::transaction> const &txs)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
for (const auto &tx : txs)
|
||||
{
|
||||
if (tx.get_type() != cryptonote::transaction::type_deregister)
|
||||
continue;
|
||||
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
LOG_ERROR("Could not get deregister from tx, possibly corrupt tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
deregister_group desired_group = {};
|
||||
desired_group.block_height = deregister.block_height;
|
||||
desired_group.service_node_index = deregister.service_node_index;
|
||||
m_deregisters.erase(desired_group);
|
||||
}
|
||||
}
|
||||
|
||||
void deregister_vote_pool::remove_expired_votes(uint64_t height)
|
||||
{
|
||||
if (height < deregister_vote::VOTE_LIFETIME_BY_HEIGHT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
uint64_t minimum_height = height - deregister_vote::VOTE_LIFETIME_BY_HEIGHT;
|
||||
for (auto it = m_deregisters.begin(); it != m_deregisters.end();)
|
||||
{
|
||||
const deregister_group &deregister_for = it->first;
|
||||
if (deregister_for.block_height < minimum_height)
|
||||
{
|
||||
it = m_deregisters.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}; // namespace service_nodes
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// Copyright (c) 2018, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
|
||||
#include "math_helper.h"
|
||||
#include "syncobj.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
struct vote_verification_context;
|
||||
struct tx_extra_service_node_deregister;
|
||||
};
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
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
|
||||
{
|
||||
static const uint64_t VOTE_LIFETIME_BY_HEIGHT = BLOCKS_EXPECTED_IN_HOURS(2);
|
||||
static const uint64_t DEREGISTER_LIFETIME_BY_HEIGHT = VOTE_LIFETIME_BY_HEIGHT;
|
||||
|
||||
uint64_t block_height;
|
||||
uint32_t service_node_index;
|
||||
uint32_t voters_quorum_index;
|
||||
crypto::signature signature;
|
||||
|
||||
static crypto::signature sign_vote(uint64_t block_height, uint32_t service_node_index, const crypto::public_key& pub, const crypto::secret_key& sec);
|
||||
static bool verify_vote_signature (uint64_t block_height, uint32_t service_node_index, crypto::public_key const &p, crypto::signature const &s);
|
||||
static bool verify_votes_signature(uint64_t block_height, uint32_t service_node_index, const std::vector<std::pair<crypto::public_key, crypto::signature>>& keys_and_sigs);
|
||||
|
||||
static bool verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
|
||||
cryptonote::vote_verification_context& vvc,
|
||||
const service_nodes::quorum_uptime_proof &quorum);
|
||||
|
||||
static bool verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::quorum_uptime_proof &quorum);
|
||||
};
|
||||
|
||||
class deregister_vote_pool
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @return True if vote was valid and in the pool already or just added (check vote verfication for specific case).
|
||||
*/
|
||||
bool add_vote(const int hf_version,
|
||||
const deregister_vote& new_vote,
|
||||
cryptonote::vote_verification_context& vvc,
|
||||
const service_nodes::quorum_uptime_proof &uptime_quorum,
|
||||
cryptonote::transaction &tx);
|
||||
|
||||
// TODO(loki): Review relay behaviour and all the cases when it should be triggered
|
||||
void set_relayed (const std::vector<deregister_vote>& votes);
|
||||
void remove_expired_votes(uint64_t height);
|
||||
void remove_used_votes (std::vector<cryptonote::transaction> const &txs);
|
||||
std::vector<deregister_vote> get_relayable_votes () const;
|
||||
|
||||
cryptonote::network_type m_nettype = cryptonote::UNDEFINED;
|
||||
|
||||
private:
|
||||
struct deregister_pool_entry
|
||||
{
|
||||
deregister_pool_entry(uint64_t time_last_sent_p2p, deregister_vote vote) : m_time_last_sent_p2p(time_last_sent_p2p), m_vote(vote) {}
|
||||
uint64_t m_time_last_sent_p2p;
|
||||
deregister_vote m_vote;
|
||||
};
|
||||
|
||||
struct deregister_group
|
||||
{
|
||||
uint64_t block_height;
|
||||
uint32_t service_node_index;
|
||||
time_t time_group_created;
|
||||
|
||||
bool operator==(const deregister_group &other) const
|
||||
{
|
||||
bool result = (block_height == other.block_height) && (service_node_index == other.service_node_index);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct deregister_group_hasher
|
||||
{
|
||||
size_t operator()(const deregister_group& deregister) const
|
||||
{
|
||||
size_t res = 17;
|
||||
res = res * 31 + std::hash<uint64_t>()(deregister.block_height);
|
||||
res = res * 31 + std::hash<uint32_t>()(deregister.service_node_index);
|
||||
res = res * 31 + std::hash<uint32_t>()(deregister.time_group_created);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<deregister_group, std::vector<deregister_pool_entry>, deregister_group_hasher> m_deregisters;
|
||||
mutable epee::critical_section m_lock;
|
||||
};
|
||||
}; // namespace service_nodes
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "int-util.h"
|
||||
#include "common/scoped_message_writer.h"
|
||||
#include "common/i18n.h"
|
||||
#include "blockchain.h"
|
||||
#include "service_node_quorum_cop.h"
|
||||
|
||||
#include "service_node_list.h"
|
||||
|
@ -63,29 +64,11 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
service_node_list::service_node_list(cryptonote::Blockchain& blockchain)
|
||||
: m_blockchain(blockchain), m_hooks_registered(false), m_db(nullptr), m_service_node_pubkey(nullptr)
|
||||
: m_blockchain(blockchain), m_db(nullptr), m_service_node_pubkey(nullptr)
|
||||
{
|
||||
m_transient_state = {};
|
||||
}
|
||||
|
||||
void service_node_list::register_hooks(service_nodes::quorum_cop &quorum_cop)
|
||||
{
|
||||
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
|
||||
if (!m_hooks_registered)
|
||||
{
|
||||
m_hooks_registered = true;
|
||||
m_blockchain.hook_block_added(*this);
|
||||
m_blockchain.hook_blockchain_detached(*this);
|
||||
m_blockchain.hook_init(*this);
|
||||
m_blockchain.hook_validate_miner_tx(*this);
|
||||
|
||||
// NOTE: There is an implicit dependency on service node lists hooks
|
||||
m_blockchain.hook_init(quorum_cop);
|
||||
m_blockchain.hook_block_added(quorum_cop);
|
||||
m_blockchain.hook_blockchain_detached(quorum_cop);
|
||||
}
|
||||
}
|
||||
|
||||
void service_node_list::init()
|
||||
{
|
||||
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
|
||||
|
@ -151,21 +134,23 @@ namespace service_nodes
|
|||
return result;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const quorum_uptime_proof> service_node_list::get_uptime_quorum(uint64_t height) const
|
||||
std::shared_ptr<const testing_quorum> service_node_list::get_testing_quorum(quorum_type type, 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.uptime_proof;
|
||||
return nullptr;
|
||||
}
|
||||
{
|
||||
if (type == quorum_type::deregister)
|
||||
return it->second.deregister;
|
||||
else if (type == quorum_type::checkpointing)
|
||||
return it->second.checkpointing;
|
||||
else
|
||||
{
|
||||
MERROR("Developer error: Unhandled quorum enum with value: " << (size_t)type);
|
||||
assert(!"Developer error: Unhandled quorum enum");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -322,8 +307,7 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
const auto state = get_uptime_quorum(deregister.block_height);
|
||||
|
||||
const auto state = get_testing_quorum(quorum_type::deregister, deregister.block_height);
|
||||
if (!state)
|
||||
{
|
||||
// TODO(loki): Not being able to find a quorum is fatal! We want better caching abilities.
|
||||
|
@ -331,13 +315,13 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
if (deregister.service_node_index >= state->nodes_to_test.size())
|
||||
if (deregister.service_node_index >= state->workers.size())
|
||||
{
|
||||
MERROR("Service node index to vote off has become invalid, quorum rules have changed without a hardfork.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const crypto::public_key& key = state->nodes_to_test[deregister.service_node_index];
|
||||
const crypto::public_key& key = state->workers[deregister.service_node_index];
|
||||
|
||||
auto iter = m_transient_state.service_nodes_infos.find(key);
|
||||
if (iter == m_transient_state.service_nodes_infos.end())
|
||||
|
@ -1292,7 +1276,6 @@ namespace service_nodes
|
|||
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)
|
||||
{
|
||||
std::vector<size_t> result(snode_list.size());
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < snode_list.size(); i++) result[i] = i;
|
||||
|
||||
uint64_t seed = 0;
|
||||
|
@ -1307,6 +1290,7 @@ namespace service_nodes
|
|||
{
|
||||
crypto::hash block_hash;
|
||||
uint64_t const height = cryptonote::get_block_height(block);
|
||||
int const hf_version = block.major_version;
|
||||
if (!cryptonote::get_block_hash(block, block_hash))
|
||||
{
|
||||
MERROR("Block height: " << height << " returned null hash");
|
||||
|
@ -1314,72 +1298,62 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
std::vector<crypto::public_key> const snode_list = get_service_nodes_pubkeys();
|
||||
for (int type_int = 0; type_int < static_cast<int>(quorum_type::count); type_int++)
|
||||
quorum_manager &manager = m_transient_state.quorum_states[height];
|
||||
for (int type_int = 0; type_int < (int)quorum_type::count; type_int++)
|
||||
{
|
||||
auto type = static_cast<quorum_type>(type_int);
|
||||
auto type = static_cast<quorum_type>(type_int);
|
||||
size_t num_validators = 0, num_workers = 0;
|
||||
auto quorum = std::make_shared<testing_quorum>();
|
||||
std::vector<size_t> const pub_keys_indexes = generate_shuffled_service_node_index_list(snode_list, block_hash, type);
|
||||
|
||||
switch(type)
|
||||
if (type == quorum_type::deregister)
|
||||
{
|
||||
case quorum_type::uptime_proof:
|
||||
if (hf_version >= cryptonote::network_version_9_service_nodes)
|
||||
{
|
||||
// Assign indexes from shuffled list into quorum and list of nodes to test
|
||||
auto new_state = std::make_shared<quorum_uptime_proof>();
|
||||
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;
|
||||
num_validators = std::min(pub_keys_indexes.size(), DEREGISTER_QUORUM_SIZE);
|
||||
size_t num_remaining_nodes = pub_keys_indexes.size() - num_validators;
|
||||
num_workers = std::max(num_remaining_nodes/DEREGISTER_NTH_OF_THE_NETWORK_TO_TEST, std::min(DEREGISTER_MIN_NODES_TO_TEST, num_remaining_nodes));
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
}
|
||||
else if (type == quorum_type::checkpointing)
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_12_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;
|
||||
num_validators = std::min(pub_keys_indexes.size(), CHECKPOINT_QUORUM_SIZE);
|
||||
size_t num_remaining_nodes = pub_keys_indexes.size() - num_validators;
|
||||
num_workers = std::min(num_remaining_nodes, CHECKPOINT_QUORUM_SIZE);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
assert("Loki Developer Error: Unhandled enum" == 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
MERROR("Unhandled quorum type enum with value: " << type_int);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
quorum->validators.resize(num_validators);
|
||||
for (size_t i = 0; i < quorum->validators.size(); i++)
|
||||
{
|
||||
size_t node_index = pub_keys_indexes[i];
|
||||
const crypto::public_key &key = snode_list[node_index];
|
||||
quorum->validators[i] = key;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
quorum->workers.resize(num_workers);
|
||||
for (size_t i = 0; i < quorum->workers.size(); i++)
|
||||
{
|
||||
size_t node_index = pub_keys_indexes[quorum->validators.size() + i];
|
||||
const crypto::public_key &key = snode_list[node_index];
|
||||
quorum->workers[i] = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == quorum_type::deregister)
|
||||
manager.deregister = quorum;
|
||||
else
|
||||
manager.checkpointing = quorum;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1429,13 +1403,13 @@ namespace service_nodes
|
|||
quorum.height = kv_pair.first;
|
||||
quorum_manager const &manager = kv_pair.second;
|
||||
|
||||
if (manager.uptime_proof)
|
||||
quorum.uptime_quorum = *manager.uptime_proof;
|
||||
if (manager.deregister)
|
||||
quorum.quorums[(int)quorum_type::deregister] = *manager.deregister;
|
||||
|
||||
if (quorum.version >= service_node_info::version_3_checkpointing)
|
||||
{
|
||||
if (manager.checkpointing)
|
||||
quorum.checkpointing_quorum = *manager.checkpointing;
|
||||
quorum.quorums[(int)quorum_type::checkpointing] = *manager.checkpointing;
|
||||
}
|
||||
|
||||
data_to_store.quorum_states.push_back(quorum);
|
||||
|
@ -1535,13 +1509,17 @@ namespace service_nodes
|
|||
|
||||
for (const auto& states : data_in.quorum_states)
|
||||
{
|
||||
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);
|
||||
{
|
||||
testing_quorum const &deregister = states.quorums[(int)quorum_type::deregister];
|
||||
if (deregister.validators.size() > 0 || deregister.workers.size() > 0)
|
||||
m_transient_state.quorum_states[states.height].deregister = std::make_shared<testing_quorum>(deregister);
|
||||
}
|
||||
|
||||
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);
|
||||
testing_quorum const &checkpointing = states.quorums[(int)quorum_type::checkpointing];
|
||||
if (checkpointing.validators.size() > 0 || checkpointing.workers.size() > 0)
|
||||
m_transient_state.quorum_states[states.height].checkpointing = std::make_shared<testing_quorum>(checkpointing);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "blockchain.h"
|
||||
#include <boost/variant.hpp>
|
||||
#include "serialization/serialization.h"
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "cryptonote_core/service_node_rules.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
#include "cryptonote_core/service_node_quorum_cop.h"
|
||||
|
||||
namespace cryptonote { struct Blockchain; struct BlockchainDB; }
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
struct service_node_info // registration information
|
||||
|
@ -166,16 +168,15 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
class service_node_list
|
||||
: public cryptonote::Blockchain::BlockAddedHook,
|
||||
public cryptonote::Blockchain::BlockchainDetachedHook,
|
||||
public cryptonote::Blockchain::InitHook,
|
||||
public cryptonote::Blockchain::ValidateMinerTxHook
|
||||
: public cryptonote::BlockAddedHook,
|
||||
public cryptonote::BlockchainDetachedHook,
|
||||
public cryptonote::InitHook,
|
||||
public cryptonote::ValidateMinerTxHook
|
||||
{
|
||||
public:
|
||||
service_node_list(cryptonote::Blockchain& blockchain);
|
||||
void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override;
|
||||
void blockchain_detached(uint64_t height) override;
|
||||
void register_hooks(service_nodes::quorum_cop &quorum_cop);
|
||||
void init() override;
|
||||
bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, cryptonote::block_reward_parts const &base_reward) const override;
|
||||
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> get_winner_addresses_and_portions() const;
|
||||
|
@ -187,8 +188,7 @@ namespace service_nodes
|
|||
void update_swarms(uint64_t height);
|
||||
|
||||
/// Note(maxim): this should not affect thread-safety as the returned object is const
|
||||
const std::shared_ptr<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::shared_ptr<const testing_quorum> get_testing_quorum(quorum_type type, 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;
|
||||
const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_transient_state.key_image_blacklist; }
|
||||
|
@ -289,17 +289,16 @@ namespace service_nodes
|
|||
|
||||
struct quorum_for_serialization
|
||||
{
|
||||
uint8_t version;
|
||||
uint64_t height;
|
||||
quorum_uptime_proof uptime_quorum;
|
||||
quorum_checkpointing checkpointing_quorum;
|
||||
uint8_t version;
|
||||
uint64_t height;
|
||||
testing_quorum quorums[(size_t)quorum_type::count];
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(version)
|
||||
FIELD(height)
|
||||
FIELD(uptime_quorum)
|
||||
FIELD_N("deregister_quorum", quorums[(size_t)quorum_type::deregister])
|
||||
if (version >= service_node_info::version_3_checkpointing)
|
||||
FIELD(checkpointing_quorum)
|
||||
FIELD_N("checkpointing_quorum", quorums[(size_t)quorum_type::checkpointing])
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
|
@ -344,7 +343,6 @@ namespace service_nodes
|
|||
|
||||
mutable boost::recursive_mutex m_sn_mutex;
|
||||
cryptonote::Blockchain& m_blockchain;
|
||||
bool m_hooks_registered;
|
||||
crypto::public_key const *m_service_node_pubkey;
|
||||
cryptonote::BlockchainDB *m_db;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "service_node_quorum_cop.h"
|
||||
#include "service_node_deregister.h"
|
||||
#include "service_node_voting.h"
|
||||
#include "service_node_list.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_core.h"
|
||||
|
@ -41,165 +41,347 @@
|
|||
|
||||
namespace service_nodes
|
||||
{
|
||||
static_assert(quorum_cop::REORG_SAFETY_BUFFER_IN_BLOCKS < DEREGISTER_VOTE_LIFETIME, "Safety buffer should always be less than the vote lifetime");
|
||||
|
||||
quorum_cop::quorum_cop(cryptonote::core& core)
|
||||
: m_core(core), m_uptime_proof_height(0)
|
||||
: m_core(core), m_uptime_proof_height(0), m_last_checkpointed_height(0)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void quorum_cop::init()
|
||||
{
|
||||
m_uptime_proof_height = 0;
|
||||
m_uptime_proof_height = 0;
|
||||
m_last_checkpointed_height = 0;
|
||||
m_uptime_proof_seen.clear();
|
||||
|
||||
uint64_t top_height;
|
||||
crypto::hash top_hash;
|
||||
m_core.get_blockchain_top(top_height, top_hash);
|
||||
|
||||
cryptonote::block blk;
|
||||
if (m_core.get_block_by_hash(top_hash, blk))
|
||||
process_quorums(blk);
|
||||
}
|
||||
|
||||
void quorum_cop::blockchain_detached(uint64_t height)
|
||||
{
|
||||
// TODO(doyle): Assumes large reorgs that are no longer possible with checkpointing
|
||||
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_uptime_proof_height);
|
||||
LOG_ERROR("This implies a reorg occured that was over " << REORG_SAFETY_BUFFER_IN_BLOCKS << ". This should never happen! Please report this to the devs.");
|
||||
m_uptime_proof_height = height;
|
||||
}
|
||||
|
||||
if (m_last_checkpointed_height >= height)
|
||||
{
|
||||
LOG_ERROR("The blockchain was detached to height: " << height << ", but quorum cop has already processed votes up to " << m_last_checkpointed_height);
|
||||
LOG_ERROR("This implies a reorg occured that was over " << REORG_SAFETY_BUFFER_IN_BLOCKS << ". This should never happen! Please report this to the devs.");
|
||||
m_last_checkpointed_height = height;
|
||||
}
|
||||
m_vote_pool.remove_expired_votes(height);
|
||||
}
|
||||
|
||||
void quorum_cop::set_votes_relayed(std::vector<quorum_vote_t> const &relayed_votes)
|
||||
{
|
||||
m_vote_pool.set_relayed(relayed_votes);
|
||||
}
|
||||
|
||||
std::vector<quorum_vote_t> quorum_cop::get_relayable_votes()
|
||||
{
|
||||
std::vector<quorum_vote_t> result = m_vote_pool.get_relayable_votes();
|
||||
return result;
|
||||
}
|
||||
|
||||
static int find_index_in_quorum_group(std::vector<crypto::public_key> const &group, crypto::public_key const &my_pubkey)
|
||||
{
|
||||
int result = -1;
|
||||
auto it = std::find(group.begin(), group.end(), my_pubkey);
|
||||
if (it == group.end()) return result;
|
||||
result = std::distance(group.begin(), it);
|
||||
return result;
|
||||
}
|
||||
|
||||
void quorum_cop::process_quorums(cryptonote::block const &block)
|
||||
{
|
||||
crypto::public_key my_pubkey;
|
||||
crypto::secret_key my_seckey;
|
||||
if (!m_core.get_service_node_keys(my_pubkey, my_seckey))
|
||||
return;
|
||||
|
||||
uint64_t const height = cryptonote::get_block_height(block);
|
||||
for (int i = 0; i < (int)quorum_type::count; i++)
|
||||
{
|
||||
quorum_type const type = static_cast<quorum_type>(i);
|
||||
uint64_t const vote_lifetime = service_nodes::quorum_vote_lifetime(type);
|
||||
|
||||
uint64_t const latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height());
|
||||
if (latest_height < vote_lifetime)
|
||||
continue;
|
||||
|
||||
uint64_t const start_voting_from_height = latest_height - vote_lifetime;
|
||||
if (height < start_voting_from_height)
|
||||
continue;
|
||||
|
||||
int const hf_version = block.major_version;
|
||||
switch(type)
|
||||
{
|
||||
default:
|
||||
{
|
||||
assert("Unhandled quorum type " == 0);
|
||||
LOG_ERROR("Unhandled quorum type with value: " << (int)type);
|
||||
} break;
|
||||
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_9_service_nodes)
|
||||
{
|
||||
// NOTE: Wait atleast 2 hours before we're allowed to vote so that we collect uptimes from everyone on the network
|
||||
time_t const now = time(nullptr);
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
time_t const min_lifetime = 0;
|
||||
#else
|
||||
time_t const min_lifetime = 60 * 60 * 2;
|
||||
#endif
|
||||
bool alive_for_min_time = (now - m_core.get_start_time()) >= min_lifetime;
|
||||
if (!alive_for_min_time)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_uptime_proof_height < start_voting_from_height)
|
||||
m_uptime_proof_height = start_voting_from_height;
|
||||
|
||||
for (; m_uptime_proof_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_uptime_proof_height++)
|
||||
{
|
||||
if (m_core.get_hard_fork_version(m_uptime_proof_height) < 9) continue;
|
||||
|
||||
const std::shared_ptr<const testing_quorum> quorum =
|
||||
m_core.get_testing_quorum(quorum_type::deregister, m_uptime_proof_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Uptime quorum for height: " << m_uptime_proof_height << " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
|
||||
int index_in_group = find_index_in_quorum_group(quorum->validators, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum
|
||||
//
|
||||
for (size_t node_index = 0; node_index < quorum->workers.size(); ++node_index)
|
||||
{
|
||||
const crypto::public_key &node_key = quorum->workers[node_index];
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
bool vote_off_node = (m_uptime_proof_seen.find(node_key) == m_uptime_proof_seen.end());
|
||||
if (!vote_off_node) continue;
|
||||
|
||||
quorum_vote_t vote = service_nodes::make_deregister_vote(
|
||||
m_uptime_proof_height, static_cast<uint16_t>(index_in_group), node_index, my_pubkey, my_seckey);
|
||||
cryptonote::vote_verification_context vvc;
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add uptime deregister vote reason: " << print_vote_verification_context(vvc, nullptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_12_checkpointing)
|
||||
{
|
||||
if (m_last_checkpointed_height < start_voting_from_height)
|
||||
m_last_checkpointed_height = start_voting_from_height;
|
||||
|
||||
for (m_last_checkpointed_height += (m_last_checkpointed_height % CHECKPOINT_INTERVAL);
|
||||
m_last_checkpointed_height <= height;
|
||||
m_last_checkpointed_height += CHECKPOINT_INTERVAL)
|
||||
{
|
||||
if (m_core.get_hard_fork_version(m_last_checkpointed_height) < cryptonote::network_version_12_checkpointing)
|
||||
continue;
|
||||
|
||||
const std::shared_ptr<const testing_quorum> quorum =
|
||||
m_core.get_testing_quorum(quorum_type::checkpointing, m_last_checkpointed_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Checkpoint quorum for height: " << m_last_checkpointed_height
|
||||
<< " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
|
||||
int index_in_group = find_index_in_quorum_group(quorum->workers, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum, handle checkpointing
|
||||
//
|
||||
quorum_vote_t vote = {};
|
||||
vote.type = quorum_type::checkpointing;
|
||||
vote.checkpoint.block_hash = m_core.get_block_id_by_height(m_last_checkpointed_height);
|
||||
|
||||
if (vote.checkpoint.block_hash == crypto::null_hash)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Could not get block hash for block on height: " << m_last_checkpointed_height);
|
||||
continue;
|
||||
}
|
||||
|
||||
vote.block_height = m_last_checkpointed_height;
|
||||
vote.group = quorum_group::worker;
|
||||
vote.index_in_group = static_cast<uint16_t>(index_in_group);
|
||||
vote.signature = make_signature_from_vote(vote, my_pubkey, my_seckey);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add checkpoint vote reason: " << print_vote_verification_context(vvc, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void quorum_cop::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs)
|
||||
{
|
||||
process_uptime_quorum(block);
|
||||
process_checkpoint_quorum(block);
|
||||
process_quorums(block);
|
||||
|
||||
// Since our age checks for deregister votes is now (age >=
|
||||
// DEREGISTER_VOTE_LIFETIME_IN_BLOCKS) where age is
|
||||
// get_current_blockchain_height() which gives you the height that you are
|
||||
// currently mining for, i.e. (height + 1).
|
||||
|
||||
// Otherwise peers will silently drop connection from each other when they
|
||||
// go around P2Ping votes due to passing around old votes
|
||||
uint64_t const height = cryptonote::get_block_height(block) + 1;
|
||||
m_vote_pool.remove_expired_votes(height);
|
||||
m_vote_pool.remove_used_votes(txs);
|
||||
}
|
||||
|
||||
void quorum_cop::process_uptime_quorum(cryptonote::block const &block)
|
||||
bool quorum_cop::handle_vote(quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc)
|
||||
{
|
||||
uint64_t const height = cryptonote::get_block_height(block);
|
||||
if (m_core.get_hard_fork_version(height) < 9)
|
||||
return;
|
||||
|
||||
crypto::public_key my_pubkey;
|
||||
crypto::secret_key my_seckey;
|
||||
if (!m_core.get_service_node_keys(my_pubkey, my_seckey))
|
||||
return;
|
||||
|
||||
time_t const now = time(nullptr);
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
time_t const min_lifetime = 0;
|
||||
#else
|
||||
time_t const min_lifetime = 60 * 60 * 2;
|
||||
#endif
|
||||
bool alive_for_min_time = (now - m_core.get_start_time()) >= min_lifetime;
|
||||
if (!alive_for_min_time)
|
||||
vvc = {};
|
||||
switch(vote.type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t const latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height());
|
||||
if (latest_height < service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT)
|
||||
return;
|
||||
|
||||
uint64_t const execute_justice_from_height = latest_height - service_nodes::deregister_vote::VOTE_LIFETIME_BY_HEIGHT;
|
||||
if (height < execute_justice_from_height)
|
||||
return;
|
||||
|
||||
if (m_uptime_proof_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++)
|
||||
{
|
||||
if (m_core.get_hard_fork_version(m_uptime_proof_height) < 9)
|
||||
continue;
|
||||
|
||||
const std::shared_ptr<const quorum_uptime_proof> state = m_core.get_uptime_quorum(m_uptime_proof_height);
|
||||
if (!state)
|
||||
default:
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Quorum state for height: " << m_uptime_proof_height << " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
LOG_PRINT_L1("Unhandled vote type with value: " << (int)vote.type);
|
||||
assert("Unhandled vote type" == 0);
|
||||
return false;
|
||||
};
|
||||
|
||||
auto it = std::find(state->quorum_nodes.begin(), state->quorum_nodes.end(), my_pubkey);
|
||||
if (it == state->quorum_nodes.end())
|
||||
continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum
|
||||
//
|
||||
size_t my_index_in_quorum = it - state->quorum_nodes.begin();
|
||||
for (size_t node_index = 0; node_index < state->nodes_to_test.size(); ++node_index)
|
||||
case quorum_type::deregister: break;
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
const crypto::public_key &node_key = state->nodes_to_test[node_index];
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
bool vote_off_node = (m_uptime_proof_seen.find(node_key) == m_uptime_proof_seen.end());
|
||||
|
||||
if (!vote_off_node)
|
||||
continue;
|
||||
|
||||
service_nodes::deregister_vote vote = {};
|
||||
vote.block_height = m_uptime_proof_height;
|
||||
vote.service_node_index = node_index;
|
||||
vote.voters_quorum_index = my_index_in_quorum;
|
||||
vote.signature = service_nodes::deregister_vote::sign_vote(vote.block_height, vote.service_node_index, my_pubkey, my_seckey);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
if (!m_core.add_deregister_vote(vote, vvc))
|
||||
cryptonote::block block;
|
||||
if (!m_core.get_block_by_hash(vote.checkpoint.block_hash, block)) // Does vote reference a valid block hash?
|
||||
{
|
||||
LOG_ERROR("Failed to add deregister vote reason: " << print_vote_verification_context(vvc, &vote));
|
||||
LOG_PRINT_L1("Vote does not reference valid block hash: " << vote.checkpoint.block_hash);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// NOTE: Only do validation that relies on access cryptonote::core here in quorum cop, the rest goes in voting pool
|
||||
std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(vote.type, vote.block_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Quorum state for height: " << height << " was not cached in daemon!");
|
||||
return;
|
||||
LOG_ERROR("Quorum state for height: " << vote.block_height << " was not cached in daemon!");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = std::find(state->quorum_nodes.begin(), state->quorum_nodes.end(), my_pubkey);
|
||||
if (it == state->quorum_nodes.end())
|
||||
return;
|
||||
uint64_t latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height());
|
||||
std::vector<pool_vote_entry> votes = m_vote_pool.add_pool_vote_if_unique(latest_height, vote, vvc, *quorum);
|
||||
bool result = !vvc.m_verification_failed;
|
||||
|
||||
//
|
||||
// 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))
|
||||
if (!vvc.m_added_to_pool) // NOTE: Not unique vote
|
||||
return result;
|
||||
|
||||
switch(vote.type)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Could not get block hash for block on height: " << height);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOG_PRINT_L1("Unhandled vote type with value: " << (int)vote.type);
|
||||
assert("Unhandled vote type" == 0);
|
||||
return false;
|
||||
};
|
||||
|
||||
vote.block_height = height;
|
||||
vote.voters_quorum_index = my_index_in_quorum;
|
||||
crypto::generate_signature(vote.block_hash, my_pubkey, my_seckey, vote.signature);
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
if (votes.size() >= DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
{
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
deregister.block_height = vote.block_height;
|
||||
deregister.service_node_index = vote.deregister.worker_index;
|
||||
deregister.votes.reserve(votes.size());
|
||||
|
||||
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));
|
||||
std::transform(votes.begin(), votes.end(), std::back_inserter(deregister.votes), [](pool_vote_entry const &pool_vote) {
|
||||
auto result = cryptonote::tx_extra_service_node_deregister::vote(pool_vote.vote.signature, pool_vote.vote.index_in_group);
|
||||
return result;
|
||||
});
|
||||
|
||||
cryptonote::transaction deregister_tx = {};
|
||||
if (cryptonote::add_service_node_deregister_to_tx_extra(deregister_tx.extra, deregister))
|
||||
{
|
||||
int hf_version = m_core.get_blockchain_storage().get_current_hard_fork_version();
|
||||
deregister_tx.version = cryptonote::transaction::get_max_version_for_hf(hf_version, m_core.get_nettype());
|
||||
deregister_tx.type = cryptonote::transaction::type_deregister;
|
||||
|
||||
cryptonote::tx_verification_context tvc = {};
|
||||
cryptonote::blobdata const tx_blob = cryptonote::tx_to_blob(deregister_tx);
|
||||
|
||||
result &= m_core.handle_incoming_tx(tx_blob, tvc, false /*keeped_by_block*/, false /*relayed*/, false /*do_not_relay*/);
|
||||
if (!result || tvc.m_verifivation_failed)
|
||||
{
|
||||
LOG_PRINT_L1("A full deregister tx for height: " << vote.block_height <<
|
||||
" and service node: " << vote.deregister.worker_index <<
|
||||
" could not be verified and was not added to the memory pool, reason: " <<
|
||||
print_tx_verification_context(tvc, &deregister_tx));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L1("Failed to add deregister to tx extra for height: "
|
||||
<< vote.block_height << " and service node: " << vote.deregister.worker_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
if (votes.size() >= CHECKPOINT_MIN_VOTES)
|
||||
{
|
||||
cryptonote::checkpoint_t checkpoint = {};
|
||||
checkpoint.type = cryptonote::checkpoint_type::service_node;
|
||||
checkpoint.height = vote.block_height;
|
||||
checkpoint.block_hash = vote.checkpoint.block_hash;
|
||||
checkpoint.signatures.reserve(votes.size());
|
||||
|
||||
for (pool_vote_entry const &pool_vote : votes)
|
||||
{
|
||||
voter_to_signature vts = {};
|
||||
vts.voter_index = pool_vote.vote.index_in_group;
|
||||
vts.signature = pool_vote.vote.signature;
|
||||
checkpoint.signatures.push_back(vts);
|
||||
}
|
||||
|
||||
m_core.get_blockchain_storage().update_checkpoint(checkpoint);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static crypto::hash make_hash(crypto::public_key const &pubkey, uint64_t timestamp)
|
||||
{
|
||||
char buf[44] = "SUP"; // Meaningless magic bytes
|
||||
|
|
|
@ -28,14 +28,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "blockchain.h"
|
||||
#include "serialization/serialization.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler_common.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
class core;
|
||||
struct vote_verification_context;
|
||||
};
|
||||
|
||||
namespace service_nodes
|
||||
|
@ -46,42 +47,27 @@ namespace service_nodes
|
|||
uint16_t version_major, version_minor, version_patch;
|
||||
};
|
||||
|
||||
struct quorum_checkpointing
|
||||
struct testing_quorum
|
||||
{
|
||||
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;
|
||||
std::vector<crypto::public_key> validators;
|
||||
std::vector<crypto::public_key> workers;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(quorum_nodes)
|
||||
FIELD(nodes_to_test)
|
||||
FIELD(validators)
|
||||
FIELD(workers)
|
||||
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,
|
||||
std::shared_ptr<const testing_quorum> deregister;
|
||||
std::shared_ptr<const testing_quorum> checkpointing;
|
||||
};
|
||||
|
||||
class quorum_cop
|
||||
: public cryptonote::Blockchain::BlockAddedHook,
|
||||
public cryptonote::Blockchain::BlockchainDetachedHook,
|
||||
public cryptonote::Blockchain::InitHook
|
||||
: public cryptonote::BlockAddedHook,
|
||||
public cryptonote::BlockchainDetachedHook,
|
||||
public cryptonote::InitHook
|
||||
{
|
||||
public:
|
||||
explicit quorum_cop(cryptonote::core& core);
|
||||
|
@ -90,24 +76,24 @@ namespace service_nodes
|
|||
void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override;
|
||||
void blockchain_detached(uint64_t height) override;
|
||||
|
||||
void process_uptime_quorum (cryptonote::block const &block);
|
||||
void process_checkpoint_quorum(cryptonote::block const &block);
|
||||
|
||||
bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof);
|
||||
void set_votes_relayed (std::vector<quorum_vote_t> const &relayed_votes);
|
||||
std::vector<quorum_vote_t> get_relayable_votes();
|
||||
bool handle_vote (quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc);
|
||||
bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof);
|
||||
|
||||
static const uint64_t REORG_SAFETY_BUFFER_IN_BLOCKS = 20;
|
||||
static_assert(REORG_SAFETY_BUFFER_IN_BLOCKS < deregister_vote::VOTE_LIFETIME_BY_HEIGHT,
|
||||
"Safety buffer should always be less than the vote lifetime");
|
||||
bool prune_uptime_proof();
|
||||
|
||||
bool prune_uptime_proof();
|
||||
proof_info get_uptime_proof(const crypto::public_key &pubkey) const;
|
||||
|
||||
void generate_uptime_proof_request(cryptonote::NOTIFY_UPTIME_PROOF::request& req) const;
|
||||
void generate_uptime_proof_request(cryptonote::NOTIFY_UPTIME_PROOF::request& req) const;
|
||||
|
||||
private:
|
||||
void process_quorums(cryptonote::block const &block);
|
||||
|
||||
cryptonote::core& m_core;
|
||||
uint64_t m_uptime_proof_height;
|
||||
voting_pool m_vote_pool;
|
||||
uint64_t m_uptime_proof_height;
|
||||
uint64_t m_last_checkpointed_height;
|
||||
|
||||
std::unordered_map<crypto::public_key, proof_info> m_uptime_proof_seen;
|
||||
mutable epee::critical_section m_lock;
|
||||
|
|
|
@ -2,17 +2,34 @@
|
|||
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "service_node_deregister.h"
|
||||
#include "service_node_voting.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace service_nodes {
|
||||
constexpr size_t QUORUM_SIZE = 10;
|
||||
constexpr size_t QUORUM_LIFETIME = (6 * deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT);
|
||||
constexpr size_t MIN_VOTES_TO_KICK_SERVICE_NODE = 7;
|
||||
constexpr size_t MIN_VOTES_TO_CHECKPOINT = MIN_VOTES_TO_KICK_SERVICE_NODE;
|
||||
constexpr size_t NTH_OF_THE_NETWORK_TO_TEST = 100;
|
||||
constexpr size_t MIN_NODES_TO_TEST = 50;
|
||||
constexpr size_t DEREGISTER_QUORUM_SIZE = 10;
|
||||
constexpr size_t DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE = 7;
|
||||
constexpr size_t DEREGISTER_NTH_OF_THE_NETWORK_TO_TEST = 100;
|
||||
constexpr size_t DEREGISTER_MIN_NODES_TO_TEST = 50;
|
||||
constexpr uint64_t DEREGISTER_VOTE_LIFETIME = BLOCKS_EXPECTED_IN_HOURS(2);
|
||||
|
||||
constexpr uint64_t CHECKPOINT_INTERVAL = 4;
|
||||
constexpr uint64_t CHECKPOINT_VOTE_LIFETIME = ((CHECKPOINT_INTERVAL * 3) - 1);
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 2;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 2;
|
||||
#else
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 20;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 18;
|
||||
#endif
|
||||
|
||||
static_assert(DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE <= DEREGISTER_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
static_assert(CHECKPOINT_MIN_VOTES <= CHECKPOINT_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
|
||||
constexpr size_t MAX_SWARM_SIZE = 10;
|
||||
// We never create a new swarm unless there are SWARM_BUFFER extra nodes
|
||||
// available in the queue.
|
||||
constexpr size_t SWARM_BUFFER = 5;
|
||||
// if a swarm has strictly less nodes than this, it is considered unhealthy
|
||||
// and nearby swarms will mirror it's data. It will disappear, and is already considered gone.
|
||||
constexpr size_t MIN_SWARM_SIZE = 5;
|
||||
|
@ -28,22 +45,38 @@ namespace service_nodes {
|
|||
constexpr size_t STEALING_SWARM_UPPER_PERCENTILE = 75;
|
||||
constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1;
|
||||
constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
|
||||
constexpr uint64_t CHECKPOINT_INTERVAL = 4;
|
||||
|
||||
using swarm_id_t = uint64_t;
|
||||
constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX;
|
||||
static_assert(MIN_VOTES_TO_KICK_SERVICE_NODE <= QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
static_assert(MIN_VOTES_TO_CHECKPOINT <= QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
constexpr uint64_t DEREGISTER_TX_LIFETIME_IN_BLOCKS = DEREGISTER_VOTE_LIFETIME;
|
||||
constexpr size_t QUORUM_LIFETIME = (6 * DEREGISTER_TX_LIFETIME_IN_BLOCKS);
|
||||
|
||||
inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype)
|
||||
{
|
||||
switch(nettype)
|
||||
|
||||
using swarm_id_t = uint64_t;
|
||||
constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX;
|
||||
|
||||
inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype)
|
||||
{
|
||||
switch (nettype)
|
||||
{
|
||||
case cryptonote::FAKECHAIN: return 30;
|
||||
case cryptonote::TESTNET: return BLOCKS_EXPECTED_IN_DAYS(2);
|
||||
default: return BLOCKS_EXPECTED_IN_DAYS(30);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint64_t quorum_vote_lifetime(quorum_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case quorum_type::deregister: return DEREGISTER_VOTE_LIFETIME;
|
||||
case quorum_type::checkpointing: return CHECKPOINT_VOTE_LIFETIME;
|
||||
default:
|
||||
{
|
||||
assert("Unhandled enum type" == 0);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static_assert(STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
|
||||
// return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in loki atomic units
|
||||
|
|
|
@ -0,0 +1,540 @@
|
|||
// Copyright (c) 2018, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "service_node_voting.h"
|
||||
#include "service_node_list.h"
|
||||
#include "cryptonote_basic/tx_extra.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_basic/connection_context.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
||||
|
||||
#include "misc_log_ex.h"
|
||||
#include "string_tools.h"
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#undef LOKI_DEFAULT_LOG_CATEGORY
|
||||
#define LOKI_DEFAULT_LOG_CATEGORY "service_nodes"
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
bool convert_deregister_vote_to_legacy(quorum_vote_t const &vote, legacy_deregister_vote &legacy_vote)
|
||||
{
|
||||
if (vote.type != quorum_type::deregister)
|
||||
return false;
|
||||
|
||||
legacy_vote.block_height = vote.block_height;
|
||||
legacy_vote.service_node_index = vote.deregister.worker_index;
|
||||
legacy_vote.voters_quorum_index = vote.index_in_group;
|
||||
legacy_vote.signature = vote.signature;
|
||||
return true;
|
||||
}
|
||||
|
||||
quorum_vote_t convert_legacy_deregister_vote(legacy_deregister_vote const &vote)
|
||||
{
|
||||
quorum_vote_t result = {};
|
||||
result.type = quorum_type::deregister;
|
||||
result.block_height = vote.block_height;
|
||||
result.signature = vote.signature;
|
||||
result.group = quorum_group::validator;
|
||||
result.index_in_group = vote.voters_quorum_index;
|
||||
result.deregister.worker_index = vote.service_node_index;
|
||||
return result;
|
||||
}
|
||||
|
||||
static crypto::hash make_deregister_vote_hash(uint64_t block_height, uint32_t service_node_index)
|
||||
{
|
||||
const int buf_size = sizeof(block_height) + sizeof(service_node_index);
|
||||
char buf[buf_size];
|
||||
|
||||
memcpy(buf, reinterpret_cast<void *>(&block_height), sizeof(block_height));
|
||||
memcpy(buf + sizeof(block_height), reinterpret_cast<void *>(&service_node_index), sizeof(service_node_index));
|
||||
|
||||
crypto::hash result;
|
||||
crypto::cn_fast_hash(buf, buf_size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
crypto::signature make_signature_from_vote(quorum_vote_t const &vote, const crypto::public_key& pub, const crypto::secret_key& sec)
|
||||
{
|
||||
crypto::signature result = {};
|
||||
switch(vote.type)
|
||||
{
|
||||
default:
|
||||
{
|
||||
LOG_PRINT_L1("Unhandled vote type with value: " << (int)vote.type);
|
||||
assert("Unhandled vote type" == 0);
|
||||
return result;
|
||||
};
|
||||
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
crypto::hash hash = make_deregister_vote_hash(vote.block_height, vote.deregister.worker_index);
|
||||
crypto::generate_signature(hash, pub, sec, result);
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
crypto::hash hash = vote.checkpoint.block_hash;
|
||||
crypto::generate_signature(hash, pub, sec, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
crypto::signature make_signature_from_tx_deregister(cryptonote::tx_extra_service_node_deregister const &deregister, crypto::public_key const &pub, crypto::secret_key const &sec)
|
||||
{
|
||||
crypto::signature result;
|
||||
crypto::hash hash = make_deregister_vote_hash(deregister.block_height, deregister.service_node_index);
|
||||
crypto::generate_signature(hash, pub, sec, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool bounds_check_worker_index(service_nodes::testing_quorum const &quorum, size_t worker_index, cryptonote::vote_verification_context &vvc)
|
||||
{
|
||||
if (worker_index >= quorum.workers.size())
|
||||
{
|
||||
vvc.m_worker_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Quorum worker index in was out of bounds: " << worker_index << ", expected to be in range of: [0, " << quorum.workers.size() << ")");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bounds_check_validator_index(service_nodes::testing_quorum const &quorum, size_t validator_index, cryptonote::vote_verification_context &vvc)
|
||||
{
|
||||
if (validator_index >= quorum.validators.size())
|
||||
{
|
||||
vvc.m_validator_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Validator's index was out of bounds: " << validator_index << ", expected to be in range of: [0, " << quorum.validators.size() << ")");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify_tx_deregister(const cryptonote::tx_extra_service_node_deregister &deregister,
|
||||
cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::testing_quorum &quorum)
|
||||
{
|
||||
if (deregister.votes.size() < service_nodes::DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
{
|
||||
LOG_PRINT_L1("Not enough votes");
|
||||
vvc.m_not_enough_votes = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deregister.votes.size() > service_nodes::DEREGISTER_QUORUM_SIZE)
|
||||
{
|
||||
LOG_PRINT_L1("Too many votes");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bounds_check_worker_index(quorum, deregister.service_node_index, vvc))
|
||||
return false;
|
||||
|
||||
crypto::hash const hash = make_deregister_vote_hash(deregister.block_height, deregister.service_node_index);
|
||||
std::array<int, service_nodes::DEREGISTER_QUORUM_SIZE> validator_set = {};
|
||||
for (const cryptonote::tx_extra_service_node_deregister::vote& vote : deregister.votes)
|
||||
{
|
||||
if (!bounds_check_validator_index(quorum, vote.validator_index, vvc))
|
||||
return false;
|
||||
|
||||
if (++validator_set[vote.validator_index] > 1)
|
||||
{
|
||||
vvc.m_duplicate_voters = true;
|
||||
LOG_PRINT_L1("Voter quorum index is duplicated: " << vote.validator_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
crypto::public_key const &key = quorum.validators[vote.validator_index];
|
||||
if (!crypto::check_signature(hash, key, vote.signature))
|
||||
{
|
||||
LOG_PRINT_L1("Invalid signatures for votes");
|
||||
vvc.m_signature_not_valid = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
quorum_vote_t make_deregister_vote(uint64_t block_height, uint16_t validator_index, uint16_t worker_index, crypto::public_key const &pub_key, crypto::secret_key const &sec_key)
|
||||
{
|
||||
quorum_vote_t result = {};
|
||||
result.type = quorum_type::deregister;
|
||||
result.block_height = block_height;
|
||||
result.group = quorum_group::validator;
|
||||
result.index_in_group = validator_index;
|
||||
result.deregister.worker_index = worker_index;
|
||||
result.signature = make_signature_from_vote(result, pub_key, sec_key);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool verify_vote(const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc, const service_nodes::testing_quorum &quorum)
|
||||
{
|
||||
bool result = true;
|
||||
if (vote.group == quorum_group::invalid)
|
||||
result = false;
|
||||
else if (vote.group == quorum_group::validator)
|
||||
result = bounds_check_validator_index(quorum, vote.index_in_group, vvc);
|
||||
else
|
||||
result = bounds_check_worker_index(quorum, vote.index_in_group, vvc);
|
||||
|
||||
if (!result)
|
||||
return result;
|
||||
|
||||
uint64_t max_vote_age = 0;
|
||||
{
|
||||
crypto::public_key key = crypto::null_pkey;
|
||||
crypto::hash hash = crypto::null_hash;
|
||||
|
||||
switch(vote.type)
|
||||
{
|
||||
default:
|
||||
{
|
||||
LOG_PRINT_L1("Unhandled vote type with value: " << (int)vote.type);
|
||||
assert("Unhandled vote type" == 0);
|
||||
return false;
|
||||
};
|
||||
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
if (vote.group != quorum_group::validator)
|
||||
{
|
||||
LOG_PRINT_L1("Vote received specifies incorrect voting group, expected vote from validator");
|
||||
vvc.m_incorrect_voting_group = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
key = quorum.validators[vote.index_in_group];
|
||||
max_vote_age = service_nodes::DEREGISTER_VOTE_LIFETIME;
|
||||
hash = make_deregister_vote_hash(vote.block_height, vote.deregister.worker_index);
|
||||
|
||||
bool result = bounds_check_worker_index(quorum, vote.deregister.worker_index, vvc);
|
||||
if (!result)
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
if (vote.group != quorum_group::worker)
|
||||
{
|
||||
LOG_PRINT_L1("Vote received specifies incorrect voting group, expected vote from worker");
|
||||
vvc.m_incorrect_voting_group = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
key = quorum.workers[vote.index_in_group];
|
||||
max_vote_age = service_nodes::CHECKPOINT_VOTE_LIFETIME;
|
||||
hash = vote.checkpoint.block_hash;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: Validate vote signature
|
||||
//
|
||||
result = crypto::check_signature(hash, key, vote.signature);
|
||||
if (!result)
|
||||
{
|
||||
vvc.m_signature_not_valid = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: Validate vote age
|
||||
//
|
||||
{
|
||||
uint64_t delta_height = latest_height - vote.block_height;
|
||||
if (vote.block_height < latest_height && delta_height >= max_vote_age)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is older than: " << max_vote_age
|
||||
<< " blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
else if (vote.block_height > latest_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is newer than: " << latest_height
|
||||
<< " (latest block height) and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
|
||||
if (vvc.m_invalid_block_height)
|
||||
{
|
||||
result = false;
|
||||
vvc.m_verification_failed = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void voting_pool::set_relayed(const std::vector<quorum_vote_t>& votes)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
const time_t now = time(NULL);
|
||||
|
||||
for (const quorum_vote_t &find_vote : votes)
|
||||
{
|
||||
std::vector<pool_vote_entry> *vote_pool = nullptr;
|
||||
switch(find_vote.type)
|
||||
{
|
||||
default:
|
||||
{
|
||||
LOG_PRINT_L1("Unhandled find_vote type with value: " << (int)find_vote.type);
|
||||
assert("Unhandled find_vote type" == 0);
|
||||
break;
|
||||
};
|
||||
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
auto it = std::find_if(m_deregister_pool.begin(), m_deregister_pool.end(), [find_vote](deregister_pool_entry const &entry) {
|
||||
return (entry.height == find_vote.block_height &&
|
||||
entry.worker_index == find_vote.deregister.worker_index);
|
||||
});
|
||||
|
||||
if (it == m_deregister_pool.end())
|
||||
break;
|
||||
|
||||
vote_pool = &it->votes;
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
auto it = std::find_if(m_checkpoint_pool.begin(), m_checkpoint_pool.end(), [find_vote](checkpoint_pool_entry const &entry) {
|
||||
return (entry.height == find_vote.block_height && entry.hash == find_vote.checkpoint.block_hash);
|
||||
});
|
||||
|
||||
if (it == m_checkpoint_pool.end())
|
||||
break;
|
||||
|
||||
vote_pool = &it->votes;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (vote_pool) // We found the group that this vote belongs to
|
||||
{
|
||||
auto vote = std::find_if(vote_pool->begin(), vote_pool->end(), [find_vote](pool_vote_entry const &entry) {
|
||||
return (find_vote.index_in_group == entry.vote.index_in_group);
|
||||
});
|
||||
|
||||
if (vote != vote_pool->end())
|
||||
{
|
||||
vote->time_last_sent_p2p = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<quorum_vote_t> voting_pool::get_relayable_votes() const
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
|
||||
// TODO(doyle): Rate-limiting: A better threshold value that follows suite with transaction relay time back-off
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
const time_t TIME_BETWEEN_RELAY = 0;
|
||||
#else
|
||||
const time_t TIME_BETWEEN_RELAY = 60 * 2;
|
||||
#endif
|
||||
|
||||
time_t const now = time(nullptr);
|
||||
std::vector<quorum_vote_t> result;
|
||||
for (deregister_pool_entry const &pool_entry : m_deregister_pool)
|
||||
{
|
||||
for (pool_vote_entry const &vote_entry : pool_entry.votes)
|
||||
{
|
||||
const time_t last_sent = now - vote_entry.time_last_sent_p2p;
|
||||
if (last_sent > TIME_BETWEEN_RELAY)
|
||||
result.push_back(vote_entry.vote);
|
||||
}
|
||||
}
|
||||
|
||||
for (checkpoint_pool_entry const &pool_entry : m_checkpoint_pool)
|
||||
{
|
||||
for (pool_vote_entry const &vote_entry : pool_entry.votes)
|
||||
{
|
||||
const time_t last_sent = now - vote_entry.time_last_sent_p2p;
|
||||
if (last_sent > TIME_BETWEEN_RELAY)
|
||||
result.push_back(vote_entry.vote);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// return: True if the vote was unique
|
||||
template <typename T>
|
||||
static bool add_vote_to_pool_if_unique(T &vote_pool, quorum_vote_t const &vote)
|
||||
{
|
||||
auto vote_it = std::find_if(vote_pool.votes.begin(), vote_pool.votes.end(), [&vote](pool_vote_entry const &pool_entry) {
|
||||
assert(pool_entry.vote.group == vote.group);
|
||||
return (pool_entry.vote.index_in_group == vote.index_in_group);
|
||||
});
|
||||
|
||||
bool result = false;
|
||||
if (vote_it == vote_pool.votes.end())
|
||||
{
|
||||
pool_vote_entry entry = {};
|
||||
entry.vote = vote;
|
||||
vote_pool.votes.push_back(entry);
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<pool_vote_entry> voting_pool::add_pool_vote_if_unique(uint64_t latest_height,
|
||||
const quorum_vote_t& vote,
|
||||
cryptonote::vote_verification_context& vvc,
|
||||
const service_nodes::testing_quorum &quorum)
|
||||
{
|
||||
std::vector<pool_vote_entry> result = {};
|
||||
|
||||
if (!verify_vote(vote, latest_height, vvc, quorum))
|
||||
{
|
||||
LOG_PRINT_L1("Signature verification failed for deregister vote");
|
||||
return result;
|
||||
}
|
||||
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
switch(vote.type)
|
||||
{
|
||||
default:
|
||||
{
|
||||
LOG_PRINT_L1("Unhandled vote type with value: " << (int)vote.type);
|
||||
assert("Unhandled vote type" == 0);
|
||||
return result;
|
||||
};
|
||||
|
||||
case quorum_type::deregister:
|
||||
{
|
||||
time_t const now = time(NULL);
|
||||
auto it = std::find_if(m_deregister_pool.begin(), m_deregister_pool.end(), [&vote](deregister_pool_entry const &entry) {
|
||||
return (entry.height == vote.block_height && entry.worker_index == vote.deregister.worker_index);
|
||||
});
|
||||
|
||||
if (it == m_deregister_pool.end())
|
||||
{
|
||||
m_deregister_pool.emplace_back(vote.block_height, vote.deregister.worker_index);
|
||||
it = (m_deregister_pool.end() - 1);
|
||||
}
|
||||
|
||||
deregister_pool_entry &pool_entry = (*it);
|
||||
vvc.m_added_to_pool = add_vote_to_pool_if_unique(pool_entry, vote);
|
||||
result = pool_entry.votes;
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
// Get Matching Checkpoint
|
||||
auto it = std::find_if(m_checkpoint_pool.begin(), m_checkpoint_pool.end(), [&vote](checkpoint_pool_entry const &entry) {
|
||||
return (entry.height == vote.block_height && entry.hash == vote.checkpoint.block_hash);
|
||||
});
|
||||
|
||||
if (it == m_checkpoint_pool.end())
|
||||
{
|
||||
m_checkpoint_pool.emplace_back(vote.block_height, vote.checkpoint.block_hash);
|
||||
it = (m_checkpoint_pool.end() - 1);
|
||||
}
|
||||
|
||||
checkpoint_pool_entry &pool_entry = (*it);
|
||||
vvc.m_added_to_pool = add_vote_to_pool_if_unique(pool_entry, vote);
|
||||
result = pool_entry.votes;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void voting_pool::remove_used_votes(std::vector<cryptonote::transaction> const &txs)
|
||||
{
|
||||
// TODO(doyle): Cull checkpoint votes
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
if (m_deregister_pool.empty())
|
||||
return;
|
||||
|
||||
for (const cryptonote::transaction &tx : txs)
|
||||
{
|
||||
if (tx.get_type() != cryptonote::transaction::type_deregister)
|
||||
continue;
|
||||
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
||||
{
|
||||
LOG_ERROR("Could not get deregister from tx, possibly corrupt tx");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::find_if(m_deregister_pool.begin(), m_deregister_pool.end(), [&deregister](deregister_pool_entry const &entry){
|
||||
return (entry.height == deregister.block_height) && (entry.worker_index == deregister.service_node_index);
|
||||
});
|
||||
|
||||
if (it != m_deregister_pool.end())
|
||||
m_deregister_pool.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void cull_votes(std::vector<T> &vote_pool, uint64_t min_height, uint64_t max_height)
|
||||
{
|
||||
for (auto it = vote_pool.begin(); it != vote_pool.end(); ++it)
|
||||
{
|
||||
const T &pool_entry = *it;
|
||||
if (pool_entry.height < min_height || pool_entry.height > max_height)
|
||||
{
|
||||
it = vote_pool.erase(it);
|
||||
it--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void voting_pool::remove_expired_votes(uint64_t height)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
uint64_t deregister_min_height = (height < DEREGISTER_VOTE_LIFETIME) ? 0 : height - DEREGISTER_VOTE_LIFETIME;
|
||||
cull_votes(m_deregister_pool, deregister_min_height, height);
|
||||
|
||||
uint64_t checkpoint_min_height = (height < CHECKPOINT_VOTE_LIFETIME) ? 0 : height - CHECKPOINT_VOTE_LIFETIME;
|
||||
cull_votes(m_checkpoint_pool, checkpoint_min_height, height);
|
||||
}
|
||||
}; // namespace service_nodes
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) 2018, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
|
||||
#include "math_helper.h"
|
||||
#include "syncobj.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
struct vote_verification_context;
|
||||
struct tx_extra_service_node_deregister;
|
||||
};
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
struct testing_quorum;
|
||||
|
||||
struct voter_to_signature
|
||||
{
|
||||
uint16_t voter_index;
|
||||
crypto::signature signature;
|
||||
};
|
||||
|
||||
struct checkpoint_vote { crypto::hash block_hash; };
|
||||
struct deregister_vote { uint16_t worker_index; };
|
||||
|
||||
enum struct quorum_type
|
||||
{
|
||||
deregister = 0,
|
||||
checkpointing,
|
||||
count,
|
||||
};
|
||||
|
||||
enum struct quorum_group { invalid, validator, worker };
|
||||
struct quorum_vote_t
|
||||
{
|
||||
uint8_t version = 0;
|
||||
quorum_type type;
|
||||
uint64_t block_height;
|
||||
quorum_group group;
|
||||
uint16_t index_in_group;
|
||||
crypto::signature signature;
|
||||
|
||||
union
|
||||
{
|
||||
deregister_vote deregister;
|
||||
checkpoint_vote checkpoint;
|
||||
};
|
||||
};
|
||||
|
||||
quorum_vote_t make_deregister_vote(uint64_t block_height, uint16_t index_in_group, uint16_t worker_index, crypto::public_key const &pub_key, crypto::secret_key const &secret_key);
|
||||
|
||||
bool verify_tx_deregister (const cryptonote::tx_extra_service_node_deregister& deregister, cryptonote::vote_verification_context& vvc, const service_nodes::testing_quorum &quorum);
|
||||
bool verify_vote (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc, const service_nodes::testing_quorum &quorum);
|
||||
crypto::signature make_signature_from_vote (quorum_vote_t const &vote, const crypto::public_key& pub, const crypto::secret_key& sec);
|
||||
crypto::signature make_signature_from_tx_deregister(cryptonote::tx_extra_service_node_deregister const &deregister, crypto::public_key const &pub, crypto::secret_key const &sec);
|
||||
|
||||
// NOTE: This preserves the deregister vote format pre-checkpointing so that
|
||||
// up to the hardfork, we can still deserialize and serialize until we switch
|
||||
// over to the new format
|
||||
struct legacy_deregister_vote
|
||||
{
|
||||
uint64_t block_height;
|
||||
uint32_t service_node_index;
|
||||
uint32_t voters_quorum_index;
|
||||
crypto::signature signature;
|
||||
};
|
||||
|
||||
bool convert_deregister_vote_to_legacy(quorum_vote_t const &vote, legacy_deregister_vote &legacy);
|
||||
quorum_vote_t convert_legacy_deregister_vote (legacy_deregister_vote const &vote);
|
||||
|
||||
struct pool_vote_entry
|
||||
{
|
||||
quorum_vote_t vote;
|
||||
uint64_t time_last_sent_p2p;
|
||||
};
|
||||
|
||||
struct voting_pool
|
||||
{
|
||||
// return: The vector of votes if the vote is valid (and even if it is not unique) otherwise nullptr
|
||||
std::vector<pool_vote_entry> add_pool_vote_if_unique(uint64_t latest_height,
|
||||
const quorum_vote_t& vote,
|
||||
cryptonote::vote_verification_context& vvc,
|
||||
const service_nodes::testing_quorum &quorum);
|
||||
|
||||
// TODO(loki): Review relay behaviour and all the cases when it should be triggered
|
||||
void set_relayed (const std::vector<quorum_vote_t>& votes);
|
||||
void remove_expired_votes(uint64_t height);
|
||||
void remove_used_votes (std::vector<cryptonote::transaction> const &txs);
|
||||
std::vector<quorum_vote_t> get_relayable_votes () const;
|
||||
|
||||
private:
|
||||
struct deregister_pool_entry
|
||||
{
|
||||
deregister_pool_entry(uint64_t height, uint32_t worker_index): height(height), worker_index(worker_index) {}
|
||||
uint64_t height;
|
||||
uint32_t worker_index;
|
||||
std::vector<pool_vote_entry> votes;
|
||||
};
|
||||
std::vector<deregister_pool_entry> m_deregister_pool;
|
||||
|
||||
struct checkpoint_pool_entry
|
||||
{
|
||||
checkpoint_pool_entry(uint64_t height, crypto::hash const &hash): height(height), hash(hash) {}
|
||||
uint64_t height;
|
||||
crypto::hash hash;
|
||||
std::vector<pool_vote_entry> votes;
|
||||
};
|
||||
std::vector<checkpoint_pool_entry> m_checkpoint_pool;
|
||||
|
||||
mutable epee::critical_section m_lock;
|
||||
};
|
||||
}; // namespace service_nodes
|
||||
|
|
@ -34,10 +34,15 @@
|
|||
#include "serialization/keyvalue_serialization.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
|
||||
#include "common/loki.h"
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
struct legacy_deregister_vote;
|
||||
struct quorum_vote_t;
|
||||
};
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
|
||||
|
@ -309,11 +314,14 @@ namespace cryptonote
|
|||
/************************************************************************/
|
||||
struct NOTIFY_NEW_DEREGISTER_VOTE
|
||||
{
|
||||
// TODO(doyle): We need to remove this code post fork, pre checkpointing
|
||||
// fork which revamps the voting system, we need to continue accepting
|
||||
// deregisters using the old system.
|
||||
const static int ID = BC_COMMANDS_POOL_BASE + 10;
|
||||
|
||||
struct request
|
||||
{
|
||||
std::vector<service_nodes::deregister_vote> votes;
|
||||
std::vector<service_nodes::legacy_deregister_vote> votes;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(votes)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
@ -351,13 +359,12 @@ namespace cryptonote
|
|||
/************************************************************************/
|
||||
/* */
|
||||
/************************************************************************/
|
||||
struct NOTIFY_NEW_CHECKPOINT_VOTE
|
||||
struct NOTIFY_NEW_SERVICE_NODE_VOTE
|
||||
{
|
||||
const static int ID = BC_COMMANDS_POOL_BASE + 12;
|
||||
|
||||
struct request
|
||||
{
|
||||
std::vector<service_nodes::checkpoint_vote> votes;
|
||||
std::vector<service_nodes::quorum_vote_t> votes;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(votes)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace cryptonote
|
|||
HANDLE_NOTIFY_T2(NOTIFY_REQUEST_FLUFFY_MISSING_TX, &cryptonote_protocol_handler::handle_request_fluffy_missing_tx)
|
||||
HANDLE_NOTIFY_T2(NOTIFY_NEW_DEREGISTER_VOTE, &cryptonote_protocol_handler::handle_notify_new_deregister_vote)
|
||||
HANDLE_NOTIFY_T2(NOTIFY_UPTIME_PROOF, &cryptonote_protocol_handler::handle_uptime_proof)
|
||||
HANDLE_NOTIFY_T2(NOTIFY_NEW_CHECKPOINT_VOTE, &cryptonote_protocol_handler::handle_notify_new_checkpoint_vote)
|
||||
HANDLE_NOTIFY_T2(NOTIFY_NEW_SERVICE_NODE_VOTE, &cryptonote_protocol_handler::handle_notify_new_service_node_vote)
|
||||
END_INVOKE_MAP2()
|
||||
|
||||
bool on_idle();
|
||||
|
@ -131,7 +131,7 @@ namespace cryptonote
|
|||
int handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context);
|
||||
int handle_notify_new_deregister_vote(int command, NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& context);
|
||||
int handle_uptime_proof(int command, NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& context);
|
||||
int handle_notify_new_checkpoint_vote(int command, NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& context);
|
||||
int handle_notify_new_service_node_vote(int command, NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& context);
|
||||
|
||||
//----------------- i_bc_protocol_layout ---------------------------------------
|
||||
template<class T>
|
||||
|
@ -157,7 +157,7 @@ namespace cryptonote
|
|||
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_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);
|
||||
virtual bool relay_service_node_votes(NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& exclude_context);
|
||||
//----------------------------------------------------------------------------------
|
||||
//bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
|
||||
bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe);
|
||||
|
|
|
@ -798,23 +798,23 @@ namespace cryptonote
|
|||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
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)
|
||||
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_service_node_vote(int command, NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& context)
|
||||
{
|
||||
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_CHECKPOINT_VOTE (" << arg.votes.size() << " txes)");
|
||||
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_SERVICE_NODE_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");
|
||||
LOG_DEBUG_CC(context, "Received new service node 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);
|
||||
m_core.add_service_node_vote(*it, vvc);
|
||||
|
||||
if (vvc.m_verification_failed)
|
||||
{
|
||||
|
@ -834,9 +834,7 @@ namespace cryptonote
|
|||
}
|
||||
|
||||
if (arg.votes.size())
|
||||
{
|
||||
relay_checkpoint_votes(arg, context);
|
||||
}
|
||||
relay_service_node_votes(arg, context);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -921,6 +919,11 @@ namespace cryptonote
|
|||
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_deregister_vote(int command, NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& context)
|
||||
{
|
||||
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_DEREGISTER_VOTE (" << arg.votes.size() << " txes)");
|
||||
if (m_core.get_blockchain_storage().get_current_hard_fork_version() >= cryptonote::network_version_12_checkpointing)
|
||||
{
|
||||
LOG_DEBUG_CC(context, "Received new deregister vote in HF12, reject. We have a new style of voting");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(context.m_state != cryptonote_connection_context::state_normal)
|
||||
return 1;
|
||||
|
@ -934,7 +937,9 @@ namespace cryptonote
|
|||
for(auto it = arg.votes.begin(); it != arg.votes.end();)
|
||||
{
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
m_core.add_deregister_vote(*it, vvc);
|
||||
service_nodes::legacy_deregister_vote const &legacy_vote = (*it);
|
||||
service_nodes::quorum_vote_t const vote = service_nodes::convert_legacy_deregister_vote(legacy_vote);
|
||||
m_core.add_service_node_vote(vote, vvc);
|
||||
|
||||
if (vvc.m_verification_failed)
|
||||
{
|
||||
|
@ -2305,9 +2310,9 @@ skip:
|
|||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
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)
|
||||
bool t_cryptonote_protocol_handler<t_core>::relay_service_node_votes(NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& exclude_context)
|
||||
{
|
||||
bool result = relay_on_public_network_generic<NOTIFY_NEW_CHECKPOINT_VOTE>(arg, exclude_context);
|
||||
bool result = relay_on_public_network_generic<NOTIFY_NEW_SERVICE_NODE_VOTE>(arg, exclude_context);
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace cryptonote
|
|||
virtual bool relay_uptime_proof(NOTIFY_UPTIME_PROOF::request& arg, cryptonote_connection_context& exclude_context)=0;
|
||||
//virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0;
|
||||
virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0;
|
||||
virtual bool relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0;
|
||||
virtual bool relay_service_node_votes(NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -65,7 +65,7 @@ namespace cryptonote
|
|||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool relay_checkpoint_votes(NOTIFY_NEW_CHECKPOINT_VOTE::request& arg, cryptonote_connection_context& exclude_context)
|
||||
virtual bool relay_service_node_votes(NOTIFY_NEW_SERVICE_NODE_VOTE::request& arg, cryptonote_connection_context& exclude_context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2479,18 +2479,18 @@ namespace cryptonote
|
|||
PERF_TIMER(on_get_quorum_state);
|
||||
bool r;
|
||||
|
||||
const auto uptime_quorum = m_core.get_uptime_quorum(req.height);
|
||||
const auto uptime_quorum = m_core.get_testing_quorum(service_nodes::quorum_type::deregister, req.height);
|
||||
r = (uptime_quorum != nullptr);
|
||||
if (r)
|
||||
{
|
||||
res.status = CORE_RPC_STATUS_OK;
|
||||
res.quorum_nodes.reserve (uptime_quorum->quorum_nodes.size());
|
||||
res.nodes_to_test.reserve(uptime_quorum->nodes_to_test.size());
|
||||
res.quorum_nodes.reserve (uptime_quorum->validators.size());
|
||||
res.nodes_to_test.reserve(uptime_quorum->workers.size());
|
||||
|
||||
for (const auto &key : uptime_quorum->quorum_nodes)
|
||||
for (const auto &key : uptime_quorum->validators)
|
||||
res.quorum_nodes.push_back(epee::string_tools::pod_to_hex(key));
|
||||
|
||||
for (const auto &key : uptime_quorum->nodes_to_test)
|
||||
for (const auto &key : uptime_quorum->workers)
|
||||
res.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key));
|
||||
}
|
||||
else
|
||||
|
@ -2525,7 +2525,7 @@ namespace cryptonote
|
|||
res.quorum_entries.reserve(height_end - height_begin + 1);
|
||||
for (auto h = height_begin; h <= height_end; ++h)
|
||||
{
|
||||
const auto uptime_quorum = m_core.get_uptime_quorum(h);
|
||||
const auto uptime_quorum = m_core.get_testing_quorum(service_nodes::quorum_type::deregister, h);
|
||||
|
||||
if (!uptime_quorum) {
|
||||
failed_height = h;
|
||||
|
@ -2537,13 +2537,13 @@ namespace cryptonote
|
|||
auto &entry = res.quorum_entries.back();
|
||||
|
||||
entry.height = h;
|
||||
entry.quorum_nodes.reserve(uptime_quorum->quorum_nodes.size());
|
||||
entry.nodes_to_test.reserve(uptime_quorum->nodes_to_test.size());
|
||||
entry.quorum_nodes.reserve(uptime_quorum->validators.size());
|
||||
entry.nodes_to_test.reserve(uptime_quorum->workers.size());
|
||||
|
||||
for (const auto &key : uptime_quorum->quorum_nodes)
|
||||
for (const auto &key : uptime_quorum->validators)
|
||||
entry.quorum_nodes.push_back(epee::string_tools::pod_to_hex(key));
|
||||
|
||||
for (const auto &key : uptime_quorum->nodes_to_test)
|
||||
for (const auto &key : uptime_quorum->workers)
|
||||
entry.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key));
|
||||
|
||||
}
|
||||
|
|
|
@ -281,7 +281,7 @@ namespace cryptonote
|
|||
void on_relay_uptime_and_votes()
|
||||
{
|
||||
m_core.submit_uptime_proof();
|
||||
m_core.relay_deregister_votes();
|
||||
m_core.relay_service_node_votes();
|
||||
std::cout << "Votes and uptime relayed";
|
||||
loki::write_redirected_stdout_to_shared_mem();
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "cryptonote_basic/difficulty.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
#include "rpc/rpc_handler.h"
|
||||
#include "common/varint.h"
|
||||
#include "common/perf_timer.h"
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <ios>
|
||||
#include <boost/mpl/bool.hpp>
|
||||
#include <boost/type_traits/is_integral.hpp>
|
||||
#include <boost/type_traits/integral_constant.hpp>
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
#include "common/scoped_message_writer.h"
|
||||
#include "common/loki_integration_test_hooks.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "simplewallet.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace tests
|
||||
|
@ -108,8 +108,7 @@ namespace tests
|
|||
uint64_t prevalidate_block_hashes(uint64_t height, const std::list<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
|
||||
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 add_service_node_vote(const service_nodes::quorum_vote_t& vote, cryptonote::vote_verification_context &vvc) { return false; }
|
||||
bool pad_transactions() const { return false; }
|
||||
uint32_t get_blockchain_pruning_seed() const { return 0; }
|
||||
bool prune_blockchain(uint32_t pruning_seed) const { return true; }
|
||||
|
|
|
@ -197,7 +197,7 @@ cryptonote::block linear_chain_generator::create_block_on_fork(const cryptonote:
|
|||
|
||||
QuorumState linear_chain_generator::get_quorum_idxs(const cryptonote::block& block) const
|
||||
{
|
||||
if (sn_list_.size() <= service_nodes::QUORUM_SIZE) {
|
||||
if (sn_list_.size() <= service_nodes::DEREGISTER_QUORUM_SIZE) {
|
||||
std::cerr << "Not enough service nodes\n";
|
||||
return {};
|
||||
}
|
||||
|
@ -218,11 +218,11 @@ QuorumState linear_chain_generator::get_quorum_idxs(const cryptonote::block& blo
|
|||
|
||||
QuorumState quorum;
|
||||
|
||||
for (auto i = 0u; i < service_nodes::QUORUM_SIZE; ++i) {
|
||||
for (auto i = 0u; i < service_nodes::DEREGISTER_QUORUM_SIZE; ++i) {
|
||||
quorum.voters.push_back({ sn_list_.at(pub_keys_indexes[i]).keys.pub, i });
|
||||
}
|
||||
|
||||
for (auto i = service_nodes::QUORUM_SIZE; i < pub_keys_indexes.size(); ++i) {
|
||||
for (auto i = service_nodes::DEREGISTER_QUORUM_SIZE; i < pub_keys_indexes.size(); ++i) {
|
||||
quorum.to_test.push_back({ sn_list_.at(pub_keys_indexes[i]).keys.pub, i });
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,7 @@ cryptonote::transaction linear_chain_generator::create_deregister_tx(const crypt
|
|||
|
||||
deregister.service_node_index = *idx; /// idx inside nodes to test
|
||||
|
||||
/// need to create MIN_VOTES_TO_KICK_SERVICE_NODE (7) votes
|
||||
/// need to create DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE (7) votes
|
||||
for (const auto voter : voters) {
|
||||
|
||||
const auto reg = sn_list_.find_registration(voter.sn_pk);
|
||||
|
@ -298,10 +298,9 @@ cryptonote::transaction linear_chain_generator::create_deregister_tx(const crypt
|
|||
|
||||
const auto pk = reg->keys.pub;
|
||||
const auto sk = reg->keys.sec;
|
||||
const auto signature =
|
||||
service_nodes::deregister_vote::sign_vote(deregister.block_height, deregister.service_node_index, pk, sk);
|
||||
|
||||
deregister.votes.push_back({ signature, (uint32_t)voter.idx_in_quorum });
|
||||
service_nodes::quorum_vote_t deregister_vote = service_nodes::make_deregister_vote(deregister.block_height, voter.idx_in_quorum, deregister.service_node_index, pk, sk);
|
||||
deregister.votes.push_back({ deregister_vote.signature, (uint32_t)voter.idx_in_quorum });
|
||||
}
|
||||
|
||||
if (commit) deregistration_buffer_.push_back(pk);
|
||||
|
@ -325,7 +324,7 @@ boost::optional<uint32_t> linear_chain_generator::get_idx_in_tested(const crypto
|
|||
const auto& to_test = get_quorum_idxs(height).to_test;
|
||||
|
||||
for (const auto& sn : to_test) {
|
||||
if (sn.sn_pk == pk) return sn.idx_in_quorum - service_nodes::QUORUM_SIZE;
|
||||
if (sn.sn_pk == pk) return sn.idx_in_quorum - service_nodes::DEREGISTER_QUORUM_SIZE;
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
|
|
|
@ -369,7 +369,7 @@ bool test_deregister_safety_buffer::generate(std::vector<test_event_entry> &even
|
|||
/// register 21 random service nodes
|
||||
std::vector<cryptonote::transaction> reg_txs;
|
||||
|
||||
constexpr auto SERVICE_NODES_NEEDED = service_nodes::QUORUM_SIZE * 2 + 1;
|
||||
constexpr auto SERVICE_NODES_NEEDED = service_nodes::DEREGISTER_QUORUM_SIZE * 2 + 1;
|
||||
static_assert(SN_KEYS_COUNT >= SERVICE_NODES_NEEDED, "not enough pre-computed service node keys");
|
||||
|
||||
for (auto i = 0u; i < SERVICE_NODES_NEEDED; ++i)
|
||||
|
@ -589,7 +589,7 @@ bool deregister_too_old::generate(std::vector<test_event_entry>& events)
|
|||
const auto dereg_tx = gen.build_deregister(pk, false).build();
|
||||
|
||||
/// create enough blocks to make deregistrations invalid (60 blocks)
|
||||
gen.rewind_blocks_n(service_nodes::deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT);
|
||||
gen.rewind_blocks_n(service_nodes::DEREGISTER_TX_LIFETIME_IN_BLOCKS);
|
||||
|
||||
/// In the real world, this transaction should not make it into a block, but in this case we do try to add it (as in
|
||||
/// tests we must add specify transactions manually), which should exercise the same validation code and reject the
|
||||
|
@ -691,9 +691,9 @@ bool sn_test_rollback::test_registrations(cryptonote::core& c, size_t ev_index,
|
|||
tx_extra_service_node_deregister deregistration;
|
||||
get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration);
|
||||
|
||||
const auto uptime_quorum = c.get_uptime_quorum(deregistration.block_height);
|
||||
const auto uptime_quorum = c.get_testing_quorum(service_nodes::quorum_type::deregister, deregistration.block_height);
|
||||
CHECK_TEST_CONDITION(uptime_quorum);
|
||||
const auto pk_a = uptime_quorum->nodes_to_test.at(deregistration.service_node_index);
|
||||
const auto pk_a = uptime_quorum->workers.at(deregistration.service_node_index);
|
||||
|
||||
/// Check present
|
||||
const bool found_a = contains(sn_list, pk_a);
|
||||
|
|
|
@ -34,13 +34,10 @@
|
|||
#include "p2p/net_node.inl"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.inl"
|
||||
#include "cryptonote_core/blockchain.h"
|
||||
|
||||
#define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0}
|
||||
|
||||
namespace cryptonote {
|
||||
class blockchain_storage;
|
||||
}
|
||||
|
||||
class test_core
|
||||
{
|
||||
public:
|
||||
|
@ -63,7 +60,7 @@ public:
|
|||
bool on_idle(){return true;}
|
||||
bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;}
|
||||
bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;}
|
||||
cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); }
|
||||
cryptonote::Blockchain &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); }
|
||||
bool get_test_drop_download() const {return true;}
|
||||
bool get_test_drop_download_height() const {return true;}
|
||||
bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; }
|
||||
|
@ -90,8 +87,7 @@ public:
|
|||
void stop() {}
|
||||
|
||||
// TODO(loki): Write tests
|
||||
bool add_deregister_vote(const service_nodes::deregister_vote& vote, cryptonote::vote_verification_context &vvc) { return true; }
|
||||
bool add_checkpoint_vote(const service_nodes::checkpoint_vote& vote, cryptonote::vote_verification_context &vvc) { return true; }
|
||||
bool add_service_node_vote(const service_nodes::quorum_vote_t& vote, cryptonote::vote_verification_context &vvc) { return false; }
|
||||
};
|
||||
|
||||
typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
#include "blockchain_utilities/blockchain_objects.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
||||
static const uint64_t test_distribution[32] = {
|
||||
0, 0, 0, 0, 0, 1, 5, 1, 4, 0, 0, 1, 0, 1, 2, 3, 1, 0, 2, 0, 1, 3, 8, 1, 3, 5, 7, 1, 5, 0, 2, 3
|
||||
|
|
|
@ -30,8 +30,10 @@
|
|||
|
||||
#include "gtest/gtest.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "cryptonote_core/service_node_deregister.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
#include "cryptonote_core/cryptonote_tx_utils.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_config.h"
|
||||
|
||||
TEST(service_nodes, staking_requirement)
|
||||
|
@ -123,53 +125,49 @@ TEST(service_nodes, vote_validation)
|
|||
cryptonote::keypair service_node_voter = cryptonote::keypair::generate(hw::get_device("default"));
|
||||
int voter_index = 0;
|
||||
|
||||
service_nodes::quorum_uptime_proof state = {};
|
||||
service_nodes::testing_quorum state = {};
|
||||
{
|
||||
state.quorum_nodes.resize(10);
|
||||
state.nodes_to_test.resize(state.quorum_nodes.size());
|
||||
state.validators.resize(10);
|
||||
state.workers.resize(state.validators.size());
|
||||
|
||||
for (size_t i = 0; i < state.quorum_nodes.size(); ++i)
|
||||
for (size_t i = 0; i < state.validators.size(); ++i)
|
||||
{
|
||||
state.quorum_nodes[i] = (i == voter_index) ? service_node_voter.pub : cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
state.nodes_to_test[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
state.validators[i] = (i == voter_index) ? service_node_voter.pub : cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
state.workers[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
}
|
||||
}
|
||||
|
||||
// Valid vote
|
||||
service_nodes::deregister_vote valid_vote = {};
|
||||
uint64_t block_height = 70;
|
||||
service_nodes::quorum_vote_t valid_vote = service_nodes::make_deregister_vote(block_height, voter_index, 1 /*worker_index*/, service_node_voter.pub, service_node_voter.sec);
|
||||
{
|
||||
valid_vote.block_height = 10;
|
||||
valid_vote.service_node_index = 1;
|
||||
valid_vote.voters_quorum_index = voter_index;
|
||||
valid_vote.signature = service_nodes::deregister_vote::sign_vote(valid_vote.block_height, valid_vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_vote(cryptonote::MAINNET, valid_vote, vvc, state);
|
||||
bool result = service_nodes::verify_vote(valid_vote, block_height, vvc, state);
|
||||
if (!result)
|
||||
printf("%s\n", cryptonote::print_vote_verification_context(vvc, &valid_vote));
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Voters quorum index out of bounds
|
||||
// Voters validator index out of bounds
|
||||
{
|
||||
auto vote = valid_vote;
|
||||
vote.voters_quorum_index = state.quorum_nodes.size() + 10;
|
||||
vote.signature = service_nodes::deregister_vote::sign_vote(vote.block_height, vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
|
||||
auto vote = valid_vote;
|
||||
vote.index_in_group = state.validators.size() + 10;
|
||||
vote.signature = service_nodes::make_signature_from_vote(vote, service_node_voter.pub, service_node_voter.sec);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_vote(cryptonote::MAINNET, vote, vvc, state);
|
||||
bool result = service_nodes::verify_vote(vote, block_height, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Voters service node index out of bounds
|
||||
// Voters worker index out of bounds
|
||||
{
|
||||
auto vote = valid_vote;
|
||||
vote.service_node_index = state.nodes_to_test.size() + 10;
|
||||
vote.signature = service_nodes::deregister_vote::sign_vote(vote.block_height, vote.service_node_index, service_node_voter.pub, service_node_voter.sec);
|
||||
auto vote = valid_vote;
|
||||
vote.deregister.worker_index = state.workers.size() + 10;
|
||||
vote.signature = service_nodes::make_signature_from_vote(vote, service_node_voter.pub, service_node_voter.sec);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_vote(cryptonote::MAINNET, vote, vvc, state);
|
||||
bool result = service_nodes::verify_vote(vote, block_height, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
|
@ -177,10 +175,24 @@ TEST(service_nodes, vote_validation)
|
|||
{
|
||||
auto vote = valid_vote;
|
||||
cryptonote::keypair other_voter = cryptonote::keypair::generate(hw::get_device("default"));
|
||||
vote.signature = service_nodes::deregister_vote::sign_vote(vote.block_height, vote.service_node_index, other_voter.pub, other_voter.sec);
|
||||
vote.signature = {};
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_vote(cryptonote::MAINNET, vote, vvc, state);
|
||||
bool result = service_nodes::verify_vote(vote, block_height, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Vote too old
|
||||
{
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_vote(valid_vote, 1 /*latest_height*/, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Vote too far in the future
|
||||
{
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_vote(valid_vote, block_height + 10000, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
}
|
||||
|
@ -191,16 +203,16 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
const size_t num_voters = 10;
|
||||
cryptonote::keypair voters[num_voters] = {};
|
||||
|
||||
service_nodes::quorum_uptime_proof state = {};
|
||||
service_nodes::testing_quorum state = {};
|
||||
{
|
||||
state.quorum_nodes.resize(num_voters);
|
||||
state.nodes_to_test.resize(num_voters);
|
||||
state.validators.resize(num_voters);
|
||||
state.workers.resize(num_voters);
|
||||
|
||||
for (size_t i = 0; i < state.quorum_nodes.size(); ++i)
|
||||
for (size_t i = 0; i < state.validators.size(); ++i)
|
||||
{
|
||||
voters[i] = cryptonote::keypair::generate(hw::get_device("default"));
|
||||
state.quorum_nodes[i] = voters[i].pub;
|
||||
state.nodes_to_test[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
voters[i] = cryptonote::keypair::generate(hw::get_device("default"));
|
||||
state.validators[i] = voters[i].pub;
|
||||
state.workers[i] = cryptonote::keypair::generate(hw::get_device("default")).pub;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,14 +226,13 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
{
|
||||
cryptonote::keypair const *voter = voters + i;
|
||||
cryptonote::tx_extra_service_node_deregister::vote vote = {};
|
||||
|
||||
vote.voters_quorum_index = i;
|
||||
vote.signature = service_nodes::deregister_vote::sign_vote(valid_deregister.block_height, valid_deregister.service_node_index, voter->pub, voter->sec);
|
||||
vote.validator_index = i;
|
||||
vote.signature = service_nodes::make_signature_from_tx_deregister(valid_deregister, voter->pub, voter->sec);
|
||||
valid_deregister.votes.push_back(vote);
|
||||
}
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, valid_deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(valid_deregister, vvc, state);
|
||||
if (!result)
|
||||
printf("%s\n", cryptonote::print_vote_verification_context(vvc));
|
||||
ASSERT_TRUE(result);
|
||||
|
@ -230,11 +241,11 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
// Deregister has insufficient votes
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
while (deregister.votes.size() >= service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
while (deregister.votes.size() >= service_nodes::DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
deregister.votes.pop_back();
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
|
@ -244,7 +255,7 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
deregister.votes[0] = deregister.votes[1];
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
|
@ -254,27 +265,27 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
deregister.votes[0].signature = deregister.votes[1].signature;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister has one voter with index out of bounds
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.votes[0].voters_quorum_index = state.quorum_nodes.size() + 10;
|
||||
auto deregister = valid_deregister;
|
||||
deregister.votes[0].validator_index = state.validators.size() + 10;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister service node index is out of bounds
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.service_node_index = state.nodes_to_test.size() + 10;
|
||||
deregister.service_node_index = state.workers.size() + 10;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::deregister_vote::verify_deregister(cryptonote::MAINNET, deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue