325 lines
10 KiB
C++
325 lines
10 KiB
C++
#include <nano/node/election.hpp>
|
|
#include <nano/node/node.hpp>
|
|
|
|
#include <boost/format.hpp>
|
|
|
|
nano::election_vote_result::election_vote_result (bool replay_a, bool processed_a)
|
|
{
|
|
replay = replay_a;
|
|
processed = processed_a;
|
|
}
|
|
|
|
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> block_a, bool const skip_delay_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a) :
|
|
confirmation_action (confirmation_action_a),
|
|
confirmed_m (false),
|
|
node (node_a),
|
|
election_start (std::chrono::steady_clock::now ()),
|
|
status ({ block_a, 0, std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values<std::chrono::milliseconds>::zero (), 0, 1, 0, nano::election_status_type::ongoing }),
|
|
skip_delay (skip_delay_a),
|
|
stopped (false)
|
|
{
|
|
last_votes.emplace (node.network_params.random.not_an_account, nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () });
|
|
blocks.emplace (block_a->hash (), block_a);
|
|
update_dependent ();
|
|
}
|
|
|
|
void nano::election::confirm_once (nano::election_status_type type_a)
|
|
{
|
|
assert (!node.active.mutex.try_lock ());
|
|
if (!confirmed_m.exchange (true))
|
|
{
|
|
status.election_end = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ());
|
|
status.election_duration = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
|
|
status.confirmation_request_count = confirmation_request_count;
|
|
status.block_count = nano::narrow_cast<decltype (status.block_count)> (blocks.size ());
|
|
status.voter_count = nano::narrow_cast<decltype (status.voter_count)> (last_votes.size ());
|
|
status.type = type_a;
|
|
auto status_l (status);
|
|
auto node_l (node.shared ());
|
|
auto confirmation_action_l (confirmation_action);
|
|
node.active.election_winner_details.emplace (status.winner->hash (), shared_from_this ());
|
|
node.background ([node_l, status_l, confirmation_action_l]() {
|
|
node_l->process_confirmed (status_l);
|
|
confirmation_action_l (status_l.winner);
|
|
});
|
|
clear_blocks ();
|
|
clear_dependent ();
|
|
node.active.roots.erase (status.winner->qualified_root ());
|
|
}
|
|
}
|
|
|
|
void nano::election::stop ()
|
|
{
|
|
assert (!node.active.mutex.try_lock ());
|
|
if (!stopped && !confirmed ())
|
|
{
|
|
stopped = true;
|
|
status.election_end = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ());
|
|
status.election_duration = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
|
|
status.confirmation_request_count = confirmation_request_count;
|
|
status.block_count = nano::narrow_cast<decltype (status.block_count)> (blocks.size ());
|
|
status.voter_count = nano::narrow_cast<decltype (status.voter_count)> (last_votes.size ());
|
|
status.type = nano::election_status_type::stopped;
|
|
}
|
|
}
|
|
|
|
bool nano::election::confirmed ()
|
|
{
|
|
return confirmed_m;
|
|
}
|
|
|
|
bool nano::election::have_quorum (nano::tally_t const & tally_a, nano::uint128_t tally_sum) const
|
|
{
|
|
bool result = false;
|
|
if (tally_sum >= node.config.online_weight_minimum.number ())
|
|
{
|
|
auto i (tally_a.begin ());
|
|
++i;
|
|
auto second (i != tally_a.end () ? i->first : 0);
|
|
auto delta_l (node.delta ());
|
|
result = tally_a.begin ()->first > (second + delta_l);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
nano::tally_t nano::election::tally ()
|
|
{
|
|
std::unordered_map<nano::block_hash, nano::uint128_t> block_weights;
|
|
for (auto vote_info : last_votes)
|
|
{
|
|
block_weights[vote_info.second.hash] += node.ledger.weight (vote_info.first);
|
|
}
|
|
last_tally = block_weights;
|
|
nano::tally_t result;
|
|
for (auto item : block_weights)
|
|
{
|
|
auto block (blocks.find (item.first));
|
|
if (block != blocks.end ())
|
|
{
|
|
result.emplace (item.second, block->second);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void nano::election::confirm_if_quorum ()
|
|
{
|
|
auto tally_l (tally ());
|
|
assert (!tally_l.empty ());
|
|
auto winner (tally_l.begin ());
|
|
auto block_l (winner->second);
|
|
status.tally = winner->first;
|
|
nano::uint128_t sum (0);
|
|
for (auto & i : tally_l)
|
|
{
|
|
sum += i.first;
|
|
}
|
|
if (sum >= node.config.online_weight_minimum.number () && block_l->hash () != status.winner->hash ())
|
|
{
|
|
auto node_l (node.shared ());
|
|
node_l->block_processor.force (block_l);
|
|
status.winner = block_l;
|
|
update_dependent ();
|
|
node_l->active.adjust_difficulty (block_l->hash ());
|
|
}
|
|
if (have_quorum (tally_l, sum))
|
|
{
|
|
if (node.config.logging.vote_logging () || blocks.size () > 1)
|
|
{
|
|
log_votes (tally_l);
|
|
}
|
|
confirm_once (nano::election_status_type::active_confirmed_quorum);
|
|
}
|
|
}
|
|
|
|
void nano::election::log_votes (nano::tally_t const & tally_a) const
|
|
{
|
|
std::stringstream tally;
|
|
std::string line_end (node.config.logging.single_line_record () ? "\t" : "\n");
|
|
tally << boost::str (boost::format ("%1%Vote tally for root %2%") % line_end % status.winner->root ().to_string ());
|
|
for (auto i (tally_a.begin ()), n (tally_a.end ()); i != n; ++i)
|
|
{
|
|
tally << boost::str (boost::format ("%1%Block %2% weight %3%") % line_end % i->second->hash ().to_string () % i->first.convert_to<std::string> ());
|
|
}
|
|
for (auto i (last_votes.begin ()), n (last_votes.end ()); i != n; ++i)
|
|
{
|
|
tally << boost::str (boost::format ("%1%%2% %3%") % line_end % i->first.to_account () % i->second.hash.to_string ());
|
|
}
|
|
node.logger.try_log (tally.str ());
|
|
}
|
|
|
|
nano::election_vote_result nano::election::vote (nano::account rep, uint64_t sequence, nano::block_hash block_hash)
|
|
{
|
|
// see republish_vote documentation for an explanation of these rules
|
|
auto replay (false);
|
|
auto online_stake (node.online_reps.online_stake ());
|
|
auto weight (node.ledger.weight (rep));
|
|
auto should_process (false);
|
|
if (node.network_params.network.is_test_network () || weight > node.minimum_principal_weight (online_stake))
|
|
{
|
|
unsigned int cooldown;
|
|
if (weight < online_stake / 100) // 0.1% to 1%
|
|
{
|
|
cooldown = 15;
|
|
}
|
|
else if (weight < online_stake / 20) // 1% to 5%
|
|
{
|
|
cooldown = 5;
|
|
}
|
|
else // 5% or above
|
|
{
|
|
cooldown = 1;
|
|
}
|
|
auto last_vote_it (last_votes.find (rep));
|
|
if (last_vote_it == last_votes.end ())
|
|
{
|
|
should_process = true;
|
|
}
|
|
else
|
|
{
|
|
auto last_vote (last_vote_it->second);
|
|
if (last_vote.sequence < sequence || (last_vote.sequence == sequence && last_vote.hash < block_hash))
|
|
{
|
|
if (last_vote.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown))
|
|
{
|
|
should_process = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
replay = true;
|
|
}
|
|
}
|
|
if (should_process)
|
|
{
|
|
node.stats.inc (nano::stat::type::election, nano::stat::detail::vote_new);
|
|
last_votes[rep] = { std::chrono::steady_clock::now (), sequence, block_hash };
|
|
if (!confirmed ())
|
|
{
|
|
confirm_if_quorum ();
|
|
}
|
|
}
|
|
}
|
|
return nano::election_vote_result (replay, should_process);
|
|
}
|
|
|
|
bool nano::election::publish (std::shared_ptr<nano::block> block_a)
|
|
{
|
|
auto result (false);
|
|
if (blocks.size () >= 10)
|
|
{
|
|
if (last_tally[block_a->hash ()] < node.online_reps.online_stake () / 10)
|
|
{
|
|
result = true;
|
|
}
|
|
}
|
|
if (!result)
|
|
{
|
|
auto existing = blocks.find (block_a->hash ());
|
|
if (existing == blocks.end ())
|
|
{
|
|
blocks.emplace (std::make_pair (block_a->hash (), block_a));
|
|
insert_inactive_votes_cache (block_a->hash ());
|
|
node.network.flood_block (block_a, nano::buffer_drop_policy::no_limiter_drop);
|
|
}
|
|
else
|
|
{
|
|
result = true;
|
|
existing->second = block_a;
|
|
if (status.winner->hash () == block_a->hash ())
|
|
{
|
|
status.winner = block_a;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t nano::election::last_votes_size ()
|
|
{
|
|
nano::lock_guard<std::mutex> lock (node.active.mutex);
|
|
return last_votes.size ();
|
|
}
|
|
|
|
void nano::election::update_dependent ()
|
|
{
|
|
assert (!node.active.mutex.try_lock ());
|
|
std::vector<nano::block_hash> blocks_search;
|
|
auto hash (status.winner->hash ());
|
|
auto previous (status.winner->previous ());
|
|
if (!previous.is_zero ())
|
|
{
|
|
blocks_search.push_back (previous);
|
|
}
|
|
auto source (status.winner->source ());
|
|
if (!source.is_zero () && source != previous)
|
|
{
|
|
blocks_search.push_back (source);
|
|
}
|
|
auto link (status.winner->link ());
|
|
if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous)
|
|
{
|
|
blocks_search.push_back (link);
|
|
}
|
|
for (auto & block_search : blocks_search)
|
|
{
|
|
auto existing (node.active.blocks.find (block_search));
|
|
if (existing != node.active.blocks.end () && !existing->second->confirmed () && !existing->second->stopped)
|
|
{
|
|
if (existing->second->dependent_blocks.find (hash) == existing->second->dependent_blocks.end ())
|
|
{
|
|
existing->second->dependent_blocks.insert (hash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nano::election::clear_dependent ()
|
|
{
|
|
for (auto & dependent_block : dependent_blocks)
|
|
{
|
|
node.active.adjust_difficulty (dependent_block);
|
|
}
|
|
}
|
|
|
|
void nano::election::clear_blocks ()
|
|
{
|
|
auto winner_hash (status.winner->hash ());
|
|
for (auto & block : blocks)
|
|
{
|
|
auto & hash (block.first);
|
|
auto erased (node.active.blocks.erase (hash));
|
|
(void)erased;
|
|
// clear_blocks () can be called in active_transactions::publish () before blocks insertion if election was confirmed
|
|
assert (erased == 1 || confirmed ());
|
|
node.active.erase_inactive_votes_cache (hash);
|
|
// Notify observers about dropped elections & blocks lost confirmed elections
|
|
if (stopped || hash != winner_hash)
|
|
{
|
|
node.observers.active_stopped.notify (hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nano::election::insert_inactive_votes_cache (nano::block_hash const & hash_a)
|
|
{
|
|
auto cache (node.active.find_inactive_votes_cache (hash_a));
|
|
for (auto & rep : cache.voters)
|
|
{
|
|
auto inserted (last_votes.emplace (rep, nano::vote_info{ std::chrono::steady_clock::time_point::min (), 0, hash_a }));
|
|
if (inserted.second)
|
|
{
|
|
node.stats.inc (nano::stat::type::election, nano::stat::detail::vote_cached);
|
|
}
|
|
}
|
|
if (!confirmed () && !cache.voters.empty ())
|
|
{
|
|
auto delay (std::chrono::duration_cast<std::chrono::seconds> (std::chrono::steady_clock::now () - cache.arrival));
|
|
if (delay > late_blocks_delay)
|
|
{
|
|
node.stats.inc (nano::stat::type::election, nano::stat::detail::late_block);
|
|
node.stats.add (nano::stat::type::election, nano::stat::detail::late_block_seconds, nano::stat::dir::in, delay.count (), true);
|
|
}
|
|
confirm_if_quorum ();
|
|
}
|
|
}
|