Skip to content

net: bound who stays connected - #877

Merged
reardencode merged 12 commits into
net/peer-resourcesfrom
net/peer-lifecycle
Oct 4, 2026
Merged

reardencode merged 12 commits into
net/peer-resourcesfrom
net/peer-lifecycle

Conversation

@rearden-grok

@rearden-grok rearden-grok Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Inbound eviction keeps a share of the longest-connected peers, then disconnects the newest peer in the largest netgroup. That netgroup is fixed when the peer is accepted and does not read the asmap.
  • A misbehavior disconnect refuses that address for one day, in memory only. A netgroup that just lost an inbound slot waits ten minutes. The set does not grow past its cap. There is no ban file and no new ban-time flag.
  • During initial download, only a block this node requested is queued or moves the stall clock. Requested block bodies are not limited by the tip-follow rate window, and the requested-body channel stays unbounded. Light decodes take a permit without waiting on the reader.

Stacked on net/peer-resources.

Test plan

  • cargo test -p rbitcoin-net --lib -- eviction_drops_the_newest_in_the_largest_netgroup misbehavior_disconnect_refuses_the_same_address evicted_netgroup_waits_less_than_a_day_and_the_set_is_capped unsolicited_block_does_not_refresh_progress light_decode_permit_does_not_wait -- --quiet

@rearden-grok rearden-grok Bot added the core-functional Run Core functional nightly.sh on this PR (inventory + release-pin warn) label Oct 3, 2026
@rearden-grok
rearden-grok Bot force-pushed the net/peer-lifecycle branch from b60f86f to 787b4d9 Compare October 3, 2026 03:35

@rearden-grok rearden-grok Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Inbound eviction and the in-memory refusal set match the decisions that were already made: longest-connected peers stay, the newest peer in the largest fixed netgroup goes, and asmap is not read. The IBD reader does keep the requested-body channel unbounded, does not wait on a decode permit, and does not put an unsolicited hash on that channel or on the stall clock. Two holes remain. A hash stays solicited until the IBD thread applies the first copy, so replays of that hash skip the 16 MB/s window, refresh progress, and pile onto the shared body channel. The one-day address refusal runs only from punish_disconnect, not from the read-loop exits that already disconnect for the misbehavior threshold.

Issue counts by severity

  • bugs: 2
  • suggestions: 2
  • nits: 0

Comment thread crates/rbitcoin-net/src/ibd/peer_io.rs Outdated
&sol_ms_r,
&hash,
n,
) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] note_solicited_block returns true for every frame whose header hash is still in the per-peer set, and it leaves the hash there. The set is cleared only when the IBD thread later applies BlockFramed and calls track_remove. Until that happens, a peer that was asked for a block can resend the same hash as fast as it can write. Each copy skips the 16 MB/s window, adds solicited_bytes, and stores a new solicited_ms, so the stall clock and the relative-slow EWMA treat the replay as progress. Each copy is also send_body'd onto the shared unbounded body channel. The first copy must stay unmetered; the copies behind it are not new requests. The door stays open for every other in-flight hash while the thread drains those duplicates.

Suggestion: Under the same requested lock, remove the hash when the check hits, and only then add the bytes and return true. track_remove is already idempotent. Leave the channel unbounded and do not rate-limit that first copy.

Comment thread crates/rbitcoin-net/src/peer.rs Outdated
if let Some(s) = session {
s.request_disconnect();
if let Some(hub) = s.peer_hub() {
hub.note_misbehavior_addr(s.addr.ip());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] The one-day refusal is recorded only here. The session read loop still disconnects without calling it. Rate-limit and oversize frames return NetError::Protocol("peer misbehavior threshold") directly, and after handle_peer_frame the misbehavior_disconnects exit does the same for a score that climbed there by tx or compact-block penalties (including getblocktxn out-of-range, which request_disconnects on its own). Those are the disconnects a flood hits, and that address is accepted again on the next inbound. The new IBD reader path that sends Dead for the same threshold also never records the address. apply_peer_dead cools the dial only while first_data_ms is still 0, and one applied body clears that.

Suggestion: Record the address in one place used by every threshold exit: punish_disconnect, the read-loop rate-limit and oversize returns, and the misbehavior_disconnects return. For an IBD Dead with this reason, cool the dial address even when a body was already counted.

.discouraged_addrs
.lock()
.unwrap_or_else(|e| e.into_inner());
if !g.contains_key(&ip) && g.len() >= Self::DISCOURAGE_CAP {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] note_misbehavior_addr and note_slot_evict apply the cap to map length and do not drop expired rows. The only sweep is inbound_discouraged, which the accept loop runs after a note that already returned. When the map is at the cap and at least one row is expired, the new address is ignored. The following accept sweeps, does not find that address, and allows it.

Suggestion: Drop expired keys before the cap check in both note functions. Refreshing a key that is already present can stay as it is.

Comment thread crates/rbitcoin-net/src/ibd/peer_io.rs Outdated

pub(crate) fn track_retain(&mut self, mut keep: impl FnMut(&BlockHash) -> bool) {
self.in_flight.retain(|h| keep(h));
self.requested_set().retain(|h| keep(h));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] track_retain runs keep twice. The caller passes has_block, which takes the confirmed-set read lock. The second pass holds the per-peer requested mutex that the IBD reader blocks on inside note_solicited_block. Assign runs this for every live peer on the work loop. A confirm or reorg between the two passes can also drop the hash from only one set, so the reader and in_flight disagree about whether the next body is solicited.

Suggestion: Choose the retained hashes once, without the mutex, then replace in_flight and the requested set under the lock. The reader then waits only for that update, not for chain lookups.

@rearden-grok
rearden-grok Bot force-pushed the net/peer-lifecycle branch from 787b4d9 to 0c2526c Compare October 3, 2026 17:04
@rearden-grok
rearden-grok Bot force-pushed the net/peer-lifecycle branch 4 times, most recently from a07991d to d8600b3 Compare October 4, 2026 07:54
rearden-grok Bot added 11 commits October 4, 2026 02:20
Full inbound slots disconnected the longest-connected unprotected peer.
Keep a share of those long-lived peers, and disconnect the newest peer
in the largest netgroup instead. The group id is fixed when the peer
is accepted.
punish_disconnect dropped the peer and forgot the address, so the same
host reconnected at once. Refuse that address for one day, and make a
netgroup that just lost an inbound slot wait ten minutes. Both sets are
capped, in memory, and swept when the next inbound is accepted.
Unsolicited blocks and decoys were moving the stall clock, and a block
body was queued before the node checked that it had asked for it. The
reader drops a block that is not in the requested set, and the stall
sample uses that set's bytes. Light decodes take a permit without waiting.
The wire counter is no longer the stall clock. The disconnect line is
where that total is still read, so a decoy flood stays visible without
counting as block progress.
The index links 060-062 for eviction, in-memory discouragement, and
IBD progress. Each note names the severity, the behavior, and the
regression.
Rate-limit, oversize, and score-threshold exits dropped the peer
without the one-day refusal that punish_disconnect records. Those
exits now share that record. An IBD death for the same reason cools
the dial even after a block body was counted.
The 21-peer set is fully protected once the longest-connected peers
stay. The pin now expects no victim there, and expects the newest
slow peer once ten slow peers leave one unprotected.
read_ibd_peer and apply_decoded_message were each one function, so the
coverage CRAP gate scored them above 30. Frame handling now lives in
small functions, and the tests drive those shipped paths.
Sealing each mainnet scripthash shard walked all 2^25 ingest slots even
when occupancy was known zero. That walk holds the ingest lock through
tip entry, so an empty --sh-index genesis missed the RPC cookie window.
A one-day refusal of 127.0.0.1 blocks every later local connection after a protocol disconnect. Core disconnects that peer and leaves the address usable.
The Core script fills 21 inbound peers and expects the next connection
to evict one slow peer. Those peers are all protected, including a
share of the longest-connected, so the extra inbound is rejected.
The unmodified script is not a run.
@rearden-grok
rearden-grok Bot force-pushed the net/peer-lifecycle branch from 2ef5f2e to 8e7670b Compare October 4, 2026 09:20
@reardencode
reardencode added this pull request to stack #904 October 4, 2026 14:22
The second pass held the reader mutex across another has_block lookup
and could keep a different set than the assign loop.
@reardencode
reardencode merged commit 096d202 into master Oct 4, 2026
23 of 24 checks passed
@rearden-grok
rearden-grok Bot deleted the net/peer-lifecycle branch October 4, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-functional Run Core functional nightly.sh on this PR (inventory + release-pin warn)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant