Merge pull request #4337 from pwojcikdev/stateless-frontier-server

Support for frontier requests in stateless bootstrap server
This commit is contained in:
Piotr Wójcik 2023-12-01 11:07:47 +01:00 committed by GitHub
commit 1ef520a258
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 431 additions and 81 deletions

View file

@ -5,6 +5,7 @@
#include <gtest/gtest.h>
#include <iterator>
#include <map>
using namespace std::chrono_literals;
@ -31,6 +32,13 @@ public:
return responses.size ();
}
void connect (nano::bootstrap_server & server)
{
server.on_response.add ([&] (auto & response, auto & channel) {
add (response);
});
}
private:
nano::mutex mutex;
std::vector<nano::asc_pull_ack> responses;
@ -65,9 +73,7 @@ TEST (bootstrap_server, serve_account_blocks)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 128);
auto [first_account, first_blocks] = chains.front ();
@ -77,7 +83,7 @@ TEST (bootstrap_server, serve_account_blocks)
request.id = 7;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = first_account;
request_payload.count = nano::bootstrap_server::max_blocks;
request_payload.start_type = nano::asc_pull_req::hash_type::account;
@ -109,9 +115,7 @@ TEST (bootstrap_server, serve_hash)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 256);
auto [account, blocks] = chains.front ();
@ -124,7 +128,7 @@ TEST (bootstrap_server, serve_hash)
request.id = 7;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = blocks.front ()->hash ();
request_payload.count = nano::bootstrap_server::max_blocks;
request_payload.start_type = nano::asc_pull_req::hash_type::block;
@ -156,9 +160,7 @@ TEST (bootstrap_server, serve_hash_one)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 256);
auto [account, blocks] = chains.front ();
@ -171,7 +173,7 @@ TEST (bootstrap_server, serve_hash_one)
request.id = 7;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = blocks.front ()->hash ();
request_payload.count = 1;
request_payload.start_type = nano::asc_pull_req::hash_type::block;
@ -200,9 +202,7 @@ TEST (bootstrap_server, serve_end_of_chain)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 128);
auto [account, blocks] = chains.front ();
@ -212,7 +212,7 @@ TEST (bootstrap_server, serve_end_of_chain)
request.id = 7;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = blocks.back ()->hash ();
request_payload.count = nano::bootstrap_server::max_blocks;
request_payload.start_type = nano::asc_pull_req::hash_type::block;
@ -242,9 +242,7 @@ TEST (bootstrap_server, serve_missing)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 128);
@ -253,7 +251,7 @@ TEST (bootstrap_server, serve_missing)
request.id = 7;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = nano::test::random_hash ();
request_payload.count = nano::bootstrap_server::max_blocks;
request_payload.start_type = nano::asc_pull_req::hash_type::block;
@ -282,9 +280,7 @@ TEST (bootstrap_server, serve_multiple)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 32, 16);
@ -298,7 +294,7 @@ TEST (bootstrap_server, serve_multiple)
request.id = next_id++;
request.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload request_payload;
nano::asc_pull_req::blocks_payload request_payload{};
request_payload.start = account;
request_payload.count = nano::bootstrap_server::max_blocks;
request_payload.start_type = nano::asc_pull_req::hash_type::account;
@ -345,9 +341,7 @@ TEST (bootstrap_server, serve_account_info)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 128);
auto [account, blocks] = chains.front ();
@ -357,7 +351,7 @@ TEST (bootstrap_server, serve_account_info)
request.id = 7;
request.type = nano::asc_pull_type::account_info;
nano::asc_pull_req::account_info_payload request_payload;
nano::asc_pull_req::account_info_payload request_payload{};
request_payload.target = account;
request_payload.target_type = nano::asc_pull_req::hash_type::account;
@ -393,9 +387,7 @@ TEST (bootstrap_server, serve_account_info_missing)
auto & node = *system.add_node ();
responses_helper responses;
node.bootstrap_server.on_response.add ([&] (auto & response, auto & channel) {
responses.add (response);
});
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, 1, 128);
auto [account, blocks] = chains.front ();
@ -405,7 +397,7 @@ TEST (bootstrap_server, serve_account_info_missing)
request.id = 7;
request.type = nano::asc_pull_type::account_info;
nano::asc_pull_req::account_info_payload request_payload;
nano::asc_pull_req::account_info_payload request_payload{};
request_payload.target = nano::test::random_account ();
request_payload.target_type = nano::asc_pull_req::hash_type::account;
@ -434,3 +426,123 @@ TEST (bootstrap_server, serve_account_info_missing)
// Ensure we don't get any unexpected responses
ASSERT_ALWAYS (1s, responses.size () == 1);
}
TEST (bootstrap_server, serve_frontiers)
{
nano::test::system system{};
auto & node = *system.add_node ();
responses_helper responses;
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, /* chain count */ 32, /* block count */ 4);
// Request all frontiers
nano::asc_pull_req request{ node.network_params.network };
request.id = 7;
request.type = nano::asc_pull_type::frontiers;
nano::asc_pull_req::frontiers_payload request_payload{};
request_payload.count = nano::bootstrap_server::max_frontiers;
request_payload.start = 0;
request.payload = request_payload;
request.update_header ();
node.network.inbound (request, nano::test::fake_channel (node));
ASSERT_TIMELY (5s, responses.size () == 1);
auto response = responses.get ().front ();
// Ensure we got response exactly for what we asked for
ASSERT_EQ (response.id, 7);
ASSERT_EQ (response.type, nano::asc_pull_type::frontiers);
nano::asc_pull_ack::frontiers_payload response_payload;
ASSERT_NO_THROW (response_payload = std::get<nano::asc_pull_ack::frontiers_payload> (response.payload));
ASSERT_EQ (response_payload.frontiers.size (), chains.size () + 1); // +1 for genesis
// Ensure frontiers match what we expect
std::map<nano::account, nano::block_hash> expected_frontiers;
for (auto & [account, blocks] : chains)
{
expected_frontiers[account] = blocks.back ()->hash ();
}
expected_frontiers[nano::dev::genesis_key.pub] = node.latest (nano::dev::genesis_key.pub);
for (auto & [account, frontier] : response_payload.frontiers)
{
ASSERT_EQ (frontier, expected_frontiers[account]);
expected_frontiers.erase (account);
}
ASSERT_TRUE (expected_frontiers.empty ());
}
TEST (bootstrap_server, serve_frontiers_invalid_count)
{
nano::test::system system{};
auto & node = *system.add_node ();
responses_helper responses;
responses.connect (node.bootstrap_server);
auto chains = nano::test::setup_chains (system, node, /* chain count */ 4, /* block count */ 4);
// Zero count
{
nano::asc_pull_req request{ node.network_params.network };
request.id = 7;
request.type = nano::asc_pull_type::frontiers;
nano::asc_pull_req::frontiers_payload request_payload{};
request_payload.count = 0;
request_payload.start = 0;
request.payload = request_payload;
request.update_header ();
node.network.inbound (request, nano::test::fake_channel (node));
}
ASSERT_TIMELY_EQ (5s, node.stats.count (nano::stat::type::bootstrap_server, nano::stat::detail::invalid), 1);
// Count larger than allowed
{
nano::asc_pull_req request{ node.network_params.network };
request.id = 7;
request.type = nano::asc_pull_type::frontiers;
nano::asc_pull_req::frontiers_payload request_payload{};
request_payload.count = nano::bootstrap_server::max_frontiers + 1;
request_payload.start = 0;
request.payload = request_payload;
request.update_header ();
node.network.inbound (request, nano::test::fake_channel (node));
}
ASSERT_TIMELY_EQ (5s, node.stats.count (nano::stat::type::bootstrap_server, nano::stat::detail::invalid), 2);
// Max numeric value
{
nano::asc_pull_req request{ node.network_params.network };
request.id = 7;
request.type = nano::asc_pull_type::frontiers;
nano::asc_pull_req::frontiers_payload request_payload{};
request_payload.count = std::numeric_limits<decltype (request_payload.count)>::max ();
request_payload.start = 0;
request.payload = request_payload;
request.update_header ();
node.network.inbound (request, nano::test::fake_channel (node));
}
ASSERT_TIMELY_EQ (5s, node.stats.count (nano::stat::type::bootstrap_server, nano::stat::detail::invalid), 3);
// Ensure we don't get any unexpected responses
ASSERT_ALWAYS (1s, responses.size () == 0);
}

View file

@ -262,7 +262,7 @@ TEST (message, asc_pull_req_serialization_blocks)
original.id = 7;
original.type = nano::asc_pull_type::blocks;
nano::asc_pull_req::blocks_payload original_payload;
nano::asc_pull_req::blocks_payload original_payload{};
original_payload.start = nano::test::random_hash ();
original_payload.count = 111;
@ -303,7 +303,7 @@ TEST (message, asc_pull_req_serialization_account_info)
original.id = 7;
original.type = nano::asc_pull_type::account_info;
nano::asc_pull_req::account_info_payload original_payload;
nano::asc_pull_req::account_info_payload original_payload{};
original_payload.target = nano::test::random_hash ();
original.payload = original_payload;
@ -336,16 +336,55 @@ TEST (message, asc_pull_req_serialization_account_info)
ASSERT_TRUE (nano::at_end (stream));
}
TEST (message, asc_pull_req_serialization_frontiers)
{
nano::asc_pull_req original{ nano::dev::network_params.network };
original.id = 7;
original.type = nano::asc_pull_type::frontiers;
nano::asc_pull_req::frontiers_payload original_payload{};
original_payload.start = nano::test::random_account ();
original_payload.count = 123;
original.payload = original_payload;
original.update_header ();
// Serialize
std::vector<uint8_t> bytes;
{
nano::vectorstream stream{ bytes };
original.serialize (stream);
}
nano::bufferstream stream{ bytes.data (), bytes.size () };
// Header
bool error = false;
nano::message_header header (error, stream);
ASSERT_FALSE (error);
ASSERT_EQ (nano::message_type::asc_pull_req, header.type);
// Message
nano::asc_pull_req message (error, stream, header);
ASSERT_FALSE (error);
ASSERT_EQ (original.id, message.id);
ASSERT_EQ (original.type, message.type);
nano::asc_pull_req::frontiers_payload message_payload;
ASSERT_NO_THROW (message_payload = std::get<nano::asc_pull_req::frontiers_payload> (message.payload));
ASSERT_EQ (original_payload.start, message_payload.start);
ASSERT_EQ (original_payload.count, message_payload.count);
ASSERT_TRUE (nano::at_end (stream));
}
TEST (message, asc_pull_ack_serialization_blocks)
{
nano::asc_pull_ack original{ nano::dev::network_params.network };
original.id = 11;
original.type = nano::asc_pull_type::blocks;
nano::asc_pull_ack::blocks_payload original_payload;
// Generate blocks
const int num_blocks = 128; // Maximum allowed
for (int n = 0; n < num_blocks; ++n)
nano::asc_pull_ack::blocks_payload original_payload{};
for (int n = 0; n < nano::asc_pull_ack::blocks_payload::max_blocks; ++n)
{
original_payload.blocks.push_back (random_block ());
}
@ -391,7 +430,7 @@ TEST (message, asc_pull_ack_serialization_account_info)
original.id = 11;
original.type = nano::asc_pull_type::account_info;
nano::asc_pull_ack::account_info_payload original_payload;
nano::asc_pull_ack::account_info_payload original_payload{};
original_payload.account = nano::test::random_account ();
original_payload.account_open = nano::test::random_hash ();
original_payload.account_head = nano::test::random_hash ();
@ -435,6 +474,49 @@ TEST (message, asc_pull_ack_serialization_account_info)
ASSERT_TRUE (nano::at_end (stream));
}
TEST (message, asc_pull_ack_serialization_frontiers)
{
nano::asc_pull_ack original{ nano::dev::network_params.network };
original.id = 11;
original.type = nano::asc_pull_type::frontiers;
nano::asc_pull_ack::frontiers_payload original_payload{};
for (int n = 0; n < nano::asc_pull_ack::frontiers_payload::max_frontiers; ++n)
{
original_payload.frontiers.push_back ({ nano::test::random_account (), nano::test::random_hash () });
}
original.payload = original_payload;
original.update_header ();
// Serialize
std::vector<uint8_t> bytes;
{
nano::vectorstream stream{ bytes };
original.serialize (stream);
}
nano::bufferstream stream{ bytes.data (), bytes.size () };
// Header
bool error = false;
nano::message_header header (error, stream);
ASSERT_FALSE (error);
ASSERT_EQ (nano::message_type::asc_pull_ack, header.type);
// Message
nano::asc_pull_ack message (error, stream, header);
ASSERT_FALSE (error);
ASSERT_EQ (original.id, message.id);
ASSERT_EQ (original.type, message.type);
nano::asc_pull_ack::frontiers_payload message_payload;
ASSERT_NO_THROW (message_payload = std::get<nano::asc_pull_ack::frontiers_payload> (message.payload));
ASSERT_EQ (original_payload.frontiers, message_payload.frontiers);
ASSERT_TRUE (nano::at_end (stream));
}
TEST (message, node_id_handshake_query_serialization)
{
nano::node_id_handshake::query_payload query{};

View file

@ -372,10 +372,12 @@ public:
/** Initial value is ACTIVE_NETWORK compile flag, but can be overridden by a CLI flag */
static nano::networks active_network;
/** Current protocol version */
uint8_t const protocol_version = 0x13;
uint8_t const protocol_version = 0x14;
/** Minimum accepted protocol version */
uint8_t const protocol_version_min = 0x12;
/** Minimum accepted protocol version used when bootstrapping */
uint8_t const bootstrap_protocol_version_min = 0x13;
};

View file

@ -242,6 +242,8 @@ enum class detail : uint8_t
response_blocks,
response_account_info,
channel_full,
response_frontiers,
frontiers,
// backlog
activated,

View file

@ -1,7 +1,9 @@
#include <nano/lib/utility.hpp>
#include <nano/node/bootstrap/bootstrap_server.hpp>
#include <nano/node/transport/channel.hpp>
#include <nano/node/transport/transport.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/store/account.hpp>
#include <nano/store/block.hpp>
#include <nano/store/component.hpp>
#include <nano/store/confirmation_height.hpp>
@ -21,7 +23,6 @@ nano::bootstrap_server::bootstrap_server (nano::store::component & store_a, nano
nano::bootstrap_server::~bootstrap_server ()
{
stop ();
}
void nano::bootstrap_server::start ()
@ -42,6 +43,7 @@ bool nano::bootstrap_server::verify_request_type (nano::asc_pull_type type) cons
return false;
case asc_pull_type::blocks:
case asc_pull_type::account_info:
case asc_pull_type::frontiers:
return true;
}
return false;
@ -68,6 +70,10 @@ bool nano::bootstrap_server::verify (const nano::asc_pull_req & message) const
{
return !pld.target.is_zero ();
}
bool operator() (nano::asc_pull_req::frontiers_payload const & pld) const
{
return pld.count > 0 && pld.count <= max_frontiers;
}
};
return std::visit (verify_visitor{}, message.payload);
@ -115,6 +121,11 @@ void nano::bootstrap_server::respond (nano::asc_pull_ack & response, std::shared
{
stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::response_account_info, nano::stat::dir::out);
}
void operator() (nano::asc_pull_ack::frontiers_payload const & pld)
{
stats.inc (nano::stat::type::bootstrap_server, nano::stat::detail::response_frontiers, nano::stat::dir::out);
stats.add (nano::stat::type::bootstrap_server, nano::stat::detail::frontiers, nano::stat::dir::out, pld.frontiers.size ());
}
};
std::visit (stat_visitor{ stats }, response.payload);
@ -140,6 +151,8 @@ void nano::bootstrap_server::process_batch (std::deque<request_t> & batch)
for (auto & [request, channel] : batch)
{
transaction.refresh_if_needed ();
if (!channel->max (nano::transport::traffic_type::bootstrap))
{
auto response = process (transaction, request);
@ -168,7 +181,7 @@ nano::asc_pull_ack nano::bootstrap_server::process (const store::transaction &,
}
/*
* Blocks response
* Blocks request
*/
nano::asc_pull_ack nano::bootstrap_server::process (store::transaction const & transaction, nano::asc_pull_req::id_t id, nano::asc_pull_req::blocks_payload const & request)
@ -203,7 +216,7 @@ nano::asc_pull_ack nano::bootstrap_server::process (store::transaction const & t
nano::asc_pull_ack nano::bootstrap_server::prepare_response (store::transaction const & transaction, nano::asc_pull_req::id_t id, nano::block_hash start_block, std::size_t count)
{
debug_assert (count <= max_blocks);
debug_assert (count <= max_blocks); // Should be filtered out earlier
auto blocks = prepare_blocks (transaction, start_block, count);
debug_assert (blocks.size () <= count);
@ -212,7 +225,7 @@ nano::asc_pull_ack nano::bootstrap_server::prepare_response (store::transaction
response.id = id;
response.type = nano::asc_pull_type::blocks;
nano::asc_pull_ack::blocks_payload response_payload;
nano::asc_pull_ack::blocks_payload response_payload{};
response_payload.blocks = blocks;
response.payload = response_payload;
@ -235,7 +248,7 @@ nano::asc_pull_ack nano::bootstrap_server::prepare_empty_blocks_response (nano::
std::vector<std::shared_ptr<nano::block>> nano::bootstrap_server::prepare_blocks (store::transaction const & transaction, nano::block_hash start_block, std::size_t count) const
{
debug_assert (count <= max_blocks);
debug_assert (count <= max_blocks); // Should be filtered out earlier
std::vector<std::shared_ptr<nano::block>> result;
if (!start_block.is_zero ())
@ -253,7 +266,7 @@ std::vector<std::shared_ptr<nano::block>> nano::bootstrap_server::prepare_blocks
}
/*
* Account info response
* Account info request
*/
nano::asc_pull_ack nano::bootstrap_server::process (const store::transaction & transaction, nano::asc_pull_req::id_t id, const nano::asc_pull_req::account_info_payload & request)
@ -301,3 +314,27 @@ nano::asc_pull_ack nano::bootstrap_server::process (const store::transaction & t
response.update_header ();
return response;
}
/*
* Frontiers request
*/
nano::asc_pull_ack nano::bootstrap_server::process (const store::transaction & transaction, nano::asc_pull_req::id_t id, const nano::asc_pull_req::frontiers_payload & request)
{
debug_assert (request.count <= max_frontiers); // Should be filtered out earlier
nano::asc_pull_ack response{ network_constants };
response.id = id;
response.type = nano::asc_pull_type::frontiers;
nano::asc_pull_ack::frontiers_payload response_payload{};
for (auto it = store.account.begin (transaction, request.start), end = store.account.end (); it != end && response_payload.frontiers.size () < request.count; ++it)
{
response_payload.frontiers.emplace_back (it->first, it->second.head);
}
response.payload = response_payload;
response.update_header ();
return response;
}

View file

@ -57,7 +57,7 @@ private:
nano::asc_pull_ack process (store::transaction const &, nano::asc_pull_req::id_t id, nano::empty_payload const & request);
/*
* Blocks response
* Blocks request
*/
nano::asc_pull_ack process (store::transaction const &, nano::asc_pull_req::id_t id, nano::asc_pull_req::blocks_payload const & request);
nano::asc_pull_ack prepare_response (store::transaction const &, nano::asc_pull_req::id_t id, nano::block_hash start_block, std::size_t count);
@ -65,10 +65,15 @@ private:
std::vector<std::shared_ptr<nano::block>> prepare_blocks (store::transaction const &, nano::block_hash start_block, std::size_t count) const;
/*
* Account info response
* Account info request
*/
nano::asc_pull_ack process (store::transaction const &, nano::asc_pull_req::id_t id, nano::asc_pull_req::account_info_payload const & request);
/*
* Frontiers request
*/
nano::asc_pull_ack process (store::transaction const &, nano::asc_pull_req::id_t id, nano::asc_pull_req::frontiers_payload const & request);
/*
* Checks if the request should be dropped early on
*/
@ -82,10 +87,11 @@ private: // Dependencies
nano::stats & stats;
private:
processing_queue<request_t> request_queue;
nano::processing_queue<request_t> request_queue;
public: // Config
/** Maximum number of blocks to send in a single response, cannot be higher than capacity of a single `asc_pull_ack` message */
constexpr static std::size_t max_blocks = nano::asc_pull_ack::blocks_payload::max_blocks;
constexpr static std::size_t max_frontiers = nano::asc_pull_ack::frontiers_payload::max_frontiers;
};
}

View file

@ -366,6 +366,7 @@ void nano::bootstrap_ascending::service::process (nano::asc_pull_ack const & mes
on_reply.notify (tag);
condition.notify_all ();
std::visit ([this, &tag] (auto && request) { return process (request, tag); }, message.payload);
}
else
@ -416,6 +417,11 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::acco
// TODO: Make use of account info
}
void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::frontiers_payload & response, const nano::bootstrap_ascending::service::async_tag & tag)
{
// TODO: Make use of frontiers info
}
void nano::bootstrap_ascending::service::process (const nano::empty_payload & response, const nano::bootstrap_ascending::service::async_tag & tag)
{
// Should not happen

View file

@ -109,6 +109,7 @@ namespace bootstrap_ascending
void process (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag);
void process (nano::asc_pull_ack::account_info_payload const & response, async_tag const & tag);
void process (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag);
void process (nano::empty_payload const & response, async_tag const & tag);
enum class verify_result

View file

@ -1556,14 +1556,21 @@ void nano::asc_pull_req::deserialize_payload (nano::stream & stream)
{
case asc_pull_type::blocks:
{
blocks_payload pld;
blocks_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
}
case asc_pull_type::account_info:
{
account_info_payload pld;
account_info_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
}
case asc_pull_type::frontiers:
{
frontiers_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
@ -1575,12 +1582,13 @@ void nano::asc_pull_req::deserialize_payload (nano::stream & stream)
void nano::asc_pull_req::update_header ()
{
// TODO: Avoid serializing the payload twice
std::vector<uint8_t> bytes;
{
nano::vectorstream payload_stream (bytes);
serialize_payload (payload_stream);
}
debug_assert (bytes.size () <= 65535u); // Max int16 for storing size
debug_assert (bytes.size () <= std::numeric_limits<uint16_t>::max ()); // Max uint16 for storing size
debug_assert (bytes.size () >= 1);
header.extensions = std::bitset<16> (bytes.size ());
}
@ -1609,6 +1617,10 @@ bool nano::asc_pull_req::verify_consistency () const
{
debug_assert (type == asc_pull_type::account_info);
}
void operator() (frontiers_payload) const
{
debug_assert (type == asc_pull_type::frontiers);
}
};
std::visit (consistency_visitor{ type }, payload);
return true; // Just for convenience of calling from asserts
@ -1677,6 +1689,22 @@ void nano::asc_pull_req::account_info_payload::deserialize (stream & stream)
nano::read (stream, target_type);
}
/*
* asc_pull_req::frontiers_payload
*/
void nano::asc_pull_req::frontiers_payload::serialize (nano::stream & stream) const
{
nano::write (stream, start);
nano::write_big_endian (stream, count);
}
void nano::asc_pull_req::frontiers_payload::deserialize (nano::stream & stream)
{
nano::read (stream, start);
nano::read_big_endian (stream, count);
}
/*
* asc_pull_ack
*/
@ -1699,7 +1727,7 @@ void nano::asc_pull_ack::visit (nano::message_visitor & visitor) const
void nano::asc_pull_ack::serialize (nano::stream & stream) const
{
debug_assert (header.extensions.to_ulong () > 0); // Block payload must have least `not_a_block` terminator
debug_assert (header.extensions.to_ulong () > 0); // Block payload must have at least `not_a_block` terminator
header.serialize (stream);
nano::write (stream, type);
nano::write_big_endian (stream, id);
@ -1738,14 +1766,21 @@ void nano::asc_pull_ack::deserialize_payload (nano::stream & stream)
{
case asc_pull_type::blocks:
{
blocks_payload pld;
blocks_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
}
case asc_pull_type::account_info:
{
account_info_payload pld;
account_info_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
}
case asc_pull_type::frontiers:
{
frontiers_payload pld{};
pld.deserialize (stream);
payload = pld;
break;
@ -1757,12 +1792,13 @@ void nano::asc_pull_ack::deserialize_payload (nano::stream & stream)
void nano::asc_pull_ack::update_header ()
{
// TODO: Avoid serializing the payload twice
std::vector<uint8_t> bytes;
{
nano::vectorstream payload_stream (bytes);
serialize_payload (payload_stream);
}
debug_assert (bytes.size () <= 65535u); // Max int16 for storing size
debug_assert (bytes.size () <= std::numeric_limits<uint16_t>::max ()); // Max uint16 for storing size
debug_assert (bytes.size () >= 1);
header.extensions = std::bitset<16> (bytes.size ());
}
@ -1791,6 +1827,10 @@ bool nano::asc_pull_ack::verify_consistency () const
{
debug_assert (type == asc_pull_type::account_info);
}
void operator() (frontiers_payload) const
{
debug_assert (type == asc_pull_type::frontiers);
}
};
std::visit (consistency_visitor{ type }, payload);
return true; // Just for convenience of calling from asserts
@ -1842,6 +1882,7 @@ std::string nano::asc_pull_ack::to_string () const
void nano::asc_pull_ack::blocks_payload::serialize (nano::stream & stream) const
{
debug_assert (blocks.size () <= max_blocks);
for (auto & block : blocks)
{
debug_assert (block != nullptr);
@ -1884,3 +1925,44 @@ void nano::asc_pull_ack::account_info_payload::deserialize (nano::stream & strea
nano::read (stream, account_conf_frontier);
nano::read_big_endian (stream, account_conf_height);
}
/*
* asc_pull_ack::frontiers_payload
*/
void nano::asc_pull_ack::frontiers_payload::serialize_frontier (nano::stream & stream, nano::asc_pull_ack::frontiers_payload::frontier const & frontier)
{
auto const & [account, hash] = frontier;
nano::write (stream, account);
nano::write (stream, hash);
}
nano::asc_pull_ack::frontiers_payload::frontier nano::asc_pull_ack::frontiers_payload::deserialize_frontier (nano::stream & stream)
{
nano::account account;
nano::block_hash hash;
nano::read (stream, account);
nano::read (stream, hash);
return { account, hash };
}
void nano::asc_pull_ack::frontiers_payload::serialize (nano::stream & stream) const
{
debug_assert (frontiers.size () <= max_frontiers);
for (auto const & frontier : frontiers)
{
serialize_frontier (stream, frontier);
}
serialize_frontier (stream, { nano::account{ 0 }, nano::block_hash{ 0 } });
}
void nano::asc_pull_ack::frontiers_payload::deserialize (nano::stream & stream)
{
auto current = deserialize_frontier (stream);
while ((!current.first.is_zero () && !current.second.is_zero ()) && frontiers.size () < max_frontiers)
{
frontiers.push_back (current);
current = deserialize_frontier (stream);
}
}

View file

@ -390,11 +390,11 @@ enum class asc_pull_type : uint8_t
invalid = 0x0,
blocks = 0x1,
account_info = 0x2,
frontiers = 0x3,
};
class empty_payload
struct empty_payload
{
public:
void serialize (nano::stream &) const
{
debug_assert (false);
@ -445,36 +445,43 @@ public: // Payload definitions
block = 1,
};
class blocks_payload
struct blocks_payload
{
public:
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
public:
// Payload
nano::hash_or_account start{ 0 };
uint8_t count{ 0 };
asc_pull_req::hash_type start_type{ 0 };
hash_type start_type{};
};
class account_info_payload
struct account_info_payload
{
public:
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
public:
// Payload
nano::hash_or_account target{ 0 };
asc_pull_req::hash_type target_type{ 0 };
hash_type target_type{};
};
struct frontiers_payload
{
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
// Payload
nano::account start{ 0 };
uint16_t count{ 0 };
};
public: // Payload
/** Currently unused, allows extensions in the future */
asc_pull_type type{ asc_pull_type::invalid };
id_t id{ 0 };
/** Payload depends on `asc_pull_type` */
std::variant<empty_payload, blocks_payload, account_info_payload> payload;
std::variant<empty_payload, blocks_payload, account_info_payload, frontiers_payload> payload;
public:
/** Size of message without payload */
@ -515,27 +522,24 @@ private: // Debug
bool verify_consistency () const;
public: // Payload definitions
class blocks_payload
struct blocks_payload
{
public:
/* Header allows for 16 bit extensions; 65536 bytes / 500 bytes (block size with some future margin) ~ 131 */
constexpr static std::size_t max_blocks = 128;
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
public:
std::vector<std::shared_ptr<nano::block>> blocks{};
public:
/* Header allows for 16 bit extensions; 65535 bytes / 500 bytes (block size with some future margin) ~ 131 */
constexpr static std::size_t max_blocks = 128;
// Payload
std::vector<std::shared_ptr<nano::block>> blocks;
};
class account_info_payload
struct account_info_payload
{
public:
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
public:
// Payload
nano::account account{ 0 };
nano::block_hash account_open{ 0 };
nano::block_hash account_head{ 0 };
@ -544,13 +548,29 @@ public: // Payload definitions
uint64_t account_conf_height{ 0 };
};
struct frontiers_payload
{
/* Header allows for 16 bit extensions; 65536 bytes / 64 bytes (account + frontier) ~ 1024, but we need some space for null frontier terminator */
constexpr static std::size_t max_frontiers = 1000;
using frontier = std::pair<nano::account, nano::block_hash>;
void serialize (nano::stream &) const;
void deserialize (nano::stream &);
static void serialize_frontier (nano::stream &, frontier const &);
static frontier deserialize_frontier (nano::stream &);
// Payload
std::vector<frontier> frontiers;
};
public: // Payload
/** Currently unused, allows extensions in the future */
asc_pull_type type{ asc_pull_type::invalid };
id_t id{ 0 };
/** Payload depends on `asc_pull_type` */
std::variant<empty_payload, blocks_payload, account_info_payload> payload;
std::variant<empty_payload, blocks_payload, account_info_payload, frontiers_payload> payload;
public:
/** Size of message without payload */