Skip to content

Commit

Permalink
Merge pull request #1481 from AntelopeIO/GH-1432-spec-block
Browse files Browse the repository at this point in the history
Smarter block deadlines for speculative blocks
  • Loading branch information
heifner authored Aug 15, 2023
2 parents 062e7f1 + e3adab4 commit a5d8f72
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
#pragma once
#include <eosio/chain/block_timestamp.hpp>
#include <eosio/chain/config.hpp>
#include <eosio/chain/producer_schedule.hpp>

namespace eosio {

enum class pending_block_mode { producing, speculating };

namespace block_timing_util {

// Store watermarks
// Watermarks are recorded times that the specified producer has produced.
// Used by calculate_producer_wake_up_time to skip over already produced blocks avoiding duplicate production.
class producer_watermarks {
public:
void consider_new_watermark(chain::account_name producer, uint32_t block_num, chain::block_timestamp_type timestamp) {
auto itr = _producer_watermarks.find(producer);
if (itr != _producer_watermarks.end()) {
itr->second.first = std::max(itr->second.first, block_num);
itr->second.second = std::max(itr->second.second, timestamp);
} else {
_producer_watermarks.emplace(producer, std::make_pair(block_num, timestamp));
}
}

using producer_watermark = std::pair<uint32_t, chain::block_timestamp_type>;
std::optional<producer_watermark> get_watermark(chain::account_name producer) const {
auto itr = _producer_watermarks.find(producer);

if (itr == _producer_watermarks.end())
return {};

return itr->second;
}
private:
std::map<chain::account_name, producer_watermark> _producer_watermarks;
};

// Calculate when a producer can start producing a given block represented by its block_time
//
// In the past, a producer would always start a block `config::block_interval_us` ahead of its block time. However,
// it causes the last block in a block production round being released too late for the next producer to have
// received it and start producing on schedule. To mitigate the problem, we leave no time gap in block producing. For
// example, given block_interval=500 ms and cpu effort=400 ms, assuming the our round start at time point 0; in the
// past, the block start time points would be at time point -500, 0, 500, 1000, 1500, 2000 .... With this new
// approach, the block time points would become -500, -100, 300, 700, 1200 ...
// approach, the block time points would become -500, -100, 300, 700, 1100 ...
inline fc::time_point production_round_block_start_time(uint32_t cpu_effort_us, chain::block_timestamp_type block_time) {
uint32_t block_slot = block_time.slot;
uint32_t production_round_start_block_slot =
Expand All @@ -25,22 +54,95 @@ namespace block_timing_util {
fc::microseconds(cpu_effort_us * production_round_index);
}

inline fc::time_point calculate_block_deadline(uint32_t cpu_effort_us, pending_block_mode mode, chain::block_timestamp_type block_time) {
const auto hard_deadline =
block_time.to_time_point() - fc::microseconds(chain::config::block_interval_us - cpu_effort_us);
if (mode == pending_block_mode::producing) {
auto estimated_deadline = production_round_block_start_time(cpu_effort_us, block_time) + fc::microseconds(cpu_effort_us);
auto now = fc::time_point::now();
if (estimated_deadline > now) {
return estimated_deadline;
inline fc::time_point calculate_producing_block_deadline(uint32_t cpu_effort_us, chain::block_timestamp_type block_time) {
auto estimated_deadline = production_round_block_start_time(cpu_effort_us, block_time) + fc::microseconds(cpu_effort_us);
auto now = fc::time_point::now();
if (estimated_deadline > now) {
return estimated_deadline;
} else {
// This could only happen when the producer stop producing and then comes back alive in the middle of its own
// production round. In this case, we just use the hard deadline.
const auto hard_deadline = block_time.to_time_point() - fc::microseconds(chain::config::block_interval_us - cpu_effort_us);
return std::min(hard_deadline, now + fc::microseconds(cpu_effort_us));
}
}

namespace detail {
inline uint32_t calculate_next_block_slot(const chain::account_name& producer_name, uint32_t current_block_slot, uint32_t block_num,
size_t producer_index, size_t active_schedule_size, const producer_watermarks& prod_watermarks) {
uint32_t minimum_offset = 1; // must at least be the "next" block

// account for a watermark in the future which is disqualifying this producer for now
// this is conservative assuming no blocks are dropped. If blocks are dropped the watermark will
// disqualify this producer for longer but it is assumed they will wake up, determine that they
// are disqualified for longer due to skipped blocks and re-calculate their next block with better
// information then
auto current_watermark = prod_watermarks.get_watermark(producer_name);
if (current_watermark) {
const auto watermark = *current_watermark;
if (watermark.first > block_num) {
// if I have a watermark block number then I need to wait until after that watermark
minimum_offset = watermark.first - block_num + 1;
}
if (watermark.second.slot > current_block_slot) {
// if I have a watermark block timestamp then I need to wait until after that watermark timestamp
minimum_offset = std::max(minimum_offset, watermark.second.slot - current_block_slot + 1);
}
}

// this producers next opportunity to produce is the next time its slot arrives after or at the calculated minimum
uint32_t minimum_slot = current_block_slot + minimum_offset;
size_t minimum_slot_producer_index =
(minimum_slot % (active_schedule_size * chain::config::producer_repetitions)) / chain::config::producer_repetitions;
if (producer_index == minimum_slot_producer_index) {
// this is the producer for the minimum slot, go with that
return minimum_slot;
} else {
// This could only happen when the producer stop producing and then comes back alive in the middle of its own
// production round. In this case, we just use the hard deadline.
return std::min(hard_deadline, now + fc::microseconds(cpu_effort_us));
// calculate how many rounds are between the minimum producer and the producer in question
size_t producer_distance = producer_index - minimum_slot_producer_index;
// check for unsigned underflow
if (producer_distance > producer_index) {
producer_distance += active_schedule_size;
}

// align the minimum slot to the first of its set of reps
uint32_t first_minimum_producer_slot = minimum_slot - (minimum_slot % chain::config::producer_repetitions);

// offset the aligned minimum to the *earliest* next set of slots for this producer
uint32_t next_block_slot = first_minimum_producer_slot + (producer_distance * chain::config::producer_repetitions);
return next_block_slot;
}
} else {
return hard_deadline;
}
}
};

// Return the *next* block start time according to its block time slot.
// Returns empty optional if no producers are in the active_schedule.
// block_num is only used for watermark minimum offset.
inline std::optional<fc::time_point> calculate_producer_wake_up_time(uint32_t cpu_effort_us, uint32_t block_num,
const chain::block_timestamp_type& ref_block_time,
const std::set<chain::account_name>& producers,
const std::vector<chain::producer_authority>& active_schedule,
const producer_watermarks& prod_watermarks) {
auto ref_block_slot = ref_block_time.slot;
// if we have any producers then we should at least set a timer for our next available slot
uint32_t wake_up_slot = UINT32_MAX;
for (const auto& p : producers) {
// determine if this producer is in the active schedule and if so, where
auto itr = std::find_if(active_schedule.begin(), active_schedule.end(), [&](const auto& asp) { return asp.producer_name == p; });
if (itr == active_schedule.end()) {
continue;
}
size_t producer_index = itr - active_schedule.begin();

auto next_producer_block_slot = detail::calculate_next_block_slot(p, ref_block_slot, block_num, producer_index, active_schedule.size(), prod_watermarks);
wake_up_slot = std::min(next_producer_block_slot, wake_up_slot);
}
if (wake_up_slot == UINT32_MAX) {
return {};
}

return production_round_block_start_time(cpu_effort_us, chain::block_timestamp_type(wake_up_slot));
}

} // namespace block_timing_util
} // namespace eosio
Loading

0 comments on commit a5d8f72

Please sign in to comment.