Phase 3 (0.6.2-beta): merged pull + recipient-match
A non-follower can now receive DMs addressed to them via a normal pull cycle, with no distinguishable "searching for DMs" traffic pattern — the pull query is a uniform list of NodeIds that the server matches against both authors and wrapped-key recipients. Schema (migrations on first 0.6.2 launch): - New post_recipients(post_id, recipient) index table with index on recipient column - Seed migration scans existing encrypted posts, extracts recipients and group members from visibility JSON, populates the index Write path: - store_post_with_visibility / store_post_with_intent populate post_recipients on successful insert - update_post_visibility rebuilds the index for the updated post - apply_delete cascade-removes post_recipients entries Server pull handler (should_send_post): - Renamed semantic: requester_follows → query_list. Contains every NodeId the client wants posts for (follows + own NodeId). - Encrypted/GroupEncrypted posts match if ANY recipient / group member is in query_list (previously only if == requester). - Wire protocol unchanged — the same PullSyncRequestPayload.follows field now carries both follow targets and own NodeId indistinguishably. Client pull paths (all three call sites in network.rs + connection.rs): - Always append own NodeId to the query list before sending pull sync. Storage helper: - get_post_ids_for_recipients(nids) — bulk IN-match using the idx_post_recipients_recipient index, for future SQL-side filtering. Tests: - should_send_post's recipient tests updated to pass query_list containing requester (matches new contract). - Added encrypted_post_matches_via_query_list_even_for_third_party_recipient proving the server matches on any recipient in the list, not just the requester itself. All 111 core tests pass. Smoke-tested end-to-end: A posts encrypted DM to B; B connects + syncs; B decrypts and reads DM; both sides' post_recipients correctly populated on store. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d9ba22427
commit
975e7b9bfe
3 changed files with 201 additions and 21 deletions
|
|
@ -1346,14 +1346,22 @@ impl ConnectionManager {
|
|||
conn: iroh::endpoint::Connection,
|
||||
storage: &Arc<StoragePool>,
|
||||
peer_id: &NodeId,
|
||||
our_node_id: NodeId,
|
||||
) -> anyhow::Result<PullSyncStats> {
|
||||
let (our_follows, follows_sync) = {
|
||||
let s = storage.get().await;
|
||||
(s.list_follows()?, s.get_follows_with_last_sync().unwrap_or_default())
|
||||
};
|
||||
|
||||
// Merged pull: include our own NodeId in the query so the peer returns
|
||||
// posts where we're either a followed author OR a recipient (DM).
|
||||
let mut query_list = our_follows;
|
||||
if !query_list.contains(&our_node_id) {
|
||||
query_list.push(our_node_id);
|
||||
}
|
||||
|
||||
let request = PullSyncRequestPayload {
|
||||
follows: our_follows,
|
||||
follows: query_list,
|
||||
have_post_ids: vec![],
|
||||
since_ms: follows_sync,
|
||||
};
|
||||
|
|
@ -1903,9 +1911,15 @@ impl ConnectionManager {
|
|||
)
|
||||
};
|
||||
|
||||
// Merged pull: include our own NodeId in the query list.
|
||||
let mut query_list = our_follows;
|
||||
if !query_list.contains(&self.our_node_id) {
|
||||
query_list.push(self.our_node_id);
|
||||
}
|
||||
|
||||
let (mut send, mut recv) = pull_conn.open_bi().await?;
|
||||
let request = PullSyncRequestPayload {
|
||||
follows: our_follows,
|
||||
follows: query_list,
|
||||
have_post_ids: vec![], // v4: empty, using since_ms instead
|
||||
since_ms: follows_sync,
|
||||
};
|
||||
|
|
@ -1995,8 +2009,14 @@ impl ConnectionManager {
|
|||
)
|
||||
};
|
||||
|
||||
// Merged pull: include our own NodeId in the query list.
|
||||
let mut query_list = our_follows;
|
||||
if !query_list.contains(&self.our_node_id) {
|
||||
query_list.push(self.our_node_id);
|
||||
}
|
||||
|
||||
let request = PullSyncRequestPayload {
|
||||
follows: our_follows,
|
||||
follows: query_list,
|
||||
have_post_ids: vec![], // v4: empty, using since_ms instead
|
||||
since_ms: follows_sync,
|
||||
};
|
||||
|
|
@ -8295,15 +8315,16 @@ impl ConnectionActor {
|
|||
let _ = reply.send(r);
|
||||
}
|
||||
ConnCommand::PullFromPeer { peer, reply } => {
|
||||
// Brief lock: grab connection clone + follows data
|
||||
// Brief lock: grab connection clone + our_node_id
|
||||
let gather = {
|
||||
let cm = self.cm.lock().await;
|
||||
cm.connections.get(&peer).map(|pc| pc.connection.clone())
|
||||
cm.connections.get(&peer)
|
||||
.map(|pc| (pc.connection.clone(), cm.our_node_id))
|
||||
};
|
||||
let r = match gather {
|
||||
Some(conn) => {
|
||||
Some((conn, our_node_id)) => {
|
||||
// All I/O outside the lock, storage accessed via hoisted Arc
|
||||
ConnectionManager::pull_from_peer_unlocked(conn, &self.storage, &peer).await
|
||||
ConnectionManager::pull_from_peer_unlocked(conn, &self.storage, &peer, our_node_id).await
|
||||
}
|
||||
None => Err(anyhow::anyhow!("not connected to {}", hex::encode(peer))),
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue