Return per hash vote result (#4510)
* Election use `nano::vote_code` * Return map of results from `active_transactions::vote (...)` * Fix test * Use `to_stat_detail (nano::vote_code)` helper * Ignore duplicate hashes when processing votes * Compilation fix * Fix compilation
This commit is contained in:
parent
e8beaa8f89
commit
d2e6f97283
13 changed files with 121 additions and 109 deletions
|
|
@ -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
|
// 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 });
|
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::vote, node.active.vote (vote_send1).at (send1->hash ()));
|
||||||
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 ()));
|
||||||
|
|
||||||
// Wait until the election is removed, at which point the vote is still a replay since it's been recently confirmed
|
// 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_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
|
// Open new account
|
||||||
auto vote_open1 = nano::test::make_final_vote (nano::dev::genesis_key, { open1 });
|
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::vote, node.active.vote (vote_open1).at (open1->hash ()));
|
||||||
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_TIMELY (5s, node.active.empty ());
|
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));
|
ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub));
|
||||||
|
|
||||||
// send 1 raw to key to key
|
// 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
|
// 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 vote1_send2 = nano::test::make_final_vote (nano::dev::genesis_key, { send2 });
|
||||||
auto vote2_send2 = nano::test::make_vote (key, { send2 }, 0, 0);
|
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 (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 (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
|
// 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_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 (vote1_send2).at (send2->hash ()));
|
||||||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2));
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2).at (send2->hash ()));
|
||||||
|
|
||||||
// Removing blocks as recently confirmed makes every vote indeterminate
|
// Removing blocks as recently confirmed makes every vote indeterminate
|
||||||
{
|
{
|
||||||
nano::lock_guard<nano::mutex> guard (node.active.mutex);
|
nano::lock_guard<nano::mutex> guard (node.active.mutex);
|
||||||
node.active.recently_confirmed.clear ();
|
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_send1).at (send1->hash ()));
|
||||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1));
|
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));
|
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));
|
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2).at (send2->hash ()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ TEST (election, quorum_minimum_flip_success)
|
||||||
ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2);
|
ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2);
|
||||||
|
|
||||||
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2->hash () });
|
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 ());
|
ASSERT_TIMELY (5s, election->confirmed ());
|
||||||
auto const winner = election->winner ();
|
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
|
// 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 () });
|
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
|
// 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
|
// 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_NE (nullptr, election);
|
||||||
ASSERT_EQ (1, election->blocks ().size ());
|
ASSERT_EQ (1, election->blocks ().size ());
|
||||||
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
|
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_NE (nullptr, node1.block (send1->hash ()));
|
||||||
ASSERT_TIMELY (5s, election->confirmed ());
|
ASSERT_TIMELY (5s, election->confirmed ());
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ TEST (election, quorum_minimum_confirm_fail)
|
||||||
ASSERT_EQ (1, election->blocks ().size ());
|
ASSERT_EQ (1, election->blocks ().size ());
|
||||||
|
|
||||||
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
|
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
|
// give the election a chance to confirm
|
||||||
WAIT (1s);
|
WAIT (1s);
|
||||||
|
|
@ -251,7 +251,7 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks)
|
||||||
ASSERT_EQ (1, election->blocks ().size ());
|
ASSERT_EQ (1, election->blocks ().size ());
|
||||||
|
|
||||||
auto vote1 = nano::test::make_final_vote (nano::dev::genesis_key, { send1->hash () });
|
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 ());
|
auto channel = node1.network.find_node_id (node2.get_node_id ());
|
||||||
ASSERT_NE (channel, nullptr);
|
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.
|
// 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;
|
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_TIMELY (5s, election->confirmed ());
|
||||||
ASSERT_NE (nullptr, node1.block (send1->hash ()));
|
ASSERT_NE (nullptr, node1.block (send1->hash ()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -932,9 +932,9 @@ TEST (votes, add_one)
|
||||||
auto election1 = node1.active.election (send1->qualified_root ());
|
auto election1 = node1.active.election (send1->qualified_root ());
|
||||||
ASSERT_EQ (1, election1->votes ().size ());
|
ASSERT_EQ (1, election1->votes ().size ());
|
||||||
auto vote1 = nano::test::make_vote (nano::dev::genesis_key, { send1 }, nano::vote::timestamp_min * 1, 0);
|
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);
|
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 ());
|
ASSERT_EQ (2, election1->votes ().size ());
|
||||||
auto votes1 (election1->votes ());
|
auto votes1 (election1->votes ());
|
||||||
auto existing1 (votes1.find (nano::dev::genesis_key.pub));
|
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 ()));
|
ASSERT_TIMELY (5s, node1.active.election (send1->qualified_root ()));
|
||||||
auto election1 = 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);
|
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
|
// Block is already processed from vote
|
||||||
ASSERT_TRUE (node1.active.publish (send1));
|
ASSERT_TRUE (node1.active.publish (send1));
|
||||||
ASSERT_EQ (nano::vote::timestamp_min * 1, election1->last_votes[nano::dev::genesis_key.pub].timestamp);
|
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);
|
auto vote_info1 = election1->get_last_vote (nano::dev::genesis_key.pub);
|
||||||
vote_info1.time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
vote_info1.time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
||||||
election1->set_last_vote (nano::dev::genesis_key.pub, vote_info1);
|
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);
|
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
|
// Also resend the old vote, and see if we respect the timestamp
|
||||||
auto vote_info2 = election1->get_last_vote (nano::dev::genesis_key.pub);
|
auto vote_info2 = election1->get_last_vote (nano::dev::genesis_key.pub);
|
||||||
vote_info2.time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
vote_info2.time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
||||||
election1->set_last_vote (nano::dev::genesis_key.pub, vote_info2);
|
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);
|
ASSERT_EQ (nano::vote::timestamp_min * 2, election1->votes ()[nano::dev::genesis_key.pub].timestamp);
|
||||||
auto votes (election1->votes ());
|
auto votes (election1->votes ());
|
||||||
ASSERT_EQ (2, votes.size ());
|
ASSERT_EQ (2, votes.size ());
|
||||||
|
|
|
||||||
|
|
@ -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
|
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.
|
// 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 () });
|
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.
|
// Make sure the vote was processed.
|
||||||
auto election (node.active.election (send->qualified_root ()));
|
auto election (node.active.election (send->qualified_root ()));
|
||||||
ASSERT_NE (nullptr, election);
|
ASSERT_NE (nullptr, election);
|
||||||
|
|
@ -256,7 +256,7 @@ TEST (vote_processor, local_broadcast_without_a_representative)
|
||||||
node.start_election (send);
|
node.start_election (send);
|
||||||
// Process a vote without a representative
|
// 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 () });
|
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.
|
// Make sure the vote was processed.
|
||||||
std::shared_ptr<nano::election> election;
|
std::shared_ptr<nano::election> election;
|
||||||
ASSERT_TIMELY (5s, election = node.active.election (send->qualified_root ()));
|
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.
|
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.
|
// 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 () });
|
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.
|
// Make sure the vote was processed.
|
||||||
auto election (node.active.election (send->qualified_root ()));
|
auto election (node.active.election (send->qualified_root ()));
|
||||||
ASSERT_NE (nullptr, election);
|
ASSERT_NE (nullptr, election);
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ enum class detail : uint8_t
|
||||||
none,
|
none,
|
||||||
success,
|
success,
|
||||||
unknown,
|
unknown,
|
||||||
|
cache,
|
||||||
|
|
||||||
// processing queue
|
// processing queue
|
||||||
queue,
|
queue,
|
||||||
|
|
@ -167,12 +168,15 @@ enum class detail : uint8_t
|
||||||
frontier_confirmation_failed,
|
frontier_confirmation_failed,
|
||||||
error_socket_close,
|
error_socket_close,
|
||||||
|
|
||||||
// vote specific
|
// vote result
|
||||||
vote_valid,
|
vote,
|
||||||
vote_replay,
|
valid,
|
||||||
vote_indeterminate,
|
replay,
|
||||||
vote_invalid,
|
indeterminate,
|
||||||
|
|
||||||
|
// vote processor
|
||||||
vote_overflow,
|
vote_overflow,
|
||||||
|
vote_ignored,
|
||||||
|
|
||||||
// election specific
|
// election specific
|
||||||
vote_new,
|
vote_new,
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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 };
|
std::unordered_map<nano::block_hash, nano::vote_code> results;
|
||||||
// If all hashes were recently confirmed then it is a replay
|
std::unordered_map<nano::block_hash, std::shared_ptr<nano::election>> process;
|
||||||
unsigned recently_confirmed_counter (0);
|
|
||||||
|
|
||||||
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
|
std::vector<nano::block_hash> inactive; // Hashes that should be added to inactive vote cache
|
||||||
|
|
||||||
{
|
{
|
||||||
nano::unique_lock<nano::mutex> lock{ mutex };
|
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));
|
// Ignore duplicate hashes (should not happen with a well-behaved voting node)
|
||||||
if (existing != blocks.end ())
|
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))
|
else if (!recently_confirmed.exists (hash))
|
||||||
{
|
{
|
||||||
inactive.emplace_back (hash);
|
inactive.emplace_back (hash);
|
||||||
|
results[hash] = nano::vote_code::indeterminate;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
++recently_confirmed_counter;
|
results[hash] = nano::vote_code::replay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -470,37 +472,38 @@ nano::vote_code nano::active_transactions::vote (std::shared_ptr<nano::vote> con
|
||||||
// Process inactive votes outside of the critical section
|
// Process inactive votes outside of the critical section
|
||||||
for (auto & hash : inactive)
|
for (auto & hash : inactive)
|
||||||
{
|
{
|
||||||
add_vote_cache (hash, vote_a);
|
add_vote_cache (hash, vote);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.empty ())
|
if (!process.empty ())
|
||||||
{
|
{
|
||||||
bool replay = false;
|
|
||||||
bool processed = 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);
|
auto const vote_result = election->vote (vote->account, vote->timestamp (), block_hash);
|
||||||
processed |= (vote_result == nano::election::vote_result::processed);
|
results[block_hash] = vote_result;
|
||||||
replay |= (vote_result == nano::election::vote_result::replay);
|
|
||||||
|
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)
|
// Republish vote if it is new and the node does not host a principal representative (or close to)
|
||||||
if (processed)
|
if (processed)
|
||||||
{
|
{
|
||||||
auto const reps (node.wallets.reps ());
|
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 ())
|
|
||||||
{
|
// All hashes should have their result set
|
||||||
result = nano::vote_code::replay;
|
debug_assert (std::all_of (vote->hashes.begin (), vote->hashes.end (), [&results] (auto const & hash) {
|
||||||
}
|
return results.find (hash) != results.end ();
|
||||||
return result;
|
}));
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nano::active_transactions::active (nano::qualified_root const & root_a) const
|
bool nano::active_transactions::active (nano::qualified_root const & root_a) const
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ public:
|
||||||
*/
|
*/
|
||||||
nano::election_insertion_result insert (std::shared_ptr<nano::block> const &, nano::election_behavior = nano::election_behavior::normal);
|
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
|
// 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
|
// Is the root of this block in the roots container
|
||||||
bool active (nano::block const &) const;
|
bool active (nano::block const &) const;
|
||||||
bool active (nano::qualified_root const &) const;
|
bool active (nano::qualified_root const &) const;
|
||||||
|
|
|
||||||
|
|
@ -434,12 +434,12 @@ std::shared_ptr<nano::block> nano::election::find (nano::block_hash const & hash
|
||||||
return result;
|
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);
|
auto weight = node.ledger.weight (rep);
|
||||||
if (!node.network_params.network.is_dev_network () && weight <= node.minimum_principal_weight ())
|
if (!node.network_params.network.is_dev_network () && weight <= node.minimum_principal_weight ())
|
||||||
{
|
{
|
||||||
return vote_result::ignored;
|
return vote_code::indeterminate;
|
||||||
}
|
}
|
||||||
|
|
||||||
nano::unique_lock<nano::mutex> lock{ mutex };
|
nano::unique_lock<nano::mutex> 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);
|
auto last_vote_l (last_vote_it->second);
|
||||||
if (last_vote_l.timestamp > timestamp_a)
|
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))
|
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<uint64_t>::max () && last_vote_l.timestamp < timestamp_a;
|
auto max_vote = timestamp_a == std::numeric_limits<uint64_t>::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)
|
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);
|
confirm_if_quorum (lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return vote_result::processed;
|
return vote_code::vote;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool nano::election::publish (std::shared_ptr<nano::block> const & block_a)
|
bool nano::election::publish (std::shared_ptr<nano::block> const & block_a)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <nano/node/election_behavior.hpp>
|
#include <nano/node/election_behavior.hpp>
|
||||||
#include <nano/node/election_status.hpp>
|
#include <nano/node/election_status.hpp>
|
||||||
#include <nano/node/vote_with_weight_info.hpp>
|
#include <nano/node/vote_with_weight_info.hpp>
|
||||||
|
#include <nano/secure/common.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -42,24 +43,10 @@ struct election_extended_status final
|
||||||
void operator() (nano::object_stream &) const;
|
void operator() (nano::object_stream &) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class election final : public std::enable_shared_from_this<nano::election>
|
class election final : public std::enable_shared_from_this<election>
|
||||||
{
|
{
|
||||||
nano::id_t const id{ nano::next_id () }; // Track individual objects when tracing
|
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:
|
private:
|
||||||
// Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations
|
// Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations
|
||||||
std::chrono::milliseconds base_latency () const;
|
std::chrono::milliseconds base_latency () const;
|
||||||
|
|
@ -117,7 +104,7 @@ public: // Interface
|
||||||
* Process vote. Internally uses cooldown to throttle non-final votes
|
* Process vote. Internally uses cooldown to throttle non-final votes
|
||||||
* If the election reaches consensus, it will be confirmed
|
* 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<nano::block> const & block_a);
|
bool publish (std::shared_ptr<nano::block> const & block_a);
|
||||||
// Confirm this block if quorum is met
|
// Confirm this block if quorum is met
|
||||||
void confirm_if_quorum (nano::unique_lock<nano::mutex> &);
|
void confirm_if_quorum (nano::unique_lock<nano::mutex> &);
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ std::size_t nano::vote_cache::entry::fill (std::shared_ptr<nano::election> const
|
||||||
std::size_t inserted = 0;
|
std::size_t inserted = 0;
|
||||||
for (const auto & entry : voters_m)
|
for (const auto & entry : voters_m)
|
||||||
{
|
{
|
||||||
auto result = election->vote (entry.representative, entry.timestamp, hash_m, nano::election::vote_source::cache);
|
auto result = election->vote (entry.representative, entry.timestamp, hash_m, nano::vote_source::cache);
|
||||||
if (result == nano::election::vote_result::processed)
|
if (result == nano::vote_code::vote)
|
||||||
{
|
{
|
||||||
inserted++;
|
inserted++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<nano::vote> const & vote_a, std::shared_ptr<nano::transport::channel> const & channel_a, bool validated)
|
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 ())
|
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);
|
observers.vote.notify (vote_a, channel_a, result);
|
||||||
}
|
}
|
||||||
std::string status;
|
|
||||||
switch (result)
|
stats.inc (nano::stat::type::vote, to_stat_detail (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;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace (nano::log::type::vote_processor, nano::log::detail::vote_processed,
|
logger.trace (nano::log::type::vote_processor, nano::log::detail::vote_processed,
|
||||||
nano::log::arg{ "vote", vote_a },
|
nano::log::arg{ "vote", vote_a },
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,20 @@ nano::block_hash const & nano::unchecked_key::key () const
|
||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nano::stat::detail nano::to_stat_detail (nano::vote_code code)
|
||||||
|
{
|
||||||
|
auto value = magic_enum::enum_cast<nano::stat::detail> (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<nano::stat::detail> (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)
|
std::string_view nano::to_string (nano::block_status code)
|
||||||
{
|
{
|
||||||
return magic_enum::enum_name (code);
|
return magic_enum::enum_name (code);
|
||||||
|
|
|
||||||
|
|
@ -191,9 +191,20 @@ enum class vote_code
|
||||||
invalid, // Vote is not signed correctly
|
invalid, // Vote is not signed correctly
|
||||||
replay, // Vote does not have the highest timestamp, it's a replay
|
replay, // Vote does not have the highest timestamp, it's a replay
|
||||||
vote, // Vote has the highest timestamp
|
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
|
enum class block_status
|
||||||
{
|
{
|
||||||
progress, // Hasn't been seen before, signed correctly
|
progress, // Hasn't been seen before, signed correctly
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue