Fix merged-pull query: include posting identities, not network id
Phase 3 merged-pull was shipped when network_id == posting_id (pre-
v0.6.1), so adding our_node_id to the pull query's follows list was
enough to trigger recipient-match on DMs. v0.6.1 broke that:
- Fresh installs generate independent network + posting keys; DMs are
encrypted to posting id, but the pull query carried network id.
Recipient-match never fired. Non-follower DMs never reached.
- Upgraders rotated the network key; DMs addressed to the old key
(now the default posting id) never matched either.
Fix: pull-request builders now include every entry from
list_posting_identities() in query_list. Network id is omitted — it
is never an author and never a wrapped_key.recipient, so adding it
would only leak the boundary without ever matching.
Four call sites fixed: Network::pull_from_peer, and
ConnectionManager::{pull_from_peer, pull_from_peer_unlocked, and the
post-notification-triggered pull path}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c5b80d017
commit
4da6a8dc85
2 changed files with 38 additions and 19 deletions
|
|
@ -1346,18 +1346,25 @@ impl ConnectionManager {
|
||||||
conn: iroh::endpoint::Connection,
|
conn: iroh::endpoint::Connection,
|
||||||
storage: &Arc<StoragePool>,
|
storage: &Arc<StoragePool>,
|
||||||
peer_id: &NodeId,
|
peer_id: &NodeId,
|
||||||
our_node_id: NodeId,
|
_our_node_id: NodeId,
|
||||||
) -> anyhow::Result<PullSyncStats> {
|
) -> anyhow::Result<PullSyncStats> {
|
||||||
let (our_follows, follows_sync) = {
|
let (our_follows, follows_sync, our_personas) = {
|
||||||
let s = storage.get().await;
|
let s = storage.get().await;
|
||||||
(s.list_follows()?, s.get_follows_with_last_sync().unwrap_or_default())
|
(
|
||||||
|
s.list_follows()?,
|
||||||
|
s.get_follows_with_last_sync().unwrap_or_default(),
|
||||||
|
s.list_posting_identities().unwrap_or_default(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merged pull: include our own NodeId in the query so the peer returns
|
// Merged pull: include every posting identity we hold so DMs to any
|
||||||
// posts where we're either a followed author OR a recipient (DM).
|
// of our personas match via wrapped_key.recipient. Network NodeId is
|
||||||
|
// never an author or recipient and would never match.
|
||||||
let mut query_list = our_follows;
|
let mut query_list = our_follows;
|
||||||
if !query_list.contains(&our_node_id) {
|
for pi in &our_personas {
|
||||||
query_list.push(our_node_id);
|
if !query_list.contains(&pi.node_id) {
|
||||||
|
query_list.push(pi.node_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = PullSyncRequestPayload {
|
let request = PullSyncRequestPayload {
|
||||||
|
|
@ -1911,18 +1918,21 @@ impl ConnectionManager {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let (our_follows, follows_sync) = {
|
let (our_follows, follows_sync, our_personas) = {
|
||||||
let storage = self.storage.get().await;
|
let storage = self.storage.get().await;
|
||||||
(
|
(
|
||||||
storage.list_follows()?,
|
storage.list_follows()?,
|
||||||
storage.get_follows_with_last_sync().unwrap_or_default(),
|
storage.get_follows_with_last_sync().unwrap_or_default(),
|
||||||
|
storage.list_posting_identities().unwrap_or_default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merged pull: include our own NodeId in the query list.
|
// Merged pull: include every posting identity so DMs match recipient.
|
||||||
let mut query_list = our_follows;
|
let mut query_list = our_follows;
|
||||||
if !query_list.contains(&self.our_node_id) {
|
for pi in &our_personas {
|
||||||
query_list.push(self.our_node_id);
|
if !query_list.contains(&pi.node_id) {
|
||||||
|
query_list.push(pi.node_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut send, mut recv) = pull_conn.open_bi().await?;
|
let (mut send, mut recv) = pull_conn.open_bi().await?;
|
||||||
|
|
@ -2009,18 +2019,21 @@ impl ConnectionManager {
|
||||||
.get(peer_id)
|
.get(peer_id)
|
||||||
.ok_or_else(|| anyhow::anyhow!("not connected to {}", hex::encode(peer_id)))?;
|
.ok_or_else(|| anyhow::anyhow!("not connected to {}", hex::encode(peer_id)))?;
|
||||||
|
|
||||||
let (our_follows, follows_sync) = {
|
let (our_follows, follows_sync, our_personas) = {
|
||||||
let storage = self.storage.get().await;
|
let storage = self.storage.get().await;
|
||||||
(
|
(
|
||||||
storage.list_follows()?,
|
storage.list_follows()?,
|
||||||
storage.get_follows_with_last_sync().unwrap_or_default(),
|
storage.get_follows_with_last_sync().unwrap_or_default(),
|
||||||
|
storage.list_posting_identities().unwrap_or_default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merged pull: include our own NodeId in the query list.
|
// Merged pull: include every posting identity so DMs match recipient.
|
||||||
let mut query_list = our_follows;
|
let mut query_list = our_follows;
|
||||||
if !query_list.contains(&self.our_node_id) {
|
for pi in &our_personas {
|
||||||
query_list.push(self.our_node_id);
|
if !query_list.contains(&pi.node_id) {
|
||||||
|
query_list.push(pi.node_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = PullSyncRequestPayload {
|
let request = PullSyncRequestPayload {
|
||||||
|
|
|
||||||
|
|
@ -1713,17 +1713,23 @@ impl Network {
|
||||||
/// Pull posts from a peer (persistent if available, ephemeral otherwise).
|
/// Pull posts from a peer (persistent if available, ephemeral otherwise).
|
||||||
pub async fn pull_from_peer(&self, peer_id: &NodeId) -> anyhow::Result<PullStats> {
|
pub async fn pull_from_peer(&self, peer_id: &NodeId) -> anyhow::Result<PullStats> {
|
||||||
let conn = self.get_connection(peer_id).await?;
|
let conn = self.get_connection(peer_id).await?;
|
||||||
let (our_follows, follows_sync) = {
|
let (our_follows, follows_sync, our_personas) = {
|
||||||
let storage = self.storage.get().await;
|
let storage = self.storage.get().await;
|
||||||
(
|
(
|
||||||
storage.list_follows()?,
|
storage.list_follows()?,
|
||||||
storage.get_follows_with_last_sync().unwrap_or_default(),
|
storage.get_follows_with_last_sync().unwrap_or_default(),
|
||||||
|
storage.list_posting_identities().unwrap_or_default(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
// Merged pull: include our own NodeId so DMs addressed to us match.
|
// Merged pull: include every posting identity we hold so DMs addressed
|
||||||
|
// to any of our personas match on recipient. Our network NodeId is
|
||||||
|
// never an author nor a wrapped_key recipient — including it would
|
||||||
|
// never match and would leak the network↔posting boundary.
|
||||||
let mut query_list = our_follows;
|
let mut query_list = our_follows;
|
||||||
if !query_list.contains(&self.our_node_id) {
|
for pi in &our_personas {
|
||||||
query_list.push(self.our_node_id);
|
if !query_list.contains(&pi.node_id) {
|
||||||
|
query_list.push(pi.node_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let (mut send, mut recv) = conn.open_bi().await?;
|
let (mut send, mut recv) = conn.open_bi().await?;
|
||||||
write_typed_message(
|
write_typed_message(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue