This commit is contained in:
spaced4ndy
2026-06-29 18:14:46 +04:00
parent 4b6b99292b
commit 38ce2427b2
8 changed files with 72 additions and 9 deletions
+2
View File
@@ -144,6 +144,7 @@ library
Simplex.Chat.Store.Postgres.Migrations.M20260531_member_removed_at
Simplex.Chat.Store.Postgres.Migrations.M20260601_relay_sent_web_domain
Simplex.Chat.Store.Postgres.Migrations.M20260602_group_roster
Simplex.Chat.Store.Postgres.Migrations.M20260629_member_roster_served
else
exposed-modules:
Simplex.Chat.Archive
@@ -306,6 +307,7 @@ library
Simplex.Chat.Store.SQLite.Migrations.M20260531_member_removed_at
Simplex.Chat.Store.SQLite.Migrations.M20260601_relay_sent_web_domain
Simplex.Chat.Store.SQLite.Migrations.M20260602_group_roster
Simplex.Chat.Store.SQLite.Migrations.M20260629_member_roster_served
other-modules:
Paths_simplex_chat
hs-source-dirs:
+6 -3
View File
@@ -1186,8 +1186,9 @@ serveRoster :: User -> GroupInfo -> GroupMember -> CM ()
serveRoster user gInfo member =
when (member `supportsVersion` groupRosterVersion) $ do
cxt <- chatStoreCxt
withStore' (\db -> getGroupRoster db gInfo) >>= \case
Just (ownerGMId, brokerTs, sm@SignedMsg {signedBody}, blob_) ->
-- read the served version with the blob, so the recorded served version matches what is served
withStore' (\db -> (,) <$> getGroupRoster db gInfo <*> getGroupRosterVersion db gInfo) >>= \case
(Just (ownerGMId, brokerTs, sm@SignedMsg {signedBody}, blob_), rosterVer_) ->
case J.eitherDecodeStrict' signedBody :: Either String (ChatMessage 'Json) of
Left e -> logError $ "serveRoster: cannot decode saved roster message: " <> tshow e
Right chatMsg@ChatMessage {msgId} ->
@@ -1197,8 +1198,10 @@ serveRoster user gInfo member =
sendFwdMemberMessage member fwd (VMSigned MSSVerified sm chatMsg)
forM_ ((,) <$> msgId <*> blob_) $ \(sid, blob) ->
sendInlineBlobChunks user gInfo [member] sid blob
-- record the served version so a member can't re-trigger a full serve at a version it already got
forM_ rosterVer_ $ \v -> withStore' $ \db -> setMemberRosterServedVersion db member v
Left e -> logError $ "serveRoster: roster owner not found: " <> tshow e
Nothing -> pure ()
(Nothing, _) -> pure ()
-- Used in groups with relays to introduce moderators and above to a new member,
-- and to announce the new member to moderators and above.
+7 -4
View File
@@ -3497,13 +3497,16 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
_ -> pure ()
-- A relay re-serves the full roster to a subscriber that detected a version gap, but only when it holds
-- something newer than the requester's pre-gap version (rate-limiting same-version requests). serveRoster
-- is the join/resume path: signed header + inline blob chunks to the one requester; a no-op without a roster.
-- something newer than BOTH the requester's claimed version and the version it last served this member -
-- the latter bounds reflected amplification (a member can't re-trigger a full serve with reqVer = 0).
-- serveRoster records the served version (on all serve paths) and is a no-op without a roster.
xGrpRosterRequest :: GroupInfo -> GroupMember -> VersionRoster -> CM ()
xGrpRosterRequest gInfo m reqVer =
when (isUserGrpFwdRelay gInfo) $ do
cur <- withStore' $ \db -> getGroupRosterVersion db gInfo
when (maybe True (> reqVer) cur) $ serveRoster user gInfo m
(cur_, served_) <- withStore' $ \db ->
(,) <$> getGroupRosterVersion db gInfo <*> getMemberRosterServedVersion db m
forM_ cur_ $ \cur ->
when (cur > reqVer && maybe True (cur >) served_) $ serveRoster user gInfo m
checkHostRole :: GroupMember -> GroupMemberRole -> CM ()
checkHostRole GroupMember {memberRole, localDisplayName} memRole =
+14
View File
@@ -90,6 +90,8 @@ module Simplex.Chat.Store.Groups
getPublishableGroupRelays,
setGroupRosterVersion,
getGroupRosterVersion,
setMemberRosterServedVersion,
getMemberRosterServedVersion,
getGroupRoster,
RcvRosterTransfer (..),
createRosterTransfer,
@@ -1478,6 +1480,18 @@ getGroupRosterVersion db GroupInfo {groupId} =
fmap join . maybeFirstRow fromOnly $
DB.query db "SELECT roster_version FROM groups WHERE group_id = ?" (Only groupId)
-- The newest roster version a relay re-served to this member on its catch-up request: bounds reflected
-- amplification, so a member can't re-trigger a full serve at a version it was already served.
setMemberRosterServedVersion :: DB.Connection -> GroupMember -> VersionRoster -> IO ()
setMemberRosterServedVersion db GroupMember {groupMemberId} v = do
currentTs <- getCurrentTime
DB.execute db "UPDATE group_members SET roster_served_version = ?, updated_at = ? WHERE group_member_id = ?" (v, currentTs, groupMemberId)
getMemberRosterServedVersion :: DB.Connection -> GroupMember -> IO (Maybe VersionRoster)
getMemberRosterServedVersion db GroupMember {groupMemberId} =
fmap join . maybeFirstRow fromOnly $
DB.query db "SELECT roster_served_version FROM group_members WHERE group_member_id = ?" (Only groupMemberId)
-- The live roster header a relay re-serves to joiners, with the completed blob served alongside it
-- (both are written together at completion, so the blob is present whenever the header is).
getGroupRoster :: DB.Connection -> GroupInfo -> IO (Maybe (GroupMemberId, UTCTime, SignedMsg, Maybe ByteString))
@@ -38,6 +38,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260530_client_services
import Simplex.Chat.Store.Postgres.Migrations.M20260531_member_removed_at
import Simplex.Chat.Store.Postgres.Migrations.M20260601_relay_sent_web_domain
import Simplex.Chat.Store.Postgres.Migrations.M20260602_group_roster
import Simplex.Chat.Store.Postgres.Migrations.M20260629_member_roster_served
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
@@ -75,7 +76,8 @@ schemaMigrations =
("20260530_client_services", m20260530_client_services, Just down_m20260530_client_services),
("20260531_member_removed_at", m20260531_member_removed_at, Just down_m20260531_member_removed_at),
("20260601_relay_sent_web_domain", m20260601_relay_sent_web_domain, Just down_m20260601_relay_sent_web_domain),
("20260602_group_roster", m20260602_group_roster, Just down_m20260602_group_roster)
("20260602_group_roster", m20260602_group_roster, Just down_m20260602_group_roster),
("20260629_member_roster_served", m20260629_member_roster_served, Just down_m20260629_member_roster_served)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,19 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.Postgres.Migrations.M20260629_member_roster_served where
import Data.Text (Text)
import Text.RawString.QQ (r)
m20260629_member_roster_served :: Text
m20260629_member_roster_served =
[r|
ALTER TABLE group_members ADD COLUMN roster_served_version BIGINT;
|]
down_m20260629_member_roster_served :: Text
down_m20260629_member_roster_served =
[r|
ALTER TABLE group_members DROP COLUMN roster_served_version;
|]
+3 -1
View File
@@ -161,6 +161,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260530_client_services
import Simplex.Chat.Store.SQLite.Migrations.M20260531_member_removed_at
import Simplex.Chat.Store.SQLite.Migrations.M20260601_relay_sent_web_domain
import Simplex.Chat.Store.SQLite.Migrations.M20260602_group_roster
import Simplex.Chat.Store.SQLite.Migrations.M20260629_member_roster_served
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -321,7 +322,8 @@ schemaMigrations =
("20260530_client_services", m20260530_client_services, Just down_m20260530_client_services),
("20260531_member_removed_at", m20260531_member_removed_at, Just down_m20260531_member_removed_at),
("20260601_relay_sent_web_domain", m20260601_relay_sent_web_domain, Just down_m20260601_relay_sent_web_domain),
("20260602_group_roster", m20260602_group_roster, Just down_m20260602_group_roster)
("20260602_group_roster", m20260602_group_roster, Just down_m20260602_group_roster),
("20260629_member_roster_served", m20260629_member_roster_served, Just down_m20260629_member_roster_served)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,18 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20260629_member_roster_served where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20260629_member_roster_served :: Query
m20260629_member_roster_served =
[sql|
ALTER TABLE group_members ADD COLUMN roster_served_version INTEGER;
|]
down_m20260629_member_roster_served :: Query
down_m20260629_member_roster_served =
[sql|
ALTER TABLE group_members DROP COLUMN roster_served_version;
|]