Skip to content

Commit 2decca4

Browse files
committed
Add V3 handshake with node capabilities field
Extend the handshake protocol to advertise node capabilities as a bitfield in the response payload. Replace the v2 optional with a variant over v1/v2/v3 payloads and propagate capabilities to the channel on connection.
1 parent 8780348 commit 2decca4

18 files changed

Lines changed: 400 additions & 82 deletions

nano/core_test/message.cpp

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ TEST (message, node_id_handshake_response_serialization)
786786
ASSERT_FALSE (error);
787787
ASSERT_FALSE (message.query);
788788
ASSERT_TRUE (message.response);
789-
ASSERT_FALSE (message.response->v2);
789+
ASSERT_TRUE (std::holds_alternative<std::monostate> (message.response->ext));
790790

791791
ASSERT_EQ (original.response->node_id, message.response->node_id);
792792
ASSERT_EQ (original.response->signature, message.response->signature);
@@ -802,7 +802,7 @@ TEST (message, node_id_handshake_response_v2_serialization)
802802
nano::messages::node_id_handshake::response_payload::v2_payload v2_pld{};
803803
v2_pld.salt = 17;
804804
v2_pld.genesis = nano::block_hash{ 13 };
805-
response.v2 = v2_pld;
805+
response.ext = v2_pld;
806806

807807
nano::messages::node_id_handshake original{ nano::dev::network_params.network, std::nullopt, response };
808808

@@ -825,12 +825,63 @@ TEST (message, node_id_handshake_response_v2_serialization)
825825
ASSERT_FALSE (error);
826826
ASSERT_FALSE (message.query);
827827
ASSERT_TRUE (message.response);
828-
ASSERT_TRUE (message.response->v2);
828+
829+
auto * v2_orig = std::get_if<nano::messages::node_id_handshake::response_payload::v2_payload> (&original.response->ext);
830+
auto * v2_msg = std::get_if<nano::messages::node_id_handshake::response_payload::v2_payload> (&message.response->ext);
831+
ASSERT_TRUE (v2_orig);
832+
ASSERT_TRUE (v2_msg);
829833

830834
ASSERT_EQ (original.response->node_id, message.response->node_id);
831835
ASSERT_EQ (original.response->signature, message.response->signature);
832-
ASSERT_EQ (original.response->v2->salt, message.response->v2->salt);
833-
ASSERT_EQ (original.response->v2->genesis, message.response->v2->genesis);
836+
ASSERT_EQ (v2_orig->salt, v2_msg->salt);
837+
ASSERT_EQ (v2_orig->genesis, v2_msg->genesis);
838+
839+
ASSERT_TRUE (nano::at_end (stream));
840+
}
841+
842+
TEST (message, node_id_handshake_response_v3_serialization)
843+
{
844+
nano::messages::node_id_handshake::response_payload response{};
845+
response.node_id = nano::account{ 7 };
846+
response.signature = nano::signature{ 11 };
847+
nano::messages::node_id_handshake::response_payload::v3_payload v3_pld{};
848+
v3_pld.salt = 17;
849+
v3_pld.genesis = nano::block_hash{ 13 };
850+
v3_pld.flags = nano::node_capabilities_flags{ nano::node_capabilities::topo_index } | nano::node_capabilities::vote_storage;
851+
response.ext = v3_pld;
852+
853+
nano::messages::node_id_handshake original{ nano::dev::network_params.network, std::nullopt, response };
854+
855+
// Serialize
856+
std::vector<uint8_t> bytes;
857+
{
858+
nano::vectorstream stream{ bytes };
859+
original.serialize (stream);
860+
}
861+
nano::bufferstream stream{ bytes.data (), bytes.size () };
862+
863+
// Header
864+
bool error = false;
865+
nano::messages::message_header header (error, stream);
866+
ASSERT_FALSE (error);
867+
ASSERT_EQ (nano::messages::message_type::node_id_handshake, header.type);
868+
869+
// Message
870+
nano::messages::node_id_handshake message{ error, stream, header };
871+
ASSERT_FALSE (error);
872+
ASSERT_FALSE (message.query);
873+
ASSERT_TRUE (message.response);
874+
875+
auto * v3_orig = std::get_if<nano::messages::node_id_handshake::response_payload::v3_payload> (&original.response->ext);
876+
auto * v3_msg = std::get_if<nano::messages::node_id_handshake::response_payload::v3_payload> (&message.response->ext);
877+
ASSERT_TRUE (v3_orig);
878+
ASSERT_TRUE (v3_msg);
879+
880+
ASSERT_EQ (original.response->node_id, message.response->node_id);
881+
ASSERT_EQ (original.response->signature, message.response->signature);
882+
ASSERT_EQ (v3_orig->salt, v3_msg->salt);
883+
ASSERT_EQ (v3_orig->genesis, v3_msg->genesis);
884+
ASSERT_EQ (v3_orig->flags, v3_msg->flags);
834885

835886
ASSERT_TRUE (nano::at_end (stream));
836887
}
@@ -864,9 +915,10 @@ TEST (handshake, signature_v2)
864915

865916
nano::messages::node_id_handshake::response_payload original{};
866917
original.node_id = node_id.pub;
867-
original.v2 = nano::messages::node_id_handshake::response_payload::v2_payload{};
868-
original.v2->genesis = nano::test::random_hash ();
869-
original.v2->salt = nano::random_pool::generate<nano::uint256_union> ();
918+
nano::messages::node_id_handshake::response_payload::v2_payload v2{};
919+
v2.genesis = nano::test::random_hash ();
920+
v2.salt = nano::random_pool::generate<nano::uint256_union> ();
921+
original.ext = v2;
870922
original.sign (cookie, node_id);
871923
ASSERT_TRUE (original.validate (cookie));
872924

@@ -885,15 +937,55 @@ TEST (handshake, signature_v2)
885937
{
886938
auto message = original;
887939
ASSERT_TRUE (message.validate (cookie));
888-
message.v2->genesis = nano::test::random_hash ();
940+
std::get<nano::messages::node_id_handshake::response_payload::v2_payload> (message.ext).genesis = nano::test::random_hash ();
889941
ASSERT_FALSE (message.validate (cookie));
890942
}
891943

892944
// Invalid salt
893945
{
894946
auto message = original;
895947
ASSERT_TRUE (message.validate (cookie));
896-
message.v2->salt = nano::random_pool::generate<nano::uint256_union> ();
948+
std::get<nano::messages::node_id_handshake::response_payload::v2_payload> (message.ext).salt = nano::random_pool::generate<nano::uint256_union> ();
949+
ASSERT_FALSE (message.validate (cookie));
950+
}
951+
}
952+
953+
TEST (handshake, signature_v3)
954+
{
955+
nano::keypair node_id{};
956+
auto cookie = nano::random_pool::generate<nano::uint256_union> ();
957+
958+
nano::messages::node_id_handshake::response_payload original{};
959+
original.node_id = node_id.pub;
960+
nano::messages::node_id_handshake::response_payload::v3_payload v3{};
961+
v3.genesis = nano::test::random_hash ();
962+
v3.salt = nano::random_pool::generate<nano::uint256_union> ();
963+
v3.flags = nano::node_capabilities::topo_index;
964+
original.ext = v3;
965+
original.sign (cookie, node_id);
966+
ASSERT_TRUE (original.validate (cookie));
967+
968+
// Mutate flags -> signature should fail
969+
{
970+
auto message = original;
971+
ASSERT_TRUE (message.validate (cookie));
972+
std::get<nano::messages::node_id_handshake::response_payload::v3_payload> (message.ext).flags = {};
973+
ASSERT_FALSE (message.validate (cookie));
974+
}
975+
976+
// Mutate genesis -> signature should fail
977+
{
978+
auto message = original;
979+
ASSERT_TRUE (message.validate (cookie));
980+
std::get<nano::messages::node_id_handshake::response_payload::v3_payload> (message.ext).genesis = nano::test::random_hash ();
981+
ASSERT_FALSE (message.validate (cookie));
982+
}
983+
984+
// Mutate reserved -> signature should fail
985+
{
986+
auto message = original;
987+
ASSERT_TRUE (message.validate (cookie));
988+
std::get<nano::messages::node_id_handshake::response_payload::v3_payload> (message.ext).reserved = 42;
897989
ASSERT_FALSE (message.validate (cookie));
898990
}
899991
}

nano/core_test/tcp_server.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <nano/crypto_lib/random_pool.hpp>
2+
#include <nano/lib/node_capabilities.hpp>
23
#include <nano/node/node.hpp>
34
#include <nano/node/transport/tcp_server.hpp>
45
#include <nano/node/transport/tcp_socket.hpp>
@@ -196,7 +197,7 @@ TEST (tcp_server, handshake_self_connection_rejected)
196197

197198
nano::messages::node_id_handshake::response_payload response;
198199
response.node_id = node->node_id.pub; // Use our own node ID
199-
response.v2 = nano::messages::node_id_handshake::response_payload::v2_payload{
200+
response.ext = nano::messages::node_id_handshake::response_payload::v2_payload{
200201
nano::random_pool::generate<nano::uint256_union> (), // salt
201202
node->network_params.ledger.genesis->hash () // genesis
202203
};
@@ -366,4 +367,40 @@ TEST (tcp_server, handshake_wrong_network_id)
366367
// Verify the handshake was aborted
367368
ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_server, nano::stat::detail::handshake_abort), 1);
368369
ASSERT_TIMELY_EQ (5s, node->stats.count (nano::stat::type::tcp_server_message_error, nano::stat::detail::invalid_network), 1);
370+
}
371+
372+
/*
373+
* Verify that v3 handshake exchanges node capability flags between peers
374+
*/
375+
TEST (tcp_server, handshake_flags_exchanged)
376+
{
377+
nano::test::system system;
378+
379+
nano::node_flags flags1;
380+
flags1.capabilities_override = nano::node_capabilities_flags{ nano::node_capabilities::topo_index };
381+
auto node1 = system.add_node (flags1);
382+
383+
nano::node_flags flags2;
384+
flags2.capabilities_override = nano::node_capabilities_flags{ nano::node_capabilities::vote_storage };
385+
auto node2 = system.add_node (flags2);
386+
387+
node1->network.merge_peer (node2->network.endpoint ());
388+
389+
ASSERT_TIMELY (10s, node1->network.find_node_id (node2->node_id.pub));
390+
ASSERT_TIMELY (10s, node2->network.find_node_id (node1->node_id.pub));
391+
392+
// node1's channel to node2 should carry node2's capabilities (vote_storage)
393+
auto chan1 = node1->network.find_node_id (node2->node_id.pub);
394+
ASSERT_TRUE (chan1);
395+
ASSERT_TRUE (chan1->get_flags ().test (nano::node_capabilities::vote_storage));
396+
ASSERT_FALSE (chan1->get_flags ().test (nano::node_capabilities::topo_index));
397+
398+
// node2's channel to node1 should carry node1's capabilities (topo_index)
399+
auto chan2 = node2->network.find_node_id (node1->node_id.pub);
400+
ASSERT_TRUE (chan2);
401+
ASSERT_TRUE (chan2->get_flags ().test (nano::node_capabilities::topo_index));
402+
ASSERT_FALSE (chan2->get_flags ().test (nano::node_capabilities::vote_storage));
403+
404+
ASSERT_EQ (node1->stats.count (nano::stat::type::tcp_server, nano::stat::detail::handshake_abort), 0);
405+
ASSERT_EQ (node2->stats.count (nano::stat::type::tcp_server, nano::stat::detail::handshake_abort), 0);
369406
}

nano/lib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ add_library(
9090
network_filter.cpp
9191
networks.hpp
9292
networks.cpp
93+
node_capabilities.hpp
94+
node_capabilities.cpp
9395
numbers.hpp
9496
numbers.cpp
9597
numbers_templ.hpp

nano/lib/node_capabilities.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <nano/lib/enum_flags_templ.hpp>
2+
#include <nano/lib/enum_util.hpp>
3+
#include <nano/lib/node_capabilities.hpp>
4+
5+
std::string_view nano::to_string (nano::node_capabilities value)
6+
{
7+
return nano::enum_to_string (value);
8+
}
9+
10+
template std::ostream & nano::operator<< <nano::node_capabilities> (std::ostream &, nano::node_capabilities_flags const &);
11+
template std::string nano::to_string<nano::node_capabilities> (nano::node_capabilities_flags const &);

nano/lib/node_capabilities.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <nano/lib/enum_flags.hpp>
4+
5+
#include <cstdint>
6+
#include <string_view>
7+
8+
namespace nano
9+
{
10+
enum class node_capabilities : uint64_t
11+
{
12+
none = 0,
13+
topo_index = 1ULL << 0,
14+
vote_storage = 1ULL << 1,
15+
};
16+
17+
std::string_view to_string (node_capabilities);
18+
19+
using node_capabilities_flags = nano::enum_flags<node_capabilities>;
20+
}

nano/lib/utility.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ namespace program_options
2626

2727
namespace nano
2828
{
29+
// Helper for std::visit with multiple lambdas
30+
template <class... Ts>
31+
struct overloaded : Ts...
32+
{
33+
using Ts::operator()...;
34+
};
35+
template <class... Ts>
36+
overloaded (Ts...) -> overloaded<Ts...>;
37+
2938
// Lower priority of calling work generating thread
3039
void work_thread_reprioritize ();
3140

0 commit comments

Comments
 (0)