mirror of https://github.com/oxen-io/oxen-core.git
Pulse: Support rounds changing the leader/validators in blocks
- Previously we generated quorums based off the previous quorum. To incorporate the round, we can no longer pre-compute the quorum. Instead it must be calculated when the block arrives, just before the Service Node List is changed. - Add tests to check round changing works
This commit is contained in:
parent
62ad3d603e
commit
7883c875e0
|
@ -1276,6 +1276,9 @@ namespace service_nodes
|
|||
if (block.major_version < cryptonote::network_version_9_service_nodes)
|
||||
return true;
|
||||
|
||||
std::lock_guard lock(m_sn_mutex);
|
||||
process_block(block, txs);
|
||||
|
||||
if (block.major_version >= cryptonote::network_version_16)
|
||||
{
|
||||
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::pulse, m_state.height);
|
||||
|
@ -1329,9 +1332,6 @@ namespace service_nodes
|
|||
}
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_sn_mutex);
|
||||
process_block(block, txs);
|
||||
|
||||
if (block.major_version >= cryptonote::network_version_13_enforce_checkpoints && checkpoint)
|
||||
{
|
||||
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::checkpointing, checkpoint->height);
|
||||
|
@ -1437,6 +1437,7 @@ namespace service_nodes
|
|||
entropy[8] = crypto::hash{"\x8c\x81\x6c\x77\xf0\x62\x55\xff\x38\xec\x57\x22\x62\x2e\x49\xd4\x44\x5a\xe3\x02\x27\xb6\xb3\xe4\x3c\xc7\x9f\xb8\xc5\x90\xa1"};
|
||||
entropy[9] = crypto::hash{"\x18\x8e\x4f\xb5\x99\x74\xec\x78\xf9\x7b\x36\x87\x14\x82\x05\x32\x07\xf3\x21\x21\x6d\x8e\xfd\x35\xe6\x70\xa3\x89\x17\x22\x4b"};
|
||||
entropy[10] = crypto::hash{"\xb7\xbe\x54\xe3\x21\xe3\xeb\x88\xda\xab\xf1\xf7\x50\xb9\xef\x98\xa5\xcf\x9f\x75\x81\x74\x16\xbc\x85\x78\x6e\x0c\xaa\x14\x99"};
|
||||
entropy[11] = crypto::hash{"\xb7\x28\x54\xe3\x21\xe3\xeb\x88\xda\x12\xf1\xf7\x50\xb9\xca\x98\xa5\xcf\x9f\x75\x81\x74\x16\xbc\x85\x78\x6e\x0c\xaa\x14\x99"};
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1458,7 +1459,6 @@ namespace service_nodes
|
|||
for (cryptonote::block const &block : blocks)
|
||||
{
|
||||
crypto::hash hash = {};
|
||||
#if 0
|
||||
if (block.major_version >= cryptonote::network_version_16)
|
||||
{
|
||||
std::array<uint8_t, 1 + sizeof(block.pulse.random_value)> src = {pulse_round};
|
||||
|
@ -1466,7 +1466,6 @@ namespace service_nodes
|
|||
crypto::cn_fast_hash(src.data(), src.size(), hash.data);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
crypto::hash block_hash = cryptonote::get_block_hash(block);
|
||||
std::array<uint8_t, 1 + sizeof(hash)> src = {pulse_round};
|
||||
|
@ -1481,29 +1480,26 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
service_nodes::quorum generate_pulse_quorum(crypto::public_key const &leader, uint8_t hf_version, std::vector<pubkey_and_sninfo> const &active_snode_list, std::vector<crypto::hash> const &pulse_entropy)
|
||||
service_nodes::quorum generate_pulse_quorum(crypto::public_key const &block_winner, uint8_t hf_version, std::vector<pubkey_and_sninfo> const &active_snode_list, std::vector<crypto::hash> const &pulse_entropy, uint8_t pulse_round)
|
||||
{
|
||||
service_nodes::quorum result = {};
|
||||
if (active_snode_list.size() < (PULSE_QUORUM_SIZE + 1 /*leader*/))
|
||||
if (active_snode_list.size() < PULSE_MIN_SERVICE_NODES)
|
||||
{
|
||||
LOG_PRINT_L2("Insufficient active Service Nodes for Pulse: " << active_snode_list.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (pulse_entropy.size() < PULSE_QUORUM_SIZE)
|
||||
if (pulse_entropy.size() != PULSE_QUORUM_SIZE)
|
||||
{
|
||||
LOG_PRINT_L2("Blockchain has insufficient blocks to generate Pulse data");
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: Find the leader for this block
|
||||
result.workers.push_back(leader);
|
||||
|
||||
std::vector<pubkey_and_sninfo const *> pulse_candidates;
|
||||
pulse_candidates.reserve(active_snode_list.size());
|
||||
for (auto &node : active_snode_list)
|
||||
{
|
||||
if (node.first != leader)
|
||||
if (node.first != block_winner)
|
||||
pulse_candidates.push_back(&node);
|
||||
}
|
||||
|
||||
|
@ -1515,33 +1511,59 @@ namespace service_nodes
|
|||
return a->second->pulse_sorter < b->second->pulse_sorter;
|
||||
});
|
||||
|
||||
// NOTE: Divide the list in half, select validators from the first half of the list.
|
||||
// Swap the chosen validator into first half of the list.
|
||||
auto running_it = pulse_candidates.begin();
|
||||
size_t const partition_index = pulse_candidates.size() / 2;
|
||||
for (size_t i = 0; i < pulse_entropy.size(); i++)
|
||||
// NOTE: Order the candidates so the first (1 + half) nodes in the list is the optional new leader + validators for this round.
|
||||
// - If (round > 0) choose a service node to be the leader
|
||||
// - Swap the optional leader into the first spot
|
||||
// - Divide the remaining list in half, select validators from the first half of the list.
|
||||
// - Swap the chosen validator into the moving first half of the list.
|
||||
auto running_it = pulse_candidates.begin();
|
||||
for (size_t i = 0; i < service_nodes::PULSE_QUORUM_SIZE; i++)
|
||||
{
|
||||
crypto::hash const &entropy = pulse_entropy[i];
|
||||
std::mt19937_64 rng = quorum_rng(hf_version, entropy, quorum_type::pulse);
|
||||
size_t validators_available = std::distance(running_it, pulse_candidates.end());
|
||||
size_t swap_index = 0;
|
||||
|
||||
size_t candidate_index = tools::uniform_distribution_portable(rng, std::min(partition_index, validators_available));
|
||||
std::swap(*running_it, *(running_it + candidate_index));
|
||||
if (i == 0) // The first hash is reserved as entropy for the new leader.
|
||||
{
|
||||
// Find the leader for this block when round has iterated because, the
|
||||
// original leader (the block winner) failed to generate a Pulse Block.
|
||||
if (pulse_round > 0)
|
||||
swap_index = tools::uniform_distribution_portable(rng, pulse_candidates.size());
|
||||
}
|
||||
else // NOTE: Remaining entropy hashes for validators
|
||||
{
|
||||
size_t const partition_index = (pulse_candidates.size() - 1) / 2;
|
||||
size_t validators_available = std::distance(running_it, pulse_candidates.end());
|
||||
swap_index = tools::uniform_distribution_portable(rng, std::min(partition_index, validators_available));
|
||||
}
|
||||
|
||||
std::swap(*running_it, *(running_it + swap_index));
|
||||
running_it++;
|
||||
}
|
||||
|
||||
for (auto it = pulse_candidates.begin(); it != running_it; it++)
|
||||
{
|
||||
crypto::public_key const &validator_key = (*it)->first;
|
||||
result.validators.push_back(validator_key);
|
||||
size_t i = std::distance(pulse_candidates.begin(), it);
|
||||
crypto::public_key const &node_key = (*it)->first;
|
||||
if (i == 0)
|
||||
{
|
||||
if (pulse_round > 0)
|
||||
result.workers.push_back(node_key);
|
||||
else
|
||||
result.workers.push_back(block_winner);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.validators.push_back(node_key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static quorum_manager generate_quorums(service_node_list::state_t &state, std::vector<pubkey_and_sninfo> const &active_snode_list, cryptonote::network_type nettype, uint8_t hf_version, std::vector<crypto::hash> &pulse_entropy)
|
||||
static void generate_other_quorums(service_node_list::state_t &state, std::vector<pubkey_and_sninfo> const &active_snode_list, cryptonote::network_type nettype, uint8_t hf_version)
|
||||
{
|
||||
quorum_manager result = {};
|
||||
assert(state.block_hash != crypto::null_hash);
|
||||
|
||||
// The two quorums here have different selection criteria: the entire checkpoint quorum and the
|
||||
|
@ -1573,7 +1595,7 @@ namespace service_nodes
|
|||
size_t total_nodes = active_snode_list.size() + decomm_snode_list.size();
|
||||
num_validators = std::min(active_snode_list.size(), STATE_CHANGE_QUORUM_SIZE);
|
||||
pub_keys_indexes = generate_shuffled_service_node_index_list(hf_version, total_nodes, state.block_hash, type, num_validators, active_snode_list.size());
|
||||
result.obligations = quorum;
|
||||
state.quorums.obligations = quorum;
|
||||
size_t num_remaining_nodes = total_nodes - num_validators;
|
||||
num_workers = std::min(num_remaining_nodes, std::max(STATE_CHANGE_MIN_NODES_TO_TEST, num_remaining_nodes/STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST));
|
||||
}
|
||||
|
@ -1598,7 +1620,7 @@ namespace service_nodes
|
|||
pub_keys_indexes = generate_shuffled_service_node_index_list(hf_version, total_nodes, state.block_hash, type);
|
||||
num_validators = std::min(pub_keys_indexes.size(), CHECKPOINT_QUORUM_SIZE);
|
||||
}
|
||||
result.checkpointing = quorum;
|
||||
state.quorums.checkpointing = quorum;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1627,7 +1649,7 @@ namespace service_nodes
|
|||
}
|
||||
// Otherwise leave empty to signal that there aren't enough SNs to form a usable quorum (to
|
||||
// distinguish it from an invalid height, which gets left as a nullptr)
|
||||
result.blink = quorum;
|
||||
state.quorums.blink = quorum;
|
||||
}
|
||||
|
||||
quorum->validators.reserve(num_validators);
|
||||
|
@ -1650,31 +1672,12 @@ namespace service_nodes
|
|||
}
|
||||
break;
|
||||
|
||||
case quorum_type::pulse:
|
||||
{
|
||||
block_winner winner = state.get_block_winner();
|
||||
*quorum = generate_pulse_quorum(winner.key, hf_version, active_snode_list, pulse_entropy);
|
||||
if (quorum->workers.size() == 1 && quorum->validators.size() == PULSE_QUORUM_SIZE)
|
||||
{
|
||||
for (size_t quorum_index = 0 ; quorum_index < quorum->validators.size(); quorum_index++)
|
||||
{
|
||||
// NOTE: Send candidate to the back of the list
|
||||
crypto::public_key const &key = quorum->validators[quorum_index];
|
||||
auto &info_ptr = state.service_nodes_infos.at(key);
|
||||
service_node_info &new_info = duplicate_info(info_ptr);
|
||||
new_info.pulse_sorter.last_height_validating_in_quorum = state.height;
|
||||
new_info.pulse_sorter.quorum_index = quorum_index;
|
||||
}
|
||||
result.pulse = quorum;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// NOTE: NOP. Pulse quorums are generated pre-Service Node List changes for the block
|
||||
case quorum_type::pulse: break;
|
||||
|
||||
default: MERROR("Unhandled quorum type enum with value: " << type_int); break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void service_node_list::state_t::update_from_block(cryptonote::BlockchainDB const &db,
|
||||
|
@ -1694,6 +1697,35 @@ namespace service_nodes
|
|||
block_hash = cryptonote::get_block_hash(block);
|
||||
uint8_t const hf_version = block.major_version;
|
||||
|
||||
//
|
||||
// Generate Pulse Quorum before any SN changes are applied to the list because,
|
||||
// the Leader and Validators for this block generated Pulse Data before any
|
||||
// TX's included in the block were applied
|
||||
// i.e. before any deregistrations, registrations, decommissions, recommissions.
|
||||
//
|
||||
crypto::public_key winner_pubkey = cryptonote::get_service_node_winner_from_tx_extra(block.miner_tx.extra);
|
||||
if (hf_version >= cryptonote::network_version_16)
|
||||
{
|
||||
std::vector<crypto::hash> entropy;
|
||||
get_pulse_entropy_from_blockchain(db, cryptonote::get_block_height(block) + 1, entropy, block.pulse.round);
|
||||
quorum pulse_quorum = generate_pulse_quorum(winner_pubkey, hf_version, active_service_nodes_infos(), entropy, block.pulse.round);
|
||||
|
||||
if (pulse_quorum.workers.size() == 1 && pulse_quorum.validators.size() == PULSE_QUORUM_NUM_VALIDATORS)
|
||||
{
|
||||
// NOTE: Send candidate to the back of the list
|
||||
for (size_t quorum_index = 0 ; quorum_index < pulse_quorum.validators.size(); quorum_index++)
|
||||
{
|
||||
crypto::public_key const &key = pulse_quorum.validators[quorum_index];
|
||||
auto &info_ptr = service_nodes_infos.at(key);
|
||||
service_node_info &new_info = duplicate_info(info_ptr);
|
||||
new_info.pulse_sorter.last_height_validating_in_quorum = height;
|
||||
new_info.pulse_sorter.quorum_index = quorum_index;
|
||||
}
|
||||
|
||||
quorums.pulse = std::make_shared<service_nodes::quorum>(std::move(pulse_quorum));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Remove expired blacklisted key images
|
||||
//
|
||||
|
@ -1728,7 +1760,6 @@ namespace service_nodes
|
|||
// Advance the list to the next candidate for a reward
|
||||
//
|
||||
{
|
||||
crypto::public_key winner_pubkey = cryptonote::get_service_node_winner_from_tx_extra(block.miner_tx.extra);
|
||||
auto it = service_nodes_infos.find(winner_pubkey);
|
||||
if (it != service_nodes_infos.end())
|
||||
{
|
||||
|
@ -1788,10 +1819,7 @@ namespace service_nodes
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(doyle): Error handling, we assume it works
|
||||
std::vector<crypto::hash> entropy;
|
||||
if (hf_version >= cryptonote::network_version_16) get_pulse_entropy_from_blockchain(db, cryptonote::get_block_height(block) + 1, entropy, block.pulse.round);
|
||||
quorums = generate_quorums(*this, active_snode_list, nettype, hf_version, entropy);
|
||||
generate_other_quorums(*this, active_snode_list, nettype, hf_version);
|
||||
}
|
||||
|
||||
void service_node_list::process_block(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs)
|
||||
|
|
|
@ -638,7 +638,7 @@ namespace service_nodes
|
|||
bool make_friendly);
|
||||
|
||||
bool get_pulse_entropy_from_blockchain(cryptonote::BlockchainDB const &db, uint64_t for_height, std::vector<crypto::hash> &entropy, uint8_t pulse_round);
|
||||
service_nodes::quorum generate_pulse_quorum(crypto::public_key const &leader, uint8_t hf_version, std::vector<pubkey_and_sninfo> const &active_snode_list, std::vector<crypto::hash> const &pulse_entropy);
|
||||
service_nodes::quorum generate_pulse_quorum(crypto::public_key const &leader, uint8_t hf_version, std::vector<pubkey_and_sninfo> const &active_snode_list, std::vector<crypto::hash> const &pulse_entropy, uint8_t pulse_round);
|
||||
|
||||
const static std::vector<payout_entry> null_winner = {{cryptonote::null_address, STAKING_PORTIONS}};
|
||||
const static block_winner null_block_winner = {crypto::null_pkey, {null_winner}};
|
||||
|
|
|
@ -6,11 +6,18 @@
|
|||
|
||||
namespace service_nodes {
|
||||
constexpr size_t PULSE_QUORUM_ENTROPY_LAG = 21; // How many blocks back from the tip of the Blockchain to source entropy for the Pulse quorums.
|
||||
constexpr size_t PULSE_QUORUM_SIZE = 11;
|
||||
constexpr size_t PULSE_QUORUM_NUM_VALIDATORS = 11;
|
||||
constexpr size_t PULSE_QUORUM_SIZE = PULSE_QUORUM_NUM_VALIDATORS + 1 /*Leader*/;
|
||||
|
||||
// NOTE: PULSE_QUORUM_SIZE + 1 for when round > 0. We remove the
|
||||
// block_winner that failed in round 0 from the active list and we need
|
||||
// 1 new leader from the list to take its spot.
|
||||
// i.e. Service Node List must have 11 Validators + 1 Original Leader + 1 Optional New Leader
|
||||
constexpr size_t PULSE_MIN_SERVICE_NODES = PULSE_QUORUM_SIZE + 1 /*Optional for Leader when (Round > 0)*/;
|
||||
|
||||
constexpr size_t PULSE_BLOCK_REQUIRED_SIGNATURES = 7; // A block must have exactly N signatures to be considered properly
|
||||
constexpr size_t PULSE_MIN_SERVICE_NODES = PULSE_QUORUM_SIZE + 1 /*Leader*/;
|
||||
constexpr uint16_t PULSE_VALIDATOR_PARTICIPATION_MASK = 0b0000'0111'1111'1111; // A quorum size of 11,
|
||||
static_assert(PULSE_QUORUM_SIZE >= PULSE_BLOCK_REQUIRED_SIGNATURES, "Quorum must be larger than the required number of signatures.");
|
||||
static_assert(PULSE_QUORUM_NUM_VALIDATORS >= PULSE_BLOCK_REQUIRED_SIGNATURES, "Quorum must be larger than the required number of signatures.");
|
||||
static_assert(PULSE_QUORUM_ENTROPY_LAG >= PULSE_QUORUM_SIZE, "We need to pull atleast PULSE_QUORUM_SIZE number of blocks from the Blockchain, we can't if the amount of blocks to go back from the tip of the Blockchain is less than the blocks we need.");
|
||||
|
||||
// Service node decommissioning: as service nodes stay up they earn "credits" (measured in blocks)
|
||||
|
|
|
@ -893,27 +893,33 @@ void loki_chain_generator::block_end(loki_blockchain_entry &entry, loki_create_b
|
|||
entry.service_node_state.update_from_block(db_, cryptonote::FAKECHAIN, state_history_, {} /*state_archive*/, {} /*alt_states*/, entry.block, entry.txs, nullptr);
|
||||
}
|
||||
|
||||
void loki_chain_generator::block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const ¶ms) const
|
||||
void loki_chain_generator::block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const ¶ms, uint8_t round) const
|
||||
{
|
||||
std::vector<service_nodes::pubkey_and_sninfo> active_snode_list = params.prev.service_node_state.active_service_nodes_infos();
|
||||
assert(active_snode_list.size() >= service_nodes::PULSE_MIN_SERVICE_NODES);
|
||||
|
||||
// NOTE: Set up Pulse Header
|
||||
entry.block.pulse.validator_participation_bits = service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK; // NOTE: Everyone participates
|
||||
entry.block.pulse.round = 0;
|
||||
entry.block.pulse.round = round;
|
||||
for (size_t i = 0; i < sizeof(entry.block.pulse.random_value.data); i++)
|
||||
entry.block.pulse.random_value.data[i] = static_cast<char>(tools::uniform_distribution_portable(tools::rng, 256));
|
||||
|
||||
service_nodes::quorum const &quorum = *params.prev.service_node_state.quorums.pulse;
|
||||
assert(quorum.validators.size() == service_nodes::PULSE_QUORUM_SIZE);
|
||||
assert(quorum.workers.size() == 1);
|
||||
// NOTE: Get Pulse Quorum necessary for this block
|
||||
std::vector<crypto::hash> entropy;
|
||||
service_nodes::get_pulse_entropy_from_blockchain(db_, cryptonote::get_block_height(entry.block) + 1, entropy, entry.block.pulse.round);
|
||||
service_nodes::quorum pulse_quorum = generate_pulse_quorum(params.block_winner.key, entry.block.major_version, active_snode_list, entropy, entry.block.pulse.round);
|
||||
|
||||
assert(pulse_quorum.validators.size() == service_nodes::PULSE_QUORUM_NUM_VALIDATORS);
|
||||
assert(pulse_quorum.workers.size() == 1);
|
||||
|
||||
crypto::hash block_hash = cryptonote::get_block_hash(entry.block);
|
||||
assert(entry.block.verification.empty());
|
||||
|
||||
// NOTE: Fill Pulse Verification Data
|
||||
for (size_t i = 0; i < service_nodes::PULSE_BLOCK_REQUIRED_SIGNATURES; i++)
|
||||
{
|
||||
service_nodes::service_node_keys validator_keys = get_cached_keys(quorum.validators[i]);
|
||||
assert(validator_keys.pub == quorum.validators[i]);
|
||||
service_nodes::service_node_keys validator_keys = get_cached_keys(pulse_quorum.validators[i]);
|
||||
assert(validator_keys.pub == pulse_quorum.validators[i]);
|
||||
|
||||
cryptonote::pulse_verification verification = {};
|
||||
verification.quorum_index = i;
|
||||
|
@ -931,10 +937,9 @@ bool loki_chain_generator::create_block(loki_blockchain_entry &entry,
|
|||
|
||||
{
|
||||
std::vector<service_nodes::pubkey_and_sninfo> active_snode_list = params.prev.service_node_state.active_service_nodes_infos();
|
||||
if (entry.block.major_version >= cryptonote::network_version_16 &&
|
||||
active_snode_list.size() >= service_nodes::PULSE_MIN_SERVICE_NODES)
|
||||
if (entry.block.major_version >= cryptonote::network_version_16 && active_snode_list.size() >= service_nodes::PULSE_MIN_SERVICE_NODES)
|
||||
{
|
||||
block_fill_pulse_data(entry, params);
|
||||
block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1461,7 +1461,7 @@ struct loki_chain_generator
|
|||
bool create_block(loki_blockchain_entry &entry, loki_create_block_params ¶ms, const std::vector<cryptonote::transaction> &tx_list) const;
|
||||
|
||||
bool block_begin(loki_blockchain_entry &entry, loki_create_block_params ¶ms, const std::vector<cryptonote::transaction> &tx_list) const;
|
||||
void block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const ¶ms) const;
|
||||
void block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const ¶ms, uint8_t round) const;
|
||||
void block_end(loki_blockchain_entry &entry, loki_create_block_params const ¶ms) const;
|
||||
|
||||
uint8_t get_hf_version_at(uint64_t height) const;
|
||||
|
|
|
@ -152,6 +152,7 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY(loki_pulse_invalid_signature);
|
||||
GENERATE_AND_PLAY(loki_pulse_oob_quorum_index);
|
||||
GENERATE_AND_PLAY(loki_pulse_non_participating_validator);
|
||||
GENERATE_AND_PLAY(loki_pulse_generate_all_rounds);
|
||||
|
||||
// NOTE: Monero Tests
|
||||
GENERATE_AND_PLAY(gen_simple_chain_001);
|
||||
|
|
|
@ -3033,7 +3033,7 @@ bool loki_service_nodes_insufficient_contribution::generate(std::vector<test_eve
|
|||
return true;
|
||||
}
|
||||
|
||||
static loki_chain_generator setup_pulse_tests(std:vector<test_event_entry> &events)
|
||||
static loki_chain_generator setup_pulse_tests(std::vector<test_event_entry> &events)
|
||||
{
|
||||
std::vector<std::pair<uint8_t, uint64_t>> hard_forks = loki_generate_sequential_hard_fork_table();
|
||||
loki_chain_generator result(events, hard_forks);
|
||||
|
@ -3041,9 +3041,8 @@ static loki_chain_generator setup_pulse_tests(std:vector<test_event_entry> &even
|
|||
result.add_blocks_until_version(hard_forks.back().first);
|
||||
result.add_mined_money_unlock_blocks();
|
||||
|
||||
int constexpr NUM_SERVICE_NODES = service_nodes::PULSE_QUORUM_SIZE + 1 /*leader*/;
|
||||
std::vector<cryptonote::transaction> registration_txs(NUM_SERVICE_NODES);
|
||||
for (auto i = 0u; i < NUM_SERVICE_NODES; ++i)
|
||||
std::vector<cryptonote::transaction> registration_txs(service_nodes::PULSE_MIN_SERVICE_NODES);
|
||||
for (auto i = 0u; i < service_nodes::PULSE_MIN_SERVICE_NODES; ++i)
|
||||
registration_txs[i] = result.create_and_add_registration_tx(result.first_miner());
|
||||
|
||||
// NOTE: Generate Valid Blocks
|
||||
|
@ -3059,7 +3058,7 @@ bool loki_pulse_invalid_participation_bits::generate(std::vector<test_event_entr
|
|||
loki_blockchain_entry entry = {};
|
||||
loki_create_block_params params = gen.next_block_params();
|
||||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params);
|
||||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Overwrite participation bits to be wrong
|
||||
entry.block.pulse.validator_participation_bits = ~service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK;
|
||||
|
@ -3077,7 +3076,7 @@ bool loki_pulse_invalid_signature::generate(std::vector<test_event_entry> &event
|
|||
loki_blockchain_entry entry = {};
|
||||
loki_create_block_params params = gen.next_block_params();
|
||||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params);
|
||||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Overwrite signature
|
||||
entry.block.verification[0].signature = {};
|
||||
|
@ -3094,10 +3093,10 @@ bool loki_pulse_oob_quorum_index::generate(std::vector<test_event_entry> &events
|
|||
loki_blockchain_entry entry = {};
|
||||
loki_create_block_params params = gen.next_block_params();
|
||||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params);
|
||||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Overwrite oob quorum index
|
||||
entry.block.verification[0].quorum_index = service_nodes::PULSE_QUORUM_SIZE + 1;
|
||||
entry.block.verification[0].quorum_index = service_nodes::PULSE_QUORUM_NUM_VALIDATORS + 1;
|
||||
gen.block_end(entry, params);
|
||||
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "Invalid Pulse Block, specifies the wrong participation bits");
|
||||
|
||||
|
@ -3114,19 +3113,22 @@ bool loki_pulse_non_participating_validator::generate(std::vector<test_event_ent
|
|||
|
||||
// NOTE: Manually generate signatures to break test
|
||||
{
|
||||
// NOTE: Check active service node list invariant
|
||||
{
|
||||
std::vector<service_nodes::pubkey_and_sninfo> active_snode_list = params.prev.service_node_state.active_service_nodes_infos();
|
||||
assert(active_snode_list.size() >= service_nodes::PULSE_MIN_SERVICE_NODES);
|
||||
entry.block.pulse.round = 0;
|
||||
for (size_t i = 0; i < sizeof(entry.block.pulse.random_value.data); i++)
|
||||
entry.block.pulse.random_value.data[i] = static_cast<char>(tools::uniform_distribution_portable(tools::rng, 256));
|
||||
}
|
||||
|
||||
entry.block.pulse.round = 0;
|
||||
for (size_t i = 0; i < sizeof(entry.block.pulse.random_value.data); i++)
|
||||
entry.block.pulse.random_value.data[i] = static_cast<char>(tools::uniform_distribution_portable(tools::rng, 256));
|
||||
service_nodes::quorum quorum = {};
|
||||
{
|
||||
std::vector<service_nodes::pubkey_and_sninfo> active_snode_list = params.prev.service_node_state.active_service_nodes_infos();
|
||||
|
||||
service_nodes::quorum const &quorum = *params.prev.service_node_state.quorums.pulse;
|
||||
assert(quorum.validators.size() == service_nodes::PULSE_QUORUM_SIZE);
|
||||
assert(quorum.workers.size() == 1);
|
||||
std::vector<crypto::hash> entropy;
|
||||
service_nodes::get_pulse_entropy_from_blockchain(gen.db_, cryptonote::get_block_height(entry.block) + 1, entropy, entry.block.pulse.round);
|
||||
quorum = generate_pulse_quorum(params.block_winner.key, entry.block.major_version, active_snode_list, entropy, entry.block.pulse.round);
|
||||
assert(quorum.validators.size() == service_nodes::PULSE_QUORUM_NUM_VALIDATORS);
|
||||
assert(quorum.workers.size() == 1);
|
||||
}
|
||||
|
||||
// NOTE: First 7 validators are locked in. We received signatures from the
|
||||
// first 6 in the quorum, then the 8th validator in the quorum (who is not
|
||||
|
@ -3153,3 +3155,20 @@ bool loki_pulse_non_participating_validator::generate(std::vector<test_event_ent
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loki_pulse_generate_all_rounds::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
loki_chain_generator gen = setup_pulse_tests(events);
|
||||
|
||||
for (uint8_t round = 0; round < static_cast<uint8_t>(-1); round++)
|
||||
{
|
||||
loki_blockchain_entry entry = {};
|
||||
loki_create_block_params params = gen.next_block_params();
|
||||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params, round);
|
||||
gen.block_end(entry, params);
|
||||
gen.add_block(entry, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -81,4 +81,5 @@ struct loki_pulse_invalid_participation_bits
|
|||
struct loki_pulse_invalid_signature : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_oob_quorum_index : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_non_participating_validator : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_generate_all_rounds : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
|
||||
|
|
Loading…
Reference in New Issue