383 lines
13 KiB
C++
383 lines
13 KiB
C++
#pragma once
|
|
|
|
#include <nano/lib/blocks.hpp>
|
|
#include <nano/lib/numbers.hpp>
|
|
#include <nano/lib/work.hpp>
|
|
#include <nano/node/common.hpp>
|
|
#include <nano/node/election.hpp>
|
|
#include <nano/node/websocket_stream.hpp>
|
|
#include <nano/node/websocketconfig.hpp>
|
|
#include <nano/secure/common.hpp>
|
|
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace nano
|
|
{
|
|
class wallets;
|
|
class logger_mt;
|
|
class vote;
|
|
class election_status;
|
|
class telemetry_data;
|
|
class tls_config;
|
|
class node_observers;
|
|
enum class election_status_type : uint8_t;
|
|
|
|
namespace websocket
|
|
{
|
|
class listener;
|
|
class confirmation_options;
|
|
class session;
|
|
|
|
/** Supported topics */
|
|
enum class topic
|
|
{
|
|
invalid = 0,
|
|
/** Acknowledgement of prior incoming message */
|
|
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 **/
|
|
vote,
|
|
/** Work generation message */
|
|
work,
|
|
/** A bootstrap message */
|
|
bootstrap,
|
|
/** A telemetry message */
|
|
telemetry,
|
|
/** New block arrival message*/
|
|
new_unconfirmed_block,
|
|
/** Auxiliary length, not a valid topic, must be the last enum */
|
|
_length
|
|
};
|
|
constexpr std::size_t number_topics{ static_cast<std::size_t> (topic::_length) - static_cast<std::size_t> (topic::invalid) };
|
|
|
|
/** A message queued for broadcasting */
|
|
class message final
|
|
{
|
|
public:
|
|
message (nano::websocket::topic topic_a) :
|
|
topic (topic_a)
|
|
{
|
|
}
|
|
message (nano::websocket::topic topic_a, boost::property_tree::ptree & tree_a) :
|
|
topic (topic_a), contents (tree_a)
|
|
{
|
|
}
|
|
|
|
std::string to_string () const;
|
|
nano::websocket::topic topic;
|
|
boost::property_tree::ptree contents;
|
|
};
|
|
|
|
/** Message builder. This is expanded with new builder functions are necessary. */
|
|
class message_builder final
|
|
{
|
|
public:
|
|
message block_confirmed (std::shared_ptr<nano::block> 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<nano::vote_with_weight_info> 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<nano::vote> 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<std::string> const & bad_peers_a, bool const completed_a = true, bool const cancelled_a = false);
|
|
message work_cancelled (nano::work_version const version_a, 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);
|
|
message work_failed (nano::work_version const version_a, 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);
|
|
message bootstrap_started (std::string const & id_a, std::string const & mode_a);
|
|
message bootstrap_exited (std::string const & id_a, std::string const & mode_a, std::chrono::steady_clock::time_point const start_time_a, uint64_t const total_blocks_a);
|
|
message telemetry_received (nano::telemetry_data const &, nano::endpoint const &);
|
|
message new_block_arrived (nano::block const & block_a);
|
|
|
|
private:
|
|
/** Set the common fields for messages: timestamp and topic. */
|
|
void set_common_fields (message & message_a);
|
|
};
|
|
|
|
/** Options for subscriptions */
|
|
class options
|
|
{
|
|
public:
|
|
virtual ~options () = default;
|
|
|
|
protected:
|
|
/**
|
|
* Checks if a message should be filtered for default options (no options given).
|
|
* @param message_a the message to be checked
|
|
* @return false - the message should always be broadcasted
|
|
*/
|
|
virtual bool should_filter (message const & message_a) const
|
|
{
|
|
return false;
|
|
}
|
|
/**
|
|
* Update options, if available for a given topic
|
|
* @return false on success
|
|
*/
|
|
virtual bool update (boost::property_tree::ptree const & options_a)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
friend class session;
|
|
};
|
|
|
|
/**
|
|
* Options for block confirmation subscriptions
|
|
* Non-filtering options:
|
|
* - "include_block" (bool, default true) - if false, do not include block contents. Only account, amount and hash will be included.
|
|
* Filtering options:
|
|
* - "all_local_accounts" (bool) - will only not filter blocks that have local wallet accounts as source/destination
|
|
* - "accounts" (array of std::strings) - will only not filter blocks that have these accounts as source/destination
|
|
* @remark Both options can be given, the resulting filter is an intersection of individual filters
|
|
* @warn Legacy blocks are always filtered (not broadcasted)
|
|
*/
|
|
class confirmation_options final : public options
|
|
{
|
|
public:
|
|
confirmation_options (nano::wallets & wallets_a);
|
|
confirmation_options (boost::property_tree::ptree const & options_a, nano::wallets & wallets_a, nano::logger_mt & logger_a);
|
|
|
|
/**
|
|
* Checks if a message should be filtered for given block confirmation options.
|
|
* @param message_a the message to be checked
|
|
* @return false if the message should be broadcasted, true if it should be filtered
|
|
*/
|
|
bool should_filter (message const & message_a) const override;
|
|
|
|
/**
|
|
* Update some existing options
|
|
* Filtering options:
|
|
* - "accounts_add" (array of std::strings) - additional accounts for which blocks should not be filtered
|
|
* - "accounts_del" (array of std::strings) - accounts for which blocks should be filtered
|
|
* @return false
|
|
*/
|
|
bool update (boost::property_tree::ptree const & options_a) override;
|
|
|
|
/** Returns whether or not block contents should be included */
|
|
bool get_include_block () const
|
|
{
|
|
return include_block;
|
|
}
|
|
|
|
/** Returns whether or not to include election info, such as tally and duration */
|
|
bool get_include_election_info () const
|
|
{
|
|
return include_election_info;
|
|
}
|
|
|
|
/** Returns whether or not to include election info with votes */
|
|
bool get_include_election_info_with_votes () const
|
|
{
|
|
return include_election_info_with_votes;
|
|
}
|
|
|
|
/** Returns whether or not to include sideband info */
|
|
bool get_include_sideband_info () const
|
|
{
|
|
return include_sideband_info;
|
|
}
|
|
|
|
static constexpr uint8_t const type_active_quorum = 1;
|
|
static constexpr uint8_t const type_active_confirmation_height = 2;
|
|
static constexpr uint8_t const type_inactive = 4;
|
|
static constexpr uint8_t const type_all_active = type_active_quorum | type_active_confirmation_height;
|
|
static constexpr uint8_t const type_all = type_all_active | type_inactive;
|
|
|
|
private:
|
|
void check_filter_empty () const;
|
|
|
|
nano::wallets & wallets;
|
|
boost::optional<nano::logger_mt &> logger;
|
|
bool include_election_info{ false };
|
|
bool include_election_info_with_votes{ false };
|
|
bool include_sideband_info{ false };
|
|
bool include_block{ true };
|
|
bool has_account_filtering_options{ false };
|
|
bool all_local_accounts{ false };
|
|
uint8_t confirmation_types{ type_all };
|
|
std::unordered_set<std::string> accounts;
|
|
};
|
|
|
|
/**
|
|
* Filtering options for vote subscriptions
|
|
* Possible filtering options:
|
|
* * "representatives" (array of std::strings) - will only broadcast votes from these representatives
|
|
*/
|
|
class vote_options final : public options
|
|
{
|
|
public:
|
|
vote_options (boost::property_tree::ptree const & options_a, nano::logger_mt & logger_a);
|
|
|
|
/**
|
|
* Checks if a message should be filtered for given vote received options.
|
|
* @param message_a the message to be checked
|
|
* @return false if the message should be broadcasted, true if it should be filtered
|
|
*/
|
|
bool should_filter (message const & message_a) const override;
|
|
|
|
private:
|
|
std::unordered_set<std::string> representatives;
|
|
bool include_replays{ false };
|
|
bool include_indeterminate{ false };
|
|
};
|
|
|
|
/** A websocket session managing its own lifetime */
|
|
class session final : public std::enable_shared_from_this<session>
|
|
{
|
|
friend class listener;
|
|
|
|
public:
|
|
#ifdef NANO_SECURE_RPC
|
|
/** Constructor that takes ownership over \p socket_a and creates an SSL stream */
|
|
explicit session (nano::websocket::listener & listener_a, socket_type socket_a, boost::asio::ssl::context & ctx_a);
|
|
#endif
|
|
/** Constructor that takes ownership over \p socket_a */
|
|
explicit session (nano::websocket::listener & listener_a, socket_type socket_a);
|
|
|
|
~session ();
|
|
|
|
/** Perform Websocket handshake and start reading messages */
|
|
void handshake ();
|
|
|
|
/** Close the websocket and end the session */
|
|
void close ();
|
|
|
|
/** Read the next message. This implicitely handles incoming websocket pings. */
|
|
void read ();
|
|
|
|
/** Enqueue \p message_a for writing to the websockets */
|
|
void write (nano::websocket::message message_a);
|
|
|
|
private:
|
|
/** The owning listener */
|
|
nano::websocket::listener & ws_listener;
|
|
/** Websocket stream, supporting both plain and tls connections */
|
|
nano::websocket::stream ws;
|
|
/** Buffer for received messages */
|
|
boost::beast::multi_buffer read_buffer;
|
|
/** Outgoing messages. The send queue is protected by accessing it only through the strand */
|
|
std::deque<message> send_queue;
|
|
|
|
/** Hash functor for topic enums */
|
|
struct topic_hash
|
|
{
|
|
template <typename T>
|
|
std::size_t operator() (T t) const
|
|
{
|
|
return static_cast<std::size_t> (t);
|
|
}
|
|
};
|
|
/** Map of subscriptions -> options registered by this session. */
|
|
std::unordered_map<topic, std::unique_ptr<options>, topic_hash> subscriptions;
|
|
nano::mutex subscriptions_mutex;
|
|
|
|
/** Handle incoming message */
|
|
void handle_message (boost::property_tree::ptree const & message_a);
|
|
/** Acknowledge incoming message */
|
|
void send_ack (std::string action_a, std::string id_a);
|
|
/** Send all queued messages. This must be called from the write strand. */
|
|
void write_queued_messages ();
|
|
};
|
|
|
|
/** Creates a new session for each incoming connection */
|
|
class listener final : public std::enable_shared_from_this<listener>
|
|
{
|
|
public:
|
|
listener (std::shared_ptr<nano::tls_config> const & tls_config_a, nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a);
|
|
|
|
/** Start accepting connections */
|
|
void run ();
|
|
void accept ();
|
|
void on_accept (boost::system::error_code ec_a);
|
|
|
|
/** Close all websocket sessions and stop listening for new connections */
|
|
void stop ();
|
|
|
|
/** Broadcast block confirmation. The content of the message depends on subscription options (such as "include_block") */
|
|
void broadcast_confirmation (std::shared_ptr<nano::block> const & block_a, nano::account const & account_a, nano::amount const & amount_a, std::string const & subtype, nano::election_status const & election_status_a, std::vector<nano::vote_with_weight_info> const & election_votes_a);
|
|
|
|
/** Broadcast \p message to all session subscribing to the message topic. */
|
|
void broadcast (nano::websocket::message message_a);
|
|
|
|
nano::logger_mt & get_logger () const
|
|
{
|
|
return logger;
|
|
}
|
|
|
|
std::uint16_t listening_port ()
|
|
{
|
|
return acceptor.local_endpoint ().port ();
|
|
}
|
|
|
|
nano::wallets & get_wallets () const
|
|
{
|
|
return wallets;
|
|
}
|
|
|
|
/**
|
|
* Per-topic subscribers check. Relies on all sessions correctly increasing and
|
|
* decreasing the subscriber counts themselves.
|
|
*/
|
|
bool any_subscriber (nano::websocket::topic const & topic_a) const
|
|
{
|
|
return subscriber_count (topic_a) > 0;
|
|
}
|
|
/** Getter for subscriber count of a specific topic*/
|
|
std::size_t subscriber_count (nano::websocket::topic const & topic_a) const
|
|
{
|
|
return topic_subscriber_count[static_cast<std::size_t> (topic_a)];
|
|
}
|
|
|
|
private:
|
|
/** A websocket session can increase and decrease subscription counts. */
|
|
friend nano::websocket::session;
|
|
|
|
/** Adds to subscription count of a specific topic*/
|
|
void increase_subscriber_count (nano::websocket::topic const & topic_a);
|
|
/** Removes from subscription count of a specific topic*/
|
|
void decrease_subscriber_count (nano::websocket::topic const & topic_a);
|
|
|
|
std::shared_ptr<nano::tls_config> tls_config;
|
|
nano::logger_mt & logger;
|
|
nano::wallets & wallets;
|
|
boost::asio::ip::tcp::acceptor acceptor;
|
|
socket_type socket;
|
|
nano::mutex sessions_mutex;
|
|
std::vector<std::weak_ptr<session>> sessions;
|
|
std::array<std::atomic<std::size_t>, number_topics> topic_subscriber_count;
|
|
std::atomic<bool> stopped{ false };
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Wrapper of websocket related functionality that node interacts with
|
|
*/
|
|
class websocket_server
|
|
{
|
|
public:
|
|
websocket_server (nano::websocket::config &, nano::node_observers &, nano::wallets &, nano::ledger &, boost::asio::io_context &, nano::logger_mt &);
|
|
|
|
void start ();
|
|
void stop ();
|
|
|
|
private: // Dependencies
|
|
nano::websocket::config const & config;
|
|
nano::node_observers & observers;
|
|
nano::wallets & wallets;
|
|
nano::ledger & ledger;
|
|
boost::asio::io_context & io_ctx;
|
|
nano::logger_mt & logger;
|
|
|
|
public:
|
|
// TODO: Encapsulate, this is public just because existing code needs it
|
|
std::shared_ptr<nano::websocket::listener> server;
|
|
};
|
|
}
|