Skip to content

Commit 2471900

Browse files
patrick-ogradycursoragentclaude
authored
[p2p] Support Primary/Secondary Peers (#3499)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 04911d3 commit 2471900

54 files changed

Lines changed: 5725 additions & 2318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

broadcast/fuzz/fuzz_targets/broadcast_engine_operations.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use commonware_cryptography::{
1313
};
1414
use commonware_p2p::{simulated::Network, Recipients};
1515
use commonware_runtime::{deterministic, Buf, BufMut, Clock, Metrics, Quota, Runner};
16+
use commonware_utils::NZUsize;
1617
use libfuzzer_sys::fuzz_target;
1718
use rand::{seq::SliceRandom, SeedableRng};
1819
use std::{collections::BTreeMap, num::NonZeroU32, time::Duration};
@@ -172,26 +173,30 @@ fn resolve_recipients(pattern: &RecipientPattern, peers: &[PublicKey]) -> Recipi
172173
fn fuzz(input: FuzzInput) {
173174
let executor = deterministic::Runner::default();
174175
executor.start(|context| async move {
176+
// Generate peer identities before building the network so the initial
177+
// peer set can be seeded through the constructor.
178+
let peers = input
179+
.peer_seeds
180+
.iter()
181+
.map(|&seed| PrivateKey::from_seed(seed).public_key())
182+
.collect::<Vec<_>>();
183+
175184
// Create network
176-
let (network, oracle) = Network::<deterministic::Context, PublicKey>::new(
185+
let (network, oracle) = Network::<deterministic::Context, PublicKey>::new_with_peers(
177186
context.with_label("network"),
178187
commonware_p2p::simulated::Config {
179188
max_size: 1024 * 1024,
180189
disconnect_on_block: false,
181-
tracked_peer_sets: None,
190+
tracked_peer_sets: NZUsize!(1),
182191
},
183-
);
192+
peers.clone(),
193+
)
194+
.await;
184195
network.start();
185196

186197
// Create peers
187-
let mut peers = Vec::new();
188198
let mut mailboxes: BTreeMap<PublicKey, Mailbox<PublicKey, FuzzMessage>> = BTreeMap::new();
189-
for (i, &seed) in input.peer_seeds.iter().enumerate() {
190-
// Create peer
191-
let crypto = PrivateKey::from_seed(seed);
192-
let public_key = crypto.public_key();
193-
peers.push(public_key.clone());
194-
199+
for (i, public_key) in peers.iter().cloned().enumerate() {
195200
// Create channel
196201
let (sender, receiver) = oracle
197202
.control(public_key.clone())

broadcast/src/buffered/config.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ pub struct Config<P: PublicKey, MCfg, D: Provider<PublicKey = P>> {
1818
/// The configuration for the codec item.
1919
pub codec_config: MCfg,
2020

21-
/// Provider for peer set changes. Per-peer caches are freed when a
22-
/// peer leaves all tracked peer sets.
21+
/// Provider for peer set changes (eviction follows latest primary; see [`buffered`](super)).
2322
pub peer_provider: D,
2423
}

broadcast/src/buffered/engine.rs

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ struct Waiter<M> {
2525
responder: oneshot::Sender<M>,
2626
}
2727

28+
/// Result of buffering an incoming or locally sent digest (inserted, duplicate, or ineligible).
29+
enum InsertMessageResult {
30+
Inserted,
31+
Duplicate,
32+
Ineligible,
33+
}
34+
2835
/// Instance of the main engine for the module.
2936
///
3037
/// It is responsible for:
@@ -90,6 +97,9 @@ where
9097
/// the message once.
9198
counts: BTreeMap<M::Digest, usize>,
9299

100+
/// Latest primary peer set allowed to keep buffered messages resident.
101+
latest_primary_peers: Set<P>,
102+
93103
////////////////////////////////////////
94104
// Metrics
95105
////////////////////////////////////////
@@ -124,6 +134,7 @@ where
124134
deques: BTreeMap::new(),
125135
items: BTreeMap::new(),
126136
counts: BTreeMap::new(),
137+
latest_primary_peers: Set::default(),
127138
peer_provider: cfg.peer_provider,
128139
metrics,
129140
};
@@ -159,6 +170,14 @@ where
159170
on_stopped => {
160171
debug!("shutdown");
161172
},
173+
// Handle peer set subscription messages
174+
Some(update) = peer_set_subscription.recv() else {
175+
debug!("peer set subscription closed");
176+
break;
177+
} => {
178+
// Evict by latest primary only; see buffered module docs.
179+
self.update_latest_primary_peers(update.latest.primary);
180+
},
162181
// Handle mailbox messages
163182
Some(msg) = self.mailbox_receiver.recv() else {
164183
error!("mailbox receiver failed");
@@ -210,12 +229,6 @@ where
210229
.inc();
211230
self.handle_network(peer, msg);
212231
},
213-
Some((_, _, tracked_peers)) = peer_set_subscription.recv() else {
214-
debug!("peer set subscription closed");
215-
break;
216-
} => {
217-
self.evict_untracked_peers(&tracked_peers);
218-
},
219232
}
220233
}
221234

@@ -232,7 +245,8 @@ where
232245
responder: oneshot::Sender<Vec<P>>,
233246
) {
234247
// Store the message, continue even if it was already stored
235-
let _ = self.insert_message(self.public_key.clone(), msg.clone());
248+
let digest = msg.digest();
249+
let _ = self.insert_message(self.public_key.clone(), digest, msg.clone());
236250

237251
// Broadcast the message to the network
238252
let sent_to = sender
@@ -271,13 +285,20 @@ where
271285

272286
/// Handles a message that was received from a peer.
273287
fn handle_network(&mut self, peer: P, msg: M) {
274-
if !self.insert_message(peer.clone(), msg) {
275-
debug!(?peer, "message already stored");
276-
self.metrics.receive.inc(Status::Dropped);
277-
return;
288+
let digest = msg.digest();
289+
match self.insert_message(peer.clone(), digest, msg) {
290+
InsertMessageResult::Inserted => {
291+
self.metrics.receive.inc(Status::Success);
292+
}
293+
InsertMessageResult::Duplicate => {
294+
debug!(?peer, "message already stored");
295+
self.metrics.receive.inc(Status::Dropped);
296+
}
297+
InsertMessageResult::Ineligible => {
298+
debug!(?peer, "message from peer outside latest.primary not cached");
299+
self.metrics.receive.inc(Status::Dropped);
300+
}
278301
}
279-
280-
self.metrics.receive.inc(Status::Success);
281302
}
282303

283304
////////////////////////////////////////
@@ -286,18 +307,21 @@ where
286307

287308
/// Inserts a message into the cache.
288309
///
289-
/// Returns `true` if the message was inserted, `false` if it was already present.
290-
/// Updates the deque, item count, and message cache, potentially evicting an old message.
291-
fn insert_message(&mut self, peer: P, msg: M) -> bool {
292-
let digest = msg.digest();
293-
310+
/// Waiters are notified even when a sender is not eligible to keep a
311+
/// buffered cache entry resident.
312+
fn insert_message(&mut self, peer: P, digest: M::Digest, msg: M) -> InsertMessageResult {
294313
// Send the message to the waiters, if any
295314
if let Some(waiters) = self.waiters.remove(&digest) {
296315
for waiter in waiters {
297316
self.respond_subscribe(waiter.responder, msg.clone());
298317
}
299318
}
300319

320+
// Only peers listed in `latest.primary` may buffer
321+
if self.latest_primary_peers.position(&peer).is_none() {
322+
return InsertMessageResult::Ineligible;
323+
}
324+
301325
// Get the relevant deque for the peer
302326
let deque = self
303327
.deques
@@ -310,7 +334,7 @@ where
310334
let v = deque.remove(i).unwrap(); // Must exist
311335
deque.push_front(v);
312336
}
313-
return false;
337+
return InsertMessageResult::Duplicate;
314338
};
315339

316340
// - Insert the digest into the peer cache
@@ -336,20 +360,20 @@ where
336360
decrement_digest_refcount(&mut self.counts, &mut self.items, &stale);
337361
}
338362

339-
true
363+
InsertMessageResult::Inserted
340364
}
341365

342-
fn evict_untracked_peers(&mut self, tracked_peers: &Set<P>) {
343-
let tracked = tracked_peers.as_ref();
366+
fn update_latest_primary_peers(&mut self, peers: Set<P>) {
344367
for (peer, deque) in self
345368
.deques
346-
.extract_if(.., |peer, _| !tracked.contains(peer))
369+
.extract_if(.., |peer, _| peers.position(peer).is_none())
347370
{
348371
debug!(?peer, digests = deque.len(), "evicting disconnected peer");
349372
for digest in deque {
350373
decrement_digest_refcount(&mut self.counts, &mut self.items, &digest);
351374
}
352375
}
376+
self.latest_primary_peers = peers;
353377
}
354378

355379
////////////////////////////////////////

0 commit comments

Comments
 (0)