diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index 890aef5d19..bdb2f20bbb 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -160,6 +160,72 @@ TEST (websocket, subscription_edge) node1->stop (); } +// Test client subscribing to changes in active_difficulty +TEST (websocket, active_difficulty) +{ + nano::system system (24000, 1); + nano::node_init init1; + nano::node_config config; + nano::node_flags node_flags; + config.websocket_config.enabled = true; + config.websocket_config.port = 24078; + + auto node1 (std::make_shared (init1, system.io_ctx, nano::unique_path (), system.alarm, config, system.work, node_flags)); + node1->start (); + system.nodes.push_back (node1); + + ASSERT_EQ (0, node1->websocket_server->subscriber_count (nano::websocket::topic::active_difficulty)); + + // Subscribe to active_difficulty and wait for response asynchronously + ack_ready = false; + auto client_task = ([&node1]() -> boost::optional { + auto response = websocket_test_call ("::1", "24078", R"json({"action": "subscribe", "topic": "active_difficulty", "ack": true})json", true, true); + return response; + }); + auto client_future = std::async (std::launch::async, client_task); + + // 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::active_difficulty)); + + // Fake history records to force trended_active_difficulty change + node1->active.multipliers_cb.push_front (10.); + + // Wait to receive the active_difficulty message + system.deadline_set (5s); + while (client_future.wait_for (std::chrono::seconds (0)) != std::future_status::ready) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Check active_difficulty response + auto response = client_future.get (); + ASSERT_TRUE (response); + std::stringstream stream; + stream << response; + boost::property_tree::ptree event; + boost::property_tree::read_json (stream, event); + ASSERT_EQ (event.get ("topic"), "active_difficulty"); + + auto message_contents = event.get_child ("message"); + uint64_t network_minimum; + nano::from_string_hex (message_contents.get ("network_minimum"), network_minimum); + ASSERT_EQ (network_minimum, node1->network_params.network.publish_threshold); + + uint64_t network_current; + nano::from_string_hex (message_contents.get ("network_current"), network_current); + ASSERT_EQ (network_current, node1->active.active_difficulty ()); + + double multiplier = message_contents.get ("multiplier"); + ASSERT_NEAR (multiplier, nano::difficulty::to_multiplier (node1->active.active_difficulty (), node1->network_params.network.publish_threshold), 1e-6); + + node1->stop (); +} + /** Subscribes to block confirmations, confirms a block and then awaits websocket notification */ TEST (websocket, confirmation) { diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 829cb76b2b..39eb41885b 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -639,7 +639,13 @@ void nano::active_transactions::update_active_difficulty (std::unique_lock= node.network_params.network.publish_threshold); + + bool notify_change = trended_active_difficulty != difficulty; trended_active_difficulty = difficulty; + if (notify_change) + { + node.observers.difficulty.notify (trended_active_difficulty); + } } uint64_t nano::active_transactions::active_difficulty () diff --git a/nano/node/node.cpp b/nano/node/node.cpp index a20192b739..8a683ec7fc 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -351,6 +351,15 @@ startup_time (std::chrono::steady_clock::now ()) this->websocket_server->broadcast (builder.stopped_election (hash_a)); } }); + + observers.difficulty.add ([this](uint64_t active_difficulty) { + if (this->websocket_server->any_subscriber (nano::websocket::topic::active_difficulty)) + { + nano::websocket::message_builder builder; + auto msg (builder.difficulty_changed (network_params.network.publish_threshold, active_difficulty)); + this->websocket_server->broadcast (msg); + } + }); } observers.endpoint.add ([this](std::shared_ptr channel_a) { if (channel_a->get_type () == nano::transport::transport_type::udp) diff --git a/nano/node/node_observers.hpp b/nano/node/node_observers.hpp index 3969e80a18..33e6c53363 100644 --- a/nano/node/node_observers.hpp +++ b/nano/node/node_observers.hpp @@ -20,6 +20,7 @@ class node_observers final nano::observer_set account_balance; nano::observer_set> endpoint; nano::observer_set<> disconnect; + nano::observer_set difficulty; }; std::unique_ptr collect_seq_con_info (node_observers & node_observers, const std::string & name); diff --git a/nano/node/websocket.cpp b/nano/node/websocket.cpp index 723fea881f..cea1d41853 100644 --- a/nano/node/websocket.cpp +++ b/nano/node/websocket.cpp @@ -316,6 +316,11 @@ nano::websocket::topic to_topic (std::string topic_a) { topic = nano::websocket::topic::ack; } + else if (topic_a == "active_difficulty") + { + topic = nano::websocket::topic::active_difficulty; + } + return topic; } @@ -338,6 +343,10 @@ std::string from_topic (nano::websocket::topic topic_a) { topic = "ack"; } + else if (topic_a == nano::websocket::topic::active_difficulty) + { + topic = "active_difficulty"; + } return topic; } } @@ -616,6 +625,22 @@ nano::websocket::message nano::websocket::message_builder::vote_received (std::s return message_l; } +nano::websocket::message nano::websocket::message_builder::difficulty_changed (uint64_t publish_threshold, uint64_t difficulty_active) +{ + nano::websocket::message message_l (nano::websocket::topic::active_difficulty); + set_common_fields (message_l); + + // Active difficulty information + boost::property_tree::ptree difficulty_l; + difficulty_l.put ("network_minimum", nano::to_string_hex (publish_threshold)); + difficulty_l.put ("network_current", nano::to_string_hex (difficulty_active)); + auto multiplier = nano::difficulty::to_multiplier (difficulty_active, publish_threshold); + difficulty_l.put ("multiplier", nano::to_string (multiplier)); + + message_l.contents.add_child ("message", difficulty_l); + return message_l; +} + void nano::websocket::message_builder::set_common_fields (nano::websocket::message & message_a) { using namespace std::chrono; diff --git a/nano/node/websocket.hpp b/nano/node/websocket.hpp index 6ff0f21612..2681c1d385 100644 --- a/nano/node/websocket.hpp +++ b/nano/node/websocket.hpp @@ -53,6 +53,8 @@ namespace websocket stopped_election, /** A vote message **/ vote, + /** An active difficulty message */ + active_difficulty, /** Auxiliary length, not a valid topic, must be the last enum */ _length }; @@ -83,6 +85,7 @@ namespace websocket message block_confirmed (std::shared_ptr block_a, nano::account const & account_a, nano::amount const & amount_a, std::string subtype, bool include_block, nano::election_status_type election_status_type_a); message stopped_election (nano::block_hash const & hash_a); message vote_received (std::shared_ptr vote_a); + message difficulty_changed (uint64_t publish_threshold, uint64_t difficulty_active); private: /** Set the common fields for messages: timestamp and topic. */