52 lines
1.8 KiB
Markdown
52 lines
1.8 KiB
Markdown
# UNDF: UNDF-2026-000000831
|
||
# Defect: simplex-chat-0004 — introduceToRemaining `notElem` O(N×M) member dedup
|
||
|
||
**Project:** simplex-chat
|
||
**File:** `src/Simplex/Chat/Library/Internal.hs`
|
||
**Function:** `introduceToRemaining` / `introduceMemP`
|
||
**Complexity:** O(|members| × |introducedGMIds|) per member join event
|
||
**Severity:** MEDIUM
|
||
**Fix:** Convert `introducedGMIds :: [GroupMemberId]` to `Set GroupMemberId` before filter
|
||
|
||
## Pattern
|
||
|
||
```haskell
|
||
-- DEFECT: O(|members| × |introducedGMIds|)
|
||
introduceToRemaining vr user gInfo m = do
|
||
(members, introducedGMIds) <-
|
||
withStore' $ \db -> (,) <$> getGroupMembers db vr user gInfo
|
||
<*> getIntroducedGroupMemberIds db m
|
||
let recipients = filter (introduceMemP introducedGMIds) members
|
||
...
|
||
where
|
||
introduceMemP introducedGMIds mem =
|
||
memberCurrent mem
|
||
&& groupMemberId' mem `notElem` introducedGMIds -- O(|introducedGMIds|) scan
|
||
&& groupMemberId' mem /= groupMemberId' m
|
||
```
|
||
|
||
## Fix
|
||
|
||
```haskell
|
||
-- FIX: O(|members| × log|introducedGMIds|)
|
||
import qualified Data.Set as S
|
||
|
||
introduceToRemaining vr user gInfo m = do
|
||
(members, introducedGMIds) <-
|
||
withStore' $ \db -> (,) <$> getGroupMembers db vr user gInfo
|
||
<*> getIntroducedGroupMemberIds db m
|
||
let introducedSet = S.fromList introducedGMIds
|
||
recipients = filter (introduceMemP introducedSet) members
|
||
...
|
||
where
|
||
introduceMemP introducedSet mem =
|
||
memberCurrent mem
|
||
&& groupMemberId' mem `S.notMember` introducedSet -- O(log N)
|
||
&& groupMemberId' mem /= groupMemberId' m
|
||
```
|
||
|
||
## Impact
|
||
|
||
In a group with N members where M introductions have been made:
|
||
- Per new member join: O(N×M) → O(N log M)
|
||
- At N=1000 members, M=500 introductions: 500,000 → 9,000 comparisons (**55× overhead**)
|