From be8d51b6423ce5cb8be71add73c0cea7e1fef23e Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:03:27 +0000 Subject: [PATCH] core: re-use link owner members when connecting to a group again Connecting to a prepared group creates the link owner members from the link data inside withFastStore, before the relay loop, and throws when every relay connection fails. That error is an agent BROKER error, which the clients treat as retryable: they offer Retry and re-send the same command, which inserted the same owner members a second time - UNIQUE constraint failed: group_members.group_id, group_members.member_id so joining a channel over a flaky network and retrying failed on a raw SQLite error. Owner member creation was the only step in the command that was not already idempotent: connectToRelay re-uses the relay member record via getCreateRelayForMember, and a join that failed leaves the connection at ConnPrepared for joinPreparedConn' to resume, because joinContact advances the status only after the agent call returns. --- src/Simplex/Chat/Library/Commands.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 2226f80fab..c55f2934e2 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2272,7 +2272,7 @@ processChatCommand cxt nm = \case Just GroupOwnerContact {contactId, memberId} | memberId == MemberId ownerId -> Just contactId _ -> Nothing - void $ createLinkOwnerMember db cxt user gInfo' ctId_ (MemberId ownerId) ownerKey + void $ getCreateLinkOwnerMember db cxt user gInfo' ctId_ (MemberId ownerId) ownerKey pure gInfo' rs <- withGroupLock "connectPreparedGroup" groupId $ mapConcurrently (connectToRelay user gInfo') relays diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d607541bda..a45a814436 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -193,7 +193,7 @@ module Simplex.Chat.Store.Groups getXGrpLinkMemReceived, setXGrpLinkMemReceived, createNewUnknownGroupMember, - createLinkOwnerMember, + getCreateLinkOwnerMember, updatePreparedChannelMember, updateUnknownMemberAnnounced, updateRosterMemberAnnounced, @@ -3505,6 +3505,15 @@ createNewUnknownGroupMember db cxt user@User {userId, userContactId} GroupInfo { where VersionRange minV maxV = vr cxt +-- Re-use owner member record on retry: owners are created before the relay loop, +-- which is what fails when the network does (see getCreateRelayForMember) +getCreateLinkOwnerMember :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember +getCreateLinkOwnerMember db cxt user gInfo contactId_ memberId ownerKey = + liftIO (runExceptT $ getGroupMemberByMemberId db cxt user gInfo memberId) >>= \case + Right m -> pure m + Left (SEGroupMemberNotFoundByMemberId _) -> createLinkOwnerMember db cxt user gInfo contactId_ memberId ownerKey + Left e -> throwError e + createLinkOwnerMember :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember createLinkOwnerMember db cxt user@User {userId, userContactId} GroupInfo {groupId} contactId_ memberId ownerKey = do currentTs <- liftIO getCurrentTime