Integration/2.0.x to main#14
Merged
Merged
Conversation
v2 sessions derive node.ID from ephemeral X25519 keys, so the existing self-check (c.node.ID() == localnode.ID()) cannot fire for v2 peers. A node behind a hairpinning NAT therefore dials its own external IP and accepts the looped-back TCP connection. Add isSelfEndpoint helper and gate DialV2 on it. Extend the v2 branch of postHandshakeChecks with the same comparison as defense-in-depth for paths that bypass DialV2 or for the narrow window where localnode's IP updates mid-dial. (cherry picked from commit 5008df1)
The disc-protocol quorum can ingest the local node's observed external IP into addrman as a KeyType=0x00 entry. Without filter, V2Iter re-emits it every cycle and DialV2's self-guard burns the dial slot rejecting it. Plumb an IsSelfFunc through NewV2Iter and reuse Server.isSelfEndpoint so the iterator silently skips self-matching candidates. (cherry picked from commit 3311102)
Once our quorum-confirmed external IP propagates to peers via sendSelfAdvertise, peers gossip it back to us. Without filtering at the ingest boundary, AddrmanBackend.HandlePeers writes the self entry into addrman where it persists across restarts via addrbook.rlp. Plumb an IsSelfFunc through NewAddrmanBackend (wired to Server.IsSelfEndpoint at node setup) and skip matching entries before m.AddOne. Export IsSelfEndpoint so the disc package and node wiring can consult the same source of truth as the dial / handshake / V2Iter guards. Add NewPeerForTest(id, name, caps, fd) so tests in other packages can drive code paths that depend on Peer.RemoteAddr(). (cherry picked from commit 04dc741)
Hello carries protocol version, services flags, peer's claimed listen port, and a self-connect detection nonce. Mirrors the role of Bitcoin Core's version message (src/protocol.h ProtocolMessageType::VERSION). Tail uses rlp:"tail" with []rlp.RawValue so future fields can be appended without breaking the existing decoder. Wire-only commit; the handler plumbing lands separately. (cherry picked from commit 24d1285)
helloNonce is a 64-bit value drawn from crypto/rand once per Server lifetime, exposed via Server.HelloNonce(). The disc handler embeds it in every outgoing Hello so a self-connected session can recognize its own greeting on receipt. Bitcoin Core analog: nLocalHostNonce in src/net.cpp PushNodeVersion. Wire-only commit; the disc handler integration lands separately. (cherry picked from commit f39a414)
NewAddrmanBackend takes an additional HelloProvider closure that returns the local node's outgoing Hello (nonce, listen port, services). LocalHello() forwards it; HandleHello() runs the self-connect nonce check and stores the peer's Hello in a peerHello map. PeerDisconnected purges the entry. node.go wires the provider to Server.HelloNonce() plus the LocalNode's TCP port and the standard NodeNetwork|RelayTx service flags. The handler send/receive integration lands separately. (cherry picked from commit d419e92)
Both sides write Hello before YourAddr on session start. The receive path enforces protocol-order: any non-Hello message that arrives before our peer has sent Hello ends the session. A second Hello on the same session is also a violation. Backend interface gains LocalHello() and HandleHello(). The production AddrmanBackend already had these from the Hello plumbing commit; testBackend gains stubs that capture incoming Hellos. drainAndOpen replaces drainGreeting in tests that send post-Hello messages so the protocol-order gate opens properly. Self-connect detection via Hello.Nonce flows through naturally: AddrmanBackend.HandleHello returns an error when h.Nonce matches LocalHello().Nonce; the handler propagates and the session ends. (cherry picked from commit 8b54bbf)
Confirms that when AddrmanBackend.HandleHello returns errSelfConnect (the peer echoed our own nonce), the handler propagates the error and Run returns it. The upstream peer-run loop tears down the session. Backend-level tests already cover the nonce comparison; this test pins the integration so a refactor that drops the error propagation in handleOne would be caught. (cherry picked from commit 67ef239)
alreadyConnectedTo now consults each peer's effective listen address: outbound peers' RemoteAddr.Port is already the listen port; inbound peers' is ephemeral, so substitute the listen port disclosed via parallax-disc/1 Hello when available. Server gains PeerListenPortLookup interface plus SetPeerListenPortLookup setter so the disc backend can serve as the per-peer Hello cache. peerListenAddr returns ok=false in the pre-Hello window for inbound peers, which keeps the check conservative; the post-Hello dedup hook in HandleHello catches duplicates that race through. Cross-dial dedup runs in AddrmanBackend.HandleHello: scan peers for one whose listen-addr matches the new peer's (IP, ListenPort) and disconnect the loser. Tie-break prefers outbound retention; same-direction ties keep the older connection. Implements Bitcoin Core's preference (src/net.cpp ThreadOpenConnections). Peer.Created accessor exposes p.created for the cross-dial age comparison; previously private. (cherry picked from commit 96958ea)
Atomic fields on Peer carry the telemetry the inbound eviction algorithm consumes: - minPing smallest RTT observed on ping/pong - lastPingSent for RTT computation on pong receipt - lastBlockRx most recent block-bearing message - lastTxRx most recent transaction-bearing message - bytesRx/Tx payload byte counters - relayTxs mirror of Hello.Services & ServiceRelayTx - blockRelayOnly outbound slot-bucket flag Mirrors NodeEvictionCandidate fields in Bitcoin Core src/node/eviction.h:18-33. Storage and accessors only; the actual stamping (RTT in pingLoop, prl per-message timestamps, bytes counters) lands in the next checkpoints. RelayTxs defaults to true so peers that haven't disclosed Services yet are treated as tx-relayers. (cherry picked from commit f15f894)
pingLoop stamps lastPingSent before sending pingMsg. handle() gains an explicit pongMsg branch that calls recordPongRTT, which computes (now - lastPingSent) and updates minPing if the new sample improves on the running minimum. CompareAndSwap loop keeps the update race-free under concurrent pong arrivals. Pre-send-stamp pongs (test scaffolding, adversarial peer that sends pong unprompted) and negative deltas (clock skew) are ignored. Mirrors Bitcoin Core's m_min_ping_time on CNode. (cherry picked from commit d2491b3)
Block-bearing handlers (NewBlockHashes, NewBlock, BlockHeaders, BlockBodies) call peer.MarkBlockRx; transaction-bearing handlers (Transactions, PooledTransactions) call peer.MarkTxRx. Each stamp is mclock.Now() on the embedded *p2p.Peer. These timestamps drive the eviction algorithm's "newest tx" / "newest block" / "block-relay-only with services" protection rounds. Mirrors Bitcoin Core's m_last_tx_time / m_last_block_time on Peer (src/net_processing.cpp). (cherry picked from commit 473a1df)
readLoop increments p.bytesRx by msg.Size after each successful ReadMsg. Payload-only accounting (framing overhead excluded) keeps the counter comparable across transports. bytesTx is reserved as a symmetric field but not yet instrumented — Bitcoin Core's NodeEvictionCandidate has no byte counter, so the eviction algorithm doesn't depend on it. The field shape is locked in now so a future admin diagnostic can fill it without churn. (cherry picked from commit 22889be)
NetworkGroupForIP returns canonical /16-IPv4 / /32-IPv6 prefix bytes prepended with a network-tag byte so v4 and v6 groups can never collide. IPv4-mapped IPv6 collapses to v4. Mirrors Bitcoin Core's CNetAddr::GetGroup defaults (src/netaddress.cpp). Peer caches its group via computeAndCacheNetworkGroup, called from launchPeer once the conn's RemoteAddr is resolved. Eviction protection passes (next checkpoint) read NetworkGroup() on every candidate without recomputing the prefix. SameNetworkGroup treats nil as a singleton — peers with non-TCP RemoteAddr (test pipes) are each their own group. (cherry picked from commit 9c32e6d)
Port of CConnman::AttemptToEvictConnection (src/net.cpp:1684)
plus SelectNodeToEvict (src/node/eviction.cpp:178). Six
protection rounds in order:
1. Network-group diversity (4 peers, anti-eclipse)
2. Lowest min ping (8 peers, responsiveness)
3. Newest tx relay among tx-relayers (4 peers, conditional)
4. Newest blocks among block-relay-only peers (8 peers,
conditional)
5. Newest block announce overall (4 peers)
6. Oldest 50% by connection age
Final tie-break (eviction.cpp:217): find the most-populated
network group among survivors, then evict the youngest peer in
that group.
evictionCandidates excludes trusted, static, outbound, and
peers younger than evictMinAge=30s (Bitcoin Core's
MINIMUM_CONNECT_TIME). Pure-function helpers
(protectByMin/Max/Conditional, protectByNetGroupKeyed,
protectByRatio, pickEvictionVictim) keep the algorithm
unit-testable without a running Server.
evictInbound is the entrypoint; postHandshakeChecks integration
lands in the next checkpoint.
(cherry picked from commit 8f20249)
postHandshakeChecks now invokes evictInbound when an inbound slot is full, instead of immediately returning DiscTooManyPeers. Successful eviction lets the new peer join optimistically; the loser's delpeer is processed asynchronously by the run loop. Failed eviction (everyone protected) falls through to the existing hard-reject. MaxPeers (total cap, system-resource ceiling) still hard-rejects without eviction — that knob is meant to be a hard ceiling. Inbound saturation is what the algorithm targets, mirroring Bitcoin Core's CConnman::AcceptConnection (src/net.cpp:1684-1731). Phase 3 complete: per-peer quality telemetry, RTT measurement, prl receipt timestamps, network-group cache, the full eviction algorithm, and saturation wiring. Phase 4 (outbound diversity + block-relay-only + feeler) is next. (cherry picked from commit 69e29ae)
dialScheduler tracks outboundGroups, a map keyed by network group bytes (/16 IPv4 or /32 IPv6) counting how many current outbound peers occupy each group. checkDial returns errOutboundGroupOccupied when a candidate's group is already represented, preventing a single AS from monopolizing our outbound slots. Loopback and link-local addresses bypass the diversity check so dev / test setups using 127.x.x.x continue to work. Mirrors Bitcoin Core's m_is_local exemption (eviction.cpp:115) and the outbound_ipv46_peer_netgroups guard (net.cpp:2685). This is the highest-value piece of phase 4 — it directly counters eclipse attacks at the dial layer. Block-relay-only peer behavior, feeler dials, and anchor persistence are deferred to follow-up commits. (cherry picked from commit c094af7)
Add the blockRelayConn flag and a 2-slot reservation in the dial scheduler for block-relay-only outbound peers. Bitcoin Core's MAX_BLOCK_RELAY_ONLY_CONNECTIONS (src/net.h:73) — anti-eclipse insurance over and above full-relay outbound. Slot picking prioritizes full-relay first, then block-relay (mirrors ThreadOpenConnections at src/net.cpp:2715-2765). Counts include in-flight dials so a burst of concurrent picks doesn't all classify the same way before any peer reaches addPeerCh. Behavior on a block-relay-only peer: * prl handlers drop TransactionsMsg, NewPooledTransactionHashesMsg, PooledTransactionsMsg, GetPooledTransactionsMsg before any decode (mirrors net_processing.cpp:3681 m_relay_txs gate). * fullnode tx-broadcast set excludes block-relay-only peers, so we never push tx in their direction either. * disc/1 Hello clears the ServiceRelayTx bit on outgoing greeting (mirrors PushNodeVersion fRelay=false). * disc/1 outbound greeting skips sendSelfAdvertise + RequestPeers, and incoming GetPeers from block-relay-only peers is silently dropped — no participation in address gossip. Block traffic is unchanged. Tests pin Hello bit clearing, the greeting-shape skip, GetPeers drop, the tx-handler drops via a tracking decoder, and the dial-scheduler bucket fill. (cherry picked from commit 5bfb81a)
runFeeler probes one addrman entry every ~2 minutes (Bitcoin Core FEELER_INTERVAL, src/net.h:61). Selection prefers SelectTriedCollision (test-before-evict) before falling back to Select(newOnly), mirroring src/net.cpp:2796-2810. Each tick also runs ResolveCollisions so stale collision entries don't shadow live draws. Hits go through DialV2 → addrmanGood at peer attach; the connection self-disconnects after a short lifetime so the feeler doesn't squat on an outbound slot. runAddrFetch is the cold-start one-shot variant: when the addrman holds fewer than addrFetchThreshold (1000) entries on startup, dial each BootstrapNodesV2 once. The disc-protocol outbound greeting (GetPeers) warms the addrbook from each, then we disconnect after addrFetchLifetime. Bitcoin Core ConnectionType::ADDR_FETCH (src/net.cpp:2422). Both loops are gated behind addrman + !NoDial. Tests cover the selection ordering, the empty-addrman short-circuit, and the NetAddr → *net.TCPAddr projection used to drive the dial API. (cherry picked from commit b039f7c)
On clean shutdown, snapshot the (IP, listen-port) of currently- connected block-relay-only outbound peers (capped at MaxBlockRelayAnchors = 2) into anchors.dat. On next startup, dial each entry as block-relay-only, then delete the file so a crash mid-startup doesn't replay the same anchors twice. Mirrors Bitcoin Core's m_anchors / anchors.dat (src/net.cpp:57, MAX_BLOCK_RELAY_ ONLY_ANCHORS). Wire format is RLP with a schema-version byte and an entry list of (network-id, addr, port). Atomic write via tmp + rename, like addrbook.rlp. Future-schema files return errAnchorFutureSchema so a downgrade can't crash on a newer file. Refactor DialV2 to dialV2WithFlags so DialV2BlockRelay can request the block-relay-only flag without duplicating the cooldown / self- endpoint / dedup checks. The anchor-replay goroutine routes through DialV2BlockRelay, putting recovered peers into the same outbound slot type they were in before shutdown. Tests cover round-trip for IPv4 / IPv6, the cap-at-MaxAnchors trim, empty-list-removes-file behavior, future-schema rejection, and the zero-port / nil-IP filter. (cherry picked from commit bd8cb83)
Two storage tiers, mirroring Bitcoin Core's BanMan (src/banman.h:63): * Banned: persistent JSON map (banlist.json), operator-controlled via setban / unban / clearbanned RPC. Default duration 24h (DEFAULT_MISBEHAVING_BANTIME, src/banman.h:19). Subnet-aware: "1.2.3.0/24" bans the whole /24. Atomic file writes (tmp + rename), schema follows Bitcoin Core's modern banned_nets format (src/addrdb.h:34). * Discouraged: in-memory Bloom filter (50k cap, 1e-6 fp), Bitcoin Core's defaults (src/common/bloom.h). Reset on restart; populated automatically from peer Misbehaving() calls. Modern Bitcoin Core (v28+) dropped the ban-score point system, so this package follows that simpler model — no nMisbehavior accumulation, no DISCOURAGEMENT_THRESHOLD. A single MisbehavingFor call is enough to flag for discourage. The Bloom uses Go's stdlib hash/maphash with k independent seeds. Sized via standard Bloom math (m = -n*ln(p)/ln(2)^2, k = m/n*ln(2), capped at k=32). Banlist persistence stores Unix-second timestamps; ban durations are ceiling'd to 1s so sub-second bans don't collapse to BannedTill == BanCreated. Expired entries are pruned lazily on IsBanned and ListBanned scans, and dropped at load time. Tests cover the full Ban / IsBanned / Unban round trip for IPv4 + IPv6, /24 subnet membership, expiry, persistence reload, expired-on- load drop, ClearBanned, Bloom membership semantics, and Bloom false- positive rate (sample 5k probes after 40k inserts, assert <= 1e-3). (cherry picked from commit 720edc8)
Add Peer.MisbehavingFor: idempotent flag-and-disconnect for a peer that has just committed a protocol-discipline violation. Mirrors Bitcoin Core's modern Misbehaving() at src/net_processing.cpp — the post-pointscore form: one trigger is enough to flag, no nMisbehavior accumulation. The wire DiscReason is the generic DiscProtocolError; we don't expose a distinct "you misbehaved" code so adversaries can't calibrate against it. Wire BanList into Server: * checkInboundConn rejects banned source IPs at the listener loop (Bitcoin Core src/net.cpp:1800 AcceptConnection ban check). Trusted peers don't get a backdoor here — operators who want a banned subnet to still reach a trusted peer should unban it. * postHandshakeChecks rejects discouraged IPs at inbound saturation before falling back to eviction (src/net.cpp:1808). No point evicting a well-behaved peer to admit a known-bad one. * run-loop delpeer hook adds the source IP to the discourage Bloom when ShouldDiscourage is set, so reconnects from the same source hit the rejection path. Convert the disc/1 handler's existing protocol-discipline disconnects to MisbehavingFor calls — oversized message, msg-before-Hello, double Hello / YourAddr, decode and validate failures, unsolicited Peers. The session still ends via the existing error returns; the addition is the discourage-list flag. Tests cover MisbehavingFor flag + reason capture (with idempotency), the inbound-conn banned-IP reject, and the existing handler tests still pass under the new MisbehavingFor calls (nil-peer guard means fuzz-style dispatch tests don't panic). (cherry picked from commit a564a7d)
Mirrors Bitcoin Core's setban (src/rpc/net.cpp:740), listbanned
(src/rpc/net.cpp:820), and clearbanned (src/rpc/net.cpp:868).
Argument shape on setban:
* subnet: IP ("1.2.3.4" → /32) or CIDR ("10.0.0.0/24").
* command: "add" or "remove".
* bantime: seconds; 0 → DefaultBanDuration. Honored only on "add".
* absolute: when true, bantime is a Unix timestamp; when false, a
relative offset from now. Default false.
On "add" any currently-connected peer whose RemoteAddr matches the
subnet is also disconnected (Bitcoin parity, rpc/net.cpp:808-811).
On "remove" returns an error if the subnet wasn't previously banned
(rpc/net.cpp:817).
listbanned returns the active (non-expired) entries sorted by subnet
string for stable RPC output. clearbanned wipes every entry — does
NOT touch the in-memory discourage filter, which is restart-only by
design.
Resolves the deferred parallax ban / unban / banlist tracker; the
CLI subcommand can be a thin wrapper over these RPCs.
Tests cover the subnet parser: bare IPv4 → /32, bare IPv6 → /128,
CIDR pass-through, empty / garbage rejection, malformed CIDR
rejection.
(cherry picked from commit be3a9f3)
- gofmt / goimports on banman.go, dial.go, anchors_test.go, disc/backend.go, server_test.go. - Drop redundant `tc := tc` and `a := a` loop-var shadows; Go 1.22+ already gives each iteration its own variable. - Remove unused resolveCollisionsInterval const in feeler.go. No behavior change. golangci-lint reports zero issues across p2p/..., p2p/banman/..., node/... (cherry picked from commit 6d7d07e)
ProtocolLength was 3 but HelloMsg = 0x03, so protoRW.WriteMsg rejected every outgoing Hello with "invalid message code: not handled" (msg.Code >= rw.Length check in p2p/peer.go). The peer never saw our Hello, then sent YourAddr first as usual, then we disconnected on "disc: msg 0x02 before Hello" plus flagged for misbehavior. Net effect: the patched build couldn't disc-handshake with any other patched build, partitioning the v2 fleet from itself. Symptom in the wild was peer count regression to a handful of v1.x legacy-RLPx peers (which never run disc/1) while every parallax/v2.0.0-rc1 inbound and outbound dropped on the same "msg 0x02 before Hello" line. Bump to 4 (one past the highest defined code). Add a regression test that asserts every defined message-code constant fits under ProtocolLength — adding a new code without bumping the length turns the test red instead of silently dropping wire traffic. (cherry picked from commit a3cba0d)
Thin sugar over admin_setban / admin_listbanned / admin_clearbanned.
parallax-cli setban <subnet> <add|remove> [bantime] [absolute]
parallax-cli listbanned
parallax-cli clearbanned
setban accepts the same argument shape the daemon RPC takes: a plain
IP ("1.2.3.4" → /32) or a CIDR ("10.0.0.0/24"); add or remove; on add
an optional bantime (seconds, 0 = default 24h); an optional absolute
flag flipping bantime from "seconds from now" to "Unix timestamp".
Trailing optionals are only forwarded when supplied so the daemon's
*int64 / *bool parameters stay nil and its default branches fire.
listbanned prints the active entries as JSON; an empty list emits "[]"
so jq-piped scripts don't choke on null. clearbanned is silent on
success.
Tests: registration check confirms all three commands are wired into
clientSugarCommands.
(cherry picked from commit ebc471a)
Add a new networking page (peer-management.mdx) covering the disc/1 session greeting, self-connect detection, cross-dial dedup, per-peer telemetry, outbound slot allocation across full-relay / block-relay-only / feeler / anchor buckets, network-group diversity, inbound eviction, addrman maintenance (feeler + addrfetch + anchor persistence), and the discourage / ban model. Linked from networking sidebar. pip-0006.mdx operator-RPC list extended with the three new ban RPCs. Cross-link added between pip-0006 and the new peer-management page. cli.mdx network row updated with addnode, removenode, addrbook-status, addrbook-resetkey, dialv2, setban, listbanned, clearbanned. admin.mdx JSON-RPC reference gains entries for every new admin method shipped in the v2.0 networking work: addnode, addrbookResetKey, addrbookStatus, clearbanned, dialV2, listbanned, removenode, setban. Inserted in the existing alphabetical order so the page stays scannable. (cherry picked from commit ab21434)
probeOne opened the parallax-disc/1 conversation with YourAddr, which the daemon-side handler now rejects as "msg 0x02 before Hello" — the same gate that protects the daemon from out-of-order messages disconnects the crawler before it can ask for peers. Send a single disc Hello (ProtoVersion=1, random Nonce, ListenPort 0, no services) ahead of YourAddr + GetPeers so the handler accepts the rest of the exchange. The probe is a one-shot tool, so a per-process random nonce is enough; we fall back to a non-zero sentinel if crypto/rand fails on the host. Fixes the regression in parallax-disc crawl / probe against any daemon running the patched build. (cherry picked from commit ee5bbab)
Workflows previously triggered only on main. Push and pull_request events on long-lived release branches (v2.0.x, v2.1.x, …) silently skipped CI, so regressions on those branches went undetected until the eventual merge to main. Add 'v*' to the branches filter on push and pull_request triggers for all three workflows. Tag-only refs are unaffected — release.yml still owns those. (cherry picked from commit 74530aa)
When --bootnodes is unset and the loaded --state is empty, seed the walker from netparams.MainnetBootnodesV2 so a fresh crawler can cold-start without operator config. Explicit --bootnodes still wins and a populated state file still cold-starts on its own. (cherry picked from commit 804a8be)
(cherry picked from commit 1425a4e)
…upted Port go-ethereum 7596db5f. The canceled-context branch raced with the in-process RPC server: select in client.send/op.wait could pick the ready request/response over ctx.Done(), so the call sometimes succeeded and tripped the 'transaction should be nil' assertion. Replace with positive lookups at index 0 and 1 plus the existing not-found check. (cherry picked from commit 8872332)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lands the v2.0.x release work onto
main. 32 commits cherry-picked from2.0.xsince the merge base, skipping 3 patches already onmain(bootnodes v2, CMDS override, dlgo 1.26.1) and the rc1/rc2 version
bumps.
version.gostays on2.1.0-unstable.Each commit carries a
(cherry picked from commit ...)trailer back tothe original on
2.0.x.What's landing
Self-dial / self-discovery hardening
p2p: reject v2 self-dial and self-handshake on advertised endpointp2p/addrman: skip self endpoint inV2Iterp2p/protocols/disc: drop self entries atHandlePeersingestDisc Hello handshake (per-startup nonce-based session abort)
p2p/protocols/disc: define HelloMsg struct and validationp2p: generate per-startup Hello nonce insetupLocalNodep2p/protocols/disc: thread Hello throughAddrmanBackendp2p/protocols/disc: send Hello first and gate on its receiptp2p/protocols/disc: end-to-end test for self-nonce session abortp2p/protocols/disc: bumpProtocolLengthto coverHelloMsgcmd/devp2p: send disc Hello first inparallax-discprobe / crawlcmd/devp2p: defaultparallax-disccrawl bootnodes toMainnetBootnodesV2Cross-dial dedupe and per-peer telemetry
p2p: dedupe v2 cross-dial via disclosed listen portp2p: add per-peer quality telemetry fieldsp2p: measure ping RTT for eviction telemetryp2p/protocols/prl: stamp last-block / last-tx receipt timestampsp2p: count payload bytes received per peerp2p: cache per-peer network group at attach timeBitcoin Core-style inbound eviction
p2p: implement Bitcoin Core inbound eviction algorithmp2p: trigger eviction on inbound saturationOutbound peer policy (network-group diversity, block-relay-only, feelers, anchors)
p2p: enforce outbound network-group diversityp2p: implement block-relay-only outbound peer behaviorp2p: add feeler and addrfetch dial loopsp2p: persist block-relay-only anchors across restartBan / discourage subsystem
p2p/banman: persistent ban list and ephemeral discourage filterp2p: peer misbehavior plus accept-time ban / discourage gatingnode:setban/listbanned/clearbannedadmin RPCcmd/parallax-cli:setban/listbanned/clearbannedsubcommandsCleanup, CI, docs
chore: lint cleanups across phase 4 / 5 changesdocs: peer-management features and operator RPC referenceci: run build / lint / unit-test on v* branchesci: trigger build / lint / unit-test on N.N.x release branchesrpc/client: drop racy canceled-context assertion fromTxInBlockInterrupted