diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index ed9fef2d..d9cba058 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -583,19 +583,19 @@ TEST (active_transactions, vote_replays) // First vote is not a replay and confirms the election, second vote should be a replay since the election has confirmed but not yet removed auto vote_send1 = nano::test::make_final_vote (nano::dev::genesis_key, { send1 }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1)); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1).at (send1->hash ())); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1).at (send1->hash ())); // Wait until the election is removed, at which point the vote is still a replay since it's been recently confirmed ASSERT_TIMELY_EQ (5s, node.active.size (), 1); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1)); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1).at (send1->hash ())); // Open new account auto vote_open1 = nano::test::make_final_vote (nano::dev::genesis_key, { open1 }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1)); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1).at (open1->hash ())); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1).at (open1->hash ())); ASSERT_TIMELY (5s, node.active.empty ()); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1).at (open1->hash ())); ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub)); // send 1 raw to key to key @@ -616,27 +616,27 @@ TEST (active_transactions, vote_replays) // vote2_send2 is a non final vote with little weight, vote1_send2 is the vote that confirms the election auto vote1_send2 = nano::test::make_final_vote (nano::dev::genesis_key, { send2 }); auto vote2_send2 = nano::test::make_vote (key, { send2 }, 0, 0); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2_send2)); // this vote cannot confirm the election + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2_send2).at (send2->hash ())); // this vote cannot confirm the election ASSERT_EQ (1, node.active.size ()); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2)); // this vote cannot confirm the election + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2).at (send2->hash ())); // this vote cannot confirm the election ASSERT_EQ (1, node.active.size ()); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2)); // this vote confirms the election + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2).at (send2->hash ())); // this vote confirms the election // this should still return replay, either because the election is still in the AEC or because it is recently confirmed - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2)); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2).at (send2->hash ())); ASSERT_TIMELY (5s, node.active.empty ()); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2)); - ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2)); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2).at (send2->hash ())); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2).at (send2->hash ())); // Removing blocks as recently confirmed makes every vote indeterminate { nano::lock_guard guard (node.active.mutex); node.active.recently_confirmed.clear (); } - ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1)); - ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1)); - ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2)); - ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2)); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1).at (send1->hash ())); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1).at (open1->hash ())); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2).at (send2->hash ())); + ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2).at (send2->hash ())); } } diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp index c46731aa..ac4c47a6 100644 --- a/nano/core_test/election.cpp +++ b/nano/core_test/election.cpp @@ -72,7 +72,7 @@ TEST (election, quorum_minimum_flip_success) ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2); auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2->hash () }); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send2->hash ())); ASSERT_TIMELY (5s, election->confirmed ()); auto const winner = election->winner (); @@ -121,7 +121,7 @@ TEST (election, quorum_minimum_flip_fail) // genesis generates a final vote for send2 but it should not be enough to reach quorum due to the online_weight_minimum being so high auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2->hash () }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote).at (send2->hash ())); // give the election some time before asserting it is not confirmed so that in case // it would be wrongfully confirmed, have that immediately fail instead of race @@ -157,7 +157,7 @@ TEST (election, quorum_minimum_confirm_success) ASSERT_NE (nullptr, election); ASSERT_EQ (1, election->blocks ().size ()); auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () }); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send1->hash ())); ASSERT_NE (nullptr, node1.block (send1->hash ())); ASSERT_TIMELY (5s, election->confirmed ()); } @@ -188,7 +188,7 @@ TEST (election, quorum_minimum_confirm_fail) ASSERT_EQ (1, election->blocks ().size ()); auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () }); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote).at (send1->hash ())); // give the election a chance to confirm WAIT (1s); @@ -251,7 +251,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks) ASSERT_EQ (1, election->blocks ().size ()); auto vote1 = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () }); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1).at (send1->hash ())); auto channel = node1.network.find_node_id (node2.get_node_id ()); ASSERT_NE (channel, nullptr); @@ -265,7 +265,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks) // Modify online_m for online_reps to more than is available, this checks that voting below updates it to current online reps. node1.online_reps.online_m = node_config.online_weight_minimum.number () + 20; } - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2).at (send1->hash ())); ASSERT_TIMELY (5s, election->confirmed ()); ASSERT_NE (nullptr, node1.block (send1->hash ())); } diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 3ee507cb..647589b6 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -932,9 +932,9 @@ TEST (votes, add_one) auto election1 = node1.active.election (send1->qualified_root ()); ASSERT_EQ (1, election1->votes ().size ()); auto vote1 = nano::test::make_vote (nano::dev::genesis_key, { send1 }, nano::vote::timestamp_min * 1, 0); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1).at (send1->hash ())); auto vote2 = nano::test::make_vote (nano::dev::genesis_key, { send1 }, nano::vote::timestamp_min * 2, 0); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::ignored, node1.active.vote (vote2).at (send1->hash ())); // Ignored due to vote cooldown ASSERT_EQ (2, election1->votes ().size ()); auto votes1 (election1->votes ()); auto existing1 (votes1.find (nano::dev::genesis_key.pub)); @@ -973,7 +973,7 @@ TEST (votes, add_existing) ASSERT_TIMELY (5s, node1.active.election (send1->qualified_root ())); auto election1 = node1.active.election (send1->qualified_root ()); auto vote1 = nano::test::make_vote (nano::dev::genesis_key, { send1 }, nano::vote::timestamp_min * 1, 0); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1).at (send1->hash ())); // Block is already processed from vote ASSERT_TRUE (node1.active.publish (send1)); ASSERT_EQ (nano::vote::timestamp_min * 1, election1->last_votes[nano::dev::genesis_key.pub].timestamp); @@ -995,13 +995,13 @@ TEST (votes, add_existing) auto vote_info1 = election1->get_last_vote (nano::dev::genesis_key.pub); vote_info1.time = std::chrono::steady_clock::now () - std::chrono::seconds (20); election1->set_last_vote (nano::dev::genesis_key.pub, vote_info1); - ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2).at (send2->hash ())); ASSERT_EQ (nano::vote::timestamp_min * 2, election1->last_votes[nano::dev::genesis_key.pub].timestamp); // Also resend the old vote, and see if we respect the timestamp auto vote_info2 = election1->get_last_vote (nano::dev::genesis_key.pub); vote_info2.time = std::chrono::steady_clock::now () - std::chrono::seconds (20); election1->set_last_vote (nano::dev::genesis_key.pub, vote_info2); - ASSERT_EQ (nano::vote_code::replay, node1.active.vote (vote1)); + ASSERT_EQ (nano::vote_code::replay, node1.active.vote (vote1).at (send1->hash ())); ASSERT_EQ (nano::vote::timestamp_min * 2, election1->votes ()[nano::dev::genesis_key.pub].timestamp); auto votes (election1->votes ()); ASSERT_EQ (2, votes.size ()); diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp index 2af6b1e9..8ed2dd22 100644 --- a/nano/core_test/vote_processor.cpp +++ b/nano/core_test/vote_processor.cpp @@ -208,7 +208,7 @@ TEST (vote_processor, no_broadcast_local) ASSERT_FALSE (node.wallets.reps ().have_half_rep ()); // Genesis balance remaining after `send' is less than the half_rep threshold // Process a vote with a key that is in the local wallet. auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector{ send->hash () }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote).at (send->hash ())); // Make sure the vote was processed. auto election (node.active.election (send->qualified_root ())); ASSERT_NE (nullptr, election); @@ -256,7 +256,7 @@ TEST (vote_processor, local_broadcast_without_a_representative) node.start_election (send); // Process a vote without a representative auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector{ send->hash () }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote).at (send->hash ())); // Make sure the vote was processed. std::shared_ptr election; ASSERT_TIMELY (5s, election = node.active.election (send->qualified_root ())); @@ -309,7 +309,7 @@ TEST (vote_processor, no_broadcast_local_with_a_principal_representative) ASSERT_TRUE (node.wallets.reps ().have_half_rep ()); // Genesis balance after `send' is over both half_rep and PR threshold. // Process a vote with a key that is in the local wallet. auto vote = std::make_shared (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector{ send->hash () }); - ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote)); + ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote).at (send->hash ())); // Make sure the vote was processed. auto election (node.active.election (send->qualified_root ())); ASSERT_NE (nullptr, election); diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index b1baaa96..01bf4b62 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -84,6 +84,7 @@ enum class detail : uint8_t none, success, unknown, + cache, // processing queue queue, @@ -167,12 +168,15 @@ enum class detail : uint8_t frontier_confirmation_failed, error_socket_close, - // vote specific - vote_valid, - vote_replay, - vote_indeterminate, - vote_invalid, + // vote result + vote, + valid, + replay, + indeterminate, + + // vote processor vote_overflow, + vote_ignored, // election specific vote_new, diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 350b4c28..9603721a 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -438,31 +438,33 @@ nano::election_insertion_result nano::active_transactions::insert (std::shared_p } // Validate a vote and apply it to the current election if one exists -nano::vote_code nano::active_transactions::vote (std::shared_ptr const & vote_a) +std::unordered_map nano::active_transactions::vote (std::shared_ptr const & vote) { - nano::vote_code result{ nano::vote_code::indeterminate }; - // If all hashes were recently confirmed then it is a replay - unsigned recently_confirmed_counter (0); - - std::vector, nano::block_hash>> process; + std::unordered_map results; + std::unordered_map> process; std::vector inactive; // Hashes that should be added to inactive vote cache - { nano::unique_lock lock{ mutex }; - for (auto const & hash : vote_a->hashes) + for (auto const & hash : vote->hashes) { - auto existing (blocks.find (hash)); - if (existing != blocks.end ()) + // Ignore duplicate hashes (should not happen with a well-behaved voting node) + if (results.find (hash) != results.end ()) { - process.emplace_back (existing->second, hash); + continue; + } + + if (auto existing = blocks.find (hash); existing != blocks.end ()) + { + process[hash] = existing->second; } else if (!recently_confirmed.exists (hash)) { inactive.emplace_back (hash); + results[hash] = nano::vote_code::indeterminate; } else { - ++recently_confirmed_counter; + results[hash] = nano::vote_code::replay; } } } @@ -470,37 +472,38 @@ nano::vote_code nano::active_transactions::vote (std::shared_ptr con // Process inactive votes outside of the critical section for (auto & hash : inactive) { - add_vote_cache (hash, vote_a); + add_vote_cache (hash, vote); } if (!process.empty ()) { - bool replay = false; bool processed = false; - for (auto const & [election, block_hash] : process) + for (auto const & [block_hash, election] : process) { - auto const vote_result = election->vote (vote_a->account, vote_a->timestamp (), block_hash); - processed |= (vote_result == nano::election::vote_result::processed); - replay |= (vote_result == nano::election::vote_result::replay); + auto const vote_result = election->vote (vote->account, vote->timestamp (), block_hash); + results[block_hash] = vote_result; + + processed |= (vote_result == nano::vote_code::vote); } // Republish vote if it is new and the node does not host a principal representative (or close to) if (processed) { auto const reps (node.wallets.reps ()); - if (!reps.have_half_rep () && !reps.exists (vote_a->account)) + if (!reps.have_half_rep () && !reps.exists (vote->account)) { - node.network.flood_vote (vote_a, 0.5f); + node.network.flood_vote (vote, 0.5f); } } - result = replay ? nano::vote_code::replay : nano::vote_code::vote; } - else if (recently_confirmed_counter == vote_a->hashes.size ()) - { - result = nano::vote_code::replay; - } - return result; + + // All hashes should have their result set + debug_assert (std::all_of (vote->hashes.begin (), vote->hashes.end (), [&results] (auto const & hash) { + return results.find (hash) != results.end (); + })); + + return results; } bool nano::active_transactions::active (nano::qualified_root const & root_a) const diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index da475cfd..a0eef10b 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -152,7 +152,7 @@ public: */ nano::election_insertion_result insert (std::shared_ptr const &, nano::election_behavior = nano::election_behavior::normal); // Distinguishes replay votes, cannot be determined if the block is not in any election - nano::vote_code vote (std::shared_ptr const &); + std::unordered_map vote (std::shared_ptr const &); // Is the root of this block in the roots container bool active (nano::block const &) const; bool active (nano::qualified_root const &) const; diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 3b2b5be4..f6780637 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -434,12 +434,12 @@ std::shared_ptr nano::election::find (nano::block_hash const & hash return result; } -auto nano::election::vote (nano::account const & rep, uint64_t timestamp_a, nano::block_hash const & block_hash_a, vote_source vote_source_a) -> vote_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 weight = node.ledger.weight (rep); if (!node.network_params.network.is_dev_network () && weight <= node.minimum_principal_weight ()) { - return vote_result::ignored; + return vote_code::indeterminate; } nano::unique_lock lock{ mutex }; @@ -450,11 +450,11 @@ auto nano::election::vote (nano::account const & rep, uint64_t timestamp_a, nano auto last_vote_l (last_vote_it->second); if (last_vote_l.timestamp > timestamp_a) { - return vote_result::replay; + return vote_code::replay; } if (last_vote_l.timestamp == timestamp_a && !(last_vote_l.hash < block_hash_a)) { - return vote_result::replay; + return vote_code::replay; } auto max_vote = timestamp_a == std::numeric_limits::max () && last_vote_l.timestamp < timestamp_a; @@ -468,7 +468,7 @@ auto nano::election::vote (nano::account const & rep, uint64_t timestamp_a, nano if (!max_vote && !past_cooldown) { - return vote_result::ignored; + return vote_code::ignored; } } @@ -494,7 +494,7 @@ auto nano::election::vote (nano::account const & rep, uint64_t timestamp_a, nano confirm_if_quorum (lock); } - return vote_result::processed; + return vote_code::vote; } bool nano::election::publish (std::shared_ptr const & block_a) diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 22dc8ab5..6f8b61ca 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -42,24 +43,10 @@ struct election_extended_status final void operator() (nano::object_stream &) const; }; -class election final : public std::enable_shared_from_this +class election final : public std::enable_shared_from_this { nano::id_t const id{ nano::next_id () }; // Track individual objects when tracing -public: - enum class vote_source - { - live, - cache, - }; - - enum class vote_result - { - ignored, - processed, - replay, - }; - private: // Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations std::chrono::milliseconds base_latency () const; @@ -117,7 +104,7 @@ public: // Interface * Process vote. Internally uses cooldown to throttle non-final votes * If the election reaches consensus, it will be confirmed */ - vote_result vote (nano::account const & representative, uint64_t timestamp, nano::block_hash const & block_hash, vote_source = vote_source::live); + nano::vote_code vote (nano::account const & representative, uint64_t timestamp, nano::block_hash const & block_hash, nano::vote_source = nano::vote_source::live); bool publish (std::shared_ptr const & block_a); // Confirm this block if quorum is met void confirm_if_quorum (nano::unique_lock &); diff --git a/nano/node/vote_cache.cpp b/nano/node/vote_cache.cpp index 7779b0af..4edab314 100644 --- a/nano/node/vote_cache.cpp +++ b/nano/node/vote_cache.cpp @@ -69,8 +69,8 @@ std::size_t nano::vote_cache::entry::fill (std::shared_ptr const std::size_t inserted = 0; for (const auto & entry : voters_m) { - auto result = election->vote (entry.representative, entry.timestamp, hash_m, nano::election::vote_source::cache); - if (result == nano::election::vote_result::processed) + auto result = election->vote (entry.representative, entry.timestamp, hash_m, nano::vote_source::cache); + if (result == nano::vote_code::vote) { inserted++; } diff --git a/nano/node/vote_processor.cpp b/nano/node/vote_processor.cpp index 337b0bdd..7b70aa0e 100644 --- a/nano/node/vote_processor.cpp +++ b/nano/node/vote_processor.cpp @@ -163,32 +163,25 @@ void nano::vote_processor::verify_votes (decltype (votes) const & votes_a) nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr const & vote_a, std::shared_ptr const & channel_a, bool validated) { - auto result (nano::vote_code::invalid); + auto result = nano::vote_code::invalid; if (validated || !vote_a->validate ()) { - result = active.vote (vote_a); + auto vote_results = active.vote (vote_a); + + // Aggregate results for individual hashes + bool replay = false; + bool processed = false; + for (auto const & [hash, hash_result] : vote_results) + { + replay |= (hash_result == nano::vote_code::replay); + processed |= (hash_result == nano::vote_code::vote); + } + result = replay ? nano::vote_code::replay : (processed ? nano::vote_code::vote : nano::vote_code::indeterminate); + observers.vote.notify (vote_a, channel_a, result); } - std::string status; - switch (result) - { - case nano::vote_code::invalid: - status = "Invalid"; - stats.inc (nano::stat::type::vote, nano::stat::detail::vote_invalid); - break; - case nano::vote_code::replay: - status = "Replay"; - stats.inc (nano::stat::type::vote, nano::stat::detail::vote_replay); - break; - case nano::vote_code::vote: - status = "Vote"; - stats.inc (nano::stat::type::vote, nano::stat::detail::vote_valid); - break; - case nano::vote_code::indeterminate: - status = "Indeterminate"; - stats.inc (nano::stat::type::vote, nano::stat::detail::vote_indeterminate); - break; - } + + stats.inc (nano::stat::type::vote, to_stat_detail (result)); logger.trace (nano::log::type::vote_processor, nano::log::detail::vote_processed, nano::log::arg{ "vote", vote_a }, diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 38520533..40dfbed7 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -341,6 +341,20 @@ nano::block_hash const & nano::unchecked_key::key () const return previous; } +nano::stat::detail nano::to_stat_detail (nano::vote_code code) +{ + auto value = magic_enum::enum_cast (magic_enum::enum_name (code)); + debug_assert (value); + return value.value_or (nano::stat::detail{}); +} + +nano::stat::detail nano::to_stat_detail (nano::vote_source source) +{ + auto value = magic_enum::enum_cast (magic_enum::enum_name (source)); + debug_assert (value); + return value.value_or (nano::stat::detail{}); +} + std::string_view nano::to_string (nano::block_status code) { return magic_enum::enum_name (code); diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index aab26e8b..ff6c6166 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -191,9 +191,20 @@ enum class vote_code invalid, // Vote is not signed correctly replay, // Vote does not have the highest timestamp, it's a replay vote, // Vote has the highest timestamp - indeterminate // Unknown if replay or vote + indeterminate, // Unknown if replay or vote + ignored, // Vote is valid, but got ingored (e.g. due to cooldown) }; +nano::stat::detail to_stat_detail (vote_code); + +enum class vote_source +{ + live, + cache, +}; + +nano::stat::detail to_stat_detail (vote_source); + enum class block_status { progress, // Hasn't been seen before, signed correctly