commit a613cc7e18c0f56beec4a131ecc9d67b5183390a
parent 5be8bc0774fad8872d0d66c68ad4fbbd0f3c7531
Author: William Casarin <jb55@jb55.com>
Date: Tue, 24 Feb 2026 17:27:18 -0800
negentropy: wait for fetched events to be ingested before next round
The next negentropy round was firing on the very next frame (~16ms)
after trigger_now(), but the REQ responses from the previous round
need a full network round-trip to arrive and be ingested into ndb.
This caused every round to see identical local state and re-identify
the same missing events.
Track fetched event IDs and gate the next round on confirming at
least one has appeared in ndb via get_note_by_id.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
1 file changed, 24 insertions(+), 0 deletions(-)
diff --git a/crates/enostr/src/negentropy.rs b/crates/enostr/src/negentropy.rs
@@ -93,6 +93,9 @@ pub struct NegentropySync {
sync_requested: bool,
/// IDs accumulated across multi-round reconciliation.
need_ids: Vec<[u8; 32]>,
+ /// IDs sent via REQ that we're waiting to see ingested into ndb
+ /// before starting the next round.
+ pending_fetch_ids: Vec<[u8; 32]>,
}
impl NegentropySync {
@@ -103,6 +106,7 @@ impl NegentropySync {
neg: None,
sync_requested: false,
need_ids: Vec::new(),
+ pending_fetch_ids: Vec::new(),
}
}
@@ -154,6 +158,24 @@ impl NegentropySync {
}
}
+ // Wait for previously-fetched events to be ingested into ndb
+ // before starting the next round. Without this, the next round
+ // starts before the REQ responses arrive, causing the same
+ // events to be identified as missing every round.
+ if self.sync_requested && !self.pending_fetch_ids.is_empty() {
+ if let Ok(txn) = Transaction::new(ndb) {
+ if ndb.get_note_by_id(&txn, &self.pending_fetch_ids[0]).is_ok() {
+ tracing::info!(
+ "negentropy: fetched events ingested, proceeding with next round"
+ );
+ self.pending_fetch_ids.clear();
+ } else {
+ // Events not yet ingested — wait for next frame
+ return fetched;
+ }
+ }
+ }
+
// Initiate sync if requested and idle
if self.sync_requested && self.state == SyncState::Idle {
self.sync_requested = false;
@@ -261,6 +283,7 @@ impl NegentropySync {
if count > 0 {
tracing::info!("negentropy: fetching {} missing events", count);
Self::fetch_missing(&missing, pool, relay_url);
+ self.pending_fetch_ids = missing;
}
count
}
@@ -278,6 +301,7 @@ impl NegentropySync {
self.sub_id = None;
self.neg = None;
self.need_ids.clear();
+ self.pending_fetch_ids.clear();
}
fn fetch_missing(ids: &[[u8; 32]], pool: &mut RelayPool, relay_url: &str) {