mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
docs: groups integrity DAGs rfc (#3258)
This commit is contained in:
parent
c1a0486c1d
commit
68873464d7
1 changed files with 229 additions and 0 deletions
229
docs/rfcs/2023-10-20-group-integrity.md
Normal file
229
docs/rfcs/2023-10-20-group-integrity.md
Normal file
|
@ -0,0 +1,229 @@
|
|||
# Group integrity
|
||||
|
||||
3 level of DAGs:
|
||||
|
||||
Owner
|
||||
- group profile and permissions, admin invites and removals
|
||||
- in case of gap vote before applying event
|
||||
|
||||
Admin
|
||||
- member invites and removals
|
||||
- prohibit to add and remove admins
|
||||
- in case of gap most destructive wins
|
||||
- link to owner dag
|
||||
|
||||
Messages
|
||||
- in case of gap show history according to local graph, correct when owner or admin dag changes
|
||||
- link to both admin and owner dags
|
||||
|
||||
```haskell
|
||||
-- protocol
|
||||
data MsgParent = MsgParent
|
||||
{ memberId :: MemberId,
|
||||
memberName :: String, -- recipient can use to display message if they don't have member introduced;
|
||||
-- optional?
|
||||
sharedMsgId :: SharedMsgId,
|
||||
msgHash :: ByteString,
|
||||
msgBody :: String? -- recipient can use to display message in case parent wasn't yet received;
|
||||
-- sender can pack as many parents as fits into block
|
||||
stored :: Bool -- whether sender has message stored, and it can be requested
|
||||
}
|
||||
|
||||
data MsgIds = MsgIds -- include into chat event
|
||||
{ sharedMsgId :: SharedMsgId,
|
||||
ownerDAGMsgId :: SharedMsgId, -- list of parents?
|
||||
adminDAGMsgId :: SharedMsgId,
|
||||
parents :: [MsgParent]
|
||||
}
|
||||
|
||||
-- model
|
||||
data OwnerDAGEventParent
|
||||
= ODEPKnown {eventId :: ?} -- DB id? sharedMsgId?
|
||||
| ODEPUnknown {eventId :: ?}
|
||||
|
||||
data OwnerDAGEvent = DAGEvent
|
||||
{ eventId :: ?,
|
||||
parents :: [OwnerDAGEventParent]
|
||||
}
|
||||
|
||||
data AdminDAGEventParent
|
||||
= ADEPKnown {eventId :: ?}
|
||||
| ADEPUnknown {eventId :: ?}
|
||||
|
||||
data AdminDAGEvent = DAGEvent
|
||||
{ eventId :: ?,
|
||||
ownerDAGEventId :: ?, -- [OwnerDAGEventParent] - parentIds? ?
|
||||
parents :: [AdminDAGEventParent]
|
||||
}
|
||||
|
||||
data MessagesDAGEventParent
|
||||
= MDEPKnown {eventId :: ?}
|
||||
| MDEPUnknown {eventId :: ?}
|
||||
|
||||
data MessagesDAGEvent = DAGEvent
|
||||
{ eventId :: ?,
|
||||
ownerDAGEventId :: ?, -- [OwnerDAGEventParent] - parentIds? ?
|
||||
adminDAGEventId :: ?, -- [AdminDAGEventParent] - parentIds? ?
|
||||
parents :: [MessagesDAGEventParent]
|
||||
}
|
||||
```
|
||||
|
||||
How to restore from destructive messages?
|
||||
Even if all message parents are known, destructive logic of message should be applied after other members refer it.
|
||||
|
||||
How to workaround members maliciously referring non-existent parents?
|
||||
For example, this can lead to an owner preventing group updates.
|
||||
|
||||
```
|
||||
-- should dag be maintained in memory? older events to be removed
|
||||
-- read on event?
|
||||
-- how long into past to get dag?
|
||||
|
||||
ClassifiedEvent = OwnerEvent | AdminEvent | MsgEvent
|
||||
|
||||
def processEvent(e: Event) =
|
||||
classifiedEvent <- classifyEvent(e)
|
||||
case classifiedEvent of
|
||||
OwnerEvent oe -> processOwnerEvent(oe)
|
||||
AdminEvent ae -> processAdminEvent(ae)
|
||||
MsgEvent me -> processMsgEvent(me)
|
||||
|
||||
def classifyEvent(e: Event) -> ClassifiedEvent? =
|
||||
case e of
|
||||
XMsgNew -> MsgEvent
|
||||
XMsgFileDescr -> Nothing -- different per member
|
||||
XMsgFileCancel -> MsgEvent
|
||||
XMsgUpdate -> MsgEvent
|
||||
XMsgDel -> MsgEvent
|
||||
XMsgReact -> MsgEvent
|
||||
XFile -> MsgEvent
|
||||
XFileCancel -> MsgEvent
|
||||
XFileAcptInv -> Nothing -- different per member
|
||||
XGrpMemNew -> OwnerEvent -- sent by owner, new member is admin or owner
|
||||
or AdminEvent -- sent by admin (or by owner and new member role is less than admin?)
|
||||
-- problem: if member role changes, members can add event to different dags
|
||||
-- what should define member role?
|
||||
XGrpMemIntro -> Nothing -- received only by invitee
|
||||
XGrpMemInv -> Nothing -- received only by host
|
||||
XGrpMemFwd -> Nothing -- different per member; not received by invitee
|
||||
XGrpMemRole -> OwnerEvent -- sent by owner about owner or admin
|
||||
or AdminEvent -- sent by admin (or by owner about member with role less than admin?)
|
||||
XGrpMemDel -> OwnerEvent -- sent by owner about owner or admin
|
||||
or AdminEvent -- sent by admin (or by owner about member with role less than admin?)
|
||||
XGrpLeave -> MsgEvent
|
||||
XGrpDel -> OwnerEvent
|
||||
XGrpInfo -> OwnerEvent
|
||||
XGrpDirectInv -> Nothing -- received by single member
|
||||
XInfoProbe -> Nothing -- per member
|
||||
XInfoProbeCheck -> Nothing -- per member
|
||||
XInfoProbeOk -> Nothing -- per member
|
||||
BFileChunk -> Nothing -- could be MsgEvent?
|
||||
_ -> Nothing -- not supported in groups
|
||||
|
||||
-- # owner events
|
||||
|
||||
def processOwnerEvent(oe: OwnerEvent) =
|
||||
process every owner event after owners reach consensus
|
||||
|
||||
// def processOwnerEvent(oe: OwnerEvent) =
|
||||
// addOwnerDagEvent(oe)
|
||||
// applyOwnerDagEvent(oe)
|
||||
//
|
||||
// def addOwnerDagEvent(oe: OwnerEvent) =
|
||||
// if (any parent of oe not in dag):
|
||||
// buffer until all parents are in ownerDag
|
||||
// else
|
||||
// add oe to ownerDag
|
||||
//
|
||||
// def applyOwnerDagEvent(oe: OwnerEvent) =
|
||||
// case oe of
|
||||
// -- process XGrpMemNew, XGrpMemRole, XGrpMemDel same as for admin dag (see below), or should vote for all events?
|
||||
// XGrpMemNew -> ...
|
||||
// XGrpMemRole -> ...
|
||||
// XGrpMemDel -> ...
|
||||
// -- how to vote - to depend on action (group - manual, update - automatic?);
|
||||
// -- wait for voting always, or if event has unknown parents? (gaps in dag)
|
||||
// -- how to treat delayed integrity violation - owner sending message to select members
|
||||
// XGrpDel ->
|
||||
// -- create "pending group deletion", wait for confirmation from majority of owners?
|
||||
// -- new protocol requiring user action from other owners?
|
||||
// XGrpInfo ->
|
||||
// -- create "unconfirmed group profile update", remember prev group profile
|
||||
// -- remove from "unconfirmed group profile update" when this event is in dag and not a leaf?
|
||||
// -- if another group profile update event is received, revert "unconfirmed" event, don't apply new
|
||||
// -- so if more than one update is received while dag is not merged to single vertice, all updates are not applied
|
||||
// -- - this would likely lock out owners from any future updates
|
||||
// -- - merge to new starting point after some time passes?
|
||||
// -- - mark parents that are never received and so always block graph merging as special type?
|
||||
|
||||
-- # admin events
|
||||
|
||||
def processAdminEvent(ae: AdminEvent) =
|
||||
lookup in owner dag - does member still have permission?
|
||||
addAdminDagEvent(ae)
|
||||
applyAdminDagEvent(ae)
|
||||
|
||||
def addAdminDagEvent(ae: AdminEvent) =
|
||||
if (any parent of ae not in dag):
|
||||
buffer until all parents are in adminDag
|
||||
else
|
||||
add ae to adminDag
|
||||
|
||||
def applyAdminDagEvent(ae: AdminEvent) =
|
||||
case ae of
|
||||
XGrpMemNew ->
|
||||
-- handles case where messages from 2 admins about member addition and deletion arrive out of order
|
||||
if member is not in "unconfirmed member deletions":
|
||||
add member
|
||||
XGrpMemRole ->
|
||||
add role change to "unconfirmed role change"
|
||||
-- remove from "unconfirmed role change" when this event is in dag and not a leaf?
|
||||
if another role change already in "unconfirmed role change":
|
||||
if new role is less than role in "unconfirmed role change":
|
||||
change role -- role change applies in direction of lower role
|
||||
XGrpMemDel ->
|
||||
add member to "unconfirmed member deletions"
|
||||
-- remove from "unconfirmed member deletions" when this event is in dag and not a leaf?
|
||||
if member found by memberId:
|
||||
delete member
|
||||
|
||||
-- ^ problem: if later admin event turns out to fail integrity check, how to revert it?
|
||||
-- member deletion: don't apply until in graph and not a leaf
|
||||
-- role change: remember previous role and revert
|
||||
-- member addition: delete member
|
||||
|
||||
-- # message events
|
||||
|
||||
def processMsgEvent(me: MsgEvent) =
|
||||
lookup points in owner and admin dag?
|
||||
- does member have permission to send event? (role changed/removed)
|
||||
addMsgDagEvent(me)
|
||||
applyMsgEvent(me)
|
||||
|
||||
def addMsgDagEvent(me: MsgEvent) =
|
||||
for me.parents not in msgDag:
|
||||
add MDEPUnknown parent to msgDag
|
||||
add me to msgDag
|
||||
|
||||
def applyMsgEvent(me: MsgEvent) =
|
||||
case me of
|
||||
XMsgNew -> message to view
|
||||
-- start process waiting for missing parents; if parents are not received:
|
||||
-- can be shown as integrity violation if parents are not received
|
||||
-- can be shown as integrity violation if other members don't refer it?
|
||||
XMsgFileCancel -> cancel file immediately
|
||||
-- wait for missing parents / referrals similarly to XMsgNew
|
||||
-- restart file reception on integrity violation?
|
||||
XMsgUpdate -> update to view -- same as XMsgNew
|
||||
XMsgDel -> mark deleted, don't apply full delete until parents/referrals are received?
|
||||
XMsgReact -> to view -- same as XMsgNew
|
||||
XFile -> -- deprecate?
|
||||
XFileCancel -> cancel -- same as XMsgFileCancel
|
||||
XGrpLeave -> mark member as left, don't delete member connection immediately
|
||||
-- member may try to maliciously remove connections selectively
|
||||
-- wait for integrity check
|
||||
```
|
||||
|
||||
# Admin blockchain
|
||||
|
||||
Suppose admin DAG is replaced with blockchain, with a conflict resolution protocol to provide consistency of membership changes. Take Simplex (not to confuse with SimpleX chat) protocol (https://simplex.blog/). To reach BFT consensus and make progress, 2n/3 votes on block proposals are required, and it's assumed `f < n/3` where f is number of malicious actors. In a highly asynchronous setting of decentralized groups operated by mobile devices, progress seems unlikely or very slow. Should "admin participation" be hosted?
|
Loading…
Add table
Add a link
Reference in a new issue