# Group (Same DHT) - Without Encryption
###### tags: `Kizuna-architecture`
<style> .ui-infobar, #doc.markdown-body { max-width: 1000px; } </style>
#### comments
- This document is a fork of this more complete [document](https://hackmd.io/4s2hZn0-ToeNt22_J57FpQ)
- This document was created for ease of implementation of group WITHOUT encryption. The other document is going to be updated more constantly.
- When HoloPort allowed cloneable DNA, this will be refactored to fit the cloneable DNA pattern
- Messages have to be push based if we want instant update for all members (but then is taxing for the sender)
- Spam protection for creating meaningless groups so many times
- limiting the number of groups that can be created by an agent within a timeframe through the usage of local timestamp + the signature of the Group entry + counter
- `leave_group` is architected minimally as there will be drastic change when this pattern shifts to cloneable DNA.
- `block_group` is not yet architected and will be with cloned DNA in mind
- right now, there is no check whether the same agent is being added twice in a same group. should we check this at input level as well as in the validation?
## Entry Structure
``` rust
// verify the signature of this entry with Group entry "creator"
#[hdk_entry(id = "group", visibility = "public")]
struct Group {
name: String,
created: Timestamp,
creator: AgentPubKey,
members: Vec<AgentPubKey>,
}
Links: {
agent_pubkey->group|member|
}
// unix_timestamp is denominated in days
Path::from("<group_entry_hash>.<unix_timestamp>")
Links: {
Path(group_entry_hash)->Path(group_entry_hash.unix_timestamp)
Path(group_entry_hash.unix_timestamp)->group_message|(day)'s message|
}
// ENCRYPTED
#[hdk_entry(id = "group_message", visibility = "public")]
struct GroupMessage {
group_hash: EntryHash,
key_ref: SecretBoxKeyRef,
// counter: u8,
// nonce: Vec<u8>, // needs to be unique per message. If we could set the nonce's length, we can do DNA hash + agentPubKey + counter
// set one nonce and counter per agent.
encrypted_payload: XSalsa20Poly1305EncryptedData, // Contains payload, craeted, sender, reply_to
}
// NOT ENCRYPTED
#[hdk_entry(id = "group_message", visibility = "public")]
struct GroupMessage {
// EntryHash of first ver of Group
group_hash: EntryHash,
payload: Payload,
created: Timestamp,
sender: AgentPubKey,
reply_to: Option<EntryHash>
}
```
## Entry Relationship Diagram
- group_v3's links are ommitted but works the same way with group_v2. Only shown to signify that update pattern.
```mermaid
graph TD
subgraph group_zome
subgraph group_entry
group_a==>|update by add/remove_members/update_name|group_a_v2
group_a_v2
group_a==>|update by add/remove_members/update_name|group_a_v3
end
subgraph agents
alice-->|member|group_a
bobby-->|member|group_a
clark-->|member|group_a
diego-->|member|group_a
group_a.->alice
group_a.->bobby
group_a.->clark
group_a_v2.->alice
group_a_v2.->bobby
group_a_v2.->clark
group_a_v2.->diego
end
subgraph group_message_entry
group_message_1.->group_a
group_message_2.->group_a
group_message_3.->group_a
group_a-->|pinned|group_message_1
end
subgraph path
group_entry_hash-->gorup_message
group_entry_hash-->unix_timestamp_2
group_entry_hash-->unix_timestamp_3
gorup_message-->|tag 'text'|group_message_1
gorup_message-->|tag 'text'|group_message_2
gorup_message-->|tag 'file'|group_message_3
end
end
```
## Zome Functions
### `create_group`
```rust
pub struct CreateGroupInput {
name: String,
// cannot be empty and must at least include 2 pubkeys
// creator AgentPubKey is not included here
members: Vec<AgentPubKey>
}
pub struct CreateGroupOutput {
content: Group,
group_id: EntryHash,
group_revision_id: HeaderHash
}
fn create_group(create_group_input: CreateGroupInput)
-> ExternResult<CreateGroupOutput>
```
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice_Lobby_CELL
participant LDHT as Lobby_DHT
participant IM as Initial_Members
AUI-->>ALD: call `create_group`
rect rgba(255, 0, 0, .5)
ALD-->>ALD: call list_blocked() to contacts zome
ALD-->>ALD: check if any members are blocked
alt if even one agent is blocked
ALD-->>AUI: return error("cannot create group with blocked agents")
note right of ALD: dep->contacts zome
end
end
ALD-->>LDHT: commit group entry
LDHT-->>ALD: return header address
loop for all members
ALD-->>LDHT: link from each member to Group entry tagged with the EntryHash of Group as String
end
loop for all added agents
ALD-->>IM: send remote signal with Group EntryHash
note right of ALD: check forum for not getting the entry just committed to DHT
end
ALD-->>ALD: construct CreateGroupOutput
ALD-->>AUI: return CreateGroupOutput
```
### `add_members`
- can only be called by the admin of the group
```rust=
pub struct UpdateMembersIO {
members: Vec<AgentPubKey>,
// assume that original HeaderHash is given here
group_id: EntryHash
group_revision_id: HeaderHash
}
pub fn add_members(add_member_input: UpdateMembersIO)
-> ExternResult<UpdateMembersIO>
```
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice(Admin)_Lobby_CELL
participant LDHT as Lobby_DHT
participant OM as Other Members
AUI-->>ALD: AddMembersInput
ALD-->>ALD: check whether members field is empty
alt if empty
ALD-->>AUI: return Err(members field is empty)
end
note right of ALD: this is also checked in the UI
rect rgba(255, 0, 0, .5)
ALD-->>ALD: call list_blocked() to contacts zome
ALD-->>ALD: check if any invitees are blocked and dont add blocked members to the group
alt if anyone is blocked
ALD-->>AUI: return error("blocked agents cannot be added to the group")
note right of ALD: dep->contacts zome
end
end
ALD-->>LDHT: get_deatils the Group entry with EntryHash given
note right of ALD: get should have the latest() option.
LDHT-->>ALD: return Group EntryDetails
ALD-->>ALD: filter the latest Header (should be element)
note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change.
ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash
note right of ALD: getOption is contents()
LDHT-->>ALD: most recent Group Entry
ALD-->>ALD: add member to members field of Group
ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash
loop for all newly added agents
ALD-->>LDHT: link from each member to original Group entry tag with the EntryHash of Group as String
end
ALD-->>OM: send remote signal with GroupMembers EntryHash to all members
ALD-->>AUI: HeaderHash
```
### `remove_members`
- can only be called by the admin of the group
- this will be drastically changed once the pattern moves to cloneable DNA
```rust=
// same with add_members input
pub struct UpdateMembersIO {
members: Vec<AgentPubKey>,
// assume that original HeaderHash is given here
group_id: EntryHash
group_revision_id: HeaderHash
}
pub fn remove_members(member: UpdateMembersIO)
-> ExternResult<UpdateMembersIO>
```
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice(Admin)_Lobby_CELL
participant LDHT as Lobby_DHT
participant OM as Other Members
AUI-->>ALD: AddMembersInput
ALD-->>ALD: check whether members field is empty
alt if empty
ALD-->>AUI: return Err(members field is empty)
end
note right of ALD: this is also checked in the UI
ALD-->>LDHT: get_deatils the Group entry with EntryHash given
note right of ALD: get should have the latest() option.
LDHT-->>ALD: return Group EntryDetails
ALD-->>ALD: filter the latest Header (should be element)
note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change.
ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash
note right of ALD: getOption is contents()
LDHT-->>ALD: most recent Group Entry
ALD-->>ALD: remove member from members field of Group
ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash
loop for all removed agents
ALD-->>LDHT: remove link from removed members to original Group entry tag "member"
note right of ALD: see if looping through all links here is too much of an overload and change implementation if necessary.
end
ALD-->>OM: send remote signal with GroupMembers EntryHash to all remaining members
```
### `get_all_my_groups`
- TODO: invited checking if the invitor is blocked (warning on ui)
- probably need to fetch the messages with the group as well
```rust
pub struct GroupOutput {
// taken from the latest update of Group entry
latest_name: String,
// taken from the latest update of Group entry
members: Vec<AgentPubKey>,
// This should be the EntryHash of the very first Group Entry
group_id: EntryHash,
// the HeaderHash of the very first Group entry
group_revision_id: HeaderHash,
// If this will only be used to get the secrets then we can probably only return the relevant info for that
// for now, no need to implement this field.
// group_versions: Vec<Group>,
created: Timestamp,
creator: AgentPubKey,
}
pub struct GroupOutputsWrapper(Vec<GroupOutput>)
pub fn get_all_groups() -> ExternResult<Groups>
```
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice(Admin)_Lobby_CELL
participant LDHT as Lobby_DHT
AUI-->>ALD: call get_all_my_groups
ALD-->>LDHT: get_links agent_pubkey->group|member|
LDHT-->>ALD: return Links
note right of ALD: can get group_id
loop for each link
ALD-->>LDHT: get_details on Target(Group EntryHash)
note right of ALD: GetOption latest()
LDHT-->>ALD: Group EntryDetails
note right of ALD: can get group_revision_id , creator, created from here
ALD-->>ALD: check if there is update or not
alt there is an update for group Entry
ALD-->>ALD: check which update header is the latest
ALD-->>LDHT: get entry of the EntryHash in latest update header
note right of ALD: GetOption contents()
note right of ALD: may change once get_details() API returns elements in update field and not just header
LDHT-->>ALD: latest Group entry
note right of ALD: can get members and latest_name
note right of ALD: need to get all versions of GroupMembers if need to know the secrets (id?)
end
ALD-->>ALD: create GroupOutput
end
ALD-->>ALD: collect GroupOutputs into Groups
ALD-->>AUI: return Groups
```
### `update_group_name`
* Admin only
* updating group entry may change in multi-admin pattern.
* store header hashes in ui.
* check whether the new name is the same with old name and return error if so (also checked in ui)
```rust
pub struct UpdateGroupNameIO {
name: String,
group_id: EntryHash,
group_revision_id: HeaderHash
}
pub fn update_group_name(group_input: UpdateGroupNameIO)
-> ExternResult<UpdateGroupNameIO>
```
```mermaid
sequenceDiagram
participant AUI as Alice_UI
participant ALD as Alice(Admin)_Lobby_CELL
participant LDHT as Lobby_DHT
participant OM as Other Members
AUI-->>ALD: UpdateGroupNameInput
ALD-->>LDHT: get_deatils the Group entry with EntryHash given
note right of ALD: get should have the latest() option.
LDHT-->>ALD: return Group EntryDetails
ALD-->>ALD: filter the latest Header (should be element)
note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change.
ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash
note right of ALD: getOption is contents()
LDHT-->>ALD: most recent Group Entry
ALD-->>ALD: check whether the new name and the old name are the same
alt same name
ALD-->>AUI: return Err("cannot update group name with same group name")
end
ALD-->>ALD: update the name field
ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash
LDHT-->>ALD: Header
ALD-->>AUI: Header
```
### `leave_group`
- this function will be architected once moved to cloneable DNA pattern
- this will change current validation of group_members
### `block_group`
- this function will be architected once moved to cloneable DNA pattern
- this will change current validation of group_members
### Validations
#### Entries
* `group`
* create is valid if creator pubkey matches the signature
* create is valid if group name is not more than 50 characters
* create is valid if group name is at least one character long
* craete is valid only if members field do not contain the id of creator
* delete is valid if creator pubkey matches the signature (not implemented)
* update is valid if creator pubkey matches the signature
* update is only valid if the old_entry's header is Create
* update is valid only if members > 2
* update is valid only if members field do not contain the id of creator
<!--- update is only valid if old_group_name != new_group_name | old_members != new_members
* `group_message` --->
* <!--- This has been disabled due to not having get_details available in validation. The cost of ensuring this is much more expensive than updating the Group entry with same content --->
* create is only valid when the author is part of the group members given GroupMembers entry hash field in GroupMessage
#### Links
* `agent_pubkey->group|member|`
* `Path(all_groups)->group`
* `Path(CREATOR_PUB_KEY)->group`
* `Path(unix_timestamp)->group_message`
* create is only valid if the author of the link is part of the group members given group members being the target of the link
## [Redux](https://hackmd.io/Ga6gwkLAR3KWqstFwjJjGg#Redux)