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:
Doyle 2019-05-31 11:06:42 +10:00 committed by GitHub
parent efc9e44137
commit 8af377d2b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1420 additions and 1260 deletions

View File

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

View File

@ -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) { }
};

View File

@ -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)
{

View File

@ -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;
};
}

View File

@ -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;
};
/************************************************************************/
/* */
/************************************************************************/

View File

@ -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)
{

View File

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

View File

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

View File

@ -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()
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()

View File

@ -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);

View File

@ -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;
}
//------------------------------------------------------------------------------------------------------------------------

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

@ -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; }

View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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);
}
}