Skip to content

Conversation

@patrick-ogrady
Copy link
Contributor

@patrick-ogrady patrick-ogrady commented Dec 19, 2025

Closes: #2556
Closes: #2557

TODO

  • Add metric for block vs unregistered attempts

Summary

This PR adds time-based block expiry for p2p peers. When a peer is blocked, the block automatically expires after a configurable duration (default: 1 hour for production, 1 minute for tests). This allows nodes that were blocked due to temporary issues (misconfiguration, one-off bugs) to reconnect without requiring restarts of all other nodes.

Changes

Discovery Module

  • Add block_duration configuration field
  • Address::Blocked now stores SystemTime (block expiry time) instead of being a unit variant
  • Record methods now take now: SystemTime parameter to check block expiry:
    • blocked(now), eligible(now), want(now, ...), dialable(now, ...), acceptable(now), reserve(now), update(now, ...)
  • Acceptable enum (Yes, Blocked, Unknown) for detailed rejection logging
  • Listener logs distinguish blocked peers from unknown peers

Lookup Module

  • Add block_duration configuration field
  • Address::Blocked now stores SystemTime (block expiry time)
  • Record methods updated similarly to discovery module
  • ListenerFilter struct carries blocked IPs with expiry times to listener
  • Listener checks blocked IPs before accepting connections

Behavior

  1. When a peer is blocked, the block expires after block_duration
  2. After expiry, the peer becomes eligible again (can accept incoming connections)
  3. Blocked peers are not dialable even after expiry (address info is lost on block)
  4. Once a new peer set is registered containing the peer, their address is restored

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 19, 2025

Deploying monorepo with  Cloudflare Pages  Cloudflare Pages

Latest commit: f7f6369
Status: ✅  Deploy successful!
Preview URL: https://65f89e6f.monorepo-eu0.pages.dev
Branch Preview URL: https://bounded-block.monorepo-eu0.pages.dev

View logs

pub fn listen(&mut self, peer: &C) -> Option<Reservation<C>> {
/// Returns `Ok(Reservation)` on success, or an error indicating why the reservation failed.
pub fn listen(&mut self, peer: &C) -> Result<Reservation<C>, super::ingress::ListenError> {
use super::ingress::ListenError;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove this

context,
|peer| tracker.acceptable(peer),
|peer| {
let mut tracker = tracker.clone();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is jank but technically this the way you'd need to do this given the interface of listen

/// - We are not already connected or reserved
/// - The ingress address is allowed (DNS enabled, Socket IP is global or private IPs allowed)
///
/// Note: Blocked peers are never dialable regardless of expiry since we don't retain
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should change this?

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 30, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp f7f6369 Jan 01 2026, 11:28 PM

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 92.59259% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.62%. Comparing base (6854396) to head (971ac5d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...c/authenticated/discovery/actors/tracker/record.rs 94.61% 7 Missing ⚠️
p2p/src/authenticated/lookup/actors/listener.rs 82.75% 5 Missing ⚠️
.../src/authenticated/lookup/actors/tracker/record.rs 94.25% 5 Missing ⚠️
p2p/src/authenticated/discovery/actors/listener.rs 69.23% 4 Missing ⚠️
...uthenticated/discovery/actors/tracker/directory.rs 91.17% 3 Missing ⚠️
p2p/src/authenticated/discovery/config.rs 33.33% 2 Missing ⚠️
p2p/src/authenticated/lookup/config.rs 33.33% 2 Missing ⚠️
@@            Coverage Diff             @@
##             main    #2570      +/-   ##
==========================================
- Coverage   92.63%   92.62%   -0.01%     
==========================================
  Files         355      355              
  Lines      102890   103042     +152     
==========================================
+ Hits        95314    95446     +132     
- Misses       7576     7596      +20     
Files with missing lines Coverage Δ
...rc/authenticated/discovery/actors/tracker/actor.rs 95.75% <100.00%> (+0.03%) ⬆️
.../authenticated/discovery/actors/tracker/ingress.rs 88.23% <100.00%> (ø)
p2p/src/authenticated/discovery/network.rs 91.73% <100.00%> (+0.06%) ⬆️
...p/src/authenticated/lookup/actors/tracker/actor.rs 95.50% <100.00%> (+0.11%) ⬆️
...c/authenticated/lookup/actors/tracker/directory.rs 93.76% <100.00%> (+0.37%) ⬆️
p2p/src/authenticated/lookup/network.rs 91.37% <100.00%> (+0.15%) ⬆️
p2p/src/authenticated/discovery/config.rs 32.07% <33.33%> (+0.03%) ⬆️
p2p/src/authenticated/lookup/config.rs 31.25% <33.33%> (+0.08%) ⬆️
...uthenticated/discovery/actors/tracker/directory.rs 87.60% <91.17%> (+0.34%) ⬆️
p2p/src/authenticated/discovery/actors/listener.rs 89.04% <69.23%> (-0.34%) ⬇️
... and 3 more

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6854396...971ac5d. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@patrick-ogrady patrick-ogrady added this to the v0.1.0 milestone Dec 30, 2025
|peer| tracker.acceptable(peer),
|peer| {
let mut tracker = tracker.clone();
async move { tracker.acceptable(peer).await == tracker::Acceptable::Yes }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we just make this async?

.await
{
Ok(x) => x,
Err(StreamError::PeerRejected(bytes)) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make error generic over key?

Err(StreamError::PeerRejected(bytes)) => {
// Decode the peer public key and query for rejection reason
if let Ok(peer) = C::PublicKey::read(&mut bytes.as_slice()) {
match tracker.acceptable(peer).await {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is super racy and inefficient.

/// Attempt to block a peer, updating the metrics accordingly.
pub fn block(&mut self, peer: &C) {
if self.peers.get_mut(peer).is_some_and(|r| r.block()) {
let blocked_until = self.context.current() + self.block_duration;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

saturating add

self.peers.get(peer).is_some_and(|r| r.acceptable())
/// Returns the acceptance status for a peer.
pub fn acceptable(&self, peer: &C) -> super::ingress::Acceptable {
use super::ingress::Acceptable;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove inline import

pub fn block(&mut self) -> bool {
if matches!(self.address, Address::Blocked | Address::Myself) {
pub fn block(&mut self, blocked_until: SystemTime) -> bool {
if matches!(self.address, Address::Blocked(_) | Address::Myself) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should use later of block times?

/// IPs of eligible peers we should accept connections from.
pub registered_ips: HashSet<IpAddr>,
/// IPs of blocked peers with their expiry times.
pub blocked_ips: std::collections::HashMap<IpAddr, SystemTime>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just import?

// Send the updated listenable IPs to the listener.
let _ = self.listener.send(self.directory.listenable()).await;
// Send the updated filter to the listener.
let filter = ListenerFilter {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

send just blocked IPs here (don't need to resend listenable?)


// Check whether the IP is registered
if !self.attempt_unregistered_handshakes && !self.registered_ips.contains(&ip) {
self.handshakes_blocked.inc();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add metrics for different block reasons.

# Conflicts:
#	p2p/src/authenticated/lookup/actors/tracker/actor.rs
#	p2p/src/authenticated/lookup/actors/tracker/directory.rs
#	p2p/src/authenticated/lookup/actors/tracker/mod.rs
#	p2p/src/authenticated/lookup/actors/tracker/record.rs
#	p2p/src/authenticated/lookup/network.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[p2p/lookup] Improve Logging on Blocked Filtering [p2p] Add Block Expiry (and/or Peer Set Registration Unblocking)

2 participants