From bf224b6460330ce4fcfb22363807b4d631cd0a73 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:36:51 +0400 Subject: [PATCH] wip --- src/Simplex/Chat/Library/Commands.hs | 6 ++--- src/Simplex/Chat/Library/Internal.hs | 37 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 535a566ae8..1c49b0f05a 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2769,9 +2769,8 @@ processChatCommand cxt nm = \case throwCmdError $ "the number of members, moderators and admins would exceed the limit of " <> show maxGroupRosterSize (errs1, changed1) <- changeRoleInvitedMems user gInfo invitedMems let doBumpRoster = useRelays' gInfo && memberRole' (membership gInfo) == GROwner && (isRosterRole newRole || anyPrivilegedTarget) - rosterVer <- if doBumpRoster then Just <$> reserveRosterVersion gInfo else pure Nothing -- roster (with the change projected in) before the delta, so a relay stores the blob at this version before forwarding the delta - forM_ rosterVer $ \v -> broadcastRoster user gInfo v (RDRoleChanged newRole currentMems) `catchAllErrors` eToView + rosterVer <- if doBumpRoster then Just <$> broadcastRoster user gInfo (RDRoleChanged newRole currentMems) else pure Nothing (errs2, changed2, acis, msgSigned) <- changeRoleCurrentMems user g rosterVer currentMems unless (null acis) $ toView $ CEvtNewChatItems user acis let errs = errs1 <> errs2 @@ -2898,9 +2897,8 @@ processChatCommand cxt nm = \case (errs1, deleted1) <- deleteInvitedMems user invitedMems let recipients = filter memberCurrent members let doBumpRoster = useRelays' gInfo && memberRole' (membership gInfo) == GROwner && anyPrivilegedRemoved - rosterVer <- if doBumpRoster then Just <$> reserveRosterVersion gInfo else pure Nothing -- roster (excluding the removed members) before the delta, so a relay stores the blob at this version before forwarding the delta - forM_ rosterVer $ \v -> broadcastRoster user gInfo v (RDRemoved currentMems) `catchAllErrors` eToView + rosterVer <- if doBumpRoster then Just <$> broadcastRoster user gInfo (RDRemoved currentMems) else pure Nothing (errs2, deleted2, acis2, signed2) <- deleteMemsSend user gInfo Nothing rosterVer recipients currentMems (errs3, deleted3, acis3, signed3) <- foldM (\acc m -> deletePendingMember acc user gInfo [m] m) ([], [], [], False) pendingApprvMems diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 60862a3e3e..064bc36150 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -2270,17 +2270,6 @@ sendGroupMessage' user gInfo members chatMsgEvent = ((Right msg) :| [], _) -> pure msg _ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message" --- TODO [relays] improvement: publish roster_version in link data so the owner can recover the latest version --- TODO after restoring from a stale backup (relays accept only strictly-greater versions) --- Persist the next roster version before sending the events that carry it (so a recipient never advances --- past a version the owner hasn't recorded). The matching blob is broadcast separately, by broadcastRoster, --- with the change projected onto the served roster - so it excludes demoted/removed members. -reserveRosterVersion :: GroupInfo -> CM VersionRoster -reserveRosterVersion gInfo = do - let rosterVer = maybe (VersionRoster 0) (\(VersionRoster n) -> VersionRoster (n + 1)) (rosterVersion gInfo) - withStore' $ \db -> setGroupRosterVersion db gInfo rosterVer - pure rosterVer - -- The roster change being broadcast, projected onto the current roster members in broadcastRoster. This lets the -- roster blob be built (and sent) before the change is applied to the owner's own member records, so the owner -- never demotes/removes a member locally before the change has been propagated to relays. @@ -2295,13 +2284,25 @@ applyRosterDelta delta current = case delta of where without ms = let ids = S.fromList (map groupMemberId' ms) in filter ((`S.notMember` ids) . groupMemberId') current -broadcastRoster :: User -> GroupInfo -> VersionRoster -> RosterDelta -> CM () -broadcastRoster user gInfo rosterVer delta = do - cxt <- chatStoreCxt - (relays, rosterMems) <- withStore' $ \db -> - (,) <$> getGroupRelayMembers db cxt user gInfo <*> getGroupRosterMembers db cxt user gInfo - forM_ (L.nonEmpty relays) $ \relays' -> - sendRoster user gInfo (L.toList relays') rosterVer (buildGroupRoster $ applyRosterDelta delta rosterMems) +-- TODO [relays] improvement: publish roster_version in link data so the owner can recover the latest version +-- TODO after restoring from a stale backup (relays accept only strictly-greater versions) +-- Reserve and persist the next roster version (committed before the events that carry it, so a recipient never +-- advances past a version the owner hasn't recorded), then broadcast the matching blob with the change projected +-- onto the served roster (so it excludes demoted/removed members). Returns the reserved version for the delta +-- that follows. The blob send is best-effort - a failed send heals on the next change or on resume. +broadcastRoster :: User -> GroupInfo -> RosterDelta -> CM VersionRoster +broadcastRoster user gInfo delta = do + let rosterVer = maybe (VersionRoster 0) (\(VersionRoster n) -> VersionRoster (n + 1)) (rosterVersion gInfo) + withStore' $ \db -> setGroupRosterVersion db gInfo rosterVer + sendRosterBlob rosterVer `catchAllErrors` eToView + pure rosterVer + where + sendRosterBlob rosterVer = do + cxt <- chatStoreCxt + (relays, rosterMems) <- withStore' $ \db -> + (,) <$> getGroupRelayMembers db cxt user gInfo <*> getGroupRosterMembers db cxt user gInfo + forM_ (L.nonEmpty relays) $ \relays' -> + sendRoster user gInfo (L.toList relays') rosterVer (buildGroupRoster $ applyRosterDelta delta rosterMems) -- Send the current roster (no version bump) to a newly added relay so it can serve joiners. sendGroupRosterToRelay :: User -> GroupInfo -> GroupMember -> CM ()