diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index e484aee3d64..38ab9e360bd 100755 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -30,6 +30,7 @@ #pragma once #include +#include #include #include #include "epee/net/net_utils_base.h" @@ -56,6 +57,8 @@ namespace cryptonote std::unordered_set m_requested_flash_heights; std::map> m_flash_state; // HEIGHT => {CHECKSUM, NEEDED} bool m_need_flash_sync{false}; + bool m_flash_sync_more_pending{false}; // Last flash request hit the size cap; ask for the rest + std::set m_flash_heights_requested; // Asked for, checksum not yet caught up; don't re-ask uint32_t m_drop_count{0}; // How many times we've wanted to drop uint64_t m_remote_blockchain_height{0}; uint64_t m_last_response_height{0}; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 74e07e56584..7cb44682981 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -145,6 +145,8 @@ namespace cryptonote NOTIFY_REQUEST_CHAIN::request r{}; context.m_needed_objects.clear(); m_core.get_blockchain_storage().get_short_chain_history(r.block_ids); + // handle_response_chain_entry() drops the connection if this isn't set + context.m_last_request_time = std::chrono::steady_clock::now(); MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -164,6 +166,8 @@ namespace cryptonote const uint64_t immutable_height = m_core.get_blockchain_storage().get_immutable_height(); // Delete any irrelevant heights > 0 (the mempool) and <= the immutable height context.m_flash_state.erase(context.m_flash_state.lower_bound(1), context.m_flash_state.lower_bound(immutable_height + 1)); + context.m_flash_heights_requested.erase(context.m_flash_heights_requested.lower_bound(1), + context.m_flash_heights_requested.lower_bound(immutable_height + 1)); // We can't validate flashes yet if we are syncing and haven't synced enough blocks to look // up the flash quorum. Set a cutoff at current height plus 10 because flash quorums are @@ -181,23 +185,34 @@ namespace cryptonote // We thought we needed it when we last got some data; check whether we still do: auto my_it = my_flash_hashes.find(i.first); if (my_it == my_flash_hashes.end() || i.second.first != my_it->second) + { + // Already asked for; its txes are still in flight so our checksum hasn't caught up yet. + // Skipping lets a follow-up request advance to the remainder instead of repeating this batch. + if (context.m_flash_heights_requested.count(i.first)) + continue; r.heights.push_back(i.first); + } else + { i.second.second = false; // checksum is now equal, don't need it anymore + context.m_flash_heights_requested.erase(i.first); + } } context.m_need_flash_sync = false; if (!r.heights.empty()) { - // Cap the outbound request to the protocol object limit. A peer that enforces - // CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT (see handle_request_block_flashes) drops the - // connection on an oversized list, so never send more than the limit in one request. Heights - // beyond the cap stay flagged in m_flash_state and are re-requested when the peer next - // advertises a changed flash set. + // Peers drop the connection on an oversized list, so cap it and flag the remainder: an + // unchanged advertisement never re-sets m_need_flash_sync, so nothing else would ask for it. if (r.heights.size() > CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT) + { r.heights.resize(CURRENCY_PROTOCOL_MAX_OBJECT_REQUEST_COUNT); + context.m_flash_sync_more_pending = true; + MDEBUG(context << "More flash heights needed than fit in one request; deferring the remainder"); + } MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_BLOCK_FLASHES: requesting flash tx lists for " << r.heights.size() << " blocks"); context.m_requested_flash_heights.insert(r.heights.begin(), r.heights.end()); + context.m_flash_heights_requested.insert(r.heights.begin(), r.heights.end()); post_notify(r, context); MLOG_PEER_STATE("requesting block flashes"); } @@ -444,6 +459,8 @@ namespace cryptonote { ctx_it->second.first = hash; ctx_it->second.second = true; + // New checksum for a height we already asked about: allow it to be requested again. + context.m_flash_heights_requested.erase(height); } else continue; @@ -851,6 +868,8 @@ namespace cryptonote context.m_state = cryptonote_connection_context::state_synchronizing; NOTIFY_REQUEST_CHAIN::request r{}; m_core.get_blockchain_storage().get_short_chain_history(r.block_ids); + // handle_response_chain_entry() drops the connection if this isn't set + context.m_last_request_time = std::chrono::steady_clock::now(); MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify(r, context); MLOG_PEER_STATE("requesting chain"); @@ -2564,6 +2583,17 @@ skip: } context.m_requested_flash_heights.clear(); + // Previous request was capped: ask for the rest. Must follow the clear above, or the + // "one request in flight" gate in on_callback() swallows it. + if (context.m_flash_sync_more_pending) + { + context.m_flash_sync_more_pending = false; + context.m_need_flash_sync = true; + MDEBUG(context << "Requesting the flash heights deferred from the previous capped request"); + ++context.m_callback_request_count; + m_p2p->request_callback(context); + } + m_core.get_pool().keep_missing_flashes(arg.txs); if (arg.txs.empty()) { diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index a5e2f7a43af..340a150972c 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -85,7 +85,7 @@ namespace nodetool } } //----------------------------------------------------------------------------------- - inline bool append_net_address(std::vector & seed_nodes, std::string const & addr, uint16_t default_port); + inline bool append_net_address(std::vector & seed_nodes, std::string const & addr, uint16_t default_port, bool use_ipv6); //----------------------------------------------------------------------------------- template void node_server::init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden) @@ -381,7 +381,7 @@ namespace nodetool ); std::vector resolved_addrs; - bool r = append_net_address(resolved_addrs, pr_str, default_port); + bool r = append_net_address(resolved_addrs, pr_str, default_port, m_use_ipv6); CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str); for (const epee::net_utils::network_address& addr : resolved_addrs) { @@ -516,10 +516,15 @@ namespace nodetool return true; } //----------------------------------------------------------------------------------- + // `use_ipv6` must reflect --p2p-use-ipv6 (default: off). With IPv6 disabled, boosted_tcp_server + // ::connect() resolves only as v4, finds nothing for an IPv6 literal, and bails with + // MERROR("Failed to resolve ..."). Storing IPv6 peers we can never dial just burns a connection + // attempt each round and fills the log with resolve errors, so filter them out here instead. inline bool append_net_address( std::vector & seed_nodes , std::string const & addr , uint16_t default_port + , bool use_ipv6 ) { using namespace boost::asio; @@ -551,6 +556,7 @@ namespace nodetool ip::tcp::resolver::results_type result = resolver.resolve(host, port, boost::asio::ip::tcp::resolver::canonical_name, ec); CHECK_AND_ASSERT_MES(!ec, false, "Failed to resolve host name '" << host << "': " << ec.message() << ':' << ec.value()); + size_t added = 0, skipped_v6 = 0; auto i = result.begin(); auto iend = result.end(); for (; i != iend; ++i) @@ -560,15 +566,34 @@ namespace nodetool { epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_uint()), endpoint.port()}}; seed_nodes.push_back(na); + added++; MINFO("Added node: " << na.str()); } else { + if (!use_ipv6) + { + skipped_v6++; + MINFO("Skipping IPv6 address for " << host << " (" << endpoint.address().to_v6().to_string() + << "): IPv6 is disabled, enable it with --p2p-use-ipv6"); + continue; + } epee::net_utils::network_address na{epee::net_utils::ipv6_network_address{endpoint.address().to_v6(), endpoint.port()}}; seed_nodes.push_back(na); + added++; MINFO("Added node: " << na.str()); } } + + if (!added) + { + // Deliberately still a success: two callers turn a false return into a hard startup failure + // via CHECK_AND_ASSERT_MES, and an IPv6-only peer on an IPv4-only node is a misconfiguration + // to warn about, not a reason to refuse to boot. A genuine resolve failure is already caught + // by the error_code check above. + MWARNING("Resolved '" << host << "' but added no usable addresses" + << (skipped_v6 ? " (all " + std::to_string(skipped_v6) + " result(s) were IPv6; enable --p2p-use-ipv6 to use them)" : "")); + } return true; } @@ -1423,7 +1448,7 @@ namespace nodetool for (const auto& full_addr : get_seed_nodes()) { MDEBUG("Seed node: " << full_addr); - append_net_address(m_seed_nodes, full_addr, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); + append_net_address(m_seed_nodes, full_addr, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT, m_use_ipv6); } MDEBUG("Number of seed nodes: " << m_seed_nodes.size()); m_seed_nodes_initialized = true; @@ -1463,7 +1488,7 @@ namespace nodetool for (const auto &peer: get_seed_nodes(m_nettype)) { MDEBUG("Fallback seed node: " << peer); - append_net_address(m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); + append_net_address(m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT, m_use_ipv6); } } shlock.lock(); @@ -2317,7 +2342,7 @@ namespace nodetool continue; } std::vector resolved_addrs; - bool r = append_net_address(resolved_addrs, pr_str, default_port); + bool r = append_net_address(resolved_addrs, pr_str, default_port, m_use_ipv6); CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str); for (const epee::net_utils::network_address& addr : resolved_addrs) {