mirror of
https://github.com/oxen-io/oxen-core.git
synced 2023-12-14 02:22:56 +01:00
Merge pull request #1233 from Doy-lee/PulseRound255
Pulse: Clear round state on prepare_for_round properly, handle round > 255, some misc debug logging
This commit is contained in:
commit
bb811f3d58
3 changed files with 122 additions and 62 deletions
|
@ -123,7 +123,6 @@ struct round_context
|
|||
{
|
||||
uint64_t height; // Current blockchain height that Pulse wants to generate a block for
|
||||
crypto::hash top_hash; // Latest block hash included in signatures for rejecting out of date nodes
|
||||
uint64_t top_block_timestamp; // Latest block timestamp for setting the ideal block target time
|
||||
pulse::time_point round_0_start_time; // When round 0 should start and subsequent round timings are derived from.
|
||||
} wait_for_next_block;
|
||||
|
||||
|
@ -303,7 +302,7 @@ std::string msg_source_string(round_context const &context, pulse::message const
|
|||
if (msg.quorum_position >= context.prepare_for_round.quorum.validators.size()) return "XX";
|
||||
|
||||
std::stringstream stream;
|
||||
stream << "'" << message_type_string(msg.type) << "' from " << msg.quorum_position;
|
||||
stream << "'" << message_type_string(msg.type) << "' from " << msg.quorum_position << ", round " << +msg.round;
|
||||
if (context.state >= round_state::prepare_for_round)
|
||||
{
|
||||
if (msg.quorum_position < context.prepare_for_round.quorum.validators.size())
|
||||
|
@ -660,6 +659,39 @@ void pulse::handle_message(void *quorumnet_state, pulse::message const &msg)
|
|||
cryptonote::quorumnet_pulse_relay_message_to_quorum(quorumnet_state, msg, context.prepare_for_round.quorum, context.prepare_for_round.participant == sn_type::producer);
|
||||
}
|
||||
|
||||
bool pulse::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t height, pulse::timings ×)
|
||||
{
|
||||
times = {};
|
||||
|
||||
crypto::hash top_hash = blockchain.get_block_id_by_height(height - 1);
|
||||
cryptonote::block top_block = {};
|
||||
if (bool orphan = false; !blockchain.get_block_by_hash(top_hash, top_block, &orphan) || orphan)
|
||||
return false;
|
||||
|
||||
static uint64_t const hf16_height = cryptonote::HardFork::get_hardcoded_hard_fork_height(blockchain.nettype(), cryptonote::network_version_16);
|
||||
crypto::hash genesis_hash = blockchain.get_block_id_by_height(hf16_height - 1);
|
||||
cryptonote::block genesis_block = {};
|
||||
if (bool orphaned = false; !blockchain.get_block_by_hash(genesis_hash, genesis_block, &orphaned) || orphaned)
|
||||
return false;
|
||||
|
||||
#if 1
|
||||
uint64_t const delta_height = height - cryptonote::get_block_height(genesis_block);
|
||||
times.genesis_timestamp = pulse::time_point(std::chrono::seconds(genesis_block.timestamp));
|
||||
|
||||
times.prev_hash = top_hash;
|
||||
times.prev_timestamp = pulse::time_point(std::chrono::seconds(top_block.timestamp));
|
||||
|
||||
times.ideal_timestamp = pulse::time_point(times.genesis_timestamp + (TARGET_BLOCK_TIME * delta_height));
|
||||
times.r0_timestamp = std::clamp(times.ideal_timestamp,
|
||||
times.prev_timestamp + service_nodes::PULSE_MIN_TARGET_BLOCK_TIME,
|
||||
times.prev_timestamp + service_nodes::PULSE_MAX_TARGET_BLOCK_TIME);
|
||||
#else // NOTE: Debug, make next block start relatively soon
|
||||
times.r0_timestamp = times.prev_timestamp + service_nodes::PULSE_ROUND_TIME;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Pulse progresses via a state-machine that is iterated through job submissions
|
||||
to 1 dedicated Pulse thread, started by LMQ.
|
||||
|
@ -833,6 +865,7 @@ round_state goto_preparing_for_next_round(round_context &context)
|
|||
|
||||
round_state wait_for_next_block(uint64_t hf16_height, round_context &context, cryptonote::Blockchain const &blockchain)
|
||||
{
|
||||
|
||||
//
|
||||
// NOTE: If already processing pulse for height, wait for next height
|
||||
//
|
||||
|
@ -844,65 +877,24 @@ round_state wait_for_next_block(uint64_t hf16_height, round_context &context, cr
|
|||
return round_state::wait_for_next_block;
|
||||
}
|
||||
|
||||
uint64_t top_height = curr_height - 1;
|
||||
crypto::hash top_hash = blockchain.get_block_id_by_height(top_height);
|
||||
if (top_hash == crypto::null_hash)
|
||||
{
|
||||
for (static uint64_t last_height = 0; last_height != top_height; last_height = top_height)
|
||||
MERROR(log_prefix(context) << "Block hash for height " << top_height << " does not exist!");
|
||||
return round_state::wait_for_next_block;
|
||||
}
|
||||
|
||||
cryptonote::block top_block = {};
|
||||
if (bool orphan = false;
|
||||
!blockchain.get_block_by_hash(top_hash, top_block, &orphan) || orphan)
|
||||
{
|
||||
for (static uint64_t last_height = 0; last_height != top_height; last_height = top_height)
|
||||
MERROR(log_prefix(context) << "Failed to query previous block in blockchain at height " << top_height);
|
||||
return round_state::wait_for_next_block;
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: Query Pulse Genesis
|
||||
// TODO(loki): After HF16 genesis block is checkpointed, move this out of the loop/hardcode this as it can't change.
|
||||
//
|
||||
crypto::hash genesis_hash = blockchain.get_block_id_by_height(hf16_height - 1);
|
||||
cryptonote::block genesis_block = {};
|
||||
if (bool orphaned = false; !blockchain.get_block_by_hash(genesis_hash, genesis_block, &orphaned) || orphaned)
|
||||
pulse::timings times = {};
|
||||
if (!get_round_timings(blockchain, curr_height, times))
|
||||
{
|
||||
for (static bool once = true; once; once = !once)
|
||||
MERROR(log_prefix(context) << "Failed to query the genesis block for Pulse at height " << hf16_height - 1);
|
||||
MERROR(log_prefix(context) << "Failed to query the block data for Pulse timings");
|
||||
return round_state::wait_for_next_block;
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: Block Timing
|
||||
//
|
||||
uint64_t const delta_height = context.wait_for_next_block.height - cryptonote::get_block_height(genesis_block);
|
||||
#if 1
|
||||
auto genesis_timestamp = pulse::time_point(std::chrono::seconds(genesis_block.timestamp));
|
||||
pulse::time_point ideal_timestamp = genesis_timestamp + (TARGET_BLOCK_TIME * delta_height);
|
||||
pulse::time_point prev_timestamp = pulse::time_point(std::chrono::seconds(top_block.timestamp));
|
||||
context.wait_for_next_block.round_0_start_time =
|
||||
std::clamp(ideal_timestamp,
|
||||
prev_timestamp + service_nodes::PULSE_MIN_TARGET_BLOCK_TIME,
|
||||
prev_timestamp + service_nodes::PULSE_MAX_TARGET_BLOCK_TIME);
|
||||
#else // NOTE: Debug, make next block start relatively soon
|
||||
pulse::time_point prev_timestamp = pulse::time_point(std::chrono::seconds(top_block.timestamp));
|
||||
context.wait_for_next_block.round_0_start_time = prev_timestamp + service_nodes::PULSE_ROUND_TIME;
|
||||
#endif
|
||||
|
||||
context.wait_for_next_block.height = curr_height;
|
||||
context.wait_for_next_block.top_hash = top_hash;
|
||||
context.wait_for_next_block.top_block_timestamp = top_block.timestamp;
|
||||
context.prepare_for_round = {};
|
||||
context.wait_for_next_block.round_0_start_time = times.r0_timestamp;
|
||||
context.wait_for_next_block.height = curr_height;
|
||||
context.wait_for_next_block.top_hash = times.prev_hash;
|
||||
context.prepare_for_round = {};
|
||||
|
||||
return round_state::prepare_for_round;
|
||||
}
|
||||
|
||||
round_state prepare_for_round(round_context &context, service_nodes::service_node_keys const &key, cryptonote::Blockchain const &blockchain)
|
||||
{
|
||||
|
||||
// Clear memory
|
||||
{
|
||||
context.transient = {};
|
||||
|
@ -910,6 +902,17 @@ round_state prepare_for_round(round_context &context, service_nodes::service_nod
|
|||
auto &old_random_values_array = context.transient.random_value.wait.data;
|
||||
memwipe(old_random_value.data, sizeof(old_random_value));
|
||||
memwipe(old_random_values_array.data(), old_random_values_array.size() * sizeof(old_random_values_array[0]));
|
||||
|
||||
// Store values
|
||||
bool queue_for_round = context.prepare_for_round.queue_for_next_round;
|
||||
uint8_t round = context.prepare_for_round.round;
|
||||
|
||||
// Blanket clear
|
||||
context.prepare_for_round = {};
|
||||
|
||||
// Restore values
|
||||
context.prepare_for_round.round = round;
|
||||
context.prepare_for_round.queue_for_next_round = queue_for_round;
|
||||
}
|
||||
|
||||
if (context.prepare_for_round.queue_for_next_round)
|
||||
|
@ -917,7 +920,14 @@ round_state prepare_for_round(round_context &context, service_nodes::service_nod
|
|||
// Set when an intermediate Pulse stage has failed and we wait on the
|
||||
// next round to occur.
|
||||
context.prepare_for_round.queue_for_next_round = false;
|
||||
context.prepare_for_round.round++; //TODO: Overflow check
|
||||
|
||||
if (context.prepare_for_round.round >= 255)
|
||||
{
|
||||
// If the next round overflows, we consider the network stalled. Wait for
|
||||
// the next block and allow PoW to return.
|
||||
return round_state::wait_for_next_block;
|
||||
}
|
||||
context.prepare_for_round.round++;
|
||||
|
||||
// Also check if the blockchain has changed, in which case we stop and
|
||||
// restart Pulse stages.
|
||||
|
@ -932,8 +942,11 @@ round_state prepare_for_round(round_context &context, service_nodes::service_nod
|
|||
auto now = pulse::clock::now();
|
||||
auto const time_since_block = now <= context.wait_for_next_block.round_0_start_time ? std::chrono::seconds(0) : (now - context.wait_for_next_block.round_0_start_time);
|
||||
size_t round_usize = time_since_block / service_nodes::PULSE_ROUND_TIME;
|
||||
uint8_t curr_round = static_cast<uint8_t>(round_usize); // TODO: Overflow check
|
||||
|
||||
if (round_usize > 255) // Network stalled
|
||||
return round_state::wait_for_next_block;
|
||||
|
||||
uint8_t curr_round = static_cast<uint8_t>(round_usize);
|
||||
if (curr_round > context.prepare_for_round.round)
|
||||
context.prepare_for_round.round = curr_round;
|
||||
}
|
||||
|
@ -964,6 +977,8 @@ round_state prepare_for_round(round_context &context, service_nodes::service_nod
|
|||
return round_state::wait_for_next_block;
|
||||
}
|
||||
|
||||
MDEBUG(log_prefix(context) << "Generate Pulse quorum: " << context.prepare_for_round.quorum);
|
||||
|
||||
//
|
||||
// NOTE: Quorum participation
|
||||
//
|
||||
|
@ -1434,9 +1449,10 @@ void pulse::main(void *quorumnet_state, cryptonote::core &core)
|
|||
}
|
||||
|
||||
for (auto last_state = round_state::null_state;
|
||||
last_state != context.state || last_state == round_state::null_state;
|
||||
last_state = context.state)
|
||||
last_state != context.state || last_state == round_state::null_state;)
|
||||
{
|
||||
last_state = context.state;
|
||||
|
||||
switch (context.state)
|
||||
{
|
||||
case round_state::null_state:
|
||||
|
|
|
@ -83,4 +83,17 @@ struct message
|
|||
void main(void *quorumnet_state, cryptonote::core &core);
|
||||
void handle_message(void *quorumnet_state, pulse::message const &msg);
|
||||
|
||||
struct timings
|
||||
{
|
||||
pulse::time_point genesis_timestamp;
|
||||
|
||||
crypto::hash prev_hash;
|
||||
pulse::time_point prev_timestamp;
|
||||
|
||||
pulse::time_point ideal_timestamp;
|
||||
pulse::time_point r0_timestamp;
|
||||
};
|
||||
|
||||
bool get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t height, timings ×);
|
||||
|
||||
} // namespace pulse
|
||||
|
|
|
@ -53,6 +53,7 @@ extern "C" {
|
|||
#include "blockchain.h"
|
||||
#include "service_node_quorum_cop.h"
|
||||
|
||||
#include "pulse.h"
|
||||
#include "service_node_list.h"
|
||||
#include "service_node_rules.h"
|
||||
#include "service_node_swarm.h"
|
||||
|
@ -2096,21 +2097,51 @@ namespace service_nodes
|
|||
crypto::public_key block_producer_key = {};
|
||||
if (hf_version >= cryptonote::network_version_16)
|
||||
{
|
||||
// TODO(doyle): We need to verify that the block round is within a "range" of acceptable block rounds depending on the clock
|
||||
quorum pulse_quorum = generate_pulse_quorum(m_blockchain.nettype(), m_blockchain.get_db(), height + 1, block_leader.key, hf_version, m_state.active_service_nodes_infos(), block.pulse.round);
|
||||
if (block.signatures.size() || pulse_quorum.workers.size())
|
||||
|
||||
if (verify_pulse_quorum_sizes(pulse_quorum))
|
||||
{
|
||||
if (pulse_quorum.workers.empty())
|
||||
pulse::timings times = {};
|
||||
if (!pulse::get_round_timings(m_blockchain, height, times))
|
||||
{
|
||||
MERROR("The block specified pulse signatures but there is no producer specified in the Pulse Quorum for this height: " << height);
|
||||
MERROR("Failed to query the block data for Pulse timings to validate incoming block at height " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
block_producer_key = pulse_quorum.workers[0];
|
||||
mode = (block_producer_key == block_leader.key) ? verify_mode::pulse_block_leader_is_producer : verify_mode::pulse_different_block_producer;
|
||||
|
||||
if (block.pulse.round == 0 && (mode == verify_mode::pulse_different_block_producer))
|
||||
auto r256_timestamp = times.r0_timestamp + (service_nodes::PULSE_ROUND_TIME * 256);
|
||||
if (auto now = pulse::clock::now(); now < r256_timestamp)
|
||||
{
|
||||
MERROR("The block producer in pulse round 0 should be the same node as the block leader: " << block_leader.key << ", actual producer: " << block_producer_key);
|
||||
block_producer_key = pulse_quorum.workers[0];
|
||||
mode = (block_producer_key == block_leader.key) ? verify_mode::pulse_block_leader_is_producer : verify_mode::pulse_different_block_producer;
|
||||
|
||||
if (block.pulse.round == 0 && (mode == verify_mode::pulse_different_block_producer))
|
||||
{
|
||||
MERROR("The block producer in pulse round 0 should be the same node as the block leader: " << block_leader.key << ", actual producer: " << block_producer_key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// else, timestamp is way past the 255th round, network has stalled. Verify the block in miner mode
|
||||
}
|
||||
|
||||
if (mode == verify_mode::miner)
|
||||
{
|
||||
if (block.pulse.round != 0)
|
||||
{
|
||||
MERROR("Miner block given but unexpectedly set round " << block.pulse.round << " on height " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block.pulse.validator_bitset != 0)
|
||||
{
|
||||
std::bitset<8 * sizeof(block.pulse.validator_bitset)> const bitset = block.pulse.validator_bitset;
|
||||
MERROR("Miner block given but unexpectedly set validator bitset " << bitset << " on height " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (block.signatures.size())
|
||||
{
|
||||
MERROR("Miner block given but unexpectedly has " << block.signatures.size() << " signatures on height " << height);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue