dncurrency/nano/node/election.cpp
2025-06-04 15:00:44 +02:00

943 lines
28 KiB
C++

#include <nano/lib/blocks.hpp>
#include <nano/lib/enum_util.hpp>
#include <nano/node/active_elections.hpp>
#include <nano/node/confirmation_solicitor.hpp>
#include <nano/node/election.hpp>
#include <nano/node/local_vote_history.hpp>
#include <nano/node/network.hpp>
#include <nano/node/node.hpp>
#include <nano/node/online_reps.hpp>
#include <nano/node/vote_generator.hpp>
#include <nano/node/vote_router.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/vote.hpp>
using namespace std::chrono;
std::chrono::milliseconds nano::election::base_latency () const
{
return node.network_params.network.is_dev_network () ? 25ms : 1000ms;
}
/*
* election
*/
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> const & block_a, std::function<void (std::shared_ptr<nano::block> const &)> const & confirmation_action_a, std::function<void (nano::account const &)> const & vote_action_a, nano::election_behavior election_behavior_a) :
confirmation_action (confirmation_action_a),
vote_action (vote_action_a),
node (node_a),
behavior_m (election_behavior_a),
status (block_a),
height (block_a->sideband ().height),
root (block_a->root ()),
qualified_root (block_a->qualified_root ()),
account (block_a->account ())
{
last_votes.emplace (nano::account::null (), nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () });
last_blocks.emplace (block_a->hash (), block_a);
}
void nano::election::confirm_once (nano::unique_lock<nano::mutex> & lock)
{
debug_assert (lock.owns_lock ());
debug_assert (!mutex.try_lock ());
bool just_confirmed = state_m != nano::election_state::confirmed;
state_m = nano::election_state::confirmed;
if (just_confirmed)
{
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)> (last_blocks.size ());
status.voter_count = nano::narrow_cast<decltype (status.voter_count)> (last_votes.size ());
auto const status_l = status;
node.active.recently_confirmed.put (qualified_root, status_l.winner->hash ());
auto const extended_status = current_status_locked ();
node.stats.inc (nano::stat::type::election, nano::stat::detail::confirm_once);
node.logger.trace (nano::log::type::election, nano::log::detail::election_confirmed,
nano::log::arg{ "id", id },
nano::log::arg{ "qualified_root", qualified_root },
nano::log::arg{ "status", extended_status });
node.logger.debug (nano::log::type::election, "Election confirmed with winner: {} (behavior: {}, state: {}, voters: {}, blocks: {}, duration: {}ms, confirmation requests: {})",
status_l.winner->hash (),
to_string (behavior_m),
to_string (state_m),
extended_status.status.voter_count,
extended_status.status.block_count,
extended_status.status.election_duration.count (),
extended_status.status.confirmation_request_count);
node.cementing_set.add (status_l.winner->hash (), shared_from_this ());
lock.unlock ();
node.election_workers.post ([status_l, confirmation_action_l = confirmation_action] () {
if (confirmation_action_l)
{
confirmation_action_l (status_l.winner);
}
});
}
else
{
node.stats.inc (nano::stat::type::election, nano::stat::detail::confirm_once_failed);
lock.unlock ();
}
}
bool nano::election::valid_change (nano::election_state expected_a, nano::election_state desired_a) const
{
switch (expected_a)
{
case nano::election_state::passive:
switch (desired_a)
{
case nano::election_state::active:
case nano::election_state::confirmed:
case nano::election_state::expired_unconfirmed:
case nano::election_state::cancelled:
return true; // Valid
default:
break;
}
break;
case nano::election_state::active:
switch (desired_a)
{
case nano::election_state::confirmed:
case nano::election_state::expired_unconfirmed:
case nano::election_state::cancelled:
return true; // Valid
default:
break;
}
break;
case nano::election_state::confirmed:
switch (desired_a)
{
case nano::election_state::expired_confirmed:
return true; // Valid
default:
break;
}
break;
case nano::election_state::expired_unconfirmed:
case nano::election_state::expired_confirmed:
case nano::election_state::cancelled:
// No transitions are valid from these states
break;
}
return false;
}
bool nano::election::state_change (nano::election_state expected_a, nano::election_state desired_a)
{
bool result = true;
if (valid_change (expected_a, desired_a))
{
if (state_m == expected_a)
{
state_m = desired_a;
state_start = std::chrono::steady_clock::now ().time_since_epoch ();
result = false;
}
}
return result;
}
std::chrono::milliseconds nano::election::confirm_req_time () const
{
switch (behavior_m)
{
case election_behavior::manual:
case election_behavior::priority:
case election_behavior::hinted:
return base_latency () * 5;
case election_behavior::optimistic:
return base_latency () * 2;
}
debug_assert (false);
return {};
}
void nano::election::send_confirm_req (nano::confirmation_solicitor & solicitor_a)
{
debug_assert (!mutex.try_lock ());
if (confirm_req_time () < (std::chrono::steady_clock::now () - last_req))
{
if (!solicitor_a.add (*this))
{
last_req = std::chrono::steady_clock::now ();
++confirmation_request_count;
node.stats.inc (nano::stat::type::election, nano::stat::detail::confirmation_request);
node.logger.debug (nano::log::type::election, "Sent confirmation request for root: {} (behavior: {}, state: {}, voters: {}, blocks: {}, duration: {}ms, confirmation requests: {})",
qualified_root,
to_string (behavior_m),
to_string (state_m),
status.voter_count,
status.block_count,
duration ().count (),
confirmation_request_count.load ());
}
}
}
void nano::election::transition_active ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
state_change (nano::election_state::passive, nano::election_state::active);
}
bool nano::election::transition_priority ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
if (behavior_m == nano::election_behavior::priority || behavior_m == nano::election_behavior::manual)
{
return false;
}
behavior_m = nano::election_behavior::priority;
last_vote = std::chrono::steady_clock::time_point{}; // allow new outgoing votes immediately
node.logger.debug (nano::log::type::election, "Transitioned election behavior to priority from {} for root: {} (duration: {}ms)",
to_string (behavior_m),
qualified_root,
duration ().count ());
return true;
}
void nano::election::cancel ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
state_change (state_m, nano::election_state::cancelled);
}
bool nano::election::confirmed_locked () const
{
debug_assert (!mutex.try_lock ());
return state_m == nano::election_state::confirmed || state_m == nano::election_state::expired_confirmed;
}
bool nano::election::confirmed () const
{
nano::unique_lock<nano::mutex> lock{ mutex };
return confirmed_locked ();
}
bool nano::election::failed () const
{
nano::unique_lock<nano::mutex> lock{ mutex };
return state_m == nano::election_state::expired_unconfirmed;
}
bool nano::election::broadcast_block_predicate () const
{
debug_assert (!mutex.try_lock ());
// Broadcast the block if enough time has passed since the last broadcast (or it's the first broadcast)
if (last_block + node.config.network_params.network.block_broadcast_interval < std::chrono::steady_clock::now ())
{
return true;
}
// Or the current election winner has changed
if (status.winner->hash () != last_block_hash)
{
return true;
}
return false;
}
void nano::election::broadcast_block (nano::confirmation_solicitor & solicitor_a)
{
debug_assert (!mutex.try_lock ());
if (broadcast_block_predicate ())
{
if (!solicitor_a.broadcast (*this))
{
last_block = std::chrono::steady_clock::now ();
last_block_hash = status.winner->hash ();
node.stats.inc (nano::stat::type::election, last_block_hash.is_zero () ? nano::stat::detail::broadcast_block_initial : nano::stat::detail::broadcast_block_repeat);
node.logger.debug (nano::log::type::election, "Broadcasting current winner: {} for root: {} (behavior: {}, state: {}, voters: {}, blocks: {}, duration: {}ms)",
status.winner->hash (),
qualified_root,
to_string (behavior_m),
to_string (state_m),
status.voter_count,
status.block_count,
duration ().count ());
}
}
}
void nano::election::broadcast_vote ()
{
nano::unique_lock<nano::mutex> lock{ mutex };
broadcast_vote_locked (lock);
}
nano::vote_info nano::election::get_last_vote (nano::account const & account)
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_votes[account];
}
void nano::election::set_last_vote (nano::account const & account, nano::vote_info vote_info)
{
nano::lock_guard<nano::mutex> guard{ mutex };
last_votes[account] = vote_info;
}
nano::election_status nano::election::get_status () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return status;
}
bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a)
{
nano::unique_lock<nano::mutex> lock{ mutex };
bool result = false;
switch (state_m)
{
case nano::election_state::passive:
if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now ().time_since_epoch () - state_start)
{
state_change (nano::election_state::passive, nano::election_state::active);
}
break;
case nano::election_state::active:
broadcast_vote_locked (lock);
broadcast_block (solicitor_a);
send_confirm_req (solicitor_a);
break;
case nano::election_state::confirmed:
result = true; // Return true to indicate this election should be cleaned up
broadcast_block (solicitor_a); // Ensure election winner is broadcasted
state_change (nano::election_state::confirmed, nano::election_state::expired_confirmed);
break;
case nano::election_state::expired_unconfirmed:
case nano::election_state::expired_confirmed:
debug_assert (false);
break;
case nano::election_state::cancelled:
return true; // Clean up cancelled elections immediately
}
if (!confirmed_locked () && time_to_live () < std::chrono::steady_clock::now () - election_start)
{
// It is possible the election confirmed while acquiring the mutex
// state_change returning true would indicate it
if (!state_change (state_m, nano::election_state::expired_unconfirmed))
{
node.logger.trace (nano::log::type::election, nano::log::detail::election_expired,
nano::log::arg{ "id", id },
nano::log::arg{ "qualified_root", qualified_root },
nano::log::arg{ "status", current_status_locked () });
result = true; // Return true to indicate this election should be cleaned up
status.type = nano::election_status_type::stopped;
}
}
return result;
}
std::chrono::milliseconds nano::election::time_to_live () const
{
switch (behavior_m)
{
case election_behavior::manual:
case election_behavior::priority:
return std::chrono::milliseconds (5 * 60 * 1000);
case election_behavior::hinted:
case election_behavior::optimistic:
return std::chrono::milliseconds (30 * 1000);
}
debug_assert (false);
return {};
}
std::chrono::seconds nano::election::cooldown_time (nano::uint128_t weight) const
{
auto online_stake = node.online_reps.trended ();
if (weight > online_stake / 20) // Reps with more than 5% weight
{
return std::chrono::seconds{ 1 };
}
if (weight > online_stake / 100) // Reps with more than 1% weight
{
return std::chrono::seconds{ 5 };
}
// The rest of smaller reps
return std::chrono::seconds{ 15 };
}
bool nano::election::have_quorum (nano::tally_t const & tally_a) const
{
auto i (tally_a.begin ());
++i;
auto second (i != tally_a.end () ? i->first : 0);
auto delta_l (node.online_reps.delta ());
release_assert (tally_a.begin ()->first >= second);
bool result{ (tally_a.begin ()->first - second) >= delta_l };
return result;
}
nano::tally_t nano::election::tally () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return tally_impl ();
}
nano::tally_t nano::election::tally_impl () const
{
std::unordered_map<nano::block_hash, nano::uint128_t> block_weights;
std::unordered_map<nano::block_hash, nano::uint128_t> final_weights_l;
for (auto const & [account, info] : last_votes)
{
auto rep_weight (node.ledger.weight (account));
block_weights[info.hash] += rep_weight;
if (info.timestamp == std::numeric_limits<uint64_t>::max ())
{
final_weights_l[info.hash] += rep_weight;
}
}
last_tally = block_weights;
nano::tally_t result;
for (auto const & [hash, amount] : block_weights)
{
auto block (last_blocks.find (hash));
if (block != last_blocks.end ())
{
result.emplace (amount, block->second);
}
}
// Calculate final votes sum for winner
if (!final_weights_l.empty () && !result.empty ())
{
auto winner_hash (result.begin ()->second->hash ());
auto find_final (final_weights_l.find (winner_hash));
if (find_final != final_weights_l.end ())
{
final_weight = find_final->second;
}
}
return result;
}
void nano::election::confirm_if_quorum (nano::unique_lock<nano::mutex> & lock_a)
{
debug_assert (lock_a.owns_lock ());
auto tally_l (tally_impl ());
release_assert (!tally_l.empty ());
auto winner (tally_l.begin ());
auto block_l (winner->second);
auto const & winner_hash_l (block_l->hash ());
status.tally = winner->first;
status.final_tally = final_weight;
auto const & status_winner_hash_l (status.winner->hash ());
nano::uint128_t sum (0);
for (auto & i : tally_l)
{
sum += i.first;
}
if (sum >= node.online_reps.delta () && winner_hash_l != status_winner_hash_l)
{
status.winner = block_l;
remove_votes (status_winner_hash_l);
node.logger.debug (nano::log::type::election, "Winning fork changed from {} to {} for root: {} (behavior: {}, state: {}, voters: {}, blocks: {}, duration: {}ms)",
status_winner_hash_l,
winner_hash_l,
qualified_root,
to_string (behavior_m),
to_string (state_m),
status.voter_count,
status.block_count,
duration ().count ());
node.block_processor.force (block_l);
}
if (have_quorum (tally_l))
{
if (!is_quorum.exchange (true) && node.config.enable_voting && node.wallets.reps ().voting > 0)
{
++vote_broadcast_count;
node.final_generator.add (root, status.winner->hash ());
}
if (final_weight >= node.online_reps.delta ())
{
// In some edge cases block might get rolled back while the election is confirming, reprocess it to ensure it's present in the ledger
node.block_processor.add (block_l, nano::block_source::election);
confirm_once (lock_a);
debug_assert (!lock_a.owns_lock ());
}
}
}
void nano::election::try_confirm (nano::block_hash const & hash)
{
nano::unique_lock<nano::mutex> election_lock{ mutex };
auto winner = status.winner;
if (winner && winner->hash () == hash)
{
if (!confirmed_locked ())
{
confirm_once (election_lock);
debug_assert (!election_lock.owns_lock ());
}
}
}
std::shared_ptr<nano::block> nano::election::find (nano::block_hash const & hash_a) const
{
std::shared_ptr<nano::block> result;
nano::lock_guard<nano::mutex> guard{ mutex };
if (auto existing = last_blocks.find (hash_a); existing != last_blocks.end ())
{
result = existing->second;
}
return result;
}
nano::vote_code nano::election::vote (nano::account const & rep, uint64_t timestamp_a, nano::block_hash const & block_hash_a, nano::vote_source vote_source_a)
{
auto const weight = node.ledger.weight (rep);
if (!node.network_params.network.is_dev_network () && weight <= node.minimum_principal_weight ())
{
return vote_code::indeterminate;
}
nano::unique_lock<nano::mutex> lock{ mutex };
auto last_vote_it (last_votes.find (rep));
if (last_vote_it != last_votes.end ())
{
auto last_vote_l (last_vote_it->second);
if (last_vote_l.timestamp > timestamp_a)
{
return vote_code::replay;
}
if (last_vote_l.timestamp == timestamp_a && !(last_vote_l.hash < block_hash_a))
{
return vote_code::replay;
}
auto max_vote = timestamp_a == std::numeric_limits<uint64_t>::max () && last_vote_l.timestamp < timestamp_a;
bool past_cooldown = true;
if (vote_source_a != vote_source::cache) // Only cooldown live votes
{
const auto cooldown = cooldown_time (weight);
past_cooldown = last_vote_l.time <= std::chrono::steady_clock::now () - cooldown;
}
if (!max_vote && !past_cooldown)
{
return vote_code::ignored;
}
}
// Update voter list entry
last_votes[rep] = { std::chrono::steady_clock::now (), timestamp_a, block_hash_a };
node.stats.inc (nano::stat::type::election, nano::stat::detail::vote);
node.stats.inc (nano::stat::type::election_vote, to_stat_detail (vote_source_a));
node.logger.trace (nano::log::type::election, nano::log::detail::vote_processed,
nano::log::arg{ "id", id },
nano::log::arg{ "qualified_root", qualified_root },
nano::log::arg{ "account", rep },
nano::log::arg{ "hash", block_hash_a },
nano::log::arg{ "final", nano::vote::is_final_timestamp (timestamp_a) },
nano::log::arg{ "timestamp", timestamp_a },
nano::log::arg{ "vote_source", vote_source_a },
nano::log::arg{ "weight", weight });
node.logger.debug (nano::log::type::election, "Vote received for hash: {} from: {} for root: {} (final: {}, weight: {}, source: {})",
block_hash_a,
rep,
qualified_root,
nano::vote::is_final_timestamp (timestamp_a),
weight,
to_string (vote_source_a));
// This must execute before calculating the vote tally to ensure accurate online weight and quorum numbers are used
if (vote_action)
{
vote_action (rep);
}
if (!confirmed_locked ())
{
confirm_if_quorum (lock);
}
return vote_code::vote;
}
bool nano::election::publish (std::shared_ptr<nano::block> const & block_a)
{
nano::unique_lock<nano::mutex> lock{ mutex };
// Do not insert new blocks if already confirmed
auto result (confirmed_locked ());
if (!result && last_blocks.size () >= max_blocks && last_blocks.find (block_a->hash ()) == last_blocks.end ())
{
if (!replace_by_weight (lock, block_a->hash ()))
{
result = true;
node.network.filter.clear (block_a);
}
debug_assert (lock.owns_lock ());
}
if (!result)
{
auto existing = last_blocks.find (block_a->hash ());
if (existing == last_blocks.end ())
{
last_blocks.emplace (std::make_pair (block_a->hash (), block_a));
}
else
{
result = true;
existing->second = block_a;
if (status.winner->hash () == block_a->hash ())
{
status.winner = block_a;
}
}
}
/*
Result is true if:
1) election is confirmed or expired
2) given election contains 10 blocks & new block didn't receive enough votes to replace existing blocks
3) given block in already in election & election contains less than 10 blocks (replacing block content with new)
*/
return result;
}
nano::election_extended_status nano::election::current_status () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return current_status_locked ();
}
nano::election_extended_status nano::election::current_status_locked () const
{
debug_assert (!mutex.try_lock ());
nano::election_status status_l = status;
status_l.confirmation_request_count = confirmation_request_count;
status_l.vote_broadcast_count = vote_broadcast_count;
status_l.block_count = nano::narrow_cast<decltype (status_l.block_count)> (last_blocks.size ());
status_l.voter_count = nano::narrow_cast<decltype (status_l.voter_count)> (last_votes.size ());
return nano::election_extended_status{ status_l, last_votes, last_blocks, tally_impl () };
}
std::shared_ptr<nano::block> nano::election::winner () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return status.winner;
}
std::chrono::milliseconds nano::election::duration () const
{
return std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
}
void nano::election::broadcast_vote_locked (nano::unique_lock<nano::mutex> & lock)
{
debug_assert (lock.owns_lock ());
if (std::chrono::steady_clock::now () < last_vote + node.config.network_params.network.vote_broadcast_interval)
{
return;
}
last_vote = std::chrono::steady_clock::now ();
if (node.config.enable_voting && node.wallets.reps ().voting > 0)
{
node.stats.inc (nano::stat::type::election, nano::stat::detail::broadcast_vote);
++vote_broadcast_count;
if (confirmed_locked () || have_quorum (tally_impl ()))
{
node.stats.inc (nano::stat::type::election, nano::stat::detail::broadcast_vote_final);
node.logger.trace (nano::log::type::election, nano::log::detail::broadcast_vote,
nano::log::arg{ "id", id },
nano::log::arg{ "qualified_root", qualified_root },
nano::log::arg{ "winner", status.winner },
nano::log::arg{ "type", "final" });
node.final_generator.add (root, status.winner->hash ()); // Broadcasts vote to the network
}
else
{
node.stats.inc (nano::stat::type::election, nano::stat::detail::broadcast_vote_normal);
node.logger.trace (nano::log::type::election, nano::log::detail::broadcast_vote,
nano::log::arg{ "id", id },
nano::log::arg{ "qualified_root", qualified_root },
nano::log::arg{ "winner", status.winner },
nano::log::arg{ "type", "normal" });
node.generator.add (root, status.winner->hash ()); // Broadcasts vote to the network
}
}
}
void nano::election::remove_votes (nano::block_hash const & hash_a)
{
debug_assert (!mutex.try_lock ());
if (node.config.enable_voting && node.wallets.reps ().voting > 0)
{
// Remove votes from election
auto list_generated_votes (node.history.votes (root, hash_a));
for (auto const & vote : list_generated_votes)
{
last_votes.erase (vote->account);
}
// Clear votes cache
node.history.erase (root);
}
}
void nano::election::remove_block (nano::block_hash const & hash_a)
{
debug_assert (!mutex.try_lock ());
if (status.winner->hash () != hash_a)
{
if (auto existing = last_blocks.find (hash_a); existing != last_blocks.end ())
{
erase_if (last_votes, [hash_a] (auto const & entry) {
return entry.second.hash == hash_a;
});
node.network.filter.clear (existing->second);
last_blocks.erase (hash_a);
}
}
}
bool nano::election::replace_by_weight (nano::unique_lock<nano::mutex> & lock_a, nano::block_hash const & hash_a)
{
debug_assert (lock_a.owns_lock ());
nano::block_hash replaced_block (0);
auto winner_hash (status.winner->hash ());
// Sort existing blocks tally
std::vector<std::pair<nano::block_hash, nano::uint128_t>> sorted;
sorted.reserve (last_tally.size ());
std::copy (last_tally.begin (), last_tally.end (), std::back_inserter (sorted));
lock_a.unlock ();
// Sort in ascending order
std::sort (sorted.begin (), sorted.end (), [] (auto const & left, auto const & right) { return left.second < right.second; });
auto votes_tally = [this] (std::vector<std::shared_ptr<nano::vote>> const & votes) {
nano::uint128_t result{ 0 };
for (auto const & vote : votes)
{
result += node.ledger.weight (vote->account);
}
return result;
};
// Replace if lowest tally is below inactive cache new block weight
auto inactive_existing = node.vote_cache.find (hash_a);
auto inactive_tally = votes_tally (inactive_existing);
if (inactive_tally > 0 && sorted.size () < max_blocks)
{
// If count of tally items is less than 10, remove any block without tally
for (auto const & [hash, block] : blocks ())
{
if (std::find_if (sorted.begin (), sorted.end (), [&hash = hash] (auto const & item_a) { return item_a.first == hash; }) == sorted.end () && hash != winner_hash)
{
replaced_block = hash;
break;
}
}
}
else if (inactive_tally > 0 && inactive_tally > sorted.front ().second)
{
if (sorted.front ().first != winner_hash)
{
replaced_block = sorted.front ().first;
}
else if (inactive_tally > sorted[1].second)
{
// Avoid removing winner
replaced_block = sorted[1].first;
}
}
bool replaced (false);
if (!replaced_block.is_zero ())
{
node.vote_router.disconnect (replaced_block);
lock_a.lock ();
remove_block (replaced_block);
replaced = true;
}
else
{
lock_a.lock ();
}
return replaced;
}
void nano::election::force_confirm ()
{
release_assert (node.network_params.network.is_dev_network ());
nano::unique_lock<nano::mutex> lock{ mutex };
confirm_once (lock);
}
std::unordered_set<nano::block_hash> nano::election::blocks_hashes () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
std::unordered_set<nano::block_hash> hashes;
for (auto const & block : last_blocks)
{
hashes.emplace (block.first);
}
return hashes;
}
std::unordered_map<nano::block_hash, std::shared_ptr<nano::block>> nano::election::blocks () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_blocks;
}
std::unordered_map<nano::account, nano::vote_info> nano::election::votes () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_votes;
}
std::vector<nano::vote_with_weight_info> nano::election::votes_with_weight () const
{
std::multimap<nano::uint128_t, nano::vote_with_weight_info, std::greater<nano::uint128_t>> sorted_votes;
std::vector<nano::vote_with_weight_info> result;
auto votes_l (votes ());
for (auto const & vote_l : votes_l)
{
if (vote_l.first != nullptr)
{
auto amount (node.ledger.weight (vote_l.first));
nano::vote_with_weight_info vote_info{ vote_l.first, vote_l.second.time, vote_l.second.timestamp, vote_l.second.hash, amount };
sorted_votes.emplace (std::move (amount), vote_info);
}
}
result.reserve (sorted_votes.size ());
std::transform (sorted_votes.begin (), sorted_votes.end (), std::back_inserter (result), [] (auto const & entry) { return entry.second; });
return result;
}
nano::election_behavior nano::election::behavior () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return behavior_m;
}
nano::election_state nano::election::state () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return state_m;
}
bool nano::election::contains (nano::block_hash const & hash) const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_blocks.contains (hash);
}
size_t nano::election::voter_count () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_votes.size ();
}
size_t nano::election::block_count () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return last_blocks.size ();
}
void nano::election::operator() (nano::object_stream & obs) const
{
obs.write ("id", id);
obs.write ("qualified_root", qualified_root);
obs.write ("behavior", behavior_m);
obs.write ("height", height);
obs.write ("status", current_status ());
}
void nano::election_extended_status::operator() (nano::object_stream & obs) const
{
obs.write ("winner", status.winner->hash ());
obs.write ("tally_amount", status.tally.to_string_dec ());
obs.write ("final_tally_amount", status.final_tally.to_string_dec ());
obs.write ("confirmation_request_count", status.confirmation_request_count);
obs.write ("vote_broadcast_count", status.vote_broadcast_count);
obs.write ("block_count", status.block_count);
obs.write ("voter_count", status.voter_count);
obs.write ("type", status.type);
obs.write_range ("votes", votes, [] (auto const & entry, nano::object_stream & obs) {
auto & [account, info] = entry;
obs.write ("account", account);
obs.write ("hash", info.hash);
obs.write ("final", nano::vote::is_final_timestamp (info.timestamp));
obs.write ("timestamp", info.timestamp);
obs.write ("time", info.time.time_since_epoch ().count ());
});
obs.write_range ("blocks", blocks, [] (auto const & entry) {
auto [hash, block] = entry;
return block;
});
obs.write_range ("tally", tally, [] (auto const & entry, nano::object_stream & obs) {
auto & [amount, block] = entry;
obs.write ("hash", block->hash ());
obs.write ("amount", amount);
});
}
/*
*
*/
std::string_view nano::to_string (nano::election_behavior behavior)
{
return nano::enum_util::name (behavior);
}
nano::stat::detail nano::to_stat_detail (nano::election_behavior behavior)
{
return nano::enum_util::cast<nano::stat::detail> (behavior);
}
std::string_view nano::to_string (nano::election_state state)
{
return nano::enum_util::name (state);
}
nano::stat::detail nano::to_stat_detail (nano::election_state state)
{
return nano::enum_util::cast<nano::stat::detail> (state);
}