diff --git a/nano/core_test/block.cpp b/nano/core_test/block.cpp index 2b69396ce6..cf9aaff446 100644 --- a/nano/core_test/block.cpp +++ b/nano/core_test/block.cpp @@ -332,6 +332,58 @@ TEST (block, confirm_req_serialization) ASSERT_EQ (*req.block, *req2.block); } +TEST (block, confirm_req_hash_serialization) +{ + nano::keypair key1; + nano::keypair key2; + nano::send_block block (1, key2.pub, 200, nano::keypair ().prv, 2, 3); + nano::confirm_req req (block.hash (), block.root ()); + std::vector bytes; + { + nano::vectorstream stream (bytes); + req.serialize (stream); + } + auto error (false); + nano::bufferstream stream2 (bytes.data (), bytes.size ()); + nano::message_header header (error, stream2); + nano::confirm_req req2 (error, stream2, header); + ASSERT_FALSE (error); + ASSERT_EQ (req, req2); + ASSERT_EQ (req.roots_hashes, req2.roots_hashes); +} + +TEST (block, confirm_req_hash_batch_serialization) +{ + nano::keypair key; + nano::keypair representative; + std::vector> roots_hashes; + nano::state_block open (key.pub, 0, representative.pub, 2, 4, key.prv, key.pub, 5); + roots_hashes.push_back (std::make_pair (open.hash (), open.root ())); + for (auto i (roots_hashes.size ()); i < 7; i++) + { + nano::keypair key1; + nano::keypair previous; + nano::state_block block (key1.pub, previous.pub, representative.pub, 2, 4, key1.prv, key1.pub, 5); + roots_hashes.push_back (std::make_pair (block.hash (), block.root ())); + } + roots_hashes.push_back (std::make_pair (open.hash (), open.root ())); + nano::confirm_req req (roots_hashes); + std::vector bytes; + { + nano::vectorstream stream (bytes); + req.serialize (stream); + } + auto error (false); + nano::bufferstream stream2 (bytes.data (), bytes.size ()); + nano::message_header header (error, stream2); + nano::confirm_req req2 (error, stream2, header); + ASSERT_FALSE (error); + ASSERT_EQ (req, req2); + ASSERT_EQ (req.roots_hashes, req2.roots_hashes); + ASSERT_EQ (req.roots_hashes, roots_hashes); + ASSERT_EQ (req2.roots_hashes, roots_hashes); +} + TEST (state_block, serialization) { nano::keypair key1; diff --git a/nano/core_test/message_parser.cpp b/nano/core_test/message_parser.cpp index 4047c56bc5..8677d2acb7 100644 --- a/nano/core_test/message_parser.cpp +++ b/nano/core_test/message_parser.cpp @@ -130,6 +130,38 @@ TEST (message_parser, exact_confirm_req_size) ASSERT_NE (parser.status, nano::message_parser::parse_status::success); } +TEST (message_parser, exact_confirm_req_hash_size) +{ + nano::system system (24000, 1); + test_visitor visitor; + nano::block_uniquer block_uniquer; + nano::vote_uniquer vote_uniquer (block_uniquer); + nano::message_parser parser (block_uniquer, vote_uniquer, visitor, system.work); + nano::send_block block (1, 1, 2, nano::keypair ().prv, 4, system.work.generate (1)); + nano::confirm_req message (block.hash (), block.root ()); + std::vector bytes; + { + nano::vectorstream stream (bytes); + message.serialize (stream); + } + ASSERT_EQ (0, visitor.confirm_req_count); + ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); + auto error (false); + nano::bufferstream stream1 (bytes.data (), bytes.size ()); + nano::message_header header1 (error, stream1); + ASSERT_FALSE (error); + parser.deserialize_confirm_req (stream1, header1); + ASSERT_EQ (1, visitor.confirm_req_count); + ASSERT_EQ (parser.status, nano::message_parser::parse_status::success); + bytes.push_back (0); + nano::bufferstream stream2 (bytes.data (), bytes.size ()); + nano::message_header header2 (error, stream2); + ASSERT_FALSE (error); + parser.deserialize_confirm_req (stream2, header2); + ASSERT_EQ (1, visitor.confirm_req_count); + ASSERT_NE (parser.status, nano::message_parser::parse_status::success); +} + TEST (message_parser, exact_publish_size) { nano::system system (24000, 1); diff --git a/nano/node/common.cpp b/nano/node/common.cpp index c8ffc41f78..d0cd62ecac 100644 --- a/nano/node/common.cpp +++ b/nano/node/common.cpp @@ -302,7 +302,7 @@ void nano::message_parser::deserialize_confirm_req (nano::stream & stream_a, nan nano::confirm_req incoming (error, stream_a, header_a, &block_uniquer); if (!error && at_end (stream_a)) { - if (!nano::work_validate (*incoming.block)) + if (incoming.block == nullptr || !nano::work_validate (*incoming.block)) { visitor.confirm_req (incoming); } @@ -484,12 +484,55 @@ block (block_a) { header.block_type_set (block->type ()); } +nano::confirm_req::confirm_req (std::vector> const & roots_hashes_a) : +message (nano::message_type::confirm_req), +roots_hashes (roots_hashes_a) +{ + // not_a_block (1) block type for hashes + roots request + header.block_type_set (nano::block_type::not_a_block); +} + +nano::confirm_req::confirm_req (nano::block_hash const & hash_a, nano::block_hash const & root_a) : +message (nano::message_type::confirm_req), +roots_hashes (std::vector> (1, std::make_pair (hash_a, root_a))) +{ + assert (!roots_hashes.empty ()); + // not_a_block (1) block type for hashes + roots request + header.block_type_set (nano::block_type::not_a_block); +} bool nano::confirm_req::deserialize (nano::stream & stream_a, nano::block_uniquer * uniquer_a) { + bool result (true); assert (header.type == nano::message_type::confirm_req); - block = nano::deserialize_block (stream_a, header.block_type (), uniquer_a); - auto result (block == nullptr); + if (header.block_type () == nano::block_type::not_a_block) + { + uint8_t count (0); + result = read (stream_a, count); + for (auto i (0); i != count && !result; ++i) + { + nano::block_hash block_hash (0); + nano::block_hash root (0); + result = read (stream_a, block_hash); + if (!result && !block_hash.is_zero ()) + { + result = read (stream_a, root); + if (!result && !root.is_zero ()) + { + roots_hashes.push_back (std::make_pair (block_hash, root)); + } + } + } + if (!result) + { + result = roots_hashes.empty () || (roots_hashes.size () != count); + } + } + else + { + block = nano::deserialize_block (stream_a, header.block_type (), uniquer_a); + result = block == nullptr; + } return result; } @@ -500,14 +543,53 @@ void nano::confirm_req::visit (nano::message_visitor & visitor_a) const void nano::confirm_req::serialize (nano::stream & stream_a) const { - assert (block != nullptr); header.serialize (stream_a); - block->serialize (stream_a); + if (header.block_type () == nano::block_type::not_a_block) + { + assert (!roots_hashes.empty ()); + // Calculate size + assert (roots_hashes.size () <= 32); + uint8_t count (roots_hashes.size ()); + write (stream_a, count); + // Write hashes & roots + for (auto & root_hash : roots_hashes) + { + write (stream_a, root_hash.first); + write (stream_a, root_hash.second); + } + } + else + { + assert (block != nullptr); + block->serialize (stream_a); + } } bool nano::confirm_req::operator== (nano::confirm_req const & other_a) const { - return *block == *other_a.block; + bool equal (false); + if (block != nullptr && other_a.block != nullptr) + { + equal = *block == *other_a.block; + } + else if (!roots_hashes.empty () && !other_a.roots_hashes.empty ()) + { + equal = roots_hashes == other_a.roots_hashes; + } + return equal; +} + +std::string nano::confirm_req::roots_string () const +{ + std::string result; + for (auto & root_hash : roots_hashes) + { + result += root_hash.first.to_string (); + result += ":"; + result += root_hash.second.to_string (); + result += ", "; + } + return result; } nano::confirm_ack::confirm_ack (bool & error_a, nano::stream & stream_a, nano::message_header const & header_a, nano::vote_uniquer * uniquer_a) : diff --git a/nano/node/common.hpp b/nano/node/common.hpp index 333f9b8328..536c015737 100644 --- a/nano/node/common.hpp +++ b/nano/node/common.hpp @@ -297,11 +297,15 @@ class confirm_req : public message public: confirm_req (bool &, nano::stream &, nano::message_header const &, nano::block_uniquer * = nullptr); confirm_req (std::shared_ptr); + confirm_req (std::vector> const &); + confirm_req (nano::block_hash const &, nano::block_hash const &); bool deserialize (nano::stream &, nano::block_uniquer * = nullptr); void serialize (nano::stream &) const override; void visit (nano::message_visitor &) const override; bool operator== (nano::confirm_req const &) const; std::shared_ptr block; + std::vector> roots_hashes; + std::string roots_string () const; }; class confirm_ack : public message { diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 4e1a35dcad..1e4a8158e2 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -235,7 +235,7 @@ void nano::network::republish (nano::block_hash const & hash_a, std::shared_ptr< BOOST_LOG (node.log) << boost::str (boost::format ("Publishing %1% to %2%") % hash_a.to_string () % endpoint_a); } std::weak_ptr node_w (node.shared ()); - send_buffer (buffer_a->data (), buffer_a->size (), endpoint_a, [buffer_a, node_w, endpoint_a](boost::system::error_code const & ec, size_t size) { + send_buffer (buffer_a->data (), buffer_a->size (), endpoint_a, [node_w, endpoint_a](boost::system::error_code const & ec, size_t size) { if (auto node_l = node_w.lock ()) { if (ec && node_l->config.logging.network_logging ()) @@ -289,6 +289,23 @@ bool confirm_block (nano::transaction const & transaction_a, nano::node & node_a return result; } +void nano::network::confirm_hashes (nano::transaction const & transaction_a, nano::endpoint const & peer_a, std::vector blocks_bundle_a) +{ + if (node.config.enable_voting) + { + node.wallets.foreach_representative (transaction_a, [this, &blocks_bundle_a, &peer_a, &transaction_a](nano::public_key const & pub_a, nano::raw_key const & prv_a) { + auto vote (this->node.store.vote_generate (transaction_a, pub_a, prv_a, blocks_bundle_a)); + nano::confirm_ack confirm (vote); + std::shared_ptr> bytes (new std::vector); + { + nano::vectorstream stream (*bytes); + confirm.serialize (stream); + } + this->node.network.confirm_send (confirm, bytes, peer_a); + }); + } +} + void nano::network::republish_block (std::shared_ptr block) { auto hash (block->hash ()); @@ -305,6 +322,22 @@ void nano::network::republish_block (std::shared_ptr block) } } +void nano::network::republish_block (std::shared_ptr block, nano::endpoint const & peer_a) +{ + auto hash (block->hash ()); + nano::publish message (block); + std::vector bytes; + { + nano::vectorstream stream (bytes); + message.serialize (stream); + } + republish (hash, std::make_shared> (bytes), peer_a); + if (node.config.logging.network_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Block %1% was republished to peer") % hash.to_string ()); + } +} + void nano::network::republish_block_batch (std::deque> blocks_a, unsigned delay_a) { auto block (blocks_a.front ()); @@ -394,6 +427,43 @@ void nano::network::broadcast_confirm_req_base (std::shared_ptr blo } } +void nano::network::broadcast_confirm_req_batch (std::unordered_map>> request_bundle_a, unsigned delay_a, bool resumption) +{ + const size_t max_reps = 10; + if (!resumption && node.config.logging.network_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Broadcasting batch confirm req to %1% representatives") % request_bundle_a.size ()); + } + auto count (0); + while (!request_bundle_a.empty () && count < max_reps) + { + auto j (request_bundle_a.begin ()); + count++; + std::vector> roots_hashes; + // Limit max request size hash + root to 6 pairs + while (roots_hashes.size () <= confirm_req_hashes_max && !j->second.empty ()) + { + roots_hashes.push_back (j->second.back ()); + j->second.pop_back (); + } + send_confirm_req_hashes (j->first, roots_hashes); + if (j->second.empty ()) + { + request_bundle_a.erase (j); + } + } + if (!request_bundle_a.empty ()) + { + std::weak_ptr node_w (node.shared ()); + node.alarm.add (std::chrono::steady_clock::now () + std::chrono::milliseconds (delay_a), [node_w, request_bundle_a, delay_a]() { + if (auto node_l = node_w.lock ()) + { + node_l->network.broadcast_confirm_req_batch (request_bundle_a, delay_a + 50, true); + } + }); + } +} + void nano::network::broadcast_confirm_req_batch (std::deque, std::shared_ptr>>> deque_a, unsigned delay_a) { auto pair (deque_a.front ()); @@ -440,6 +510,31 @@ void nano::network::send_confirm_req (nano::endpoint const & endpoint_a, std::sh }); } +void nano::network::send_confirm_req_hashes (nano::endpoint const & endpoint_a, std::vector> const & roots_hashes_a) +{ + nano::confirm_req message (roots_hashes_a); + std::vector bytes; + { + nano::vectorstream stream (bytes); + message.serialize (stream); + } + if (node.config.logging.network_message_logging ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Sending confirm req hashes to %1%") % endpoint_a); + } + std::weak_ptr node_w (node.shared ()); + node.stats.inc (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::out); + send_buffer (bytes.data (), bytes.size (), endpoint_a, [node_w](boost::system::error_code const & ec, size_t size) { + if (auto node_l = node_w.lock ()) + { + if (ec && node_l->config.logging.network_logging ()) + { + BOOST_LOG (node_l->log) << boost::str (boost::format ("Error sending confirm request: %1%") % ec.message ()); + } + } + }); +} + template void rep_query (nano::node & node_a, T const & peers_a) { @@ -515,7 +610,14 @@ class network_message_visitor : public nano::message_visitor { if (node.config.logging.network_message_logging ()) { - BOOST_LOG (node.log) << boost::str (boost::format ("Confirm_req message from %1% for %2%") % sender % message_a.block->hash ().to_string ()); + if (!message_a.roots_hashes.empty ()) + { + BOOST_LOG (node.log) << boost::str (boost::format ("Confirm_req message from %1% for hashes:roots %2%") % sender % message_a.roots_string ()); + } + else + { + BOOST_LOG (node.log) << boost::str (boost::format ("Confirm_req message from %1% for %2%") % sender % message_a.block->hash ().to_string ()); + } } node.stats.inc (nano::stat::type::message, nano::stat::detail::confirm_req, nano::stat::dir::in); node.peers.contacted (sender, message_a.header.version_using); @@ -523,11 +625,50 @@ class network_message_visitor : public nano::message_visitor if (node.config.enable_voting) { auto transaction (node.store.tx_begin_read ()); - auto successor (node.ledger.successor (transaction, nano::uint512_union (message_a.block->previous (), message_a.block->root ()))); - if (successor != nullptr) + if (message_a.block != nullptr) + { + auto successor (node.ledger.successor (transaction, nano::uint512_union (message_a.block->previous (), message_a.block->root ()))); + if (successor != nullptr) + { + auto same_block (successor->hash () == message_a.block->hash ()); + confirm_block (transaction, node, sender, std::move (successor), !same_block); + } + } + else if (!message_a.roots_hashes.empty ()) { - auto same_block (successor->hash () == message_a.block->hash ()); - confirm_block (transaction, node, sender, std::move (successor), !same_block); + std::vector blocks_bundle; + for (auto & root_hash : message_a.roots_hashes) + { + if (node.store.block_exists (transaction, root_hash.first)) + { + blocks_bundle.push_back (root_hash.first); + } + else + { + nano::block_hash successor (0); + // Search for block root + successor = node.store.block_successor (transaction, root_hash.second); + // Search for account root + if (successor.is_zero () && node.store.account_exists (transaction, root_hash.second)) + { + nano::account_info info; + auto error (node.store.account_get (transaction, root_hash.second, info)); + assert (!error); + successor = info.open_block; + } + if (!successor.is_zero ()) + { + blocks_bundle.push_back (successor); + auto successor_block (node.store.block_get (transaction, successor)); + assert (successor_block != nullptr); + node.network.republish_block (std::move (successor_block), sender); + } + } + } + if (!blocks_bundle.empty ()) + { + node.network.confirm_hashes (transaction, sender, blocks_bundle); + } } } } @@ -3305,6 +3446,7 @@ void nano::active_transactions::request_confirm (std::unique_lock & auto transaction (node.store.tx_begin_read ()); unsigned unconfirmed_count (0); unsigned unconfirmed_announcements (0); + std::unordered_map>> requests_bundle; std::deque> rebroadcast_bundle; std::deque, std::shared_ptr>>> confirm_req_bundle; @@ -3425,15 +3567,60 @@ void nano::active_transactions::request_confirm (std::unique_lock & } if ((!reps->empty () && total_weight > node.config.online_weight_minimum.number ()) || roots_size > 5) { - if (confirm_req_bundle.size () < max_broadcast_queue) + // broadcast_confirm_req_base modifies reps, so we clone it once to avoid aliasing + if (nano::nano_network != nano::nano_networks::nano_test_network) { - confirm_req_bundle.push_back (std::make_pair (i->election->status.winner, reps)); + if (confirm_req_bundle.size () < max_broadcast_queue) + { + confirm_req_bundle.push_back (std::make_pair (i->election->status.winner, reps)); + } + } + else + { + for (auto & rep : *reps) + { + auto rep_request (requests_bundle.find (rep.endpoint)); + auto block (i->election->status.winner); + auto root_hash (std::make_pair (block->hash (), block->root ())); + if (rep_request == requests_bundle.end ()) + { + if (requests_bundle.size () < max_broadcast_queue) + { + std::vector> insert_vector = { root_hash }; + requests_bundle.insert (std::make_pair (rep.endpoint, insert_vector)); + } + } + else if (rep_request->second.size () < max_broadcast_queue * nano::network::confirm_req_hashes_max) + { + rep_request->second.push_back (root_hash); + } + } } } else { - // broadcast request to all peers - confirm_req_bundle.push_back (std::make_pair (i->election->status.winner, std::make_shared> (node.peers.list_vector (100)))); + if (nano::nano_network != nano::nano_networks::nano_test_network) + { + confirm_req_bundle.push_back (std::make_pair (i->election->status.winner, std::make_shared> (node.peers.list_vector (100)))); + } + else + { + for (auto & rep : *reps) + { + auto rep_request (requests_bundle.find (rep.endpoint)); + auto block (i->election->status.winner); + auto root_hash (std::make_pair (block->hash (), block->root ())); + if (rep_request == requests_bundle.end ()) + { + std::vector> insert_vector = { root_hash }; + requests_bundle.insert (std::make_pair (rep.endpoint, insert_vector)); + } + else + { + rep_request->second.push_back (root_hash); + } + } + } } } } @@ -3445,6 +3632,11 @@ void nano::active_transactions::request_confirm (std::unique_lock & { node.network.republish_block_batch (rebroadcast_bundle); } + // Batch confirmation request + if (nano::nano_network != nano::nano_networks::nano_live_network && !requests_bundle.empty ()) + { + node.network.broadcast_confirm_req_batch (requests_bundle, 50); + } //confirm_req broadcast if (!confirm_req_bundle.empty ()) { diff --git a/nano/node/node.hpp b/nano/node/node.hpp index e69a8e808b..5ecef876ea 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -300,6 +300,7 @@ class network void rpc_action (boost::system::error_code const &, size_t); void republish_vote (std::shared_ptr); void republish_block (std::shared_ptr); + void republish_block (std::shared_ptr, nano::endpoint const &); static unsigned const broadcast_interval_ms = 10; void republish_block_batch (std::deque>, unsigned = broadcast_interval_ms); void republish (nano::block_hash const &, std::shared_ptr>, nano::endpoint); @@ -309,8 +310,11 @@ class network void send_node_id_handshake (nano::endpoint const &, boost::optional const & query, boost::optional const & respond_to); void broadcast_confirm_req (std::shared_ptr); void broadcast_confirm_req_base (std::shared_ptr, std::shared_ptr>, unsigned, bool = false); + void broadcast_confirm_req_batch (std::unordered_map>>, unsigned = broadcast_interval_ms, bool = false); void broadcast_confirm_req_batch (std::deque, std::shared_ptr>>>, unsigned = broadcast_interval_ms); void send_confirm_req (nano::endpoint const &, std::shared_ptr); + void send_confirm_req_hashes (nano::endpoint const &, std::vector> const &); + void confirm_hashes (nano::transaction const &, nano::endpoint const &, std::vector); void send_buffer (uint8_t const *, size_t, nano::endpoint const &, std::function); nano::endpoint endpoint (); nano::udp_buffer buffer_container; @@ -322,6 +326,7 @@ class network bool on; static uint16_t const node_port = nano::nano_network == nano::nano_networks::nano_live_network ? 7075 : 54000; static size_t const buffer_size = 512; + static size_t const confirm_req_hashes_max = 6; }; class node_init diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 37ed17e0c6..fbae5a299a 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -35,7 +35,7 @@ struct hash<::nano::uint512_union> } namespace nano { -const uint8_t protocol_version = 0x0f; +const uint8_t protocol_version = 0x10; const uint8_t protocol_version_min = 0x0d; const uint8_t node_id_version = 0x0c;