Skip to content

Commit

Permalink
Return map of results from active_transactions::vote (...)
Browse files Browse the repository at this point in the history
  • Loading branch information
pwojcikdev committed Mar 20, 2024
1 parent 4d69ce5 commit 9af7abf
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 53 deletions.
32 changes: 16 additions & 16 deletions nano/core_test/active_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,19 +582,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
Expand All @@ -615,27 +615,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<nano::mutex> 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 ()));
}
}

Expand Down
12 changes: 6 additions & 6 deletions nano/core_test/election.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,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 ();
Expand Down Expand Up @@ -120,7 +120,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
Expand Down Expand Up @@ -156,7 +156,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 ());
}
Expand Down Expand Up @@ -187,7 +187,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);
Expand Down Expand Up @@ -250,7 +250,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);
Expand All @@ -264,7 +264,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 ()));
}
Expand Down
10 changes: 5 additions & 5 deletions nano/core_test/ledger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -931,9 +931,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::vote, node1.active.vote (vote2).at (send1->hash ()));
ASSERT_EQ (2, election1->votes ().size ());
auto votes1 (election1->votes ());
auto existing1 (votes1.find (nano::dev::genesis_key.pub));
Expand Down Expand Up @@ -972,7 +972,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);
Expand All @@ -994,13 +994,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 ());
Expand Down
6 changes: 3 additions & 3 deletions nano/core_test/vote_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,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::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ 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);
Expand Down Expand Up @@ -254,7 +254,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::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ 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<nano::election> election;
ASSERT_TIMELY (5s, election = node.active.election (send->qualified_root ()));
Expand Down Expand Up @@ -307,7 +307,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::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::milliseconds_since_epoch (), nano::vote::duration_max, std::vector<nano::block_hash>{ 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);
Expand Down
1 change: 1 addition & 0 deletions nano/lib/stats_enums.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ enum class detail : uint8_t
vote_indeterminate,
vote_invalid,
vote_overflow,
vote_ignored,

// election specific
vote_new,
Expand Down
41 changes: 21 additions & 20 deletions nano/node/active_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,69 +438,70 @@ 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<nano::vote> const & vote_a)
std::unordered_map<nano::block_hash, nano::vote_code> nano::active_transactions::vote (std::shared_ptr<nano::vote> 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::unordered_map<nano::block_hash, nano::vote_code> results;

std::vector<std::pair<std::shared_ptr<nano::election>, nano::block_hash>> process;
std::vector<nano::block_hash> inactive; // Hashes that should be added to inactive vote cache

{
nano::unique_lock<nano::mutex> lock{ mutex };
for (auto const & hash : vote_a->hashes)
for (auto const & hash : vote->hashes)
{
auto existing (blocks.find (hash));
if (existing != blocks.end ())
debug_assert (results.find (hash) == results.end ());

if (auto existing = blocks.find (hash); existing != blocks.end ())
{
process.emplace_back (existing->second, hash);
}
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;
}
}
}

// 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)
{
auto const vote_result = election->vote (vote_a->account, vote_a->timestamp (), block_hash);
auto const vote_result = election->vote (vote->account, vote->timestamp (), block_hash);
results[block_hash] = vote_result;

processed |= (vote_result == nano::vote_code::vote);
replay |= (vote_result == nano::vote_code::replay);
}

// 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
Expand Down
2 changes: 1 addition & 1 deletion nano/node/active_transactions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class active_transactions final
*/
nano::election_insertion_result insert (std::shared_ptr<nano::block> 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<nano::vote> const &);
std::unordered_map<nano::block_hash, nano::vote_code> vote (std::shared_ptr<nano::vote> const &);
// Is the root of this block in the roots container
bool active (nano::block const &) const;
bool active (nano::qualified_root const &) const;
Expand Down
20 changes: 18 additions & 2 deletions nano/node/vote_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,24 @@ void nano::vote_processor::verify_votes (decltype (votes) const & votes_a)

nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr<nano::vote> const & vote_a, std::shared_ptr<nano::transport::channel> 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)
{
Expand All @@ -188,6 +200,10 @@ nano::vote_code nano::vote_processor::vote_blocking (std::shared_ptr<nano::vote>
status = "Indeterminate";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_indeterminate);
break;
case nano::vote_code::ignored:
status = "Ignored";
stats.inc (nano::stat::type::vote, nano::stat::detail::vote_ignored);
break;
}

logger.trace (nano::log::type::vote_processor, nano::log::detail::vote_processed,
Expand Down

0 comments on commit 9af7abf

Please sign in to comment.