Indeterminate vote status and enhanced websocket vote sub (#2444)
* Indeterminate vote status and enhanced websocket vote sub * unhandled stats switch case * disambiguate conditional expression * Return vote_code directly from active.vote and replace tribool with simpler approach
This commit is contained in:
parent
97e164f10f
commit
d511b1fed6
14 changed files with 251 additions and 146 deletions
|
@ -681,3 +681,49 @@ TEST (active_transactions, restart_dropped)
|
|||
ASSERT_EQ (work2, block->block_work ());
|
||||
}
|
||||
}
|
||||
|
||||
TEST (active_transactions, vote_replays)
|
||||
{
|
||||
nano::system system;
|
||||
nano::node_config node_config (nano::get_available_port (), system.logging);
|
||||
node_config.enable_voting = false;
|
||||
auto & node = *system.add_node (node_config);
|
||||
nano::genesis genesis;
|
||||
nano::keypair key;
|
||||
std::error_code ec;
|
||||
auto send1 (std::make_shared<nano::state_block> (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ())));
|
||||
ASSERT_NE (nullptr, send1);
|
||||
auto open1 (std::make_shared<nano::state_block> (key.pub, 0, key.pub, nano::Gxrb_ratio, send1->hash (), key.prv, key.pub, *system.work.generate (key.pub)));
|
||||
ASSERT_NE (nullptr, open1);
|
||||
node.process_active (send1);
|
||||
node.process_active (open1);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (2, node.active.size ());
|
||||
// First vote is not a replay and confirms the election, second vote should be indeterminate since the election no longer exists
|
||||
auto vote_send1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1));
|
||||
// Open new account
|
||||
auto vote_open1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, open1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1));
|
||||
ASSERT_TRUE (node.active.empty ());
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1));
|
||||
ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub));
|
||||
|
||||
auto send2 (std::make_shared<nano::state_block> (key.pub, open1->hash (), key.pub, nano::Gxrb_ratio - 1, key.pub, key.prv, key.pub, *system.work.generate (open1->hash ())));
|
||||
ASSERT_NE (nullptr, send2);
|
||||
node.process_active (send2);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
auto vote1_send2 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send2));
|
||||
auto vote2_send2 (std::make_shared<nano::vote> (key.pub, key.prv, 0, send2));
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2_send2));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2));
|
||||
ASSERT_EQ (0, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2));
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2));
|
||||
}
|
||||
|
|
|
@ -760,9 +760,9 @@ TEST (votes, add_one)
|
|||
ASSERT_EQ (1, votes1->last_votes.size ());
|
||||
lock.unlock ();
|
||||
auto vote1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1));
|
||||
ASSERT_FALSE (node1.active.vote (vote1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1));
|
||||
auto vote2 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1));
|
||||
ASSERT_FALSE (node1.active.vote (vote2));
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node1.active.vote (vote2));
|
||||
lock.lock ();
|
||||
ASSERT_EQ (2, votes1->last_votes.size ());
|
||||
auto existing1 (votes1->last_votes.find (nano::test_genesis_key.pub));
|
||||
|
@ -790,9 +790,9 @@ TEST (votes, add_two)
|
|||
nano::keypair key2;
|
||||
auto send2 (std::make_shared<nano::send_block> (genesis.hash (), key2.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0));
|
||||
auto vote2 (std::make_shared<nano::vote> (key2.pub, key2.prv, 1, send2));
|
||||
ASSERT_FALSE (node1.active.vote (vote2));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2));
|
||||
auto vote1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1));
|
||||
ASSERT_FALSE (node1.active.vote (vote1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1));
|
||||
lock.lock ();
|
||||
ASSERT_EQ (3, votes1->last_votes.size ());
|
||||
ASSERT_NE (votes1->last_votes.end (), votes1->last_votes.find (nano::test_genesis_key.pub));
|
||||
|
@ -821,7 +821,7 @@ TEST (votes, add_existing)
|
|||
}
|
||||
node1.active.start (send1);
|
||||
auto vote1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1));
|
||||
ASSERT_FALSE (node1.active.vote (vote1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1));
|
||||
// Block is already processed from vote
|
||||
ASSERT_TRUE (node1.active.publish (send1));
|
||||
nano::unique_lock<std::mutex> lock (node1.active.mutex);
|
||||
|
@ -834,14 +834,14 @@ TEST (votes, add_existing)
|
|||
// Pretend we've waited the timeout
|
||||
votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
||||
lock.unlock ();
|
||||
ASSERT_FALSE (node1.active.vote (vote2));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2));
|
||||
ASSERT_FALSE (node1.active.publish (send2));
|
||||
lock.lock ();
|
||||
ASSERT_EQ (2, votes1->last_votes[nano::test_genesis_key.pub].sequence);
|
||||
// Also resend the old vote, and see if we respect the sequence number
|
||||
votes1->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20);
|
||||
lock.unlock ();
|
||||
ASSERT_TRUE (node1.active.vote (vote1));
|
||||
ASSERT_EQ (nano::vote_code::replay, node1.active.vote (vote1));
|
||||
lock.lock ();
|
||||
ASSERT_EQ (2, votes1->last_votes[nano::test_genesis_key.pub].sequence);
|
||||
ASSERT_EQ (2, votes1->last_votes.size ());
|
||||
|
|
|
@ -2535,7 +2535,7 @@ TEST (node, vote_by_hash_bundle)
|
|||
nano::keypair key1;
|
||||
system.wallet (0)->insert_adhoc (key1.prv);
|
||||
|
||||
system.nodes[0]->observers.vote.add ([&max_hashes](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel> channel_a) {
|
||||
system.nodes[0]->observers.vote.add ([&max_hashes](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel>, nano::vote_code) {
|
||||
if (vote_a->blocks.size () > max_hashes)
|
||||
{
|
||||
max_hashes = vote_a->blocks.size ();
|
||||
|
|
|
@ -80,15 +80,11 @@ boost::optional<std::string> websocket_test_call (std::string host, std::string
|
|||
/** Tests clients subscribing multiple times or unsubscribing without a subscription */
|
||||
TEST (websocket, subscription_edge)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation));
|
||||
|
||||
|
@ -158,15 +154,11 @@ TEST (websocket, subscription_edge)
|
|||
// Test client subscribing to changes in active_difficulty
|
||||
TEST (websocket, active_difficulty)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::active_difficulty));
|
||||
|
||||
|
@ -226,16 +218,11 @@ TEST (websocket, active_difficulty)
|
|||
/** Subscribes to block confirmations, confirms a block and then awaits websocket notification */
|
||||
TEST (websocket, confirmation)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->wallets.create (nano::random_wallet_id ());
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
// Start websocket test-client in a separate thread
|
||||
ack_ready = false;
|
||||
|
@ -266,7 +253,7 @@ TEST (websocket, confirmation)
|
|||
ASSERT_TRUE (node1->websocket_server->any_subscriber (nano::websocket::topic::confirmation));
|
||||
|
||||
nano::keypair key;
|
||||
system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
auto balance = nano::genesis_amount;
|
||||
auto send_amount = node1->config.online_weight_minimum.number () + 1;
|
||||
// Quick-confirm a block, legacy blocks should work without filtering
|
||||
|
@ -334,16 +321,11 @@ TEST (websocket, confirmation)
|
|||
/** Tests getting notification of an erased election */
|
||||
TEST (websocket, stopped_election)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->wallets.create (nano::random_wallet_id ());
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
// Start websocket test-client in a separate thread
|
||||
ack_ready = false;
|
||||
|
@ -394,16 +376,11 @@ TEST (websocket, stopped_election)
|
|||
/** Tests the filtering options of block confirmations */
|
||||
TEST (websocket, confirmation_options)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->wallets.create (nano::random_wallet_id ());
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
// Start websocket test-client in a separate thread
|
||||
ack_ready = false;
|
||||
|
@ -427,7 +404,7 @@ TEST (websocket, confirmation_options)
|
|||
ack_ready = false;
|
||||
|
||||
// Confirm a state block for an in-wallet account
|
||||
system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
nano::keypair key;
|
||||
auto balance = nano::genesis_amount;
|
||||
auto send_amount = node1->config.online_weight_minimum.number () + 1;
|
||||
|
@ -545,16 +522,11 @@ TEST (websocket, confirmation_options)
|
|||
/** Subscribes to votes, sends a block and awaits websocket notification of a vote arrival */
|
||||
TEST (websocket, vote)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->wallets.create (nano::random_wallet_id ());
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
// Start websocket test-client in a separate thread
|
||||
ack_ready = false;
|
||||
|
@ -587,7 +559,7 @@ TEST (websocket, vote)
|
|||
|
||||
// Quick-confirm a block
|
||||
nano::keypair key;
|
||||
system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
nano::block_hash previous (node1->latest (nano::test_genesis_key.pub));
|
||||
auto send (std::make_shared<nano::state_block> (nano::test_genesis_key.pub, previous, nano::test_genesis_key.pub, nano::genesis_amount - (node1->config.online_weight_minimum.number () + 1), key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous)));
|
||||
node1->process_active (send);
|
||||
|
@ -603,19 +575,78 @@ TEST (websocket, vote)
|
|||
node1->stop ();
|
||||
}
|
||||
|
||||
/** Tests vote subscription options */
|
||||
TEST (websocket, vote_options)
|
||||
/** Tests vote subscription options - vote type */
|
||||
TEST (websocket, vote_options_type)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->wallets.create (nano::random_wallet_id ());
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::vote));
|
||||
|
||||
// Subscribe to votes and wait for response asynchronously
|
||||
ack_ready = false;
|
||||
std::atomic<bool> replay_received{ false };
|
||||
std::thread client_thread ([&replay_received, config]() {
|
||||
auto response = websocket_test_call ("::1", std::to_string (config.websocket_config.port),
|
||||
R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"include_replays": "true", "include_indeterminate": "false"}})json", true, true);
|
||||
ASSERT_TRUE (response);
|
||||
boost::property_tree::ptree event;
|
||||
std::stringstream stream;
|
||||
stream << response;
|
||||
boost::property_tree::read_json (stream, event);
|
||||
auto message_contents = event.get_child ("message");
|
||||
ASSERT_EQ (1, message_contents.count ("type"));
|
||||
ASSERT_EQ ("replay", message_contents.get<std::string> ("type"));
|
||||
replay_received = true;
|
||||
});
|
||||
|
||||
// Wait for acknowledge
|
||||
system.deadline_set (5s);
|
||||
while (!ack_ready)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::vote));
|
||||
|
||||
// Custom made votes for simplicity
|
||||
nano::genesis genesis;
|
||||
auto vote (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open));
|
||||
|
||||
// Indeterminates are not included
|
||||
{
|
||||
nano::websocket::message_builder builder;
|
||||
auto msg (builder.vote_received (vote, nano::vote_code::indeterminate));
|
||||
node1->websocket_server->broadcast (msg);
|
||||
}
|
||||
|
||||
// Replays are included
|
||||
{
|
||||
nano::websocket::message_builder builder;
|
||||
auto msg (builder.vote_received (vote, nano::vote_code::replay));
|
||||
node1->websocket_server->broadcast (msg);
|
||||
}
|
||||
|
||||
// Wait for the websocket client
|
||||
system.deadline_set (5s);
|
||||
while (!replay_received)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
client_thread.join ();
|
||||
node1->stop ();
|
||||
}
|
||||
|
||||
/** Tests vote subscription options - list of representatives */
|
||||
TEST (websocket, vote_options_representatives)
|
||||
{
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
// Start websocket test-client in a separate thread
|
||||
ack_ready = false;
|
||||
|
@ -650,7 +681,7 @@ TEST (websocket, vote_options)
|
|||
// Quick-confirm a block
|
||||
nano::keypair key;
|
||||
auto balance = nano::genesis_amount;
|
||||
system.wallet (1)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
auto send_amount = node1->config.online_weight_minimum.number () + 1;
|
||||
auto confirm_block = [&]() {
|
||||
nano::block_hash previous (node1->latest (nano::test_genesis_key.pub));
|
||||
|
@ -670,10 +701,10 @@ TEST (websocket, vote_options)
|
|||
std::atomic<bool> client_thread_2_finished{ false };
|
||||
std::thread client_thread_2 ([&client_thread_2_finished, config]() {
|
||||
auto response = websocket_test_call ("::1", std::to_string (config.websocket_config.port),
|
||||
R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"representatives": ["xrb_invalid"]}})json", true, true, 1s);
|
||||
R"json({"action": "subscribe", "topic": "vote", "ack": true, "options": {"representatives": ["xrb_invalid"]}})json", true, true);
|
||||
|
||||
// No response expected given the filter
|
||||
ASSERT_FALSE (response);
|
||||
// A list of invalid representatives is the same as no filter
|
||||
ASSERT_TRUE (response);
|
||||
client_thread_2_finished = true;
|
||||
});
|
||||
|
||||
|
@ -690,7 +721,6 @@ TEST (websocket, vote_options)
|
|||
// Confirm another block
|
||||
confirm_block ();
|
||||
|
||||
// No response expected
|
||||
system.deadline_set (5s);
|
||||
while (!client_thread_2_finished)
|
||||
{
|
||||
|
@ -705,15 +735,11 @@ TEST (websocket, vote_options)
|
|||
// Test client subscribing to notifications for work generation
|
||||
TEST (websocket, work)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::work));
|
||||
|
||||
|
@ -782,15 +808,12 @@ TEST (websocket, work)
|
|||
/** Tests clients subscribing multiple times or unsubscribing without a subscription */
|
||||
TEST (websocket, ws_keepalive)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
nano::node_flags node_flags;
|
||||
config.websocket_config.enabled = true;
|
||||
config.websocket_config.port = nano::get_available_port ();
|
||||
auto node1 (system.add_node (config));
|
||||
|
||||
auto node1 (std::make_shared<nano::node> (system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags));
|
||||
node1->start ();
|
||||
system.nodes.push_back (node1);
|
||||
ack_ready = false;
|
||||
std::thread subscription_thread ([config]() {
|
||||
websocket_test_call ("::1", std::to_string (config.websocket_config.port), R"json({"action": "ping"})json", true, false);
|
||||
|
|
|
@ -564,6 +564,9 @@ std::string nano::stat::detail_to_string (uint32_t key)
|
|||
case nano::stat::detail::vote_replay:
|
||||
res = "vote_replay";
|
||||
break;
|
||||
case nano::stat::detail::vote_indeterminate:
|
||||
res = "vote_indeterminate";
|
||||
break;
|
||||
case nano::stat::detail::vote_invalid:
|
||||
res = "vote_invalid";
|
||||
break;
|
||||
|
|
|
@ -255,6 +255,7 @@ public:
|
|||
// vote specific
|
||||
vote_valid,
|
||||
vote_replay,
|
||||
vote_indeterminate,
|
||||
vote_invalid,
|
||||
vote_overflow,
|
||||
|
||||
|
|
|
@ -530,9 +530,11 @@ bool nano::active_transactions::add (std::shared_ptr<nano::block> block_a, bool
|
|||
}
|
||||
|
||||
// Validate a vote and apply it to the current election if one exists
|
||||
bool nano::active_transactions::vote (std::shared_ptr<nano::vote> vote_a, bool single_lock)
|
||||
nano::vote_code nano::active_transactions::vote (std::shared_ptr<nano::vote> vote_a, bool single_lock)
|
||||
{
|
||||
std::shared_ptr<nano::election> election;
|
||||
// If none of the hashes are active, it is unknown whether it's a replay
|
||||
// In this case, votes are also not republished
|
||||
bool at_least_one (false);
|
||||
bool replay (false);
|
||||
bool processed (false);
|
||||
{
|
||||
|
@ -550,9 +552,10 @@ bool nano::active_transactions::vote (std::shared_ptr<nano::vote> vote_a, bool s
|
|||
auto existing (blocks.find (block_hash));
|
||||
if (existing != blocks.end ())
|
||||
{
|
||||
at_least_one = true;
|
||||
result = existing->second->vote (vote_a->account, vote_a->sequence, block_hash);
|
||||
}
|
||||
else
|
||||
else // possibly a vote for a recently confirmed election
|
||||
{
|
||||
add_inactive_votes_cache (block_hash, vote_a->account);
|
||||
}
|
||||
|
@ -563,6 +566,7 @@ bool nano::active_transactions::vote (std::shared_ptr<nano::vote> vote_a, bool s
|
|||
auto existing (roots.get<tag_root> ().find (block->qualified_root ()));
|
||||
if (existing != roots.get<tag_root> ().end ())
|
||||
{
|
||||
at_least_one = true;
|
||||
result = existing->election->vote (vote_a->account, vote_a->sequence, block->hash ());
|
||||
}
|
||||
else
|
||||
|
@ -570,15 +574,22 @@ bool nano::active_transactions::vote (std::shared_ptr<nano::vote> vote_a, bool s
|
|||
add_inactive_votes_cache (block->hash (), vote_a->account);
|
||||
}
|
||||
}
|
||||
replay = replay || result.replay;
|
||||
processed = processed || result.processed;
|
||||
replay = replay || result.replay;
|
||||
}
|
||||
}
|
||||
if (processed)
|
||||
if (at_least_one)
|
||||
{
|
||||
node.network.flood_vote (vote_a);
|
||||
if (processed)
|
||||
{
|
||||
node.network.flood_vote (vote_a);
|
||||
}
|
||||
return replay ? nano::vote_code::replay : nano::vote_code::vote;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nano::vote_code::indeterminate;
|
||||
}
|
||||
return replay;
|
||||
}
|
||||
|
||||
bool nano::active_transactions::active (nano::qualified_root const & root_a)
|
||||
|
|
|
@ -79,9 +79,8 @@ public:
|
|||
// clang-format off
|
||||
bool start (std::shared_ptr<nano::block>, bool const = false, std::function<void(std::shared_ptr<nano::block>)> const & = [](std::shared_ptr<nano::block>) {});
|
||||
// clang-format on
|
||||
// If this returns true, the vote is a replay
|
||||
// If this returns false, the vote may or may not be a replay
|
||||
bool vote (std::shared_ptr<nano::vote>, bool = false);
|
||||
// Distinguishes replay votes, cannot be determined if the block is not in any election
|
||||
nano::vote_code vote (std::shared_ptr<nano::vote>, bool = false);
|
||||
// Is the root of this block in the roots container
|
||||
bool active (nano::block const &);
|
||||
bool active (nano::qualified_root const &);
|
||||
|
|
|
@ -303,38 +303,41 @@ startup_time (std::chrono::steady_clock::now ())
|
|||
this->network.send_keepalive_self (channel_a);
|
||||
}
|
||||
});
|
||||
observers.vote.add ([this](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel> channel_a) {
|
||||
this->gap_cache.vote (vote_a);
|
||||
this->online_reps.observe (vote_a->account);
|
||||
nano::uint128_t rep_weight;
|
||||
observers.vote.add ([this](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel> channel_a, nano::vote_code code_a) {
|
||||
if (code_a == nano::vote_code::vote || code_a == nano::vote_code::indeterminate)
|
||||
{
|
||||
rep_weight = ledger.weight (vote_a->account);
|
||||
}
|
||||
if (rep_weight > minimum_principal_weight ())
|
||||
{
|
||||
bool rep_crawler_exists (false);
|
||||
for (auto hash : *vote_a)
|
||||
this->gap_cache.vote (vote_a);
|
||||
this->online_reps.observe (vote_a->account);
|
||||
nano::uint128_t rep_weight;
|
||||
{
|
||||
if (this->rep_crawler.exists (hash))
|
||||
{
|
||||
rep_crawler_exists = true;
|
||||
break;
|
||||
}
|
||||
rep_weight = ledger.weight (vote_a->account);
|
||||
}
|
||||
if (rep_crawler_exists)
|
||||
if (rep_weight > minimum_principal_weight ())
|
||||
{
|
||||
// We see a valid non-replay vote for a block we requested, this node is probably a representative
|
||||
if (this->rep_crawler.response (channel_a, vote_a->account, rep_weight))
|
||||
bool rep_crawler_exists (false);
|
||||
for (auto hash : *vote_a)
|
||||
{
|
||||
logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel_a->to_string ()));
|
||||
// Rebroadcasting all active votes to new representative
|
||||
auto blocks (this->active.list_blocks (true));
|
||||
for (auto i (blocks.begin ()), n (blocks.end ()); i != n; ++i)
|
||||
if (this->rep_crawler.exists (hash))
|
||||
{
|
||||
if (*i != nullptr)
|
||||
rep_crawler_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rep_crawler_exists)
|
||||
{
|
||||
// We see a valid non-replay vote for a block we requested, this node is probably a representative
|
||||
if (this->rep_crawler.response (channel_a, vote_a->account, rep_weight))
|
||||
{
|
||||
logger.try_log (boost::str (boost::format ("Found a representative at %1%") % channel_a->to_string ()));
|
||||
// Rebroadcasting all active votes to new representative
|
||||
auto blocks (this->active.list_blocks (true));
|
||||
for (auto i (blocks.begin ()), n (blocks.end ()); i != n; ++i)
|
||||
{
|
||||
nano::confirm_req req (*i);
|
||||
channel_a->send (req);
|
||||
if (*i != nullptr)
|
||||
{
|
||||
nano::confirm_req req (*i);
|
||||
channel_a->send (req);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -343,11 +346,11 @@ startup_time (std::chrono::steady_clock::now ())
|
|||
});
|
||||
if (websocket_server)
|
||||
{
|
||||
observers.vote.add ([this](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel> channel_a) {
|
||||
observers.vote.add ([this](std::shared_ptr<nano::vote> vote_a, std::shared_ptr<nano::transport::channel> channel_a, nano::vote_code code_a) {
|
||||
if (this->websocket_server->any_subscriber (nano::websocket::topic::vote))
|
||||
{
|
||||
nano::websocket::message_builder builder;
|
||||
auto msg (builder.vote_received (vote_a));
|
||||
auto msg (builder.vote_received (vote_a, code_a));
|
||||
this->websocket_server->broadcast (msg);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ public:
|
|||
using blocks_t = nano::observer_set<nano::election_status const &, nano::account const &, nano::uint128_t const &, bool>;
|
||||
blocks_t blocks;
|
||||
nano::observer_set<bool> wallet;
|
||||
nano::observer_set<std::shared_ptr<nano::vote>, std::shared_ptr<nano::transport::channel>> vote;
|
||||
nano::observer_set<std::shared_ptr<nano::vote>, std::shared_ptr<nano::transport::channel>, nano::vote_code> vote;
|
||||
nano::observer_set<nano::block_hash const &> active_stopped;
|
||||
nano::observer_set<nano::account const &, bool> account_balance;
|
||||
nano::observer_set<std::shared_ptr<nano::transport::channel>> endpoint;
|
||||
|
|
|
@ -202,29 +202,16 @@ nano::vote_code nano::vote_processor::vote_blocking (nano::transaction const & t
|
|||
auto result (nano::vote_code::invalid);
|
||||
if (validated || !vote_a->validate ())
|
||||
{
|
||||
result = active.vote (vote_a, true);
|
||||
observers.vote.notify (vote_a, channel_a, result);
|
||||
// This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them
|
||||
// Only do this if the sequence number is significantly different to account for network reordering
|
||||
// Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase
|
||||
auto max_vote (store.vote_max (transaction_a, vote_a));
|
||||
result = nano::vote_code::replay;
|
||||
if (!active.vote (vote_a, true))
|
||||
if (max_vote->sequence > vote_a->sequence + 10000)
|
||||
{
|
||||
result = nano::vote_code::vote;
|
||||
}
|
||||
switch (result)
|
||||
{
|
||||
case nano::vote_code::vote:
|
||||
observers.vote.notify (vote_a, channel_a);
|
||||
case nano::vote_code::replay:
|
||||
// This tries to assist rep nodes that have lost track of their highest sequence number by replaying our highest known vote back to them
|
||||
// Only do this if the sequence number is significantly different to account for network reordering
|
||||
// Amplify attack considerations: We're sending out a confirm_ack in response to a confirm_ack for no net traffic increase
|
||||
if (max_vote->sequence > vote_a->sequence + 10000)
|
||||
{
|
||||
nano::confirm_ack confirm (max_vote);
|
||||
channel_a->send (confirm); // this is non essential traffic as it will be resolicited if not received
|
||||
}
|
||||
break;
|
||||
case nano::vote_code::invalid:
|
||||
assert (false);
|
||||
break;
|
||||
nano::confirm_ack confirm (max_vote);
|
||||
channel_a->send (confirm); // this is non essential traffic as it will be resolicited if not received
|
||||
}
|
||||
}
|
||||
std::string status;
|
||||
|
@ -242,6 +229,10 @@ nano::vote_code nano::vote_processor::vote_blocking (nano::transaction const & t
|
|||
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;
|
||||
}
|
||||
if (config.logging.vote_logging ())
|
||||
{
|
||||
|
|
|
@ -138,6 +138,8 @@ bool nano::websocket::confirmation_options::should_filter (nano::websocket::mess
|
|||
|
||||
nano::websocket::vote_options::vote_options (boost::property_tree::ptree const & options_a, nano::logger_mt & logger_a)
|
||||
{
|
||||
include_replays = options_a.get<bool> ("include_replays", false);
|
||||
include_indeterminate = options_a.get<bool> ("include_indeterminate", false);
|
||||
auto representatives_l (options_a.get_child_optional ("representatives"));
|
||||
if (representatives_l)
|
||||
{
|
||||
|
@ -154,21 +156,25 @@ nano::websocket::vote_options::vote_options (boost::property_tree::ptree const &
|
|||
logger_a.always_log ("Websocket: invalid account given to filter votes: ", representative_l.second.data ());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Warn the user if the options resulted in an empty filter
|
||||
if (representatives.empty ())
|
||||
{
|
||||
logger_a.always_log ("Websocket: provided options resulted in an empty vote filter");
|
||||
// Warn the user if the option will be ignored
|
||||
if (representatives.empty ())
|
||||
{
|
||||
logger_a.always_log ("Websocket: account filter for votes is empty, no messages will be filtered");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::websocket::vote_options::should_filter (nano::websocket::message const & message_a) const
|
||||
{
|
||||
bool should_filter_l (true);
|
||||
auto representative_text_l (message_a.contents.get<std::string> ("message.account"));
|
||||
if (representatives.find (representative_text_l) != representatives.end ())
|
||||
auto type (message_a.contents.get<std::string> ("message.type"));
|
||||
bool should_filter_l = (!include_replays && type == "replay") || (!include_indeterminate && type == "indeterminate");
|
||||
if (!should_filter_l && !representatives.empty ())
|
||||
{
|
||||
should_filter_l = false;
|
||||
auto representative_text_l (message_a.contents.get<std::string> ("message.account"));
|
||||
if (representatives.find (representative_text_l) == representatives.end ())
|
||||
{
|
||||
should_filter_l = true;
|
||||
}
|
||||
}
|
||||
return should_filter_l;
|
||||
}
|
||||
|
@ -656,7 +662,7 @@ nano::websocket::message nano::websocket::message_builder::block_confirmed (std:
|
|||
return message_l;
|
||||
}
|
||||
|
||||
nano::websocket::message nano::websocket::message_builder::vote_received (std::shared_ptr<nano::vote> vote_a)
|
||||
nano::websocket::message nano::websocket::message_builder::vote_received (std::shared_ptr<nano::vote> vote_a, nano::vote_code code_a)
|
||||
{
|
||||
nano::websocket::message message_l (nano::websocket::topic::vote);
|
||||
set_common_fields (message_l);
|
||||
|
@ -664,6 +670,25 @@ nano::websocket::message nano::websocket::message_builder::vote_received (std::s
|
|||
// Vote information
|
||||
boost::property_tree::ptree vote_node_l;
|
||||
vote_a->serialize_json (vote_node_l);
|
||||
|
||||
// Vote processing information
|
||||
std::string vote_type = "invalid";
|
||||
switch (code_a)
|
||||
{
|
||||
case nano::vote_code::vote:
|
||||
vote_type = "vote";
|
||||
break;
|
||||
case nano::vote_code::replay:
|
||||
vote_type = "replay";
|
||||
break;
|
||||
case nano::vote_code::indeterminate:
|
||||
vote_type = "indeterminate";
|
||||
break;
|
||||
case nano::vote_code::invalid:
|
||||
assert (false);
|
||||
break;
|
||||
}
|
||||
vote_node_l.put ("type", vote_type);
|
||||
message_l.contents.add_child ("message", vote_node_l);
|
||||
return message_l;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace websocket
|
|||
public:
|
||||
message block_confirmed (std::shared_ptr<nano::block> block_a, nano::account const & account_a, nano::amount const & amount_a, std::string subtype, bool include_block, nano::election_status const & election_status_a, nano::websocket::confirmation_options const & options_a);
|
||||
message stopped_election (nano::block_hash const & hash_a);
|
||||
message vote_received (std::shared_ptr<nano::vote> vote_a);
|
||||
message vote_received (std::shared_ptr<nano::vote> vote_a, nano::vote_code code_a);
|
||||
message difficulty_changed (uint64_t publish_threshold_a, uint64_t difficulty_active_a);
|
||||
message work_generation (nano::block_hash const & root_a, uint64_t const work_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::string const & peer_a, std::vector<std::string> const & bad_peers_a, bool const completed_a = true, bool const cancelled_a = false);
|
||||
message work_cancelled (nano::block_hash const & root_a, uint64_t const difficulty_a, uint64_t const publish_threshold_a, std::chrono::milliseconds const & duration_a, std::vector<std::string> const & bad_peers_a);
|
||||
|
@ -179,6 +179,8 @@ namespace websocket
|
|||
|
||||
private:
|
||||
std::unordered_set<std::string> representatives;
|
||||
bool include_replays{ false };
|
||||
bool include_indeterminate{ false };
|
||||
};
|
||||
|
||||
/** A websocket session managing its own lifetime */
|
||||
|
|
|
@ -282,7 +282,8 @@ enum class vote_code
|
|||
{
|
||||
invalid, // Vote is not signed correctly
|
||||
replay, // Vote does not have the highest sequence number, it's a replay
|
||||
vote // Vote has the highest sequence number
|
||||
vote, // Vote has the highest sequence number
|
||||
indeterminate // Unknown if replay or vote
|
||||
};
|
||||
|
||||
enum class process_result
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue