Skip to content

Commit 47ea351

Browse files
frstrtrintegrator
andauthored
pool: hoist ban/whitelist subsystem into pool::SharechainNode (kill node.cpp drift, slice 1) (#758)
node.cpp is copy-pasted across ltc/btc/bch/dgb. This introduces the intermediate layer pool::SharechainNode<Config,ShareChain,Peer> between pool::BaseNode and each coin's NodeImpl, and hoists the FIRST byte-identical, coin-agnostic, non-consensus block into it: the peer ban-list + whitelist subsystem. Hoisted (verified byte-identical across all four coins, coin namespace aside): is_banned, is_whitelisted, set_ban_duration, set_whitelist_path, load_whitelist_from_disk, save_whitelist_to_disk, admin_list_bans, admin_ban_ip, admin_unban_ip, admin_list_whitelist, admin_whitelist_remove, and the build_bans_json/build_whitelist_json helpers, plus the members m_ban_list, m_ip_ban_list, m_ban_duration, m_whitelist_ips, m_whitelist_hosts, m_whitelist_path. This slice is deliberately limited to the self-contained ban/whitelist surface: every hoisted method touches ONLY members that move with it, so no dependent-base qualification is needed and no consensus/sharechain-timing state is involved. The bodies are relocated unchanged — pure de-duplication, behaviour identical. The connection-coupled admin endpoints (admin_whitelist_add, admin_list_peers, admin_drop_peer, admin_dial_peer) stay in NodeImpl for now: they reach into BaseNode's connection maps and the outbound-dial members, so they belong to a later slice that also hoists the outbound-connection manager. DASH (still the 668-line skeleton, no ban/whitelist block yet) is untouched and continues to derive from pool::BaseNode directly; it inherits this layer for free once its node quick-wire lands and gives it the same block. Net: -688 lines across the four coins, +243-line shared header (one source of truth for four copies). Regression evidence (this environment, system Boost/leveldb): - c2pool-{ltc,btc,bch,dgb,dash} all build green. - test_ltc_node: PASS. - core_test: 56/56 PASS (incl. version-gate transition suite). - ltc_pool_test harness constructs the node through the new base and enters the run loop with no crash. Co-authored-by: integrator <integrator@morisguide.com>
1 parent f851c85 commit 47ea351

9 files changed

Lines changed: 259 additions & 688 deletions

File tree

src/impl/bch/node.cpp

Lines changed: 0 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,155 +2175,18 @@ void NodeImpl::clean_tracker()
21752175
}); // end of m_think_pool lambda
21762176
}
21772177

2178-
bool NodeImpl::is_whitelisted(const NetService& addr) const
2179-
{
2180-
const std::string ip = addr.address();
2181-
if (m_whitelist_ips.contains(ip)) return true;
2182-
if (m_whitelist_hosts.contains(addr)) return true;
2183-
return false;
2184-
}
21852178

2186-
bool NodeImpl::is_banned(const NetService& addr) const
2187-
{
2188-
// Whitelist bypass: permanent dial targets are immune to bans.
2189-
if (is_whitelisted(addr)) return false;
2190-
2191-
auto now = std::chrono::steady_clock::now();
2192-
auto it = m_ban_list.find(addr);
2193-
if (it != m_ban_list.end() && it->second > now) return true;
2194-
2195-
auto ip_it = m_ip_ban_list.find(addr.address());
2196-
if (ip_it != m_ip_ban_list.end() && ip_it->second > now) return true;
2197-
2198-
return false;
2199-
}
22002179

22012180
// ── Admin API implementation ──────────────────────────────────────────
22022181

2203-
void NodeImpl::set_whitelist_path(const std::string& path)
2204-
{
2205-
m_whitelist_path = path;
2206-
if (!path.empty()) load_whitelist_from_disk();
2207-
}
22082182

2209-
void NodeImpl::load_whitelist_from_disk()
2210-
{
2211-
if (m_whitelist_path.empty()) return;
2212-
std::ifstream f(m_whitelist_path);
2213-
if (!f) return;
2214-
try {
2215-
nlohmann::json j;
2216-
f >> j;
2217-
if (!j.contains("entries") || !j["entries"].is_array()) return;
2218-
for (const auto& e : j["entries"]) {
2219-
if (!e.contains("host") || !e.contains("port")) continue;
2220-
std::string host = e["host"].get<std::string>();
2221-
uint16_t port = e["port"].get<uint16_t>();
2222-
m_whitelist_ips.insert(host);
2223-
m_whitelist_hosts.insert(NetService(host, port));
2224-
}
2225-
LOG_INFO << "[Pool] Loaded " << m_whitelist_hosts.size()
2226-
<< " whitelist entries from " << m_whitelist_path;
2227-
} catch (const std::exception& e) {
2228-
LOG_WARNING << "[Pool] Failed to parse whitelist " << m_whitelist_path
2229-
<< ": " << e.what();
2230-
}
2231-
}
22322183

2233-
void NodeImpl::save_whitelist_to_disk() const
2234-
{
2235-
if (m_whitelist_path.empty()) return;
2236-
try {
2237-
nlohmann::json j;
2238-
j["version"] = 1;
2239-
auto arr = nlohmann::json::array();
2240-
auto now_unix = std::chrono::duration_cast<std::chrono::seconds>(
2241-
std::chrono::system_clock::now().time_since_epoch()).count();
2242-
for (const auto& host : m_whitelist_hosts) {
2243-
arr.push_back({
2244-
{"host", host.address()},
2245-
{"port", host.port()},
2246-
{"added_unix", now_unix}
2247-
});
2248-
}
2249-
j["entries"] = arr;
2250-
std::string tmp = m_whitelist_path + ".new";
2251-
{
2252-
std::ofstream f(tmp);
2253-
f << j.dump(2);
2254-
}
2255-
std::filesystem::rename(tmp, m_whitelist_path);
2256-
} catch (const std::exception& e) {
2257-
LOG_WARNING << "[Pool] Failed to persist whitelist: " << e.what();
2258-
}
2259-
}
22602184

2261-
static nlohmann::json build_bans_json(
2262-
const std::map<NetService, std::chrono::steady_clock::time_point>& peer_bans,
2263-
const std::map<std::string, std::chrono::steady_clock::time_point>& ip_bans)
2264-
{
2265-
auto now = std::chrono::steady_clock::now();
2266-
auto arr = nlohmann::json::array();
2267-
for (const auto& [addr, expiry] : peer_bans) {
2268-
if (expiry <= now) continue;
2269-
auto secs = std::chrono::duration_cast<std::chrono::seconds>(expiry - now).count();
2270-
arr.push_back({{"host", addr.address()}, {"port", addr.port()},
2271-
{"expires_in_sec", secs}, {"source", "auto"}});
2272-
}
2273-
for (const auto& [ip, expiry] : ip_bans) {
2274-
if (expiry <= now) continue;
2275-
auto secs = std::chrono::duration_cast<std::chrono::seconds>(expiry - now).count();
2276-
arr.push_back({{"host", ip}, {"port", 0},
2277-
{"expires_in_sec", secs}, {"source", "admin"}});
2278-
}
2279-
return arr;
2280-
}
22812185

2282-
static nlohmann::json build_whitelist_json(const std::set<NetService>& hosts)
2283-
{
2284-
auto arr = nlohmann::json::array();
2285-
for (const auto& h : hosts)
2286-
arr.push_back({{"host", h.address()}, {"port", h.port()}});
2287-
return arr;
2288-
}
22892186

2290-
nlohmann::json NodeImpl::admin_list_bans() const
2291-
{
2292-
return {{"ok", true}, {"bans", build_bans_json(m_ban_list, m_ip_ban_list)}};
2293-
}
22942187

2295-
nlohmann::json NodeImpl::admin_ban_ip(const std::string& ip, int duration_sec)
2296-
{
2297-
if (ip.empty())
2298-
return {{"ok", false}, {"error", "ip required"}};
2299-
int dur = duration_sec > 0 ? duration_sec : static_cast<int>(m_ban_duration.count());
2300-
auto expiry = std::chrono::steady_clock::now() + std::chrono::seconds(dur);
2301-
m_ip_ban_list[ip] = expiry;
2302-
LOG_INFO << "[Pool] Admin ban: " << ip << " for " << dur << "s";
2303-
return {{"ok", true}, {"action", "ban"}, {"target", ip},
2304-
{"duration_sec", dur},
2305-
{"bans", build_bans_json(m_ban_list, m_ip_ban_list)}};
2306-
}
23072188

2308-
nlohmann::json NodeImpl::admin_unban_ip(const std::string& ip)
2309-
{
2310-
if (ip.empty())
2311-
return {{"ok", false}, {"error", "ip required"}};
2312-
size_t removed = m_ip_ban_list.erase(ip);
2313-
for (auto it = m_ban_list.begin(); it != m_ban_list.end(); ) {
2314-
if (it->first.address() == ip) { it = m_ban_list.erase(it); ++removed; }
2315-
else ++it;
2316-
}
2317-
LOG_INFO << "[Pool] Admin unban: " << ip << " (" << removed << " entries removed)";
2318-
return {{"ok", true}, {"action", "unban"}, {"target", ip},
2319-
{"removed", removed},
2320-
{"bans", build_bans_json(m_ban_list, m_ip_ban_list)}};
2321-
}
23222189

2323-
nlohmann::json NodeImpl::admin_list_whitelist() const
2324-
{
2325-
return {{"ok", true}, {"whitelist", build_whitelist_json(m_whitelist_hosts)}};
2326-
}
23272190

23282191
nlohmann::json NodeImpl::admin_whitelist_add(const std::string& host, uint16_t port)
23292192
{
@@ -2352,23 +2215,6 @@ nlohmann::json NodeImpl::admin_whitelist_add(const std::string& host, uint16_t p
23522215
{"whitelist", build_whitelist_json(m_whitelist_hosts)}};
23532216
}
23542217

2355-
nlohmann::json NodeImpl::admin_whitelist_remove(const std::string& host, uint16_t port)
2356-
{
2357-
if (host.empty() || port == 0)
2358-
return {{"ok", false}, {"error", "host and port required"}};
2359-
NetService addr(host, port);
2360-
size_t removed_h = m_whitelist_hosts.erase(addr);
2361-
// Only remove the IP from whitelist if no other host:port remains for it.
2362-
bool other_on_same_ip = std::any_of(
2363-
m_whitelist_hosts.begin(), m_whitelist_hosts.end(),
2364-
[&](const NetService& n) { return n.address() == host; });
2365-
if (!other_on_same_ip) m_whitelist_ips.erase(host);
2366-
if (removed_h) save_whitelist_to_disk();
2367-
LOG_INFO << "[Pool] De-whitelisted " << addr.to_string();
2368-
return {{"ok", true}, {"action", "whitelist_remove"},
2369-
{"target", addr.to_string()}, {"removed", removed_h},
2370-
{"whitelist", build_whitelist_json(m_whitelist_hosts)}};
2371-
}
23722218

23732219
nlohmann::json NodeImpl::admin_list_peers() const
23742220
{

src/impl/bch/node.hpp

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <core/version_gate.hpp> // SSOT: core::version_gate::is_v36_active
2929

3030
#include <pool/node.hpp>
31+
#include <pool/sharechain_node.hpp>
3132
#include <pool/protocol.hpp>
3233
#include <core/message.hpp>
3334
#include <core/reply_matcher.hpp>
@@ -53,8 +54,10 @@ struct ShareReplyData
5354
std::vector<chain::RawShare> m_raw_items;
5455
};
5556

56-
class NodeImpl : public pool::BaseNode<bch::Config, bch::ShareChain, bch::Peer>
57+
class NodeImpl : public pool::SharechainNode<bch::Config, bch::ShareChain, bch::Peer>
5758
{
59+
60+
using base_t = pool::SharechainNode<bch::Config, bch::ShareChain, bch::Peer>;
5861
// Async share downloader:
5962
// ID = uint256 (matches sharereq id to sharereply id)
6063
// RESPONSE = parsed shares plus their original raw payloads
@@ -592,7 +595,6 @@ class NodeImpl : public pool::BaseNode<bch::Config, bch::ShareChain, bch::Peer>
592595
void set_max_peers(size_t count) { m_max_peers = count; }
593596

594597
/// Set P2P ban duration (seconds).
595-
void set_ban_duration(int seconds) { m_ban_duration = std::chrono::seconds(seconds); }
596598

597599
/// Set cache size limits for memory control.
598600
void set_cache_limits(size_t max_shared, size_t max_known_txs, size_t max_raw) {
@@ -681,30 +683,22 @@ class NodeImpl : public pool::BaseNode<bch::Config, bch::ShareChain, bch::Peer>
681683
}
682684

683685
/// Check whether a peer address is currently banned.
684-
bool is_banned(const NetService& addr) const;
685686

686687
/// ── Runtime admin API (pool peer bans + whitelist) ─────────────────
687688
/// All methods assumed to run on the io_context thread — callers
688689
/// (web_server HTTP handlers) dispatch via thread_safe_wrap().
689690
///
690691
/// Returned JSON uses the shape:
691692
/// {"ok": true|false, "error"?: "...", "bans": [...], "whitelist": [...]}
692-
nlohmann::json admin_list_bans() const;
693-
nlohmann::json admin_ban_ip(const std::string& ip, int duration_sec);
694-
nlohmann::json admin_unban_ip(const std::string& ip);
695-
nlohmann::json admin_list_whitelist() const;
696693
nlohmann::json admin_whitelist_add(const std::string& host, uint16_t port);
697-
nlohmann::json admin_whitelist_remove(const std::string& host, uint16_t port);
698694
nlohmann::json admin_list_peers() const;
699695
nlohmann::json admin_drop_peer(const std::string& ip);
700696
nlohmann::json admin_dial_peer(const std::string& host, uint16_t port);
701697

702698
/// Path to persisted whitelist file (~/.c2pool/pool_whitelist.json).
703699
/// Set by c2pool_refactored.cpp before start(); empty = no persistence.
704-
void set_whitelist_path(const std::string& path);
705700

706701
/// True if addr's IP matches a whitelist entry (IP or host:port).
707-
bool is_whitelisted(const NetService& addr) const;
708702

709703
protected:
710704
std::string m_software_version = "/c2pool:0.1/"; // overridden by set_software_version()
@@ -750,21 +744,13 @@ class NodeImpl : public pool::BaseNode<bch::Config, bch::ShareChain, bch::Peer>
750744
std::set<NetService> m_outbound_addrs; // successfully connected outbound peers
751745

752746
// Peer banning: maps address → ban expiry time
753-
std::map<NetService, std::chrono::steady_clock::time_point> m_ban_list;
754-
std::chrono::seconds m_ban_duration{300}; // 5 minutes (configurable)
755747

756748
// IP-only manual bans (admin endpoint). Keyed by IP string so the
757749
// operator can ban/unban without knowing the peer's source port.
758-
std::map<std::string, std::chrono::steady_clock::time_point> m_ip_ban_list;
759750

760751
// Whitelist: IPs that bypass is_banned() and host:port entries kept as
761752
// permanent dial targets. Persists across restart via m_whitelist_path.
762-
std::set<std::string> m_whitelist_ips;
763-
std::set<NetService> m_whitelist_hosts;
764-
std::string m_whitelist_path;
765753

766-
void load_whitelist_from_disk();
767-
void save_whitelist_to_disk() const;
768754

769755
// Rate-limiting no longer needed: think() runs on a dedicated compute
770756
// thread (m_think_pool), serialized by m_think_running atomic flag.

0 commit comments

Comments
 (0)