From e6265b52b6fc92abe9ab7bed697fbdfe7bfaad79 Mon Sep 17 00:00:00 2001 From: Scott Reimers Date: Tue, 21 Apr 2026 20:46:34 -0400 Subject: [PATCH] Phase 1 (0.6.0-beta): remove direct PostPush for encrypted posts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encrypted posts now propagate only via the CDN (ManifestPush + neighbor header updates), eliminating the sender→recipient traffic signal on the wire. Encrypted DMs are indistinguishable from any other encrypted post. - Remove push_post_to_recipients entirely from network.rs - Remove call sites in create_post and re-encrypt-on-revoke - PostPush handler now ignores non-public visibility (kept for public audience push path) Known gap: non-follower DMs won't reach until Phase 3 (merged pull + recipient-match). Followers receive via the existing CDN path — new posts trigger neighbor-manifest updates, ManifestPush fans out to downstream holders, recipients pull missing post IDs from followed authors. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/core/src/connection.rs | 40 +++++++++++++++++++------------ crates/core/src/network.rs | 44 ----------------------------------- crates/core/src/node.rs | 15 +++++------- 3 files changed, 31 insertions(+), 68 deletions(-) diff --git a/crates/core/src/connection.rs b/crates/core/src/connection.rs index bfb0090..6b73b03 100644 --- a/crates/core/src/connection.rs +++ b/crates/core/src/connection.rs @@ -4958,24 +4958,34 @@ impl ConnectionManager { } MessageType::PostPush => { let push: PostPushPayload = read_payload(recv, MAX_PAYLOAD).await?; - let cm = conn_mgr.lock().await; - let storage = cm.storage.get().await; - if !storage.is_deleted(&push.post.id)? - && storage.get_post(&push.post.id)?.is_none() - && crate::content::verify_post_id(&push.post.id, &push.post.post) - { - let _ = storage.store_post_with_visibility( - &push.post.id, - &push.post.post, - &push.post.visibility, - ); - let prio = storage.get_post_upstreams(&push.post.id).map(|v| v.len() as u8).unwrap_or(0); - let _ = storage.add_post_upstream(&push.post.id, &remote_node_id, prio); - info!( + // Encrypted posts are no longer accepted via direct push — they propagate + // via the CDN to eliminate the sender→recipient traffic signal. + if !matches!(push.post.visibility, crate::types::PostVisibility::Public) { + debug!( peer = hex::encode(remote_node_id), post_id = hex::encode(push.post.id), - "Received direct post push" + "Ignoring non-public PostPush" ); + } else { + let cm = conn_mgr.lock().await; + let storage = cm.storage.get().await; + if !storage.is_deleted(&push.post.id)? + && storage.get_post(&push.post.id)?.is_none() + && crate::content::verify_post_id(&push.post.id, &push.post.post) + { + let _ = storage.store_post_with_visibility( + &push.post.id, + &push.post.post, + &push.post.visibility, + ); + let prio = storage.get_post_upstreams(&push.post.id).map(|v| v.len() as u8).unwrap_or(0); + let _ = storage.add_post_upstream(&push.post.id, &remote_node_id, prio); + info!( + peer = hex::encode(remote_node_id), + post_id = hex::encode(push.post.id), + "Received direct post push" + ); + } } } MessageType::AudienceRequest => { diff --git a/crates/core/src/network.rs b/crates/core/src/network.rs index 8874a2e..32273cd 100644 --- a/crates/core/src/network.rs +++ b/crates/core/src/network.rs @@ -902,50 +902,6 @@ impl Network { self.send_to_audience(MessageType::PostNotification, &payload).await } - /// Push a full post directly to recipients (persistent if available, ephemeral otherwise). - pub async fn push_post_to_recipients( - &self, - post_id: &crate::types::PostId, - post: &Post, - visibility: &PostVisibility, - ) -> usize { - let recipients: Vec = match visibility { - PostVisibility::Public => return 0, - PostVisibility::Encrypted { recipients } => { - recipients.iter().map(|wk| wk.recipient).collect() - } - PostVisibility::GroupEncrypted { group_id, .. } => { - // Push to all group members - match self.storage.get().await.get_all_group_members() { - Ok(map) => map.get(group_id).cloned().unwrap_or_default().into_iter().collect(), - Err(_) => return 0, - } - } - }; - - let payload = PostPushPayload { - post: SyncPost { - id: *post_id, - post: post.clone(), - visibility: visibility.clone(), - }, - }; - - let mut pushed = 0; - for recipient in &recipients { - if self.send_to_peer_uni(recipient, MessageType::PostPush, &payload).await.is_ok() { - pushed += 1; - debug!( - recipient = hex::encode(recipient), - post_id = hex::encode(post_id), - "Pushed post to recipient" - ); - } - } - - pushed - } - /// Push a profile update to all audience members (ephemeral-capable). pub async fn push_profile(&self, profile: &PublicProfile) -> usize { // Sanitize: if public_visible=false, strip display_name/bio from pushed profile diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 067c632..8e89b80 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -836,13 +836,12 @@ impl Node { } } - // For encrypted posts, push directly to recipients - let pushed = self.network.push_post_to_recipients(&post_id, &post, &visibility).await; - - // For public posts, push to audience members + // For public posts, push to audience members. Encrypted posts propagate + // via the CDN (ManifestPush + header-diff) to eliminate the sender→recipient + // traffic signal. let audience_pushed = self.network.push_to_audience(&post_id, &post, &visibility).await; - info!(post_id = hex::encode(post_id), pushed, audience_pushed, "Created new post"); + info!(post_id = hex::encode(post_id), audience_pushed, "Created new post"); Ok((post_id, post, visibility)) } @@ -2108,12 +2107,10 @@ impl Node { storage.store_post_with_visibility(&new_post_id, &new_post, &new_vis)?; } - // delete_post already pushes the DeleteRecord + // delete_post already pushes the DeleteRecord. + // Replacement post propagates via the CDN to remaining recipients. self.delete_post(post_id).await?; - // Push replacement post directly to remaining recipients - self.network.push_post_to_recipients(&new_post_id, &new_post, &new_vis).await; - info!( old_id = hex::encode(post_id), new_id = hex::encode(new_post_id),