diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index 9c39d3c96..fa53de44b 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -124,6 +124,54 @@ TEST (websocket, confirmation) ASSERT_TIMELY (5s, future.wait_for (0s) == std::future_status::ready); } +// Tests getting notification of a started election +TEST (websocket, started_election) +{ + 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); + + std::atomic ack_ready{ false }; + auto task = ([&ack_ready, config, &node1] () { + fake_websocket_client client (node1->websocket_server->listening_port ()); + client.send_message (R"json({"action": "subscribe", "topic": "started_election", "ack": "true"})json"); + client.await_ack (); + ack_ready = true; + EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::started_election)); + return client.get_response (); + }); + auto future = std::async (std::launch::async, task); + + ASSERT_TIMELY (5s, ack_ready); + + // Create election, causing a websocket message to be emitted + nano::keypair key1; + nano::block_builder builder; + auto send1 = builder + .send () + .previous (nano::dev::genesis->hash ()) + .destination (key1.pub) + .balance (0) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (nano::dev::genesis->hash ())) + .build_shared (); + nano::publish publish1{ nano::dev::network_params.network, send1 }; + auto channel1 = node1->network.udp_channels.create (node1->network.endpoint ()); + node1->network.inbound (publish1, channel1); + ASSERT_TIMELY (1s, node1->active.election (send1->qualified_root ())); + ASSERT_TIMELY (5s, future.wait_for (0s) == std::future_status::ready); + + auto response = future.get (); + ASSERT_TRUE (response); + boost::property_tree::ptree event; + std::stringstream stream; + stream << response.get (); + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "started_election"); +} + // Tests getting notification of an erased election TEST (websocket, stopped_election) { diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index bb411fa9b..454abbded 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -847,6 +847,7 @@ nano::election_insertion_result nano::active_transactions::insert_impl (nano::un auto const cache = find_inactive_votes_cache_impl (hash); lock_a.unlock (); result.election->insert_inactive_votes_cache (cache); + node.observers.active_started.notify (hash); node.stats.inc (nano::stat::type::election, nano::stat::detail::election_start); vacancy_update (); } diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 89dc08e03..ad0ebc099 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -282,6 +282,14 @@ nano::node::node (boost::asio::io_context & io_ctx_a, boost::filesystem::path co } }); + observers.active_started.add ([this] (nano::block_hash const & hash_a) { + if (this->websocket_server->any_subscriber (nano::websocket::topic::started_election)) + { + nano::websocket::message_builder builder; + this->websocket_server->broadcast (builder.started_election (hash_a)); + } + }); + observers.active_stopped.add ([this] (nano::block_hash const & hash_a) { if (this->websocket_server->any_subscriber (nano::websocket::topic::stopped_election)) { diff --git a/nano/node/node_observers.cpp b/nano/node/node_observers.cpp index 1544be3af..5864dbce8 100644 --- a/nano/node/node_observers.cpp +++ b/nano/node/node_observers.cpp @@ -6,6 +6,7 @@ std::unique_ptr nano::collect_container_info (na composite->add_component (collect_container_info (node_observers.blocks, "blocks")); composite->add_component (collect_container_info (node_observers.wallet, "wallet")); composite->add_component (collect_container_info (node_observers.vote, "vote")); + composite->add_component (collect_container_info (node_observers.active_started, "active_started")); composite->add_component (collect_container_info (node_observers.active_stopped, "active_stopped")); composite->add_component (collect_container_info (node_observers.account_balance, "account_balance")); composite->add_component (collect_container_info (node_observers.endpoint, "endpoint")); diff --git a/nano/node/node_observers.hpp b/nano/node/node_observers.hpp index cea357eb1..3411b60a7 100644 --- a/nano/node/node_observers.hpp +++ b/nano/node/node_observers.hpp @@ -15,6 +15,7 @@ public: blocks_t blocks; nano::observer_set wallet; nano::observer_set, std::shared_ptr, nano::vote_code> vote; + nano::observer_set active_started; nano::observer_set active_stopped; nano::observer_set account_balance; nano::observer_set> endpoint; diff --git a/nano/node/websocket.cpp b/nano/node/websocket.cpp index 67b057ac2..7489ba463 100644 --- a/nano/node/websocket.cpp +++ b/nano/node/websocket.cpp @@ -375,6 +375,10 @@ nano::websocket::topic to_topic (std::string const & topic_a) { topic = nano::websocket::topic::confirmation; } + else if (topic_a == "started_election") + { + topic = nano::websocket::topic::started_election; + } else if (topic_a == "stopped_election") { topic = nano::websocket::topic::stopped_election; @@ -414,6 +418,10 @@ std::string from_topic (nano::websocket::topic topic_a) { topic = "confirmation"; } + else if (topic_a == nano::websocket::topic::started_election) + { + topic = "started_election"; + } else if (topic_a == nano::websocket::topic::stopped_election) { topic = "stopped_election"; @@ -689,6 +697,18 @@ void nano::websocket::listener::decrease_subscriber_count (nano::websocket::topi count -= 1; } +nano::websocket::message nano::websocket::message_builder::started_election (nano::block_hash const & hash_a) +{ + nano::websocket::message message_l (nano::websocket::topic::started_election); + set_common_fields (message_l); + + boost::property_tree::ptree message_node_l; + message_node_l.add ("hash", hash_a.to_string ()); + message_l.contents.add_child ("message", message_node_l); + + return message_l; +} + nano::websocket::message nano::websocket::message_builder::stopped_election (nano::block_hash const & hash_a) { nano::websocket::message message_l (nano::websocket::topic::stopped_election); diff --git a/nano/node/websocket.hpp b/nano/node/websocket.hpp index 264a9f940..f4569e5c5 100644 --- a/nano/node/websocket.hpp +++ b/nano/node/websocket.hpp @@ -41,6 +41,8 @@ namespace websocket ack, /** A confirmation message */ confirmation, + /** Started election message*/ + started_election, /** Stopped election message (dropped elections due to bounding or block lost the elections) */ stopped_election, /** A vote message **/ @@ -81,6 +83,7 @@ namespace websocket { public: message block_confirmed (std::shared_ptr const & 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, std::vector const & election_votes_a, nano::websocket::confirmation_options const & options_a); + message started_election (nano::block_hash const & hash_a); message stopped_election (nano::block_hash const & hash_a); message vote_received (std::shared_ptr const & vote_a, nano::vote_code code_a); message work_generation (nano::work_version const version_a, 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 const & bad_peers_a, bool const completed_a = true, bool const cancelled_a = false);