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:
Doyle 2020-06-23 17:13:33 +10:00
parent 62ad3d603e
commit 7883c875e0
8 changed files with 146 additions and 85 deletions

View File

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

View File

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

View File

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

View File

@ -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 &params) const
void loki_chain_generator::block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const &params, 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
{

View File

@ -1461,7 +1461,7 @@ struct loki_chain_generator
bool create_block(loki_blockchain_entry &entry, loki_create_block_params &params, const std::vector<cryptonote::transaction> &tx_list) const;
bool block_begin(loki_blockchain_entry &entry, loki_create_block_params &params, const std::vector<cryptonote::transaction> &tx_list) const;
void block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const &params) const;
void block_fill_pulse_data(loki_blockchain_entry &entry, loki_create_block_params const &params, uint8_t round) const;
void block_end(loki_blockchain_entry &entry, loki_create_block_params const &params) const;
uint8_t get_hf_version_at(uint64_t height) const;

View File

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

View File

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

View File

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