Merge pull request #4749 from pwojcikdev/channel-cleanup
Channel class cleanup
This commit is contained in:
		
				commit
				
					
						902bc4b01d
					
				
			
		
					 26 changed files with 84 additions and 128 deletions
				
			
		| 
						 | 
				
			
			@ -1024,14 +1024,13 @@ TEST (network, loopback_channel)
 | 
			
		|||
	auto & node2 = *system.nodes[1];
 | 
			
		||||
	nano::transport::inproc::channel channel1 (node1, node1);
 | 
			
		||||
	ASSERT_EQ (channel1.get_type (), nano::transport::transport_type::loopback);
 | 
			
		||||
	ASSERT_EQ (channel1.get_endpoint (), node1.network.endpoint ());
 | 
			
		||||
	ASSERT_EQ (channel1.get_tcp_endpoint (), nano::transport::map_endpoint_to_tcp (node1.network.endpoint ()));
 | 
			
		||||
	ASSERT_EQ (channel1.get_remote_endpoint (), node1.network.endpoint ());
 | 
			
		||||
	ASSERT_EQ (channel1.get_network_version (), node1.network_params.network.protocol_version);
 | 
			
		||||
	ASSERT_EQ (channel1.get_node_id (), node1.node_id.pub);
 | 
			
		||||
	ASSERT_EQ (channel1.get_node_id_optional ().value_or (0), node1.node_id.pub);
 | 
			
		||||
	nano::transport::inproc::channel channel2 (node2, node2);
 | 
			
		||||
	++node1.network.port;
 | 
			
		||||
	ASSERT_NE (channel1.get_endpoint (), node1.network.endpoint ());
 | 
			
		||||
	ASSERT_NE (channel1.get_remote_endpoint (), node1.network.endpoint ());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ensure the network filters messages with the incorrect magic number
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2703,7 +2703,7 @@ TEST (node, peer_history_restart)
 | 
			
		|||
		ASSERT_TIMELY (10s, !node2->network.empty ());
 | 
			
		||||
		// Confirm that the peers match with the endpoints we are expecting
 | 
			
		||||
		auto list (node2->network.list (2));
 | 
			
		||||
		ASSERT_EQ (node1->network.endpoint (), list[0]->get_endpoint ());
 | 
			
		||||
		ASSERT_EQ (node1->network.endpoint (), list[0]->get_remote_endpoint ());
 | 
			
		||||
		ASSERT_EQ (1, node2->network.size ());
 | 
			
		||||
		system.stop_node (*node2);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -2726,7 +2726,7 @@ TEST (node, peer_history_restart)
 | 
			
		|||
		ASSERT_TIMELY (10s, !node3->network.empty ());
 | 
			
		||||
		// Confirm that the peers match with the endpoints we are expecting
 | 
			
		||||
		auto list (node3->network.list (2));
 | 
			
		||||
		ASSERT_EQ (node1->network.endpoint (), list[0]->get_endpoint ());
 | 
			
		||||
		ASSERT_EQ (node1->network.endpoint (), list[0]->get_remote_endpoint ());
 | 
			
		||||
		ASSERT_EQ (1, node3->network.size ());
 | 
			
		||||
		system.stop_node (*node3);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -2788,11 +2788,11 @@ TEST (node, bidirectional_tcp)
 | 
			
		|||
	ASSERT_EQ (1, node2->network.size ());
 | 
			
		||||
	auto list1 (node1->network.list (1));
 | 
			
		||||
	ASSERT_EQ (nano::transport::transport_type::tcp, list1[0]->get_type ());
 | 
			
		||||
	ASSERT_NE (node2->network.endpoint (), list1[0]->get_endpoint ()); // Ephemeral port
 | 
			
		||||
	ASSERT_NE (node2->network.endpoint (), list1[0]->get_remote_endpoint ()); // Ephemeral port
 | 
			
		||||
	ASSERT_EQ (node2->node_id.pub, list1[0]->get_node_id ());
 | 
			
		||||
	auto list2 (node2->network.list (1));
 | 
			
		||||
	ASSERT_EQ (nano::transport::transport_type::tcp, list2[0]->get_type ());
 | 
			
		||||
	ASSERT_EQ (node1->network.endpoint (), list2[0]->get_endpoint ());
 | 
			
		||||
	ASSERT_EQ (node1->network.endpoint (), list2[0]->get_remote_endpoint ());
 | 
			
		||||
	ASSERT_EQ (node1->node_id.pub, list2[0]->get_node_id ());
 | 
			
		||||
	// Test block propagation from node 1
 | 
			
		||||
	nano::keypair key;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,16 +79,12 @@ TEST (peer_container, tcp_channel_cleanup_works)
 | 
			
		|||
	ASSERT_NE (nullptr, channel1);
 | 
			
		||||
	// set the last packet sent for channel1 only to guarantee it contains a value.
 | 
			
		||||
	// it won't be necessarily the same use by the cleanup cutoff time
 | 
			
		||||
	node1.network.tcp_channels.modify (channel1, [&now] (auto channel) {
 | 
			
		||||
		channel->set_last_packet_sent (now - std::chrono::seconds (5));
 | 
			
		||||
	});
 | 
			
		||||
	channel1->set_last_packet_sent (now - std::chrono::seconds (5));
 | 
			
		||||
	auto channel2 = nano::test::establish_tcp (system, node1, outer_node2->network.endpoint ());
 | 
			
		||||
	ASSERT_NE (nullptr, channel2);
 | 
			
		||||
	// set the last packet sent for channel2 only to guarantee it contains a value.
 | 
			
		||||
	// it won't be necessarily the same use by the cleanup cutoff time
 | 
			
		||||
	node1.network.tcp_channels.modify (channel2, [&now] (auto channel) {
 | 
			
		||||
		channel->set_last_packet_sent (now + std::chrono::seconds (1));
 | 
			
		||||
	});
 | 
			
		||||
	channel2->set_last_packet_sent (now + std::chrono::seconds (1));
 | 
			
		||||
	ASSERT_EQ (2, node1.network.size ());
 | 
			
		||||
	ASSERT_EQ (2, node1.network.tcp_channels.size ());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -230,7 +230,7 @@ TEST (request_aggregator, two_endpoints)
 | 
			
		|||
 | 
			
		||||
	auto dummy_channel1 = std::make_shared<nano::transport::inproc::channel> (node1, node1);
 | 
			
		||||
	auto dummy_channel2 = std::make_shared<nano::transport::inproc::channel> (node2, node2);
 | 
			
		||||
	ASSERT_NE (nano::transport::map_endpoint_to_v6 (dummy_channel1->get_endpoint ()), nano::transport::map_endpoint_to_v6 (dummy_channel2->get_endpoint ()));
 | 
			
		||||
	ASSERT_NE (nano::transport::map_endpoint_to_v6 (dummy_channel1->get_remote_endpoint ()), nano::transport::map_endpoint_to_v6 (dummy_channel2->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	std::vector<std::pair<nano::block_hash, nano::root>> request{ { send1->hash (), send1->root () } };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,18 +68,18 @@ TEST (telemetry, basic)
 | 
			
		|||
	ASSERT_NE (nullptr, channel);
 | 
			
		||||
 | 
			
		||||
	std::optional<nano::telemetry_data> telemetry_data;
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
	ASSERT_EQ (node_server->get_node_id (), telemetry_data->node_id);
 | 
			
		||||
 | 
			
		||||
	// Check the metrics are correct
 | 
			
		||||
	ASSERT_TRUE (nano::test::compare_telemetry (*telemetry_data, *node_server));
 | 
			
		||||
 | 
			
		||||
	// Call again straight away
 | 
			
		||||
	auto telemetry_data_2 = node_client->telemetry.get_telemetry (channel->get_endpoint ());
 | 
			
		||||
	auto telemetry_data_2 = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ());
 | 
			
		||||
	ASSERT_TRUE (telemetry_data_2);
 | 
			
		||||
 | 
			
		||||
	// Call again straight away
 | 
			
		||||
	auto telemetry_data_3 = node_client->telemetry.get_telemetry (channel->get_endpoint ());
 | 
			
		||||
	auto telemetry_data_3 = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ());
 | 
			
		||||
	ASSERT_TRUE (telemetry_data_3);
 | 
			
		||||
 | 
			
		||||
	// we expect at least one consecutive repeat of telemetry
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ TEST (telemetry, basic)
 | 
			
		|||
	WAIT (3s);
 | 
			
		||||
 | 
			
		||||
	std::optional<nano::telemetry_data> telemetry_data_4;
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data_4 = node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data_4 = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
	ASSERT_NE (*telemetry_data, *telemetry_data_4);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,13 +120,13 @@ TEST (telemetry, disconnected)
 | 
			
		|||
	ASSERT_NE (nullptr, channel);
 | 
			
		||||
 | 
			
		||||
	// Ensure telemetry is available before disconnecting
 | 
			
		||||
	ASSERT_TIMELY (5s, node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	system.stop_node (*node_server);
 | 
			
		||||
	ASSERT_TRUE (channel);
 | 
			
		||||
 | 
			
		||||
	// Ensure telemetry from disconnected peer is removed
 | 
			
		||||
	ASSERT_TIMELY (5s, !node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, !node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST (telemetry, dos_tcp)
 | 
			
		||||
| 
						 | 
				
			
			@ -185,14 +185,14 @@ TEST (telemetry, disable_metrics)
 | 
			
		|||
 | 
			
		||||
	node_client->telemetry.trigger ();
 | 
			
		||||
 | 
			
		||||
	ASSERT_NEVER (1s, node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_NEVER (1s, node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	// It should still be able to receive metrics though
 | 
			
		||||
	auto channel1 = node_server->network.find_node_id (node_client->get_node_id ());
 | 
			
		||||
	ASSERT_NE (nullptr, channel1);
 | 
			
		||||
 | 
			
		||||
	std::optional<nano::telemetry_data> telemetry_data;
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_server->telemetry.get_telemetry (channel1->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_server->telemetry.get_telemetry (channel1->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	ASSERT_TRUE (nano::test::compare_telemetry (*telemetry_data, *node_client));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +237,7 @@ TEST (telemetry, maker_pruning)
 | 
			
		|||
	ASSERT_NE (nullptr, channel);
 | 
			
		||||
 | 
			
		||||
	std::optional<nano::telemetry_data> telemetry_data;
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
	ASSERT_EQ (node_server->get_node_id (), telemetry_data->node_id);
 | 
			
		||||
 | 
			
		||||
	// Ensure telemetry response indicates pruned node
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1005,7 +1005,7 @@ TEST (websocket, telemetry)
 | 
			
		|||
 | 
			
		||||
	auto channel = node1->network.find_node_id (node2->get_node_id ());
 | 
			
		||||
	ASSERT_NE (channel, nullptr);
 | 
			
		||||
	ASSERT_TIMELY (5s, node1->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, node1->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	ASSERT_TIMELY_EQ (10s, future.wait_for (0s), std::future_status::ready);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ void nano::bootstrap_connections::pool_connection (std::shared_ptr<nano::bootstr
 | 
			
		|||
{
 | 
			
		||||
	nano::unique_lock<nano::mutex> lock{ mutex };
 | 
			
		||||
	auto const & socket_l = client_a->socket;
 | 
			
		||||
	if (!stopped && !client_a->pending_stop && !node.network.excluded_peers.check (client_a->channel->get_tcp_endpoint ()))
 | 
			
		||||
	if (!stopped && !client_a->pending_stop && !node.network.excluded_peers.check (client_a->channel->get_remote_endpoint ()))
 | 
			
		||||
	{
 | 
			
		||||
		socket_l->set_timeout (node.network_params.network.idle_timeout);
 | 
			
		||||
		// Push into idle deque
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +138,7 @@ std::shared_ptr<nano::bootstrap_client> nano::bootstrap_connections::find_connec
 | 
			
		|||
	std::shared_ptr<nano::bootstrap_client> result;
 | 
			
		||||
	for (auto i (idle.begin ()), end (idle.end ()); i != end && !stopped; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		if ((*i)->channel->get_tcp_endpoint () == endpoint_a)
 | 
			
		||||
		if ((*i)->channel->get_remote_endpoint () == endpoint_a)
 | 
			
		||||
		{
 | 
			
		||||
			result = *i;
 | 
			
		||||
			idle.erase (i);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -138,7 +138,7 @@ bool nano::bootstrap_attempt_legacy::request_frontier (nano::unique_lock<nano::m
 | 
			
		|||
	lock_a.lock ();
 | 
			
		||||
	if (connection_l && !stopped)
 | 
			
		||||
	{
 | 
			
		||||
		endpoint_frontier_request = connection_l->channel->get_tcp_endpoint ();
 | 
			
		||||
		endpoint_frontier_request = connection_l->channel->get_remote_endpoint ();
 | 
			
		||||
		std::future<bool> future;
 | 
			
		||||
		{
 | 
			
		||||
			auto this_l = std::dynamic_pointer_cast<nano::bootstrap_attempt_legacy> (shared_from_this ());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2991,7 +2991,7 @@ void nano::json_handler::peers ()
 | 
			
		|||
	bool const peer_details = request.get<bool> ("peer_details", false);
 | 
			
		||||
	auto peers_list (node.network.list (std::numeric_limits<std::size_t>::max ()));
 | 
			
		||||
	std::sort (peers_list.begin (), peers_list.end (), [] (auto const & lhs, auto const & rhs) {
 | 
			
		||||
		return lhs->get_endpoint () < rhs->get_endpoint ();
 | 
			
		||||
		return lhs->get_remote_endpoint () < rhs->get_remote_endpoint ();
 | 
			
		||||
	});
 | 
			
		||||
	for (auto i (peers_list.begin ()), n (peers_list.end ()); i != n; ++i)
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -3003,9 +3003,9 @@ void nano::json_handler::peers ()
 | 
			
		|||
			boost::property_tree::ptree pending_tree;
 | 
			
		||||
			pending_tree.put ("protocol_version", std::to_string (channel->get_network_version ()));
 | 
			
		||||
			auto node_id_l (channel->get_node_id_optional ());
 | 
			
		||||
			if (node_id_l.is_initialized ())
 | 
			
		||||
			if (node_id_l.has_value ())
 | 
			
		||||
			{
 | 
			
		||||
				pending_tree.put ("node_id", node_id_l.get ().to_node_id ());
 | 
			
		||||
				pending_tree.put ("node_id", node_id_l.value ().to_node_id ());
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,7 +175,7 @@ public:
 | 
			
		|||
		if (peer0.address () == boost::asio::ip::address_v6{} && peer0.port () != 0)
 | 
			
		||||
		{
 | 
			
		||||
			// TODO: Remove this as we do not need to establish a second connection to the same peer
 | 
			
		||||
			nano::endpoint new_endpoint (channel->get_tcp_endpoint ().address (), peer0.port ());
 | 
			
		||||
			nano::endpoint new_endpoint (channel->get_remote_endpoint ().address (), peer0.port ());
 | 
			
		||||
			node.network.merge_peer (new_endpoint);
 | 
			
		||||
 | 
			
		||||
			// Remember this for future forwarding to other peers
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -496,14 +496,14 @@ void nano::network::erase (nano::transport::channel const & channel_a)
 | 
			
		|||
	auto const channel_type = channel_a.get_type ();
 | 
			
		||||
	if (channel_type == nano::transport::transport_type::tcp)
 | 
			
		||||
	{
 | 
			
		||||
		tcp_channels.erase (channel_a.get_tcp_endpoint ());
 | 
			
		||||
		tcp_channels.erase (channel_a.get_remote_endpoint ());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::network::exclude (std::shared_ptr<nano::transport::channel> const & channel)
 | 
			
		||||
{
 | 
			
		||||
	// Add to peer exclusion list
 | 
			
		||||
	excluded_peers.add (channel->get_tcp_endpoint ());
 | 
			
		||||
	excluded_peers.add (channel->get_remote_endpoint ());
 | 
			
		||||
 | 
			
		||||
	// Disconnect
 | 
			
		||||
	erase (*channel);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ void nano::rep_crawler::validate_and_process (nano::unique_lock<nano::mutex> & l
 | 
			
		|||
				rep.last_response = std::chrono::steady_clock::now ();
 | 
			
		||||
 | 
			
		||||
				// Update if representative channel was changed
 | 
			
		||||
				if (rep.channel->get_endpoint () != channel->get_endpoint ())
 | 
			
		||||
				if (rep.channel->get_remote_endpoint () != channel->get_remote_endpoint ())
 | 
			
		||||
				{
 | 
			
		||||
					debug_assert (rep.account == vote->account);
 | 
			
		||||
					updated = true;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -209,7 +209,7 @@ void nano::telemetry::run_requests ()
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::telemetry::request (std::shared_ptr<nano::transport::channel> & channel)
 | 
			
		||||
void nano::telemetry::request (std::shared_ptr<nano::transport::channel> const & channel)
 | 
			
		||||
{
 | 
			
		||||
	stats.inc (nano::stat::type::telemetry, nano::stat::detail::request);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -228,7 +228,7 @@ void nano::telemetry::run_broadcasts ()
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::telemetry::broadcast (std::shared_ptr<nano::transport::channel> & channel, const nano::telemetry_data & telemetry)
 | 
			
		||||
void nano::telemetry::broadcast (std::shared_ptr<nano::transport::channel> const & channel, const nano::telemetry_data & telemetry)
 | 
			
		||||
{
 | 
			
		||||
	stats.inc (nano::stat::type::telemetry, nano::stat::detail::broadcast);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,7 +92,7 @@ private:
 | 
			
		|||
 | 
			
		||||
		nano::endpoint endpoint () const
 | 
			
		||||
		{
 | 
			
		||||
			return channel->get_endpoint ();
 | 
			
		||||
			return channel->get_remote_endpoint ();
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -105,8 +105,8 @@ private:
 | 
			
		|||
	void run_broadcasts ();
 | 
			
		||||
	void cleanup ();
 | 
			
		||||
 | 
			
		||||
	void request (std::shared_ptr<nano::transport::channel> &);
 | 
			
		||||
	void broadcast (std::shared_ptr<nano::transport::channel> &, nano::telemetry_data const &);
 | 
			
		||||
	void request (std::shared_ptr<nano::transport::channel> const &);
 | 
			
		||||
	void broadcast (std::shared_ptr<nano::transport::channel> const &, nano::telemetry_data const &);
 | 
			
		||||
 | 
			
		||||
	bool verify (nano::telemetry_ack const &, std::shared_ptr<nano::transport::channel> const &) const;
 | 
			
		||||
	bool check_timeout (entry const &) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,22 +45,20 @@ void nano::transport::channel::send (nano::message & message_a, std::function<vo
 | 
			
		|||
 | 
			
		||||
void nano::transport::channel::set_peering_endpoint (nano::endpoint endpoint)
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> lock{ channel_mutex };
 | 
			
		||||
	nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
	peering_endpoint = endpoint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
nano::endpoint nano::transport::channel::get_peering_endpoint () const
 | 
			
		||||
{
 | 
			
		||||
	nano::unique_lock<nano::mutex> lock{ channel_mutex };
 | 
			
		||||
	if (peering_endpoint)
 | 
			
		||||
	{
 | 
			
		||||
		return *peering_endpoint;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		lock.unlock ();
 | 
			
		||||
		return get_endpoint ();
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		if (peering_endpoint)
 | 
			
		||||
		{
 | 
			
		||||
			return *peering_endpoint;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return get_remote_endpoint ();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<nano::node> nano::transport::channel::owner () const
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +68,8 @@ std::shared_ptr<nano::node> nano::transport::channel::owner () const
 | 
			
		|||
 | 
			
		||||
void nano::transport::channel::operator() (nano::object_stream & obs) const
 | 
			
		||||
{
 | 
			
		||||
	obs.write ("endpoint", get_endpoint ());
 | 
			
		||||
	obs.write ("remote_endpoint", get_remote_endpoint ());
 | 
			
		||||
	obs.write ("local_endpoint", get_local_endpoint ());
 | 
			
		||||
	obs.write ("peering_endpoint", get_peering_endpoint ());
 | 
			
		||||
	obs.write ("node_id", get_node_id ().to_node_id ());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,10 +40,10 @@ public:
 | 
			
		|||
 | 
			
		||||
	virtual void close () = 0;
 | 
			
		||||
 | 
			
		||||
	virtual std::string to_string () const = 0;
 | 
			
		||||
	virtual nano::endpoint get_endpoint () const = 0;
 | 
			
		||||
	virtual nano::tcp_endpoint get_tcp_endpoint () const = 0;
 | 
			
		||||
	virtual nano::endpoint get_remote_endpoint () const = 0;
 | 
			
		||||
	virtual nano::endpoint get_local_endpoint () const = 0;
 | 
			
		||||
 | 
			
		||||
	virtual std::string to_string () const = 0;
 | 
			
		||||
	virtual nano::transport::transport_type get_type () const = 0;
 | 
			
		||||
 | 
			
		||||
	virtual bool max (nano::transport::traffic_type = nano::transport::traffic_type::generic)
 | 
			
		||||
| 
						 | 
				
			
			@ -58,62 +58,55 @@ public:
 | 
			
		|||
 | 
			
		||||
	std::chrono::steady_clock::time_point get_last_bootstrap_attempt () const
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return last_bootstrap_attempt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_last_bootstrap_attempt (std::chrono::steady_clock::time_point const time_a)
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		last_bootstrap_attempt = time_a;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::chrono::steady_clock::time_point get_last_packet_received () const
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return last_packet_received;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_last_packet_received (std::chrono::steady_clock::time_point const time_a)
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		last_packet_received = time_a;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::chrono::steady_clock::time_point get_last_packet_sent () const
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return last_packet_sent;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_last_packet_sent (std::chrono::steady_clock::time_point const time_a)
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		last_packet_sent = time_a;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	boost::optional<nano::account> get_node_id_optional () const
 | 
			
		||||
	std::optional<nano::account> get_node_id_optional () const
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return node_id;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::account get_node_id () const
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		if (node_id.is_initialized ())
 | 
			
		||||
		{
 | 
			
		||||
			return node_id.get ();
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return node_id.value_or (0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_node_id (nano::account node_id_a)
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		node_id = node_id_a;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -132,19 +125,18 @@ public:
 | 
			
		|||
 | 
			
		||||
	std::shared_ptr<nano::node> owner () const;
 | 
			
		||||
 | 
			
		||||
	mutable nano::mutex channel_mutex;
 | 
			
		||||
protected:
 | 
			
		||||
	nano::node & node;
 | 
			
		||||
	mutable nano::mutex mutex;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::chrono::steady_clock::time_point last_bootstrap_attempt{ std::chrono::steady_clock::time_point () };
 | 
			
		||||
	std::chrono::steady_clock::time_point last_packet_received{ std::chrono::steady_clock::now () };
 | 
			
		||||
	std::chrono::steady_clock::time_point last_packet_sent{ std::chrono::steady_clock::now () };
 | 
			
		||||
	boost::optional<nano::account> node_id{ boost::none };
 | 
			
		||||
	std::optional<nano::account> node_id{};
 | 
			
		||||
	std::atomic<uint8_t> network_version{ 0 };
 | 
			
		||||
	std::optional<nano::endpoint> peering_endpoint{};
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	nano::node & node;
 | 
			
		||||
 | 
			
		||||
public: // Logging
 | 
			
		||||
	virtual void operator() (nano::object_stream &) const;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,16 +30,11 @@ namespace transport
 | 
			
		|||
				endpoint = endpoint_a;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			nano::endpoint get_endpoint () const override
 | 
			
		||||
			nano::endpoint get_remote_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return endpoint;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			nano::tcp_endpoint get_tcp_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return nano::transport::map_endpoint_to_tcp (endpoint);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			nano::endpoint get_local_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return endpoint;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,16 +22,11 @@ namespace transport
 | 
			
		|||
 | 
			
		||||
			std::string to_string () const override;
 | 
			
		||||
 | 
			
		||||
			nano::endpoint get_endpoint () const override
 | 
			
		||||
			nano::endpoint get_remote_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return endpoint;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			nano::tcp_endpoint get_tcp_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return nano::transport::map_endpoint_to_tcp (endpoint);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			nano::endpoint get_local_endpoint () const override
 | 
			
		||||
			{
 | 
			
		||||
				return endpoint;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,6 @@ nano::transport::tcp_channel::tcp_channel (nano::node & node_a, std::weak_ptr<na
 | 
			
		|||
 | 
			
		||||
nano::transport::tcp_channel::~tcp_channel ()
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> lk{ channel_mutex };
 | 
			
		||||
	// Close socket. Exception: socket is used by tcp_server
 | 
			
		||||
	if (auto socket_l = socket.lock ())
 | 
			
		||||
	{
 | 
			
		||||
		socket_l->close ();
 | 
			
		||||
| 
						 | 
				
			
			@ -26,14 +24,14 @@ nano::transport::tcp_channel::~tcp_channel ()
 | 
			
		|||
 | 
			
		||||
void nano::transport::tcp_channel::update_endpoints ()
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
	nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
 | 
			
		||||
	debug_assert (endpoint == nano::endpoint{}); // Not initialized endpoint value
 | 
			
		||||
	debug_assert (remote_endpoint == nano::endpoint{}); // Not initialized endpoint value
 | 
			
		||||
	debug_assert (local_endpoint == nano::endpoint{}); // Not initialized endpoint value
 | 
			
		||||
 | 
			
		||||
	if (auto socket_l = socket.lock ())
 | 
			
		||||
	{
 | 
			
		||||
		endpoint = socket_l->remote_endpoint ();
 | 
			
		||||
		remote_endpoint = socket_l->remote_endpoint ();
 | 
			
		||||
		local_endpoint = socket_l->local_endpoint ();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +88,7 @@ void nano::transport::tcp_channel::send_buffer (nano::shared_const_buffer const
 | 
			
		|||
 | 
			
		||||
std::string nano::transport::tcp_channel::to_string () const
 | 
			
		||||
{
 | 
			
		||||
	return nano::util::to_str (get_tcp_endpoint ());
 | 
			
		||||
	return nano::util::to_str (get_remote_endpoint ());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::transport::tcp_channel::operator() (nano::object_stream & obs) const
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,20 +24,15 @@ public:
 | 
			
		|||
 | 
			
		||||
	std::string to_string () const override;
 | 
			
		||||
 | 
			
		||||
	nano::endpoint get_endpoint () const override
 | 
			
		||||
	nano::endpoint get_remote_endpoint () const override
 | 
			
		||||
	{
 | 
			
		||||
		return nano::transport::map_tcp_to_endpoint (get_tcp_endpoint ());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::tcp_endpoint get_tcp_endpoint () const override
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		return endpoint;
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return remote_endpoint;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nano::endpoint get_local_endpoint () const override
 | 
			
		||||
	{
 | 
			
		||||
		nano::lock_guard<nano::mutex> lk (channel_mutex);
 | 
			
		||||
		nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
		return local_endpoint;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +72,7 @@ public:
 | 
			
		|||
	std::weak_ptr<nano::transport::tcp_socket> socket;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	nano::endpoint endpoint;
 | 
			
		||||
	nano::endpoint remote_endpoint;
 | 
			
		||||
	nano::endpoint local_endpoint;
 | 
			
		||||
 | 
			
		||||
public: // Logging
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -196,9 +196,9 @@ void nano::transport::tcp_channels::random_fill (std::array<nano::endpoint, 8> &
 | 
			
		|||
	auto j (target_a.begin ());
 | 
			
		||||
	for (auto i (peers.begin ()), n (peers.end ()); i != n; ++i, ++j)
 | 
			
		||||
	{
 | 
			
		||||
		debug_assert ((*i)->get_endpoint ().address ().is_v6 ());
 | 
			
		||||
		debug_assert ((*i)->get_remote_endpoint ().address ().is_v6 ());
 | 
			
		||||
		debug_assert (j < target_a.end ());
 | 
			
		||||
		*j = (*i)->get_endpoint ();
 | 
			
		||||
		*j = (*i)->get_remote_endpoint ();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -417,18 +417,6 @@ void nano::transport::tcp_channels::list (std::deque<std::shared_ptr<nano::trans
 | 
			
		|||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::transport::tcp_channels::modify (std::shared_ptr<nano::transport::tcp_channel> const & channel_a, std::function<void (std::shared_ptr<nano::transport::tcp_channel> const &)> modify_callback_a)
 | 
			
		||||
{
 | 
			
		||||
	nano::lock_guard<nano::mutex> lock{ mutex };
 | 
			
		||||
	auto existing (channels.get<endpoint_tag> ().find (channel_a->get_tcp_endpoint ()));
 | 
			
		||||
	if (existing != channels.get<endpoint_tag> ().end ())
 | 
			
		||||
	{
 | 
			
		||||
		channels.get<endpoint_tag> ().modify (existing, [modify_callback = std::move (modify_callback_a)] (channel_entry & wrapper_a) {
 | 
			
		||||
			modify_callback (wrapper_a.channel);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nano::transport::tcp_channels::start_tcp (nano::endpoint const & endpoint)
 | 
			
		||||
{
 | 
			
		||||
	node.tcp_listener.connect (endpoint.address (), endpoint.port ());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,6 @@ public:
 | 
			
		|||
	bool track_reachout (nano::endpoint const &);
 | 
			
		||||
	void purge (std::chrono::steady_clock::time_point cutoff_deadline);
 | 
			
		||||
	void list (std::deque<std::shared_ptr<nano::transport::channel>> &, uint8_t = 0, bool = true);
 | 
			
		||||
	void modify (std::shared_ptr<nano::transport::tcp_channel> const &, std::function<void (std::shared_ptr<nano::transport::tcp_channel> const &)>);
 | 
			
		||||
	void keepalive ();
 | 
			
		||||
	std::optional<nano::keepalive> sample_keepalive ();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +80,7 @@ private:
 | 
			
		|||
		}
 | 
			
		||||
		nano::tcp_endpoint endpoint () const
 | 
			
		||||
		{
 | 
			
		||||
			return channel->get_tcp_endpoint ();
 | 
			
		||||
			return channel->get_remote_endpoint ();
 | 
			
		||||
		}
 | 
			
		||||
		std::chrono::steady_clock::time_point last_bootstrap_attempt () const
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1050,7 +1050,7 @@ nano::websocket_server::websocket_server (nano::websocket::config & config_a, na
 | 
			
		|||
		if (server->any_subscriber (nano::websocket::topic::telemetry))
 | 
			
		||||
		{
 | 
			
		||||
			nano::websocket::message_builder builder;
 | 
			
		||||
			server->broadcast (builder.telemetry_received (telemetry_data, channel->get_endpoint ()));
 | 
			
		||||
			server->broadcast (builder.telemetry_received (telemetry_data, channel->get_remote_endpoint ()));
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1944,7 +1944,7 @@ void nano_qt::advanced_actions::refresh_peers ()
 | 
			
		|||
	peers_model->removeRows (0, peers_model->rowCount ());
 | 
			
		||||
	auto list (wallet.node.network.list (std::numeric_limits<size_t>::max ()));
 | 
			
		||||
	std::sort (list.begin (), list.end (), [] (auto const & lhs, auto const & rhs) {
 | 
			
		||||
		return lhs->get_endpoint () < rhs->get_endpoint ();
 | 
			
		||||
		return lhs->get_remote_endpoint () < rhs->get_remote_endpoint ();
 | 
			
		||||
	});
 | 
			
		||||
	for (auto i (list.begin ()), n (list.end ()); i != n; ++i)
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -1959,9 +1959,9 @@ void nano_qt::advanced_actions::refresh_peers ()
 | 
			
		|||
		items.push_back (version);
 | 
			
		||||
		QString node_id ("");
 | 
			
		||||
		auto node_id_l (channel->get_node_id_optional ());
 | 
			
		||||
		if (node_id_l.is_initialized ())
 | 
			
		||||
		if (node_id_l.has_value ())
 | 
			
		||||
		{
 | 
			
		||||
			node_id = node_id_l.get ().to_account ().c_str ();
 | 
			
		||||
			node_id = node_id_l.value ().to_account ().c_str ();
 | 
			
		||||
		}
 | 
			
		||||
		items.push_back (new QStandardItem (node_id));
 | 
			
		||||
		peers_model->appendRow (items);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6786,7 +6786,7 @@ TEST (rpc, telemetry_all)
 | 
			
		|||
 | 
			
		||||
	auto channel = node1->network.find_node_id (node->get_node_id ());
 | 
			
		||||
	ASSERT_TRUE (channel);
 | 
			
		||||
	ASSERT_TIMELY (10s, node1->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (10s, node1->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	boost::property_tree::ptree request;
 | 
			
		||||
	request.put ("action", "telemetry");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1374,7 +1374,7 @@ namespace transport
 | 
			
		|||
							// Pick first peer to be consistent
 | 
			
		||||
							auto peer = data.node->network.tcp_channels.channels[0].channel;
 | 
			
		||||
 | 
			
		||||
							auto maybe_telemetry = data.node->telemetry.get_telemetry (peer->get_endpoint ());
 | 
			
		||||
							auto maybe_telemetry = data.node->telemetry.get_telemetry (peer->get_remote_endpoint ());
 | 
			
		||||
							if (maybe_telemetry)
 | 
			
		||||
							{
 | 
			
		||||
								callback_process (shared_data, data, node_data, maybe_telemetry->timestamp);
 | 
			
		||||
| 
						 | 
				
			
			@ -1504,7 +1504,7 @@ TEST (telemetry, cache_read_and_timeout)
 | 
			
		|||
	ASSERT_NE (channel, nullptr);
 | 
			
		||||
 | 
			
		||||
	node_client->telemetry.trigger ();
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	auto responses = node_client->telemetry.get_all_telemetries ();
 | 
			
		||||
	ASSERT_TRUE (!responses.empty ());
 | 
			
		||||
| 
						 | 
				
			
			@ -1527,7 +1527,7 @@ TEST (telemetry, cache_read_and_timeout)
 | 
			
		|||
 | 
			
		||||
	// Request telemetry metrics again
 | 
			
		||||
	node_client->telemetry.trigger ();
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_endpoint ()));
 | 
			
		||||
	ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (channel->get_remote_endpoint ()));
 | 
			
		||||
 | 
			
		||||
	responses = node_client->telemetry.get_all_telemetries ();
 | 
			
		||||
	ASSERT_TRUE (!responses.empty ());
 | 
			
		||||
| 
						 | 
				
			
			@ -1602,7 +1602,7 @@ TEST (telemetry, many_nodes)
 | 
			
		|||
	for (auto const & peer : peers)
 | 
			
		||||
	{
 | 
			
		||||
		std::optional<nano::telemetry_data> telemetry_data;
 | 
			
		||||
		ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (peer->get_endpoint ()));
 | 
			
		||||
		ASSERT_TIMELY (5s, telemetry_data = node_client->telemetry.get_telemetry (peer->get_remote_endpoint ()));
 | 
			
		||||
		telemetry_datas.push_back (*telemetry_data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue