Skip to content

Commit

Permalink
Incremental options for ws confirmation subscription (#2566)
Browse files Browse the repository at this point in the history
Possibility to add or remove accounts in an existing subscription. This is useful for external wallets that can't use the all_local_accounts flag.
  • Loading branch information
guilhermelawless committed Feb 14, 2020
1 parent dd14ea1 commit 58080cc
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 7 deletions.
68 changes: 68 additions & 0 deletions nano/core_test/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,74 @@ TEST (websocket, confirmation_options)
}
}

// Tests updating options of block confirmations
TEST (websocket, confirmation_options_update)
{
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<bool> added{ false };
std::atomic<bool> deleted{ false };
auto task = ([&added, &deleted, config, &node1]() {
fake_websocket_client client (config.websocket_config.port);
// Subscribe initially with empty options, everything will be filtered
client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {}})json");
client.await_ack ();
EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation));
// Now update filter with an account and wait for a response
std::string add_message = boost::str (boost::format (R"json({"action": "update", "topic": "confirmation", "ack": "true", "options": {"accounts_add": ["%1%"]}})json") % nano::test_genesis_key.pub.to_account ());
client.send_message (add_message);
client.await_ack ();
EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation));
added = true;
EXPECT_TRUE (client.get_response ());
// Update the filter again, removing the account
std::string delete_message = boost::str (boost::format (R"json({"action": "update", "topic": "confirmation", "ack": "true", "options": {"accounts_del": ["%1%"]}})json") % nano::test_genesis_key.pub.to_account ());
client.send_message (delete_message);
client.await_ack ();
EXPECT_EQ (1, node1->websocket_server->subscriber_count (nano::websocket::topic::confirmation));
deleted = true;
EXPECT_FALSE (client.get_response (1s));
});
auto future = std::async (std::launch::async, task);

// Wait for update acknowledgement
system.deadline_set (5s);
while (!added)
{
ASSERT_NO_ERROR (system.poll ());
}

// Confirm a block
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
nano::genesis genesis;
nano::keypair key;
auto 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 - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous)));
node1->process_active (send);

// Wait for delete acknowledgement
system.deadline_set (5s);
while (!deleted)
{
ASSERT_NO_ERROR (system.poll ());
}

// Confirm another block
previous = send->hash ();
auto send2 (std::make_shared<nano::state_block> (nano::test_genesis_key.pub, previous, nano::test_genesis_key.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (previous)));
node1->process_active (send2);

system.deadline_set (5s);
while (future.wait_for (0s) != std::future_status::ready)
{
ASSERT_NO_ERROR (system.poll ());
}
}

// Subscribes to votes, sends a block and awaits websocket notification of a vote arrival
TEST (websocket, vote)
{
Expand Down
76 changes: 70 additions & 6 deletions nano/node/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ wallets (wallets_a)
}

nano::websocket::confirmation_options::confirmation_options (boost::property_tree::ptree const & options_a, nano::wallets & wallets_a, nano::logger_mt & logger_a) :
wallets (wallets_a)
wallets (wallets_a),
logger (logger_a)
{
// Non-account filtering options
include_block = options_a.get<bool> ("include_block", true);
Expand Down Expand Up @@ -83,11 +84,7 @@ wallets (wallets_a)
logger_a.always_log ("Websocket: Filtering option \"accounts\" requires that \"include_block\" is set to true to be effective");
}
}
// Warn the user if the options resulted in an empty filter
if (has_account_filtering_options && !all_local_accounts && accounts.empty ())
{
logger_a.always_log ("Websocket: provided options resulted in an empty block confirmation filter");
}
check_filter_empty ();
}

bool nano::websocket::confirmation_options::should_filter (nano::websocket::message const & message_a) const
Expand Down Expand Up @@ -136,6 +133,60 @@ bool nano::websocket::confirmation_options::should_filter (nano::websocket::mess
return should_filter_conf_type_l || should_filter_account;
}

bool nano::websocket::confirmation_options::update (boost::property_tree::ptree const & options_a)
{
auto update_accounts = [this](boost::property_tree::ptree const & accounts_text_a, bool insert_a) {
this->has_account_filtering_options = true;
for (auto const & account_l : accounts_text_a)
{
nano::account result_l (0);
if (!result_l.decode_account (account_l.second.data ()))
{
// Re-encode to keep old prefix support
auto encoded_l (result_l.to_account ());
if (insert_a)
{
this->accounts.insert (encoded_l);
}
else
{
this->accounts.erase (encoded_l);
}
}
else if (this->logger.is_initialized ())
{
this->logger->always_log ("Websocket: invalid account provided for filtering blocks: ", account_l.second.data ());
}
}
};

// Adding accounts as filter exceptions
auto accounts_add_l (options_a.get_child_optional ("accounts_add"));
if (accounts_add_l)
{
update_accounts (*accounts_add_l, true);
}

// Removing accounts as filter exceptions
auto accounts_del_l (options_a.get_child_optional ("accounts_del"));
if (accounts_del_l)
{
update_accounts (*accounts_del_l, false);
}

check_filter_empty ();
return false;
}

void nano::websocket::confirmation_options::check_filter_empty () const
{
// Warn the user if the options resulted in an empty filter
if (logger.is_initialized () && has_account_filtering_options && !all_local_accounts && accounts.empty ())
{
logger->always_log ("Websocket: provided options resulted in an empty block confirmation filter");
}
}

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);
Expand Down Expand Up @@ -428,6 +479,19 @@ void nano::websocket::session::handle_message (boost::property_tree::ptree const
}
action_succeeded = true;
}
else if (action == "update")
{
nano::lock_guard<std::mutex> lk (subscriptions_mutex);
auto existing (subscriptions.find (topic_l));
if (existing != subscriptions.end ())
{
auto options_text_l (message_a.get_child_optional ("options"));
if (options_text_l.is_initialized () && !existing->second->update (*options_text_l))
{
action_succeeded = true;
}
}
}
else if (action == "unsubscribe" && topic_l != nano::websocket::topic::invalid)
{
nano::lock_guard<std::mutex> lk (subscriptions_mutex);
Expand Down
27 changes: 26 additions & 1 deletion nano/node/websocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace websocket
{
class listener;
class confirmation_options;
class session;

/** Supported topics */
enum class topic
Expand Down Expand Up @@ -102,6 +103,9 @@ namespace websocket
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
Expand All @@ -111,7 +115,16 @@ namespace websocket
{
return false;
}
virtual ~options () = default;
/**
* 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;
};

/**
Expand All @@ -137,6 +150,15 @@ namespace websocket
*/
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
{
Expand All @@ -156,7 +178,10 @@ namespace websocket
static constexpr const uint8_t 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_block{ true };
bool has_account_filtering_options{ false };
Expand Down

0 comments on commit 58080cc

Please sign in to comment.