From 801c13117215d287041a5d3f286e86a23ebfe9d1 Mon Sep 17 00:00:00 2001 From: SChernykh <15806605+SChernykh@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:47:34 +0200 Subject: [PATCH] P2P: save/load onion peers, added onion seed nodes --- src/p2p_server.cpp | 96 ++++++++++++++++++++++++++++++++++++++-------- src/side_chain.cpp | 17 ++++++++ src/side_chain.h | 1 + src/util.cpp | 37 ++---------------- src/util.h | 43 +++++++++++++++++++++ 5 files changed, 145 insertions(+), 49 deletions(-) diff --git a/src/p2p_server.cpp b/src/p2p_server.cpp index 85f20f9..b0f3c6c 100644 --- a/src/p2p_server.cpp +++ b/src/p2p_server.cpp @@ -47,6 +47,7 @@ LOG_CATEGORY(P2PServer) static constexpr char saved_peer_list_file_name[] = "p2pool_peers.txt"; +static constexpr char saved_onion_peer_list_file_name[] = "p2pool_onion_peers.txt"; static const char* seed_nodes[] = { "seeds.p2pool.io", "main.p2poolpeers.net", "main.gupax.io", "" }; static const char* seed_nodes_mini[] = { "seeds-mini.p2pool.io", "mini.p2poolpeers.net", "mini.gupax.io", "" }; static const char* seed_nodes_nano[] = { "seeds-nano.p2pool.io", "nano.p2poolpeers.net", "nano.gupax.io", ""}; @@ -57,6 +58,11 @@ static constexpr uint64_t PEER_REQUEST_DELAY = 60; namespace p2pool { +static constexpr hash seed_onion_nodes[] = { + from_onion_v3_const("p2pseeds5qoenuuseyuqxhzzefzxpbhiq4z4h5hfbry5dxd5y2fwudyd.onion"), + from_onion_v3_const("p2pool2giz2r5cpqicajwoazjcxkfujxswtk3jolfk2ubilhrkqam2id.onion") +}; + P2PServer::P2PServer(p2pool* pool) : TCPServer(DEFAULT_BACKLOG, P2PClient::allocate, pool->params().m_socks5Proxy) , m_pool(pool) @@ -422,11 +428,24 @@ void P2PServer::update_peer_connections() if (!m_socks5Proxy.empty()) { std::vector pubkeys = m_pool->side_chain().seen_onion_pubkeys(); + // Add seed nodes + pubkeys.insert(pubkeys.end(), seed_onion_nodes, seed_onion_nodes + array_size(seed_onion_nodes)); + for (uint32_t i = m_numOnionConnections, n = N / 2; (i < n) && !pubkeys.empty();) { const uint64_t k = get_random64() % pubkeys.size(); const std::string addr = to_onion_v3(pubkeys[k]); - if ((connected_clients_domain.find(addr) == connected_clients_domain.end()) && connect_to_peer(addr, DEFAULT_P2P_PORT_ONION)) { + int port = DEFAULT_P2P_PORT_ONION; + + for (const hash& seed_onion : seed_onion_nodes) { + if (pubkeys[k] == seed_onion) { + const SideChain& s = m_pool->side_chain(); + port = s.is_mini() ? DEFAULT_P2P_PORT_MINI : (s.is_nano() ? DEFAULT_P2P_PORT_NANO : DEFAULT_P2P_PORT); + break; + } + } + + if ((connected_clients_domain.find(addr) == connected_clients_domain.end()) && connect_to_peer(addr, port)) { ++i; ++num_outgoing; } @@ -579,6 +598,31 @@ void P2PServer::save_peer_list() f.close(); LOGINFO(5, "peer list saved (" << peer_list.size() << " peers)"); + + const SideChain& s = m_pool->side_chain(); + + if (s.onion_pubkeys_count() > 0) { + const std::string onion_path = DATA_DIR + saved_onion_peer_list_file_name; + + f.open(onion_path, std::ios::binary); + + if (!f.is_open()) { + LOGERR(1, "failed to save onion peer list " << onion_path << ": error " << errno); + } + else { + const std::vector pubkeys = s.seen_onion_pubkeys(); + + for (const hash& h : pubkeys) { + f << to_onion_v3(h) << '\n'; + } + + f.flush(); + f.close(); + + LOGINFO(5, "onion peer list saved (" << pubkeys.size() << " peers)"); + } + } + m_peerListLastSaved = seconds_since_epoch(); } @@ -662,34 +706,54 @@ void P2PServer::load_peer_list() } }; + SideChain& s = m_pool->side_chain(); + if (m_pool->params().m_dns) { - if (m_pool->side_chain().is_default()) { + if (s.is_default()) { load_from_seed_nodes(seed_nodes, DEFAULT_P2P_PORT); } - else if (m_pool->side_chain().is_mini()) { + else if (s.is_mini()) { load_from_seed_nodes(seed_nodes_mini, DEFAULT_P2P_PORT_MINI); } - else if (m_pool->side_chain().is_nano()) { + else if (s.is_nano()) { load_from_seed_nodes(seed_nodes_nano, DEFAULT_P2P_PORT_NANO); } } - // Finally load peers from p2pool_peers.txt - const std::string path = DATA_DIR + saved_peer_list_file_name; + // Finally load peers from p2pool_peers.txt and p2pool_onion_peers.txt + for (size_t i = 0, n = (m_socks5Proxy.empty() ? 1 : 2); i < n; ++i) { + const std::string path = DATA_DIR + (i ? saved_onion_peer_list_file_name : saved_peer_list_file_name); - std::ifstream f(path); - if (f.is_open()) { - std::string address; - while (f.good()) { - std::getline(f, address); - if (!address.empty()) { - if (!saved_list.empty()) { - saved_list += ','; + std::ifstream f(path); + + if (f.is_open()) { + std::string address; + std::vector pubkeys; + + while (f.good()) { + std::getline(f, address); + + if (!address.empty()) { + if (i > 0) { + const hash h = from_onion_v3(address); + if (!h.empty()) { + pubkeys.emplace_back(h); + } + } + else { + if (!saved_list.empty()) { + saved_list += ','; + } + saved_list += address; + } } - saved_list += address; + } + f.close(); + + if (i > 0) { + s.add_onion_pubkeys(pubkeys); } } - f.close(); } if (saved_list.empty()) { diff --git a/src/side_chain.cpp b/src/side_chain.cpp index 155d312..0738c1e 100644 --- a/src/side_chain.cpp +++ b/src/side_chain.cpp @@ -1384,6 +1384,23 @@ std::vector SideChain::seen_onion_pubkeys() const return result; } +void SideChain::add_onion_pubkeys(const std::vector& pubkeys) +{ + if (pubkeys.empty()) { + return; + } + + const uint64_t cur_time = seconds_since_epoch(); + + WriteLock lock(m_seenDataLock); + + for (const hash& h : pubkeys) { + if (!h.empty()) { + m_seenOnionPubkeys[h] = cur_time; + } + } +} + void SideChain::verify_loop(PoolBlock* block) { // PoW is already checked at this point diff --git a/src/side_chain.h b/src/side_chain.h index b9c0481..481ebc0 100644 --- a/src/side_chain.h +++ b/src/side_chain.h @@ -88,6 +88,7 @@ public: [[nodiscard]] bool p2pool_update_available() const; [[nodiscard]] std::vector seen_onion_pubkeys() const; + void add_onion_pubkeys(const std::vector& pubkeys); #ifdef P2POOL_UNIT_TESTS difficulty_type m_testMainChainDiff; diff --git a/src/util.cpp b/src/util.cpp index 299272c..74d4a92 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1008,42 +1008,13 @@ hash from_onion_v3(const std::string& address) return {}; } - uint8_t buf[HASH_SIZE + 4] = {}; - uint8_t* p = buf; + const hash result = from_onion_v3_const(address.c_str()); - uint64_t data = 0; - uint64_t bit_size = 0; - - for (size_t i = 0; i < 56; ++i) { - const char c = address[i]; - uint64_t digit; - - if ('a' <= c && c <= 'z') { - digit = static_cast(c - 'a'); - } - else if ('A' <= c && c <= 'Z') { - digit = static_cast(c - 'A'); - } - else if ('2' <= c && c <= '7') { - digit = static_cast(c - '2') + 26; - } - else { - LOGWARN(3, "Invalid onion address \"" << address << "\": has an invalid character \"" << c << '"'); - return {}; - } - - data = (data << 5) | digit; - bit_size += 5; - - while (bit_size >= 8) { - bit_size -= 8; - *(p++) = static_cast(data >> bit_size); - } + if (result.empty()) { + LOGWARN(3, "Invalid onion address \"" << address << "\": has invalid character(s)"); + return {}; } - hash result; - memcpy(result.h, buf, HASH_SIZE); - // Checksum validation if (to_onion_v3(result) != address) { LOGWARN(3, "Invalid onion address \"" << address << "\": checksum failed"); diff --git a/src/util.h b/src/util.h index ae2d6dd..d1e8cee 100644 --- a/src/util.h +++ b/src/util.h @@ -400,6 +400,49 @@ FORCEINLINE void secure_zero_memory(T& value) std::string to_onion_v3(const hash& pubkey); hash from_onion_v3(const std::string& address); +static FORCEINLINE constexpr hash from_onion_v3_const(const char* address) +{ + uint8_t buf[HASH_SIZE + 4] = {}; + uint8_t* p = buf; + + uint64_t data = 0; + uint64_t bit_size = 0; + + for (size_t i = 0; i < 56; ++i) { + const char c = address[i]; + uint64_t digit = 0; + + if ('a' <= c && c <= 'z') { + digit = static_cast(c - 'a'); + } + else if ('A' <= c && c <= 'Z') { + digit = static_cast(c - 'A'); + } + else if ('2' <= c && c <= '7') { + digit = static_cast(c - '2') + 26; + } + else { + return {}; + } + + data = (data << 5) | digit; + bit_size += 5; + + while (bit_size >= 8) { + bit_size -= 8; + *(p++) = static_cast(data >> bit_size); + } + } + + hash result; + + for (size_t i = 0; i < HASH_SIZE; ++i) { + result.h[i] = buf[i]; + } + + return result; +} + } // namespace p2pool void memory_tracking_start();