Repository navigation
perf(mobile): download the identity archive list only when it changed - #9445
Conversation
|
@codex review |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
Each reconnect refetched the relay's 13535 archive list (about 228 KB on Builderlab). Remember the last verified list per community and authority and ask only for a newer one. Signed-off-by: Tom Brow <[email protected]>
…ication Signed-off-by: Tom Brow <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
1f3822e to
104c915
Compare
kazemek21
left a comment
There was a problem hiding this comment.
Kyle reviewed this PR and the Nexus review-tracker notes and approved with no comments.
Posted by Codex (gpt-6.1-sol) on behalf of kylez.
* origin/main: (24 commits) docs(readme): point desktop readers at block/buzz-app and describe where each client lives (#9493) test(cli,db): freeze the assertion test clock and give pool release tests a test-lane acquire budget (#9442) test(desktop): unmount every TerminalSubstrate tree before the window closes (#9440) fix(mobile): drop the iOS share extension from the build until it's signed (#9489) chore(release): release Buzz Desktop version 0.5.30 (#9491) perf(mobile): parse each event's thread reference once (#9460) perf(mobile): load only the directory when search opens (#9466) perf(mobile): stop rebuilding Home's channel list when a route covers it (#9459) perf(mobile): ask typing subscriptions only for the typing window (#9454) perf(mobile): skip avatar URL parsing for non-HTTP URLs (#9448) perf(mobile): download the identity archive list only when it changed (#9445) perf(mobile): don't treat a Control Center pull as a resume (#9443) fix(mobile): keep a superseded roster read out of the offline snapshot (#9452) perf(mobile): read channel bot roles from the member list (#9451) perf(mobile): read Activity once per reconnect (#9455) fix(mobile): offer role controls for agents (#9475) perf(mobile): stop rebuilding the native nav bar on every scrolled frame (#9458) perf(mobile): keep verified-name batches open as people are added (#9450) perf(mobile): read only changed custom emoji sets on reconnect (#9446) perf(mobile): keep latest-message times on a directory load (#9457) ... Signed-off-by: Will Pfleger <[email protected]> # Conflicts: # crates/buzz-cli/src/desktop_provider.rs
…eaction-recipients * origin/main: (111 commits) perf(acp): avoid repeated observer batch serialization (#9483) docs(readme): point desktop readers at block/buzz-app and describe where each client lives (#9493) test(cli,db): freeze the assertion test clock and give pool release tests a test-lane acquire budget (#9442) test(desktop): unmount every TerminalSubstrate tree before the window closes (#9440) fix(mobile): drop the iOS share extension from the build until it's signed (#9489) chore(release): release Buzz Desktop version 0.5.30 (#9491) perf(mobile): parse each event's thread reference once (#9460) perf(mobile): load only the directory when search opens (#9466) perf(mobile): stop rebuilding Home's channel list when a route covers it (#9459) perf(mobile): ask typing subscriptions only for the typing window (#9454) perf(mobile): skip avatar URL parsing for non-HTTP URLs (#9448) perf(mobile): download the identity archive list only when it changed (#9445) perf(mobile): don't treat a Control Center pull as a resume (#9443) fix(mobile): keep a superseded roster read out of the offline snapshot (#9452) perf(mobile): read channel bot roles from the member list (#9451) perf(mobile): read Activity once per reconnect (#9455) fix(mobile): offer role controls for agents (#9475) perf(mobile): stop rebuilding the native nav bar on every scrolled frame (#9458) perf(mobile): keep verified-name batches open as people are added (#9450) perf(mobile): read only changed custom emoji sets on reconnect (#9446) ... Signed-off-by: Matthew Boston <[email protected]>
…s one (#9476) ### Why When an admin archives or unarchives an identity, the relay publishes a new archived identities list (kind 13535). This list replaces the previous one. It contains all the identities that are archived in the community. Clients use it to hide archived agents from pickers and search. The list can be large (about 228 KB on Block's Builderlab), and it seldom changes. Thus the mobile app wants to download it only when it changed. To do this, the app keeps the last list it received, and asks the relay only for a list that is newer than that one (`since` its `created_at` plus one, in [#9445 archive list cache](#9445)). This works only if each new list has a `created_at` that is later than the previous list's. Today the relay does not always do this: - The relay calculates the new list's `created_at` as "the current list's `created_at` plus one second". But it reads the current list before it gets the lock that controls replacement. - Thus, when two archive requests occur at the same time, two publishers can read the same current list. Then both sign a list with the same `created_at`. The store keeps the one with the lower event id. - A client that already has the other list does not see the replacement, because it asks only for lists with a later `created_at`. It continues to hide or show the wrong agents until the next archive change or an app restart. - With many concurrent publishers it is worse: a publisher can lose that tie again and again, and then stop with `failed to publish kind:13535 after 8 concurrent replacements`. Then no list contains the latest archive change. ### What changes - New `Db::replace_addressable_event_after_head`. It gets the replacement lock first. Then it reads the current list. Then it signs and stores the new list. Thus only one publisher at a time calculates the next `created_at`, and each list is stamped strictly later than the previous one. - `publish_nipia_archival_list` uses this method. The check that publishes again when the archive state changed during a publish does not change. - NIP-IA now makes this a rule: a relay MUST publish each `kind:13535` with a `created_at` strictly greater than its previous one. Clients MAY then ask only for lists `since` their list's `created_at` plus one. ### Validation New relay test: 16 concurrent publishers all complete, and each stored list (superseded rows included) has a strictly later `created_at`. On main the same test fails, because a publisher uses all its retries. The test needs Postgres and Redis, so it is in `postgres_tests` and runs in the PostgreSQL Tests lane; a missing database or Redis makes it fail, not pass. The existing archive tests pass against a local Postgres, including the stale-publisher test. `just test` passes all 160 steps except one NIP-FI session deadline test (`deadline_refused_permit_still_queues_one_denial_when_cancel_wins`), which this change does not touch and which passes 5 of 5 reruns alone. Clippy passes. ### Regressions None expected. - A publisher now holds the replacement lock a little longer: for one read and one signature. Only archive-list writes for the same community wait on this lock, and archive changes are rare. - The new `Db` method is an addition. `replace_addressable_event` and its callers do not change. - Until this is deployed, relays keep the old behavior. [#9445 archive list cache](#9445) accepts that same-second case on its own. **Accepted limitation: a database restore.** The rule holds only while the relay keeps its newest list. If an operator restores the relay from a backup, the relay calculates the next `created_at` from the restored list (`max(restored + 1, now)`). If earlier fast publishes went ahead of the clock, that value can be at or below a `created_at` that a client already has. That client then stays stale until a list passes its cached `created_at`, or the app restarts. This change adds no repair for a restore. --------- Signed-off-by: Tom Brow <[email protected]> Co-authored-by: Tom Brow <[email protected]>
Every reconnect refetches the community's identity archive list (kind 13535) so the agent pickers stay current. On Block's Builderlab that list is about 228 KB, and it is downloaded again on every resume even though it rarely changes.
The provider now remembers the last verified list per community and relay authority, and asks the relay only for a newer one (
sincethe known list'screated_atplus one). When nothing newer exists it reuses the known list, as the same set instance so watchers (mention candidates, search, the Agents page, channel details, the members sheet) don't rebuild. A newer list replaces it after the same verification as before. A list that fails any check, including its signature, yields no archived identities and drops the remembered one.Validation
Provider tests: a refresh with an unchanged list sends
since, keeps the known set and leaves the hidden set's listeners untouched; a newer list replaces it and becomes the new baseline; every invalid list (wrong signer, missing-marker, bad signature) starting from a warm cache drops the remembered list, so a following empty answer hides nobody. Each new step fails with its fix reverted. The existing verification, community-switch and deadline tests pass with the relay fake now honoringsince. Full mobile test suite (4803 passed) andflutter analyzepass.On an iPhone 12 mini signed in to Block's Builderlab (instrumented profile build), a resume after 60 s in the background downloaded the 228 KB list each time before this change.
Regressions
Accepted tradeoff: a same-second list can be missed. The cursor assumes each list the relay publishes is stamped strictly later than the previous one. The relay nearly always does this (
publish_nipia_archival_liststamps past the current head and republishes on drift), but two lists published within one second can share acreated_at, and NIP-IA then keeps the one with the lower id. If the app already holds the other one, it misses the replacement until a later list arrives (the next archive or unarchive) or the app restarts. Meanwhile a just-archived or just-unarchived agent can show wrongly in pickers and search, never in history. We accept this for the bandwidth and resume-time win; a relay follow-up that stamps each list under the replacement lock and makes the rule a NIP-IA MUST removes the case.The same bound applies if a relay's database is restored to an older list: the app keeps its newer one until the relay publishes a list newer than it, or the app restarts.
A failed or timed-out fetch still hides nobody, as before.