From 5938aeb9c31dbc44d7b6541e0974d7f7749d21b9 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 12:21:10 -0500 Subject: [PATCH 01/36] migration --- Cargo.lock | 1 - .../migrations/2024-12-18-170645_add_dm_id/down.sql | 4 ++++ .../migrations/2024-12-18-170645_add_dm_id/up.sql | 11 +++++++++++ xmtp_mls/src/storage/encrypted_store/schema.rs | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql create mode 100644 xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql diff --git a/Cargo.lock b/Cargo.lock index 5627ff5d8..cf5f4a245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7518,7 +7518,6 @@ dependencies = [ "tracing-oslog", "tracing-subscriber", "uniffi", - "url", "uuid 1.11.0", "xmtp_api_grpc", "xmtp_common", diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql new file mode 100644 index 000000000..7069dc0db --- /dev/null +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql @@ -0,0 +1,4 @@ +ALTER TABLE groups DROP COLUMN dm_id; +ALTER TABLE groups DROP COLUMN last_message_ns; + +DROP TRIGGER IF EXISTS msg_inserted; diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql new file mode 100644 index 000000000..6593a3ff6 --- /dev/null +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -0,0 +1,11 @@ +ALTER TABLE groups ADD COLUMN dm_id TEXT; +ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; + +-- Create a trigger to auto-update group table on insert; +CREATE TRIGGER msg_iserted +AFTER INSERT ON group_messages +BEGIN + UPDATE groups + SET last_message_ns = (strftime('%s', 'now') * 1000000000) + (strftime('%f', 'now') * 1000000) + WHERE id = NEW.group_id; +END; diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index c82818161..38b077ff4 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -55,6 +55,8 @@ diesel::table! { dm_inbox_id -> Nullable, rotated_at_ns -> BigInt, conversation_type -> Integer, + dm_id -> Nullable, + last_message_ns -> Nullable, } } From b250d0209f40536a88d77c844f67944d237dc34d Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 12:44:54 -0500 Subject: [PATCH 02/36] Fill the column --- .../2024-12-18-170645_add_dm_id/down.sql | 1 + .../2024-12-18-170645_add_dm_id/up.sql | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql index 7069dc0db..22bd9bbd8 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql @@ -1,4 +1,5 @@ ALTER TABLE groups DROP COLUMN dm_id; ALTER TABLE groups DROP COLUMN last_message_ns; +ALTER TABLE groups ADD COLUMN dm_inbox_id TEXT; DROP TRIGGER IF EXISTS msg_inserted; diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index 6593a3ff6..4e7fed04c 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -1,7 +1,21 @@ ALTER TABLE groups ADD COLUMN dm_id TEXT; ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; --- Create a trigger to auto-update group table on insert; +-- Fill the dm_id column +UPDATE groups +SET dm_id = 'dm:' || + LOWER( + CASE + WHEN LOWER((SELECT inbox_id FROM identity)) < LOWER(dm_inbox_id) + THEN (SELECT inbox_id FROM identity) || ':' || dm_inbox_id + ELSE dm_inbox_id || ':' || (SELECT inbox_id FROM identity) + END + ) +WHERE dm_inbox_id IS NOT NULL; + +ALTER TABLE groups REMOVE COLUMN dm_inbox_id; + +-- Create a trigger to auto-update group table on insert CREATE TRIGGER msg_iserted AFTER INSERT ON group_messages BEGIN From 334c06657fd41732479e09ef6a53fcd7c55fca4d Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 13:27:09 -0500 Subject: [PATCH 03/36] wip --- xmtp_mls/src/storage/encrypted_store/group.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 010eab713..0fb80096c 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -37,7 +37,9 @@ pub struct StoredGroup { /// The sequence id of the welcome message pub welcome_id: Option, /// The inbox_id of the DM target - pub dm_inbox_id: Option, + pub dm_id: Option, + /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) + pub last_message_ns: Option, /// The last time the leaf node encryption key was rotated pub rotated_at_ns: i64, /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. @@ -56,7 +58,7 @@ impl StoredGroup { added_by_inbox_id: String, welcome_id: i64, conversation_type: ConversationType, - dm_inbox_id: Option, + dm_id: Option, ) -> Self { Self { id, @@ -67,7 +69,8 @@ impl StoredGroup { added_by_inbox_id, welcome_id: Some(welcome_id), rotated_at_ns: 0, - dm_inbox_id, + dm_id, + last_message_ns: None, } } @@ -77,21 +80,22 @@ impl StoredGroup { created_at_ns: i64, membership_state: GroupMembershipState, added_by_inbox_id: String, - dm_inbox_id: Option, + dm_id: Option, ) -> Self { Self { id, created_at_ns, membership_state, installations_last_checked: 0, - conversation_type: match dm_inbox_id { + conversation_type: match dm_id { Some(_) => ConversationType::Dm, None => ConversationType::Group, }, added_by_inbox_id, welcome_id: None, rotated_at_ns: 0, - dm_inbox_id, + dm_id: dm_id, + last_message_ns: None, } } @@ -111,7 +115,8 @@ impl StoredGroup { added_by_inbox_id: "".into(), welcome_id: None, rotated_at_ns: 0, - dm_inbox_id: None, + dm_id: None, + last_message_ns: None, } } } From 9b894b57c12a34c63fdb200d7a85642f1cc5382c Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 13:34:35 -0500 Subject: [PATCH 04/36] adjust column --- .../migrations/2024-12-18-170645_add_dm_id/up.sql | 2 +- xmtp_mls/src/storage/encrypted_store/group.rs | 15 ++++++++------- xmtp_mls/src/storage/encrypted_store/schema.rs | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index 4e7fed04c..88de8b3cc 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -1,5 +1,5 @@ ALTER TABLE groups ADD COLUMN dm_id TEXT; -ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; +ALTER TABLE groups ADD COLUMN last_message_ns BIGINT NOT NULL DEFAULT (created_at_ns); -- Fill the dm_id column UPDATE groups diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 0fb80096c..fabb67b9b 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -16,6 +16,7 @@ use diesel::{ sql_types::Integer, }; use serde::{Deserialize, Serialize}; +use xmtp_common::time::now_ns; pub type ID = Vec; @@ -36,14 +37,14 @@ pub struct StoredGroup { pub added_by_inbox_id: String, /// The sequence id of the welcome message pub welcome_id: Option, - /// The inbox_id of the DM target - pub dm_id: Option, - /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) - pub last_message_ns: Option, /// The last time the leaf node encryption key was rotated pub rotated_at_ns: i64, /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. pub conversation_type: ConversationType, + /// The inbox_id of the DM target + pub dm_id: Option, + /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) + pub last_message_ns: i64, } impl_fetch!(StoredGroup, groups, Vec); @@ -70,7 +71,7 @@ impl StoredGroup { welcome_id: Some(welcome_id), rotated_at_ns: 0, dm_id, - last_message_ns: None, + last_message_ns: now_ns(), } } @@ -95,7 +96,7 @@ impl StoredGroup { welcome_id: None, rotated_at_ns: 0, dm_id: dm_id, - last_message_ns: None, + last_message_ns: now_ns(), } } @@ -116,7 +117,7 @@ impl StoredGroup { welcome_id: None, rotated_at_ns: 0, dm_id: None, - last_message_ns: None, + last_message_ns: now_ns(), } } } diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 38b077ff4..083aa7329 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -56,7 +56,7 @@ diesel::table! { rotated_at_ns -> BigInt, conversation_type -> Integer, dm_id -> Nullable, - last_message_ns -> Nullable, + last_message_ns -> BigInt, } } From 7d1f8d98d977b5b22787bb7c71d37edadbf124ae Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 14:58:10 -0500 Subject: [PATCH 05/36] adjust default --- xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index 88de8b3cc..fdf925049 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -1,5 +1,5 @@ ALTER TABLE groups ADD COLUMN dm_id TEXT; -ALTER TABLE groups ADD COLUMN last_message_ns BIGINT NOT NULL DEFAULT (created_at_ns); +ALTER TABLE groups ADD COLUMN last_message_ns BIGINT NOT NULL DEFAULT ((strftime('%s', 'now') * 1000000000) + (strftime('%f', 'now') * 1000000)); -- Fill the dm_id column UPDATE groups From 13031f87e9e475932aa006ff63db109ec5889fa2 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 15:27:12 -0500 Subject: [PATCH 06/36] efficient derivation --- xmtp_mls/src/groups/mod.rs | 17 +++++++++++++++-- xmtp_mls/src/storage/encrypted_store/group.rs | 8 ++++++++ xmtp_mls/src/storage/encrypted_store/schema.rs | 1 - 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 35662bc7d..e5d6d2203 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -475,12 +475,13 @@ impl MlsGroup { )?; let group_id = mls_group.group_id().to_vec(); + let stored_group = StoredGroup::new( group_id.clone(), now_ns(), membership_state, context.inbox_id().to_string(), - Some(dm_target_inbox_id), + Some(StoredGroup::dm_id([&dm_target_inbox_id, client.inbox_id()])), ); stored_group.store(provider.conn_ref())?; @@ -1128,7 +1129,19 @@ impl MlsGroup { let group = conn .find_group(self.group_id.clone())? .ok_or(GroupError::GroupNotFound)?; - group.dm_inbox_id.ok_or(GroupError::GroupNotFound) + let inbox_id = self.client.inbox_id(); + // drop the "dm:" + let dm_id = &group.dm_id.ok_or(GroupError::GroupNotFound)?[3..]; + + // If my inbox id is the first half, return the second half, otherwise return first half + let target_inbox = if dm_id[..inbox_id.len()] == *inbox_id { + // + 1 because there is a colon (:) + &dm_id[(inbox_id.len() + 1)..] + } else { + &dm_id[..inbox_id.len()] + }; + + return Ok(target_inbox.to_string()); } /// Find the `consent_state` of the group diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index fabb67b9b..7c27e89a4 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -120,6 +120,14 @@ impl StoredGroup { last_message_ns: now_ns(), } } + + pub fn dm_id(inbox_ids: [&str; 2]) -> String { + let inbox_ids: = inbox_ids + .into_iter() + .map(str::to_lowercase) + .collect::>(); + format!("dm:{}", inbox_ids.join(":")) + } } #[derive(Debug, Default)] diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 083aa7329..423957e5b 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -52,7 +52,6 @@ diesel::table! { installations_last_checked -> BigInt, added_by_inbox_id -> Text, welcome_id -> Nullable, - dm_inbox_id -> Nullable, rotated_at_ns -> BigInt, conversation_type -> Integer, dm_id -> Nullable, From 95639d9b51c8c74eece323c76202823b5a5851d1 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 15:53:42 -0500 Subject: [PATCH 07/36] use a trait --- xmtp_mls/src/client.rs | 2 +- xmtp_mls/src/groups/mod.rs | 7 ++- xmtp_mls/src/storage/encrypted_store/group.rs | 52 +++++++++++++++---- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index 02327f11b..6b84be9b0 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -638,7 +638,7 @@ where target_inbox_id: String, ) -> Result, ClientError> { let conn = self.store().conn()?; - match conn.find_dm_group(&target_inbox_id)? { + match conn.find_dm_group(self.inbox_id(), &target_inbox_id)? { Some(dm_group) => Ok(MlsGroup::new( self.clone(), dm_group.id, diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index e5d6d2203..9150480c7 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -58,7 +58,10 @@ use self::{ intents::IntentError, validated_commit::CommitValidationError, }; -use crate::storage::StorageError; +use crate::storage::{ + group::{DmId, DmIdExt}, + StorageError, +}; use xmtp_common::time::now_ns; use xmtp_proto::xmtp::mls::{ api::v1::{ @@ -481,7 +484,7 @@ impl MlsGroup { now_ns(), membership_state, context.inbox_id().to_string(), - Some(StoredGroup::dm_id([&dm_target_inbox_id, client.inbox_id()])), + Some(DmId::from_ids([&dm_target_inbox_id, client.inbox_id()])), ); stored_group.store(provider.conn_ref())?; diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 7c27e89a4..41396e20c 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -42,7 +42,7 @@ pub struct StoredGroup { /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. pub conversation_type: ConversationType, /// The inbox_id of the DM target - pub dm_id: Option, + pub dm_id: Option, /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) pub last_message_ns: i64, } @@ -120,14 +120,6 @@ impl StoredGroup { last_message_ns: now_ns(), } } - - pub fn dm_id(inbox_ids: [&str; 2]) -> String { - let inbox_ids: = inbox_ids - .into_iter() - .map(str::to_lowercase) - .collect::>(); - format!("dm:{}", inbox_ids.join(":")) - } } #[derive(Debug, Default)] @@ -351,11 +343,14 @@ impl DbConnection { pub fn find_dm_group( &self, + inbox_id: &str, target_inbox_id: &str, ) -> Result, StorageError> { + let dm_id = DmId::from_ids([inbox_id, target_inbox_id]); + let query = dsl::groups .order(dsl::created_at_ns.asc()) - .filter(dsl::dm_inbox_id.eq(Some(target_inbox_id))); + .filter(dsl::dm_id.eq(Some(dm_id))); let groups: Vec = self.raw_query(|conn| query.load(conn))?; if groups.len() > 1 { @@ -555,6 +550,38 @@ impl std::fmt::Display for ConversationType { } } +pub type DmId = String; + +pub trait DmIdExt { + fn from_ids(inbox_ids: [&str; 2]) -> Self; + fn other_id(&self, other: &str) -> String; +} +impl DmIdExt for DmId { + fn from_ids(inbox_ids: [&str; 2]) -> Self { + let inbox_ids = inbox_ids + .into_iter() + .map(str::to_lowercase) + .collect::>(); + + format!("dm:{}", inbox_ids.join(":")) + } + + fn other_id(&self, id: &str) -> String { + // drop the "dm:" + let dm_id = &self[3..]; + + // If my id is the first half, return the second half, otherwise return first half + let target_inbox = if dm_id[..id.len()] == *id { + // + 1 because there is a colon (:) + &dm_id[(id.len() + 1)..] + } else { + &dm_id[..id.len()] + }; + + return target_inbox.to_string(); + } +} + #[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] @@ -726,7 +753,10 @@ pub(crate) mod tests { assert_eq!(dm_results[2].id, test_group_3.id); // test find_dm_group - let dm_result = conn.find_dm_group("placeholder_inbox_id").unwrap(); + + let dm_result = conn + .find_dm_group("placeholder_inbox_id", "placeholder_inbox_id") + .unwrap(); assert!(dm_result.is_some()); // test only dms are returned From 865506d3c01b98ef925cb2d71964b9d28f48612a Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 15:58:22 -0500 Subject: [PATCH 08/36] fix the migration --- xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index fdf925049..63f8191ca 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -13,7 +13,8 @@ SET dm_id = 'dm:' || ) WHERE dm_inbox_id IS NOT NULL; -ALTER TABLE groups REMOVE COLUMN dm_inbox_id; +DROP INDEX IF EXISTS idx_dm_target; +ALTER TABLE groups DROP COLUMN dm_inbox_id; -- Create a trigger to auto-update group table on insert CREATE TRIGGER msg_iserted From 1438028c665f0c52ad1db04de70cc0d3769fa963 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 16:10:07 -0500 Subject: [PATCH 09/36] minor fixes --- xmtp_mls/src/storage/encrypted_store/group.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 41396e20c..a2253b5a6 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -95,7 +95,7 @@ impl StoredGroup { added_by_inbox_id, welcome_id: None, rotated_at_ns: 0, - dm_id: dm_id, + dm_id, last_message_ns: now_ns(), } } @@ -629,7 +629,7 @@ pub(crate) mod tests { let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); - let dm_inbox_id = Some("placeholder_inbox_id".to_string()); + let dm_inbox_id = Some(DmId::from_ids(["placeholder_inbox_id"; 2])); StoredGroup::new( id, created_at_ns, From 91388a70135b242e8117eaf8f2f6be5e78603a72 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 16:21:37 -0500 Subject: [PATCH 10/36] update welcome --- xmtp_mls/src/groups/mod.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 9150480c7..78c0253f2 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -511,15 +511,9 @@ impl MlsGroup { let group_id = mls_group.group_id().to_vec(); let metadata = extract_group_metadata(&mls_group)?; let dm_members = metadata.dm_members; - let dm_inbox_id = if let Some(dm_members) = &dm_members { - if dm_members.member_one_inbox_id == client.inbox_id() { - Some(dm_members.member_two_inbox_id.clone()) - } else { - Some(dm_members.member_one_inbox_id.clone()) - } - } else { - None - }; + let dm_id = + dm_members.map(|m| DmId::from_ids([&m.member_one_inbox_id, &m.member_two_inbox_id])); + let conversation_type = metadata.conversation_type; let to_store = match conversation_type { @@ -530,7 +524,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_inbox_id, + dm_id, ), ConversationType::Dm => { validate_dm_group(client.as_ref(), &mls_group, &added_by_inbox)?; @@ -541,7 +535,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_inbox_id, + dm_id, ) } ConversationType::Sync => StoredGroup::new_from_welcome( @@ -551,7 +545,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_inbox_id, + dm_id, ), }; From 1873e99c72954038cd0b89e52553e3d395e59962 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Wed, 18 Dec 2024 17:08:31 -0500 Subject: [PATCH 11/36] use the concrete type --- xmtp_mls/src/client.rs | 9 ++- xmtp_mls/src/groups/group_metadata.rs | 50 ++++++++++++---- xmtp_mls/src/groups/mod.rs | 23 ++++---- xmtp_mls/src/groups/validated_commit.rs | 2 +- xmtp_mls/src/storage/encrypted_store/group.rs | 57 ++++++++----------- 5 files changed, 84 insertions(+), 57 deletions(-) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index 6b84be9b0..d68a2c9ca 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -36,8 +36,8 @@ use crate::groups::device_sync::WorkerHandle; use crate::{ api::ApiClientWrapper, groups::{ - device_sync::preference_sync::UserPreferenceUpdate, group_permissions::PolicySet, - GroupError, GroupMetadataOptions, MlsGroup, + device_sync::preference_sync::UserPreferenceUpdate, group_metadata::DmMembers, + group_permissions::PolicySet, GroupError, GroupMetadataOptions, MlsGroup, }, identity::{parse_credential, Identity, IdentityError}, identity_updates::{load_identity_updates, IdentityUpdateError}, @@ -638,7 +638,10 @@ where target_inbox_id: String, ) -> Result, ClientError> { let conn = self.store().conn()?; - match conn.find_dm_group(self.inbox_id(), &target_inbox_id)? { + match conn.find_dm_group(DmMembers { + member_one_inbox_id: self.inbox_id(), + member_two_inbox_id: &target_inbox_id, + })? { Some(dm_group) => Ok(MlsGroup::new( self.clone(), dm_group.id, diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index e06c7e6a1..162475ee6 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -2,6 +2,7 @@ use openmls::{extensions::Extensions, group::MlsGroup as OpenMlsGroup}; use prost::Message; use thiserror::Error; +use xmtp_id::InboxId; use xmtp_proto::xmtp::mls::message_contents::{ ConversationType as ConversationTypeProto, DmMembers as DmMembersProto, GroupMetadataV1 as GroupMetadataProto, Inbox as InboxProto, @@ -31,14 +32,14 @@ pub struct GroupMetadata { pub conversation_type: ConversationType, // TODO: Remove this once transition is completed pub creator_inbox_id: String, - pub dm_members: Option, + pub dm_members: Option>, } impl GroupMetadata { pub fn new( conversation_type: ConversationType, creator_inbox_id: String, - dm_members: Option, + dm_members: Option>, ) -> Self { Self { conversation_type, @@ -130,25 +131,54 @@ impl TryFrom for ConversationType { } #[derive(Debug, Clone, PartialEq)] -pub struct DmMembers { - pub member_one_inbox_id: String, - pub member_two_inbox_id: String, +pub struct DmMembers> { + pub member_one_inbox_id: Id, + pub member_two_inbox_id: Id, } -impl From for DmMembersProto { - fn from(value: DmMembers) -> Self { +impl<'a> DmMembers { + pub fn as_ref(&'a self) -> DmMembers<&'a str> { + DmMembers { + member_one_inbox_id: &*self.member_one_inbox_id, + member_two_inbox_id: &*self.member_two_inbox_id, + } + } +} + +impl From> for DmMembersProto +where + Id: AsRef, +{ + fn from(value: DmMembers) -> Self { DmMembersProto { dm_member_one: Some(InboxProto { - inbox_id: value.member_one_inbox_id.clone(), + inbox_id: value.member_one_inbox_id.as_ref().to_string(), }), dm_member_two: Some(InboxProto { - inbox_id: value.member_two_inbox_id.clone(), + inbox_id: value.member_two_inbox_id.as_ref().to_string(), }), } } } -impl TryFrom for DmMembers { +impl From> for String +where + Id: AsRef, +{ + fn from(members: DmMembers) -> Self { + let inbox_ids = [ + members.member_one_inbox_id.as_ref(), + members.member_two_inbox_id.as_ref(), + ] + .into_iter() + .map(str::to_lowercase) + .collect::>(); + + format!("dm:{}", inbox_ids.join(":")) + } +} + +impl TryFrom for DmMembers { type Error = GroupMetadataError; fn try_from(value: DmMembersProto) -> Result { diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 78c0253f2..b21eebad4 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -58,10 +58,7 @@ use self::{ intents::IntentError, validated_commit::CommitValidationError, }; -use crate::storage::{ - group::{DmId, DmIdExt}, - StorageError, -}; +use crate::storage::StorageError; use xmtp_common::time::now_ns; use xmtp_proto::xmtp::mls::{ api::v1::{ @@ -484,7 +481,10 @@ impl MlsGroup { now_ns(), membership_state, context.inbox_id().to_string(), - Some(DmId::from_ids([&dm_target_inbox_id, client.inbox_id()])), + Some(DmMembers { + member_one_inbox_id: dm_target_inbox_id, + member_two_inbox_id: client.inbox_id().to_string(), + }), ); stored_group.store(provider.conn_ref())?; @@ -511,8 +511,6 @@ impl MlsGroup { let group_id = mls_group.group_id().to_vec(); let metadata = extract_group_metadata(&mls_group)?; let dm_members = metadata.dm_members; - let dm_id = - dm_members.map(|m| DmId::from_ids([&m.member_one_inbox_id, &m.member_two_inbox_id])); let conversation_type = metadata.conversation_type; @@ -524,7 +522,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_id, + dm_members, ), ConversationType::Dm => { validate_dm_group(client.as_ref(), &mls_group, &added_by_inbox)?; @@ -535,7 +533,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_id, + dm_members, ) } ConversationType::Sync => StoredGroup::new_from_welcome( @@ -545,7 +543,7 @@ impl MlsGroup { added_by_inbox, welcome_id, conversation_type, - dm_id, + dm_members, ), }; @@ -1274,7 +1272,10 @@ impl MlsGroup { now_ns(), GroupMembershipState::Allowed, // Use Allowed as default for tests context.inbox_id().to_string(), - Some(dm_target_inbox_id), + Some(DmMembers { + member_one_inbox_id: client.inbox_id().to_string(), + member_two_inbox_id: dm_target_inbox_id, + }), ); stored_group.store(provider.conn_ref())?; diff --git a/xmtp_mls/src/groups/validated_commit.rs b/xmtp_mls/src/groups/validated_commit.rs index f13557236..c9dcd8c18 100644 --- a/xmtp_mls/src/groups/validated_commit.rs +++ b/xmtp_mls/src/groups/validated_commit.rs @@ -210,7 +210,7 @@ pub struct ValidatedCommit { pub removed_inboxes: Vec, pub metadata_changes: MutableMetadataChanges, pub permissions_changed: bool, - pub dm_members: Option, + pub dm_members: Option>, } impl ValidatedCommit { diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index a2253b5a6..efc346332 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -5,7 +5,9 @@ use super::{ schema::groups::{self, dsl}, Sqlite, }; -use crate::{impl_fetch, impl_store, DuplicateItem, StorageError}; +use crate::{ + groups::group_metadata::DmMembers, impl_fetch, impl_store, DuplicateItem, StorageError, +}; use diesel::{ backend::Backend, deserialize::{self, FromSql, FromSqlRow}, @@ -17,6 +19,7 @@ use diesel::{ }; use serde::{Deserialize, Serialize}; use xmtp_common::time::now_ns; +use xmtp_id::InboxIdRef; pub type ID = Vec; @@ -42,7 +45,7 @@ pub struct StoredGroup { /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. pub conversation_type: ConversationType, /// The inbox_id of the DM target - pub dm_id: Option, + pub dm_id: Option, /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) pub last_message_ns: i64, } @@ -59,7 +62,7 @@ impl StoredGroup { added_by_inbox_id: String, welcome_id: i64, conversation_type: ConversationType, - dm_id: Option, + dm_members: Option>, ) -> Self { Self { id, @@ -70,7 +73,7 @@ impl StoredGroup { added_by_inbox_id, welcome_id: Some(welcome_id), rotated_at_ns: 0, - dm_id, + dm_id: dm_members.map(String::from), last_message_ns: now_ns(), } } @@ -81,21 +84,21 @@ impl StoredGroup { created_at_ns: i64, membership_state: GroupMembershipState, added_by_inbox_id: String, - dm_id: Option, + dm_members: Option>, ) -> Self { Self { id, created_at_ns, membership_state, installations_last_checked: 0, - conversation_type: match dm_id { + conversation_type: match dm_members { Some(_) => ConversationType::Dm, None => ConversationType::Group, }, added_by_inbox_id, welcome_id: None, rotated_at_ns: 0, - dm_id, + dm_id: dm_members.map(String::from), last_message_ns: now_ns(), } } @@ -343,10 +346,9 @@ impl DbConnection { pub fn find_dm_group( &self, - inbox_id: &str, - target_inbox_id: &str, + members: DmMembers<&str>, ) -> Result, StorageError> { - let dm_id = DmId::from_ids([inbox_id, target_inbox_id]); + let dm_id = String::from(members.clone()); let query = dsl::groups .order(dsl::created_at_ns.asc()) @@ -354,10 +356,7 @@ impl DbConnection { let groups: Vec = self.raw_query(|conn| query.load(conn))?; if groups.len() > 1 { - tracing::info!( - "More than one group found for dm_inbox_id {}", - target_inbox_id - ); + tracing::info!("More than one group found for dm_inbox_id {members:?}"); } Ok(groups.into_iter().next()) @@ -550,23 +549,11 @@ impl std::fmt::Display for ConversationType { } } -pub type DmId = String; - pub trait DmIdExt { - fn from_ids(inbox_ids: [&str; 2]) -> Self; - fn other_id(&self, other: &str) -> String; + fn other_inbox_id(&self, other: &str) -> String; } -impl DmIdExt for DmId { - fn from_ids(inbox_ids: [&str; 2]) -> Self { - let inbox_ids = inbox_ids - .into_iter() - .map(str::to_lowercase) - .collect::>(); - - format!("dm:{}", inbox_ids.join(":")) - } - - fn other_id(&self, id: &str) -> String { +impl DmIdExt for String { + fn other_inbox_id(&self, id: &str) -> String { // drop the "dm:" let dm_id = &self[3..]; @@ -629,13 +616,16 @@ pub(crate) mod tests { let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); - let dm_inbox_id = Some(DmId::from_ids(["placeholder_inbox_id"; 2])); + let members = DmMembers { + member_one_inbox_id: "placeholder_inbox_id_1".to_string(), + member_two_inbox_id: "placeholder_inbox_id_2".to_string(), + }; StoredGroup::new( id, created_at_ns, membership_state, "placeholder_address".to_string(), - dm_inbox_id, + Some(members), ) } @@ -755,7 +745,10 @@ pub(crate) mod tests { // test find_dm_group let dm_result = conn - .find_dm_group("placeholder_inbox_id", "placeholder_inbox_id") + .find_dm_group(DmMembers { + member_one_inbox_id: "placeholder_inbox_id_1", + member_two_inbox_id: "placeholder_inbox_id_2", + }) .unwrap(); assert!(dm_result.is_some()); From e4e2e87bfe0299e41de572914f79c127ff6a8567 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 10:19:29 -0500 Subject: [PATCH 12/36] cleanup --- xmtp_mls/src/client.rs | 2 +- xmtp_mls/src/groups/group_metadata.rs | 25 ++++++++++++++++--- xmtp_mls/src/storage/encrypted_store/group.rs | 7 +++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index d68a2c9ca..fe4b31db6 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -638,7 +638,7 @@ where target_inbox_id: String, ) -> Result, ClientError> { let conn = self.store().conn()?; - match conn.find_dm_group(DmMembers { + match conn.find_dm_group(&DmMembers { member_one_inbox_id: self.inbox_id(), member_two_inbox_id: &target_inbox_id, })? { diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index 162475ee6..bdfe885aa 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -161,23 +161,42 @@ where } } -impl From> for String +impl From<&DmMembers> for String where Id: AsRef, { - fn from(members: DmMembers) -> Self { - let inbox_ids = [ + fn from(members: &DmMembers) -> Self { + let mut inbox_ids = [ members.member_one_inbox_id.as_ref(), members.member_two_inbox_id.as_ref(), ] .into_iter() .map(str::to_lowercase) .collect::>(); + inbox_ids.sort(); format!("dm:{}", inbox_ids.join(":")) } } +impl From> for String +where + Id: AsRef, +{ + fn from(members: DmMembers) -> Self { + String::from(&members) + } +} + +impl ToString for DmMembers +where + Id: AsRef, +{ + fn to_string(&self) -> String { + String::from(self) + } +} + impl TryFrom for DmMembers { type Error = GroupMetadataError; diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index efc346332..25079d64f 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -19,7 +19,6 @@ use diesel::{ }; use serde::{Deserialize, Serialize}; use xmtp_common::time::now_ns; -use xmtp_id::InboxIdRef; pub type ID = Vec; @@ -346,9 +345,9 @@ impl DbConnection { pub fn find_dm_group( &self, - members: DmMembers<&str>, + members: &DmMembers<&str>, ) -> Result, StorageError> { - let dm_id = String::from(members.clone()); + let dm_id = String::from(members); let query = dsl::groups .order(dsl::created_at_ns.asc()) @@ -745,7 +744,7 @@ pub(crate) mod tests { // test find_dm_group let dm_result = conn - .find_dm_group(DmMembers { + .find_dm_group(&DmMembers { member_one_inbox_id: "placeholder_inbox_id_1", member_two_inbox_id: "placeholder_inbox_id_2", }) From 825b83f5327f64442f35f7ae9d349c284521947b Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 10:50:42 -0500 Subject: [PATCH 13/36] remove the builder --- bindings_ffi/src/mls.rs | 17 ++- bindings_node/src/conversation.rs | 7 +- bindings_node/src/message.rs | 14 ++- bindings_wasm/src/conversation.rs | 8 +- bindings_wasm/src/messages.rs | 14 ++- examples/cli/cli-client.rs | 6 +- xmtp_mls/src/groups/device_sync.rs | 11 +- xmtp_mls/src/groups/mod.rs | 30 ++++- xmtp_mls/src/storage/encrypted_store/group.rs | 4 +- .../storage/encrypted_store/group_message.rs | 114 +++++++----------- 10 files changed, 114 insertions(+), 111 deletions(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 339c9c23e..7f9f471d6 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -1331,15 +1331,14 @@ impl FfiConversation { let messages: Vec = self .inner - .find_messages( - &MsgQueryArgs::default() - .maybe_sent_before_ns(opts.sent_before_ns) - .maybe_sent_after_ns(opts.sent_after_ns) - .maybe_kind(kind) - .maybe_delivery_status(delivery_status) - .maybe_limit(opts.limit) - .maybe_direction(direction), - )? + .find_messages(&MsgQueryArgs { + sent_before_ns: opts.sent_before_ns, + sent_after_ns: opts.sent_after_ns, + kind: kind, + delivery_status: delivery_status, + limit: opts.limit, + direction: direction, + })? .into_iter() .map(|msg| msg.into()) .collect(); diff --git a/bindings_node/src/conversation.rs b/bindings_node/src/conversation.rs index b520af793..de1be2ee9 100644 --- a/bindings_node/src/conversation.rs +++ b/bindings_node/src/conversation.rs @@ -178,9 +178,12 @@ impl Conversation { ConversationType::Dm => Some(XmtpGroupMessageKind::Application), ConversationType::Sync => None, }; - let opts: MsgQueryArgs = opts.into(); + let opts = MsgQueryArgs { + kind, + ..opts.into() + }; let messages: Vec = group - .find_messages(&opts.maybe_kind(kind)) + .find_messages(&opts) .map_err(ErrorWrapper::from)? .into_iter() .map(|msg| msg.into()) diff --git a/bindings_node/src/message.rs b/bindings_node/src/message.rs index fbe3975b5..9b6e3134f 100644 --- a/bindings_node/src/message.rs +++ b/bindings_node/src/message.rs @@ -82,12 +82,14 @@ impl From for MsgQueryArgs { let delivery_status = opts.delivery_status.map(Into::into); let direction = opts.direction.map(Into::into); - MsgQueryArgs::default() - .maybe_sent_before_ns(opts.sent_before_ns) - .maybe_sent_after_ns(opts.sent_after_ns) - .maybe_delivery_status(delivery_status) - .maybe_limit(opts.limit) - .maybe_direction(direction) + MsgQueryArgs { + sent_before_ns: opts.sent_before_ns, + sent_after_ns: opts.sent_after_ns, + delivery_status: delivery_status, + limit: opts.limit, + direction, + ..Default::default() + } } } diff --git a/bindings_wasm/src/conversation.rs b/bindings_wasm/src/conversation.rs index 42ca2f06e..667fa0832 100644 --- a/bindings_wasm/src/conversation.rs +++ b/bindings_wasm/src/conversation.rs @@ -201,9 +201,13 @@ impl Conversation { ConversationType::Dm => Some(XmtpGroupMessageKind::Application), ConversationType::Sync => None, }; - let opts: MsgQueryArgs = opts.into(); + + let opts = MsgQueryArgs { + kind, + ..opts.into() + }; let messages: Vec = group - .find_messages(&opts.maybe_kind(kind)) + .find_messages(&opts) .map_err(|e| JsError::new(&format!("{e}")))? .into_iter() .map(Into::into) diff --git a/bindings_wasm/src/messages.rs b/bindings_wasm/src/messages.rs index 473e5c233..95b4cd706 100644 --- a/bindings_wasm/src/messages.rs +++ b/bindings_wasm/src/messages.rs @@ -87,12 +87,14 @@ impl From for MsgQueryArgs { let delivery_status = opts.delivery_status.map(Into::into); let direction = opts.direction.map(Into::into); - MsgQueryArgs::default() - .maybe_sent_before_ns(opts.sent_before_ns) - .maybe_sent_after_ns(opts.sent_after_ns) - .maybe_delivery_status(delivery_status) - .maybe_limit(opts.limit) - .maybe_direction(direction) + MsgQueryArgs { + sent_before_ns: opts.sent_before_ns, + sent_after_ns: opts.sent_after_ns, + delivery_status, + limit: opts.limit, + direction, + ..Default::default() + } } } diff --git a/examples/cli/cli-client.rs b/examples/cli/cli-client.rs index a30060380..ee836e854 100755 --- a/examples/cli/cli-client.rs +++ b/examples/cli/cli-client.rs @@ -455,8 +455,10 @@ async fn main() -> color_eyre::eyre::Result<()> { let group = client.get_sync_group(provider.conn_ref())?; let group_id_str = hex::encode(group.group_id.clone()); group.sync().await?; - let messages = group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application))?; + let messages = group.find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + })?; info!( group_id = group_id_str, messages = messages.len(), diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index 070caabe6..b94530bfa 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -492,7 +492,10 @@ where let messages = provider.conn_ref().get_group_messages( &sync_group.group_id, - &MsgQueryArgs::default().kind(GroupMessageKind::Application), + &MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }, )?; for msg in messages.into_iter().rev() { @@ -525,8 +528,10 @@ where let sync_group = self.get_sync_group(provider.conn_ref())?; sync_group.sync_with_conn(provider).await?; - let messages = sync_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application))?; + let messages = sync_group.find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + })?; for msg in messages.into_iter().rev() { let Ok(msg_content) = diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index b21eebad4..54273a942 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -2297,7 +2297,10 @@ pub(crate) mod tests { amal_group.sync().await.expect("sync failed"); let amal_messages = amal_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap() .into_iter() .collect::>(); @@ -3295,7 +3298,10 @@ pub(crate) mod tests { ]; let messages = amal_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap() .into_iter() .collect::>(); @@ -3835,10 +3841,16 @@ pub(crate) mod tests { bo_group.sync().await.unwrap(); let alix_messages = alix_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap(); let bo_messages = bo_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap(); assert_eq!(alix_messages.len(), 2); @@ -3858,10 +3870,16 @@ pub(crate) mod tests { bo_group.sync().await.unwrap(); let alix_messages = alix_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap(); let bo_messages = bo_group - .find_messages(&MsgQueryArgs::default().kind(GroupMessageKind::Application)) + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }) .unwrap(); assert_eq!(bo_messages.len(), 3); assert_eq!(alix_messages.len(), 3); // Fails here, 2 != 3 diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 25079d64f..9c14c6be9 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -350,8 +350,8 @@ impl DbConnection { let dm_id = String::from(members); let query = dsl::groups - .order(dsl::created_at_ns.asc()) - .filter(dsl::dm_id.eq(Some(dm_id))); + .filter(dsl::dm_id.eq(Some(dm_id))) + .order(dsl::last_message_ns.desc()); let groups: Vec = self.raw_query(|conn| query.load(conn))?; if groups.len() > 1 { diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index 743800d79..d60983b10 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -116,74 +116,16 @@ impl_store_or_ignore!(StoredGroupMessage, group_messages); #[derive(Default)] pub struct MsgQueryArgs { - sent_after_ns: Option, - sent_before_ns: Option, - kind: Option, - delivery_status: Option, - limit: Option, - direction: Option, -} - -impl MsgQueryArgs { - pub fn sent_after_ns(mut self, sent_after_ns: i64) -> Self { - self.sent_after_ns = Some(sent_after_ns); - self - } - pub fn maybe_sent_after_ns(mut self, sent_after_ns: Option) -> Self { - self.sent_after_ns = sent_after_ns; - self - } - - pub fn sent_before_ns(mut self, sent_before_ns: i64) -> Self { - self.sent_before_ns = Some(sent_before_ns); - self - } - pub fn maybe_sent_before_ns(mut self, sent_before_ns: Option) -> Self { - self.sent_before_ns = sent_before_ns; - self - } - - pub fn kind(mut self, kind: GroupMessageKind) -> Self { - self.kind = Some(kind); - self - } - - pub fn maybe_kind(mut self, kind: Option) -> Self { - self.kind = kind; - self - } - - pub fn direction(mut self, direction: SortDirection) -> Self { - self.direction = Some(direction); - self - } - pub fn maybe_direction(mut self, direction: Option) -> Self { - self.direction = direction; - self - } - - pub fn delivery_status(mut self, delivery_status: DeliveryStatus) -> Self { - self.delivery_status = Some(delivery_status); - self - } - pub fn maybe_delivery_status(mut self, delivery_status: Option) -> Self { - self.delivery_status = delivery_status; - self - } - - pub fn limit(mut self, limit: i64) -> Self { - self.limit = Some(limit); - self - } - pub fn maybe_limit(mut self, limit: Option) -> Self { - self.limit = limit; - self - } + pub sent_after_ns: Option, + pub sent_before_ns: Option, + pub kind: Option, + pub delivery_status: Option, + pub limit: Option, + pub direction: Option, } impl DbConnection { /// Query for group messages - #[allow(clippy::too_many_arguments)] pub fn get_group_messages( &self, group_id: &[u8], @@ -397,21 +339,35 @@ pub(crate) mod tests { let message = conn .get_group_messages( &group.id, - &MsgQueryArgs::default() - .sent_after_ns(1_000) - .sent_before_ns(100_000), + &MsgQueryArgs { + sent_after_ns: Some(1_000), + sent_before_ns: Some(100_000), + ..Default::default() + }, ) .unwrap(); assert_eq!(message.len(), 1); assert_eq!(message.first().unwrap().sent_at_ns, 10_000); let messages = conn - .get_group_messages(&group.id, &MsgQueryArgs::default().sent_before_ns(100_000)) + .get_group_messages( + &group.id, + &MsgQueryArgs { + sent_before_ns: Some(100_000), + ..Default::default() + }, + ) .unwrap(); assert_eq!(messages.len(), 2); let messages = conn - .get_group_messages(&group.id, &MsgQueryArgs::default().sent_after_ns(10_000)) + .get_group_messages( + &group.id, + &MsgQueryArgs { + sent_after_ns: Some(10_000), + ..Default::default() + }, + ) .unwrap(); assert_eq!(messages.len(), 2); }) @@ -449,7 +405,10 @@ pub(crate) mod tests { let application_messages = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().kind(GroupMessageKind::Application), + &MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() + }, ) .unwrap(); assert_eq!(application_messages.len(), 15); @@ -457,7 +416,10 @@ pub(crate) mod tests { let membership_changes = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().kind(GroupMessageKind::MembershipChange), + &MsgQueryArgs { + kind: Some(GroupMessageKind::MembershipChange), + ..Default::default() + }, ) .unwrap(); assert_eq!(membership_changes.len(), 15); @@ -483,7 +445,10 @@ pub(crate) mod tests { let messages_asc = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().direction(SortDirection::Ascending), + &MsgQueryArgs { + direction: Some(SortDirection::Ascending), + ..Default::default() + }, ) .unwrap(); assert_eq!(messages_asc.len(), 4); @@ -495,7 +460,10 @@ pub(crate) mod tests { let messages_desc = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().direction(SortDirection::Descending), + &MsgQueryArgs { + direction: Some(SortDirection::Descending), + ..Default::default() + }, ) .unwrap(); assert_eq!(messages_desc.len(), 4); From 037b8de3faab9cfe0c068a3448739d1e94db266d Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 11:48:00 -0500 Subject: [PATCH 14/36] update the query --- .../storage/encrypted_store/group_message.rs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index d60983b10..a88077321 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -10,7 +10,10 @@ use serde::{Deserialize, Serialize}; use super::{ db_connection::DbConnection, - schema::group_messages::{self, dsl}, + schema::{ + group_messages::{self, dsl}, + groups::dsl as groups_dsl, + }, Sqlite, }; use crate::{impl_fetch, impl_store, impl_store_or_ignore, StorageError}; @@ -132,7 +135,20 @@ impl DbConnection { args: &MsgQueryArgs, ) -> Result, StorageError> { let mut query = dsl::group_messages - .filter(dsl::group_id.eq(group_id)) + .filter( + dsl::group_id.eq_any( + groups_dsl::groups + .filter( + groups_dsl::id.eq(group_id).or(groups_dsl::dm_id.eq_any( + groups_dsl::groups + .select(groups_dsl::dm_id) + .filter(groups_dsl::id.eq(group_id)) + .into_boxed(), + )), + ) + .select(groups_dsl::id), + ), + ) .into_boxed(); if let Some(sent_after) = args.sent_after_ns { From d155744f7ede4d0e2aa199eff2e9ff29072768da Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 11:51:31 -0500 Subject: [PATCH 15/36] comment --- xmtp_mls/src/storage/encrypted_store/group_message.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index a88077321..03dd21985 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -134,6 +134,8 @@ impl DbConnection { group_id: &[u8], args: &MsgQueryArgs, ) -> Result, StorageError> { + // Get all messages that have a group with an id equal the provided id, + // or a dm_id equal to the dm_id that belongs to the loaded group with the provided id. let mut query = dsl::group_messages .filter( dsl::group_id.eq_any( From 0f5c726883e97741e015af385819a5a9fb4c1c35 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 15:24:22 -0500 Subject: [PATCH 16/36] subquery --- bindings_ffi/src/mls.rs | 6 +++--- xmtp_mls/src/storage/encrypted_store/group.rs | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 7b84f1bdc..a03de17c6 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -1326,10 +1326,10 @@ impl FfiConversation { .find_messages(&MsgQueryArgs { sent_before_ns: opts.sent_before_ns, sent_after_ns: opts.sent_after_ns, - kind: kind, - delivery_status: delivery_status, limit: opts.limit, - direction: direction, + kind, + delivery_status, + direction, })? .into_iter() .map(|msg| msg.into()) diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 9c14c6be9..db624686a 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -223,8 +223,16 @@ impl DbConnection { } = args.as_ref(); let mut query = groups_dsl::groups - // Filter out sync groups from the main query .filter(groups_dsl::conversation_type.ne(ConversationType::Sync)) + // Group by dm_id and grab the latest group (conversation stitching) + .filter(sql::( + "id IN ( + SELECT id + FROM groups + GROUP BY CASE WHEN dm_id IS NULL THEN id ELSE dm_id END + ORDER BY last_message_ns DESC + )", + )) .order(groups_dsl::created_at_ns.asc()) .into_boxed(); From be69a8dbeaee80fd10e7473633ea2fdd7b9e5b6e Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 15:54:27 -0500 Subject: [PATCH 17/36] need those inbox ids to be different --- xmtp_mls/src/storage/encrypted_store/group.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index db624686a..a89699eef 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -581,6 +581,8 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use std::sync::atomic::{AtomicU16, Ordering}; + use super::*; use crate::{ storage::{ @@ -618,6 +620,8 @@ pub(crate) mod tests { } } + static INBOX_ID: AtomicU16 = AtomicU16::new(2); + /// Generate a test dm group pub fn generate_dm(state: Option) -> StoredGroup { let id = rand_vec::<24>(); @@ -625,7 +629,10 @@ pub(crate) mod tests { let membership_state = state.unwrap_or(GroupMembershipState::Allowed); let members = DmMembers { member_one_inbox_id: "placeholder_inbox_id_1".to_string(), - member_two_inbox_id: "placeholder_inbox_id_2".to_string(), + member_two_inbox_id: format!( + "placeholder_inbox_id_{}", + INBOX_ID.fetch_add(1, Ordering::SeqCst) + ), }; StoredGroup::new( id, From e7f933b25c67f9c9c3265fba1222a373c244dd54 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 17:13:15 -0500 Subject: [PATCH 18/36] test --- xmtp_mls/src/client.rs | 1 + .../src/groups/device_sync/message_sync.rs | 3 +- xmtp_mls/src/groups/mod.rs | 71 ++++++++++++++++++- xmtp_mls/src/storage/encrypted_store/group.rs | 67 +++++++++++++---- 4 files changed, 125 insertions(+), 17 deletions(-) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index fe4b31db6..bc0e5aa9f 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -923,6 +923,7 @@ where let query_args = GroupQueryArgs { consent_state, include_sync_groups: true, + include_duplicate_dms: true, ..GroupQueryArgs::default() }; let groups = provider diff --git a/xmtp_mls/src/groups/device_sync/message_sync.rs b/xmtp_mls/src/groups/device_sync/message_sync.rs index 053dea448..c4236405c 100644 --- a/xmtp_mls/src/groups/device_sync/message_sync.rs +++ b/xmtp_mls/src/groups/device_sync/message_sync.rs @@ -27,8 +27,7 @@ where &self, conn: &DbConnection, ) -> Result, DeviceSyncError> { - let groups = - conn.find_groups(GroupQueryArgs::default().conversation_type(ConversationType::Group))?; + let groups = conn.find_groups(GroupQueryArgs::default())?; let mut all_messages = vec![]; for StoredGroup { id, .. } in groups.into_iter() { diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 54273a942..e633fed65 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -1670,16 +1670,23 @@ pub(crate) mod tests { wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); use diesel::connection::SimpleConnection; + use diesel::query_dsl::methods::FilterDsl; + use diesel::{ExpressionMethods, RunQueryDsl}; use futures::future::join_all; use prost::Message; - use std::sync::Arc; + use std::{sync::Arc, time::Duration}; use wasm_bindgen_test::wasm_bindgen_test; use xmtp_common::assert_err; use xmtp_content_types::{group_updated::GroupUpdatedCodec, ContentCodec}; use xmtp_cryptography::utils::generate_local_wallet; - use xmtp_proto::xmtp::mls::api::v1::group_message::Version; + use xmtp_proto::xmtp::mls::message_contents::plaintext_envelope::Content; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; + use xmtp_proto::xmtp::mls::{ + api::v1::group_message::Version, message_contents::PlaintextEnvelope, + }; + use crate::storage::group::StoredGroup; + use crate::storage::schema::{group_messages, groups}; use crate::{ builder::ClientBuilder, groups::{ @@ -2043,6 +2050,66 @@ pub(crate) mod tests { }); } + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] + async fn test_dm_stitching() { + let alix_wallet = generate_local_wallet(); + let alix = ClientBuilder::new_test_client(&alix_wallet).await; + let alix_provider = alix.mls_provider().unwrap(); + let alix_conn = alix_provider.conn_ref(); + + let bo_wallet = generate_local_wallet(); + let bo = ClientBuilder::new_test_client(&bo_wallet).await; + let bo_provider = bo.mls_provider().unwrap(); + + let bo_dm = bo + .create_dm_by_inbox_id(&bo_provider, alix.inbox_id().to_string()) + .await + .unwrap(); + let alix_dm = alix + .create_dm_by_inbox_id(&alix_provider, bo.inbox_id().to_string()) + .await + .unwrap(); + + bo_dm.send_message(b"Hello there").await.unwrap(); + std::thread::sleep(Duration::from_millis(20)); + alix_dm + .send_message(b"No, let's use this dm") + .await + .unwrap(); + + alix.sync_all_welcomes_and_groups(&alix_provider, None) + .await + .unwrap(); + + // The dm shows up + let alix_groups = alix_conn + .raw_query(|conn| groups::table.load::(conn)) + .unwrap(); + assert_eq!(alix_groups.len(), 2); + // They should have the same ID + assert_eq!(alix_groups[0].dm_id, alix_groups[1].dm_id); + + // The dm is filtered out up + let alix_filtered_groups = alix_conn.find_groups(&GroupQueryArgs::default()).unwrap(); + assert_eq!(alix_filtered_groups.len(), 1); + + let alix_msgs = alix_conn + .raw_query(|conn| { + group_messages::table + .filter(group_messages::kind.eq(GroupMessageKind::Application)) + .load::(conn) + }) + .unwrap(); + + assert_eq!(alix_msgs.len(), 2); + + let msg = String::from_utf8_lossy(&alix_msgs[1].decrypted_message_bytes); + assert_eq!(msg, "Hello there"); + + let msg = String::from_utf8_lossy(&alix_msgs[0].decrypted_message_bytes); + assert_eq!(msg, "No, let's use this dm"); + } + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_inbox() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index a89699eef..c624943d0 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -133,6 +133,7 @@ pub struct GroupQueryArgs { pub conversation_type: Option, pub consent_state: Option, pub include_sync_groups: bool, + pub include_duplicate_dms: bool, } impl AsRef for GroupQueryArgs { @@ -220,21 +221,25 @@ impl DbConnection { conversation_type, consent_state, include_sync_groups, + include_duplicate_dms, } = args.as_ref(); let mut query = groups_dsl::groups .filter(groups_dsl::conversation_type.ne(ConversationType::Sync)) // Group by dm_id and grab the latest group (conversation stitching) - .filter(sql::( + .order(groups_dsl::created_at_ns.asc()) + .into_boxed(); + + if !include_duplicate_dms { + query = query.filter(sql::( "id IN ( SELECT id FROM groups GROUP BY CASE WHEN dm_id IS NULL THEN id ELSE dm_id END ORDER BY last_message_ns DESC )", - )) - .order(groups_dsl::created_at_ns.asc()) - .into_boxed(); + )); + } if let Some(limit) = limit { query = query.limit(*limit); @@ -581,7 +586,10 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use std::sync::atomic::{AtomicU16, Ordering}; + use std::{ + sync::atomic::{AtomicU16, Ordering}, + time::Duration, + }; use super::*; use crate::{ @@ -620,24 +628,21 @@ pub(crate) mod tests { } } - static INBOX_ID: AtomicU16 = AtomicU16::new(2); + static TARGET_INBOX_ID: AtomicU16 = AtomicU16::new(2); /// Generate a test dm group pub fn generate_dm(state: Option) -> StoredGroup { - let id = rand_vec::<24>(); - let created_at_ns = now_ns(); - let membership_state = state.unwrap_or(GroupMembershipState::Allowed); let members = DmMembers { member_one_inbox_id: "placeholder_inbox_id_1".to_string(), member_two_inbox_id: format!( "placeholder_inbox_id_{}", - INBOX_ID.fetch_add(1, Ordering::SeqCst) + TARGET_INBOX_ID.fetch_add(1, Ordering::SeqCst) ), }; StoredGroup::new( - id, - created_at_ns, - membership_state, + rand_vec::<24>(), + now_ns(), + state.unwrap_or(GroupMembershipState::Allowed), "placeholder_address".to_string(), Some(members), ) @@ -699,6 +704,42 @@ pub(crate) mod tests { }) .await } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + async fn test_dm_stitching() { + with_connection(|conn| { + let dm1 = StoredGroup::new( + rand_vec::<24>(), + now_ns(), + GroupMembershipState::Allowed, + "placeholder_address".to_string(), + Some(DmMembers { + member_one_inbox_id: "thats_me".to_string(), + member_two_inbox_id: "some_wise_guy".to_string(), + }), + ); + dm1.store(conn).unwrap(); + + let dm2 = StoredGroup::new( + rand_vec::<24>(), + now_ns(), + GroupMembershipState::Allowed, + "placeholder_address".to_string(), + Some(DmMembers { + member_one_inbox_id: "some_wise_guy".to_string(), + member_two_inbox_id: "thats_me".to_string(), + }), + ); + dm2.store(conn).unwrap(); + + let all_groups = conn.find_groups(GroupQueryArgs::default()).unwrap(); + + assert_eq!(all_groups.len(), 1); + }) + .await; + } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_find_groups() { From 4c72ddce2144f47bf70cae4c6c56d66c1eeb20a4 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 17:22:19 -0500 Subject: [PATCH 19/36] cleanup --- bindings_node/src/message.rs | 2 +- xmtp_mls/src/groups/group_metadata.rs | 8 +++++--- xmtp_mls/src/groups/mod.rs | 10 +++------- xmtp_mls/src/storage/encrypted_store/group.rs | 7 ++----- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/bindings_node/src/message.rs b/bindings_node/src/message.rs index 9b6e3134f..0d8172a50 100644 --- a/bindings_node/src/message.rs +++ b/bindings_node/src/message.rs @@ -85,7 +85,7 @@ impl From for MsgQueryArgs { MsgQueryArgs { sent_before_ns: opts.sent_before_ns, sent_after_ns: opts.sent_after_ns, - delivery_status: delivery_status, + delivery_status, limit: opts.limit, direction, ..Default::default() diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index bdfe885aa..a5db2534f 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use openmls::{extensions::Extensions, group::MlsGroup as OpenMlsGroup}; use prost::Message; use thiserror::Error; @@ -188,12 +190,12 @@ where } } -impl ToString for DmMembers +impl Display for DmMembers where Id: AsRef, { - fn to_string(&self) -> String { - String::from(self) + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", String::from(self)) } } diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index e633fed65..b41218bba 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -1136,7 +1136,7 @@ impl MlsGroup { &dm_id[..inbox_id.len()] }; - return Ok(target_inbox.to_string()); + Ok(target_inbox.to_string()) } /// Find the `consent_state` of the group @@ -1674,16 +1674,13 @@ pub(crate) mod tests { use diesel::{ExpressionMethods, RunQueryDsl}; use futures::future::join_all; use prost::Message; - use std::{sync::Arc, time::Duration}; + use std::sync::Arc; use wasm_bindgen_test::wasm_bindgen_test; use xmtp_common::assert_err; use xmtp_content_types::{group_updated::GroupUpdatedCodec, ContentCodec}; use xmtp_cryptography::utils::generate_local_wallet; - use xmtp_proto::xmtp::mls::message_contents::plaintext_envelope::Content; + use xmtp_proto::xmtp::mls::api::v1::group_message::Version; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; - use xmtp_proto::xmtp::mls::{ - api::v1::group_message::Version, message_contents::PlaintextEnvelope, - }; use crate::storage::group::StoredGroup; use crate::storage::schema::{group_messages, groups}; @@ -2071,7 +2068,6 @@ pub(crate) mod tests { .unwrap(); bo_dm.send_message(b"Hello there").await.unwrap(); - std::thread::sleep(Duration::from_millis(20)); alix_dm .send_message(b"No, let's use this dm") .await diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index c624943d0..323c5d644 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -577,7 +577,7 @@ impl DmIdExt for String { &dm_id[..id.len()] }; - return target_inbox.to_string(); + target_inbox.to_string() } } @@ -586,10 +586,7 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use std::{ - sync::atomic::{AtomicU16, Ordering}, - time::Duration, - }; + use std::sync::atomic::{AtomicU16, Ordering}; use super::*; use crate::{ From 9cefbc5eed68d80bffdfc11246bc97ec2033da3a Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 17:26:52 -0500 Subject: [PATCH 20/36] cleanup --- xmtp_mls/src/groups/mod.rs | 15 +++------------ xmtp_mls/src/storage/encrypted_store/group.rs | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index b41218bba..e8712208d 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -58,7 +58,7 @@ use self::{ intents::IntentError, validated_commit::CommitValidationError, }; -use crate::storage::StorageError; +use crate::storage::{group::DmIdExt, StorageError}; use xmtp_common::time::now_ns; use xmtp_proto::xmtp::mls::{ api::v1::{ @@ -1126,17 +1126,8 @@ impl MlsGroup { .ok_or(GroupError::GroupNotFound)?; let inbox_id = self.client.inbox_id(); // drop the "dm:" - let dm_id = &group.dm_id.ok_or(GroupError::GroupNotFound)?[3..]; - - // If my inbox id is the first half, return the second half, otherwise return first half - let target_inbox = if dm_id[..inbox_id.len()] == *inbox_id { - // + 1 because there is a colon (:) - &dm_id[(inbox_id.len() + 1)..] - } else { - &dm_id[..inbox_id.len()] - }; - - Ok(target_inbox.to_string()) + let dm_id = &group.dm_id.ok_or(GroupError::GroupNotFound)?; + Ok(dm_id.other_inbox_id(inbox_id)) } /// Find the `consent_state` of the group diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 323c5d644..57b4626cc 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -562,7 +562,7 @@ impl std::fmt::Display for ConversationType { } pub trait DmIdExt { - fn other_inbox_id(&self, other: &str) -> String; + fn other_inbox_id(&self, id: &str) -> String; } impl DmIdExt for String { fn other_inbox_id(&self, id: &str) -> String { From 17f534e3b94da38e375c7959f8bcb06c11605266 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 17:30:09 -0500 Subject: [PATCH 21/36] lint --- xmtp_mls/src/groups/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index e8712208d..8e8607927 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -2077,7 +2077,7 @@ pub(crate) mod tests { assert_eq!(alix_groups[0].dm_id, alix_groups[1].dm_id); // The dm is filtered out up - let alix_filtered_groups = alix_conn.find_groups(&GroupQueryArgs::default()).unwrap(); + let alix_filtered_groups = alix_conn.find_groups(GroupQueryArgs::default()).unwrap(); assert_eq!(alix_filtered_groups.len(), 1); let alix_msgs = alix_conn From 75c9f0d9aee93a78058af1e30fd307624859fe09 Mon Sep 17 00:00:00 2001 From: Dakota Brink <779390+codabrink@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:05:42 -0500 Subject: [PATCH 22/36] Update xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql Co-authored-by: Andrew Plaza --- xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index 63f8191ca..82c1226dd 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -17,7 +17,7 @@ DROP INDEX IF EXISTS idx_dm_target; ALTER TABLE groups DROP COLUMN dm_inbox_id; -- Create a trigger to auto-update group table on insert -CREATE TRIGGER msg_iserted +CREATE TRIGGER msg_inserted AFTER INSERT ON group_messages BEGIN UPDATE groups From 3dfef1fa358f3e07df70ff840f1796224046cd8b Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 22:10:56 -0500 Subject: [PATCH 23/36] better test --- xmtp_mls/src/groups/mod.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 8e8607927..a8df2fc4f 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -2077,23 +2077,24 @@ pub(crate) mod tests { assert_eq!(alix_groups[0].dm_id, alix_groups[1].dm_id); // The dm is filtered out up - let alix_filtered_groups = alix_conn.find_groups(GroupQueryArgs::default()).unwrap(); + let mut alix_filtered_groups = alix_conn.find_groups(GroupQueryArgs::default()).unwrap(); assert_eq!(alix_filtered_groups.len(), 1); - let alix_msgs = alix_conn - .raw_query(|conn| { - group_messages::table - .filter(group_messages::kind.eq(GroupMessageKind::Application)) - .load::(conn) + let dm_group = alix_filtered_groups.pop().unwrap(); + let dm_group = alix.group(dm_group.id).unwrap(); + let alix_msgs = dm_group + .find_messages(&MsgQueryArgs { + kind: Some(GroupMessageKind::Application), + ..Default::default() }) .unwrap(); assert_eq!(alix_msgs.len(), 2); - let msg = String::from_utf8_lossy(&alix_msgs[1].decrypted_message_bytes); + let msg = String::from_utf8_lossy(&alix_msgs[0].decrypted_message_bytes); assert_eq!(msg, "Hello there"); - let msg = String::from_utf8_lossy(&alix_msgs[0].decrypted_message_bytes); + let msg = String::from_utf8_lossy(&alix_msgs[1].decrypted_message_bytes); assert_eq!(msg, "No, let's use this dm"); } From fa4c810b82c5b5fbcadef8c2218483f8c6c1dacb Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 22:36:04 -0500 Subject: [PATCH 24/36] test the timestamp --- xmtp_mls/src/groups/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index a8df2fc4f..5893d71a9 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -1668,6 +1668,7 @@ pub(crate) mod tests { use std::sync::Arc; use wasm_bindgen_test::wasm_bindgen_test; use xmtp_common::assert_err; + use xmtp_common::time::now_ns; use xmtp_content_types::{group_updated::GroupUpdatedCodec, ContentCodec}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_proto::xmtp::mls::api::v1::group_message::Version; @@ -2081,6 +2082,11 @@ pub(crate) mod tests { assert_eq!(alix_filtered_groups.len(), 1); let dm_group = alix_filtered_groups.pop().unwrap(); + + let now = now_ns(); + let one_second = 1_000_000_000; + assert!(((now - one_second)..(now + one_second)).contains(&dm_group.last_message_ns)); + let dm_group = alix.group(dm_group.id).unwrap(); let alix_msgs = dm_group .find_messages(&MsgQueryArgs { From 55644b8197d3221290e72646fb4bdeef209f4c09 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 23:26:50 -0500 Subject: [PATCH 25/36] cleanup --- xmtp_mls/src/groups/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 5893d71a9..6c3920e14 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -1125,7 +1125,6 @@ impl MlsGroup { .find_group(self.group_id.clone())? .ok_or(GroupError::GroupNotFound)?; let inbox_id = self.client.inbox_id(); - // drop the "dm:" let dm_id = &group.dm_id.ok_or(GroupError::GroupNotFound)?; Ok(dm_id.other_inbox_id(inbox_id)) } From 4959fbe053ab8bf93ce16f8158d3a18841fca8f2 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 23:30:11 -0500 Subject: [PATCH 26/36] move comment --- xmtp_mls/src/storage/encrypted_store/group.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 57b4626cc..f234483f8 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -226,11 +226,11 @@ impl DbConnection { let mut query = groups_dsl::groups .filter(groups_dsl::conversation_type.ne(ConversationType::Sync)) - // Group by dm_id and grab the latest group (conversation stitching) .order(groups_dsl::created_at_ns.asc()) .into_boxed(); if !include_duplicate_dms { + // Group by dm_id and grab the latest group (conversation stitching) query = query.filter(sql::( "id IN ( SELECT id From e820767b251398b78012ad476c9fd980090fd2a4 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Thu, 19 Dec 2024 23:34:51 -0500 Subject: [PATCH 27/36] more cleanup --- xmtp_mls/src/groups/group_metadata.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/xmtp_mls/src/groups/group_metadata.rs b/xmtp_mls/src/groups/group_metadata.rs index a5db2534f..7fe04424c 100644 --- a/xmtp_mls/src/groups/group_metadata.rs +++ b/xmtp_mls/src/groups/group_metadata.rs @@ -168,16 +168,7 @@ where Id: AsRef, { fn from(members: &DmMembers) -> Self { - let mut inbox_ids = [ - members.member_one_inbox_id.as_ref(), - members.member_two_inbox_id.as_ref(), - ] - .into_iter() - .map(str::to_lowercase) - .collect::>(); - inbox_ids.sort(); - - format!("dm:{}", inbox_ids.join(":")) + format!("{members}") } } @@ -186,7 +177,7 @@ where Id: AsRef, { fn from(members: DmMembers) -> Self { - String::from(&members) + format!("{members}") } } @@ -195,7 +186,16 @@ where Id: AsRef, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", String::from(self)) + let mut inbox_ids = [ + self.member_one_inbox_id.as_ref(), + self.member_two_inbox_id.as_ref(), + ] + .into_iter() + .map(str::to_lowercase) + .collect::>(); + inbox_ids.sort(); + + write!(f, "dm:{}", inbox_ids.join(":")) } } From a287b35398ba5e7366c0e2cc326417d390909858 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 13:32:31 -0500 Subject: [PATCH 28/36] test it --- .../2024-12-18-170645_add_dm_id/up.sql | 2 +- xmtp_mls/src/groups/mod.rs | 4 +- xmtp_mls/src/storage/encrypted_store/group.rs | 8 +- xmtp_mls/src/storage/encrypted_store/mod.rs | 84 ++++++++++++++++++- .../src/storage/encrypted_store/schema.rs | 2 +- 5 files changed, 89 insertions(+), 11 deletions(-) diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql index 82c1226dd..0adda6092 100644 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql @@ -1,5 +1,5 @@ ALTER TABLE groups ADD COLUMN dm_id TEXT; -ALTER TABLE groups ADD COLUMN last_message_ns BIGINT NOT NULL DEFAULT ((strftime('%s', 'now') * 1000000000) + (strftime('%f', 'now') * 1000000)); +ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; -- Fill the dm_id column UPDATE groups diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 6c3920e14..7478b23f5 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -2084,7 +2084,9 @@ pub(crate) mod tests { let now = now_ns(); let one_second = 1_000_000_000; - assert!(((now - one_second)..(now + one_second)).contains(&dm_group.last_message_ns)); + assert!( + ((now - one_second)..(now + one_second)).contains(&dm_group.last_message_ns.unwrap()) + ); let dm_group = alix.group(dm_group.id).unwrap(); let alix_msgs = dm_group diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index f234483f8..680c4acd3 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -46,7 +46,7 @@ pub struct StoredGroup { /// The inbox_id of the DM target pub dm_id: Option, /// Timestamp of when the last message was sent for this group (updated automatically in a trigger) - pub last_message_ns: i64, + pub last_message_ns: Option, } impl_fetch!(StoredGroup, groups, Vec); @@ -73,7 +73,7 @@ impl StoredGroup { welcome_id: Some(welcome_id), rotated_at_ns: 0, dm_id: dm_members.map(String::from), - last_message_ns: now_ns(), + last_message_ns: Some(now_ns()), } } @@ -98,7 +98,7 @@ impl StoredGroup { welcome_id: None, rotated_at_ns: 0, dm_id: dm_members.map(String::from), - last_message_ns: now_ns(), + last_message_ns: Some(now_ns()), } } @@ -119,7 +119,7 @@ impl StoredGroup { welcome_id: None, rotated_at_ns: 0, dm_id: None, - last_message_ns: now_ns(), + last_message_ns: Some(now_ns()), } } } diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index e89e306bd..d072321c0 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -124,9 +124,9 @@ impl EncryptedMessageStore { ) -> Result { tracing::info!("Setting up DB connection pool"); let db = native::NativeDb::new(&opts, enc_key)?; - let mut this = Self { db, opts }; - this.init_db()?; - Ok(this) + let mut store = Self { db, opts }; + store.init_db()?; + Ok(store) } } @@ -473,6 +473,9 @@ where pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use diesel::sql_types::{BigInt, Blob, Integer, Text}; + use group::ConversationType; + use schema::groups; use wasm_bindgen_test::wasm_bindgen_test; use super::*; @@ -483,7 +486,7 @@ pub(crate) mod tests { }, Fetch, Store, StreamHandle as _, XmtpOpenMlsProvider, }; - use xmtp_common::{rand_vec, tmp_path}; + use xmtp_common::{rand_vec, time::now_ns, tmp_path}; /// Test harness that loads an Ephemeral store. pub async fn with_connection(fun: F) -> R @@ -586,6 +589,79 @@ pub(crate) mod tests { EncryptedMessageStore::remove_db_files(db_path) } + #[tokio::test] + async fn test_dm_id_migration() { + let db_path = tmp_path(); + let opts = StorageOption::Persistent(db_path.clone()); + + let db = + native::NativeDb::new(&opts, Some(EncryptedMessageStore::generate_enc_key())).unwrap(); + let store = EncryptedMessageStore { db, opts }; + store.db.validate(&store.opts).unwrap(); + + store + .db + .conn() + .unwrap() + .raw_query(|conn| { + for _ in 0..15 { + conn.run_next_migration(MIGRATIONS)?; + } + + sql_query( + r#" + INSERT INTO groups ( + id, + created_at_ns, + membership_state, + installations_last_checked, + added_by_inbox_id, + rotated_at_ns, + conversation_type, + dm_inbox_id + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)"#, + ) + .bind::(vec![1, 2, 3, 4, 5]) + .bind::(now_ns()) + .bind::(GroupMembershipState::Allowed as i32) + .bind::(now_ns()) + .bind::("121212") + .bind::(now_ns()) + .bind::(ConversationType::Dm as i32) + .bind::("98765") + .execute(conn)?; + + Ok::<_, StorageError>(()) + }) + .unwrap(); + + let conn = store.db.conn().unwrap(); + + let inbox_id = "inbox_id"; + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) + .store(&conn) + .unwrap(); + + let fetched_identity: StoredIdentity = conn.fetch(&()).unwrap().unwrap(); + assert_eq!(fetched_identity.inbox_id, inbox_id); + + store + .db + .conn() + .unwrap() + .raw_query(|conn| { + conn.run_pending_migrations(MIGRATIONS)?; + Ok::<_, StorageError>(()) + }) + .unwrap(); + + let groups = conn + .raw_query(|conn| groups::table.load::(conn)) + .unwrap(); + assert_eq!(groups.len(), 1); + assert_eq!(&*groups[0].dm_id.as_ref().unwrap(), "dm:98765:inbox_id"); + } + #[tokio::test] async fn mismatched_encryption_key() { let mut enc_key = [1u8; 32]; diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 423957e5b..983f559a3 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -55,7 +55,7 @@ diesel::table! { rotated_at_ns -> BigInt, conversation_type -> Integer, dm_id -> Nullable, - last_message_ns -> BigInt, + last_message_ns -> Nullable, } } From 547786546fb499f681d8c4e8dc55040bef682255 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 13:50:23 -0500 Subject: [PATCH 29/36] wasm --- .../storage/encrypted_store/group_message.rs | 18 +++++++++++++----- xmtp_mls/src/storage/encrypted_store/mod.rs | 4 ++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index 0b445e7a9..77cc34217 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -221,7 +221,7 @@ pub struct MsgQueryArgs { pub delivery_status: Option, pub limit: Option, pub direction: Option, - content_types: Option>, + pub content_types: Option>, } impl DbConnection { @@ -628,7 +628,10 @@ pub(crate) mod tests { let text_messages = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().content_types(vec![ContentType::Text]), + &MsgQueryArgs { + content_types: vec![ContentType::Text], + ..Default::default() + }, ) .unwrap(); assert_eq!(text_messages.len(), 1); @@ -639,8 +642,10 @@ pub(crate) mod tests { let membership_messages = conn .get_group_messages( &group.id, - &MsgQueryArgs::default() - .content_types(vec![ContentType::GroupMembershipChange]), + &MsgQueryArgs { + content_types: vec![ContentType::GroupMembershipChange], + ..Default::default() + }, ) .unwrap(); assert_eq!(membership_messages.len(), 1); @@ -654,7 +659,10 @@ pub(crate) mod tests { let updated_messages = conn .get_group_messages( &group.id, - &MsgQueryArgs::default().content_types(vec![ContentType::GroupUpdated]), + &MsgQueryArgs { + content_types: vec![ContentType::GroupUpdated], + ..Default::default() + }, ) .unwrap(); assert_eq!(updated_messages.len(), 1); diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index d072321c0..fa2cc4215 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -594,8 +594,12 @@ pub(crate) mod tests { let db_path = tmp_path(); let opts = StorageOption::Persistent(db_path.clone()); + #[cfg(not(target_arch = "wasm32"))] let db = native::NativeDb::new(&opts, Some(EncryptedMessageStore::generate_enc_key())).unwrap(); + #[cfg(target_arch = "wasm32")] + let db = wasm::WasmDb::new(&opts).await?; + let store = EncryptedMessageStore { db, opts }; store.db.validate(&store.opts).unwrap(); From b234f27ca803e5c0e756d61729e6fc6aaa713567 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 14:13:07 -0500 Subject: [PATCH 30/36] test fix --- xmtp_mls/src/storage/encrypted_store/group_message.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index 77cc34217..fd0e8a26e 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -629,7 +629,7 @@ pub(crate) mod tests { .get_group_messages( &group.id, &MsgQueryArgs { - content_types: vec![ContentType::Text], + content_types: Some(vec![ContentType::Text]), ..Default::default() }, ) @@ -643,7 +643,7 @@ pub(crate) mod tests { .get_group_messages( &group.id, &MsgQueryArgs { - content_types: vec![ContentType::GroupMembershipChange], + content_types: Some(vec![ContentType::GroupMembershipChange]), ..Default::default() }, ) @@ -660,7 +660,7 @@ pub(crate) mod tests { .get_group_messages( &group.id, &MsgQueryArgs { - content_types: vec![ContentType::GroupUpdated], + content_types: Some(vec![ContentType::GroupUpdated]), ..Default::default() }, ) From 918d9dad67229f833b96e8a964170785457a2a69 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 14:37:45 -0500 Subject: [PATCH 31/36] test fix --- bindings_ffi/src/mls.rs | 1 + xmtp_mls/src/groups/mod.rs | 5 ++--- xmtp_mls/src/storage/encrypted_store/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index ca845d9c6..f7f38633f 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -1331,6 +1331,7 @@ impl FfiConversation { kind, delivery_status, direction, + ..Default::default() })? .into_iter() .map(|msg| msg.into()) diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index d51f3bf3f..8b2696c0a 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -1708,8 +1708,7 @@ pub(crate) mod tests { wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); use diesel::connection::SimpleConnection; - use diesel::query_dsl::methods::FilterDsl; - use diesel::{ExpressionMethods, RunQueryDsl}; + use diesel::RunQueryDsl; use futures::future::join_all; use prost::Message; use std::sync::Arc; @@ -1722,7 +1721,7 @@ pub(crate) mod tests { use xmtp_proto::xmtp::mls::message_contents::EncodedContent; use crate::storage::group::StoredGroup; - use crate::storage::schema::{group_messages, groups}; + use crate::storage::schema::groups; use crate::{ builder::ClientBuilder, groups::{ diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index fa2cc4215..54db602b7 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -598,7 +598,7 @@ pub(crate) mod tests { let db = native::NativeDb::new(&opts, Some(EncryptedMessageStore::generate_enc_key())).unwrap(); #[cfg(target_arch = "wasm32")] - let db = wasm::WasmDb::new(&opts).await?; + let db = wasm::WasmDb::new(&opts).await.unwrap(); let store = EncryptedMessageStore { db, opts }; store.db.validate(&store.opts).unwrap(); From 3dbcd67e15edce684dbf9724f5f4672d7652cbd2 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 17:14:36 -0500 Subject: [PATCH 32/36] fix migration headaches --- xmtp_mls/diesel.toml | 2 +- .../2024-12-18-170645_add_dm_id/up.sql | 26 --- .../down.sql | 2 +- .../up.sql | 6 +- .../down.sql | 0 .../2024-12-20-214747_add_dm_id/up.sql | 76 +++++++++ .../encrypted_store/conversation_list.rs | 4 +- xmtp_mls/src/storage/encrypted_store/mod.rs | 1 + .../src/storage/encrypted_store/schema.rs | 152 +----------------- .../src/storage/encrypted_store/schema_gen.rs | 146 +++++++++++++++++ 10 files changed, 233 insertions(+), 182 deletions(-) delete mode 100644 xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql rename xmtp_mls/migrations/{2024-12-18-170645_add_dm_id => 2024-12-20-214747_add_dm_id}/down.sql (100%) create mode 100644 xmtp_mls/migrations/2024-12-20-214747_add_dm_id/up.sql create mode 100644 xmtp_mls/src/storage/encrypted_store/schema_gen.rs diff --git a/xmtp_mls/diesel.toml b/xmtp_mls/diesel.toml index 3c3d01e02..148125b34 100644 --- a/xmtp_mls/diesel.toml +++ b/xmtp_mls/diesel.toml @@ -2,7 +2,7 @@ # see https://diesel.rs/guides/configuring-diesel-cli [print_schema] -file = "src/storage/encrypted_store/schema.rs" +file = "src/storage/encrypted_store/schema_gen.rs" [migrations_directory] dir = "migrations" diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql deleted file mode 100644 index 0adda6092..000000000 --- a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/up.sql +++ /dev/null @@ -1,26 +0,0 @@ -ALTER TABLE groups ADD COLUMN dm_id TEXT; -ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; - --- Fill the dm_id column -UPDATE groups -SET dm_id = 'dm:' || - LOWER( - CASE - WHEN LOWER((SELECT inbox_id FROM identity)) < LOWER(dm_inbox_id) - THEN (SELECT inbox_id FROM identity) || ':' || dm_inbox_id - ELSE dm_inbox_id || ':' || (SELECT inbox_id FROM identity) - END - ) -WHERE dm_inbox_id IS NOT NULL; - -DROP INDEX IF EXISTS idx_dm_target; -ALTER TABLE groups DROP COLUMN dm_inbox_id; - --- Create a trigger to auto-update group table on insert -CREATE TRIGGER msg_inserted -AFTER INSERT ON group_messages -BEGIN - UPDATE groups - SET last_message_ns = (strftime('%s', 'now') * 1000000000) + (strftime('%f', 'now') * 1000000) - WHERE id = NEW.group_id; -END; diff --git a/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/down.sql b/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/down.sql index 64a220cd8..cfae20537 100644 --- a/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/down.sql +++ b/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/down.sql @@ -1 +1 @@ -DROP VIEW IF EXISTS conversation_list; \ No newline at end of file +DROP VIEW IF EXISTS conversation_list; diff --git a/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/up.sql b/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/up.sql index 01d5433ab..d7a50ecea 100644 --- a/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/up.sql +++ b/xmtp_mls/migrations/2024-12-20-143210_create_conversation_list_view/up.sql @@ -42,6 +42,6 @@ SELECT rm.authority_id FROM groups g - LEFT JOIN ranked_messages rm - ON g.id = rm.group_id AND rm.row_num = 1 -ORDER BY COALESCE(rm.sent_at_ns, g.created_at_ns) DESC; \ No newline at end of file + LEFT JOIN ranked_messages rm + ON g.id = rm.group_id AND rm.row_num = 1 +ORDER BY COALESCE(rm.sent_at_ns, g.created_at_ns) DESC; diff --git a/xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql b/xmtp_mls/migrations/2024-12-20-214747_add_dm_id/down.sql similarity index 100% rename from xmtp_mls/migrations/2024-12-18-170645_add_dm_id/down.sql rename to xmtp_mls/migrations/2024-12-20-214747_add_dm_id/down.sql diff --git a/xmtp_mls/migrations/2024-12-20-214747_add_dm_id/up.sql b/xmtp_mls/migrations/2024-12-20-214747_add_dm_id/up.sql new file mode 100644 index 000000000..d563c3216 --- /dev/null +++ b/xmtp_mls/migrations/2024-12-20-214747_add_dm_id/up.sql @@ -0,0 +1,76 @@ +DROP VIEW IF EXISTS conversation_list; +ALTER TABLE groups ADD COLUMN dm_id TEXT; +ALTER TABLE groups ADD COLUMN last_message_ns BIGINT; + +-- Fill the dm_id column +UPDATE groups +SET dm_id = 'dm:' || + LOWER( + CASE + WHEN LOWER((SELECT inbox_id FROM identity)) < LOWER(dm_inbox_id) + THEN (SELECT inbox_id FROM identity) || ':' || dm_inbox_id + ELSE dm_inbox_id || ':' || (SELECT inbox_id FROM identity) + END + ) +WHERE dm_inbox_id IS NOT NULL; + +DROP INDEX IF EXISTS idx_dm_target; +ALTER TABLE groups DROP COLUMN dm_inbox_id; + +-- Create a trigger to auto-update group table on insert +CREATE TRIGGER msg_inserted +AFTER INSERT ON group_messages +BEGIN + UPDATE groups + SET last_message_ns = (strftime('%s', 'now') * 1000000000) + (strftime('%f', 'now') * 1000000) + WHERE id = NEW.group_id; +END; + + +CREATE VIEW conversation_list AS +WITH ranked_messages AS ( + SELECT + gm.group_id, + gm.id AS message_id, + gm.decrypted_message_bytes, + gm.sent_at_ns, + gm.kind AS message_kind, + gm.sender_installation_id, + gm.sender_inbox_id, + gm.delivery_status, + gm.content_type, + gm.version_major, + gm.version_minor, + gm.authority_id, + ROW_NUMBER() OVER (PARTITION BY gm.group_id ORDER BY gm.sent_at_ns DESC) AS row_num + FROM + group_messages gm + WHERE + gm.kind = 1 +) +SELECT + g.id AS id, + g.created_at_ns, + g.membership_state, + g.installations_last_checked, + g.added_by_inbox_id, + g.welcome_id, + g.dm_id, + g.rotated_at_ns, + g.conversation_type, + rm.message_id, + rm.decrypted_message_bytes, + rm.sent_at_ns, + rm.message_kind, + rm.sender_installation_id, + rm.sender_inbox_id, + rm.delivery_status, + rm.content_type, + rm.version_major, + rm.version_minor, + rm.authority_id +FROM + groups g + LEFT JOIN ranked_messages rm + ON g.id = rm.group_id AND rm.row_num = 1 +ORDER BY COALESCE(rm.sent_at_ns, g.created_at_ns) DESC; diff --git a/xmtp_mls/src/storage/encrypted_store/conversation_list.rs b/xmtp_mls/src/storage/encrypted_store/conversation_list.rs index e82d2fd66..58f387a7c 100644 --- a/xmtp_mls/src/storage/encrypted_store/conversation_list.rs +++ b/xmtp_mls/src/storage/encrypted_store/conversation_list.rs @@ -1,6 +1,6 @@ +use super::schema::conversation_list::dsl::conversation_list; use crate::storage::group::{ConversationType, GroupMembershipState}; use crate::storage::group_message::{ContentType, DeliveryStatus, GroupMessageKind}; -use crate::storage::schema::conversation_list::dsl::conversation_list; use crate::storage::{DbConnection, StorageError}; use diesel::{QueryDsl, Queryable, RunQueryDsl, Table}; use serde::{Deserialize, Serialize}; @@ -23,7 +23,7 @@ pub struct ConversationListItem { /// The sequence id of the welcome message pub welcome_id: Option, /// The inbox_id of the DM target - pub dm_inbox_id: Option, + pub dm_id: Option, /// The last time the leaf node encryption key was rotated pub rotated_at_ns: i64, /// Enum, [`ConversationType`] signifies the group conversation type which extends to who can access it. diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index 14bb596b8..6dfe637c2 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -25,6 +25,7 @@ pub mod key_store_entry; mod native; pub mod refresh_state; pub mod schema; +mod schema_gen; #[cfg(not(target_arch = "wasm32"))] mod sqlcipher_connection; pub mod user_preferences; diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 679729bd2..5d2f68c30 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -1,133 +1,7 @@ -// @generated automatically by Diesel CLI. +pub use super::schema_gen::*; diesel::table! { - association_state (inbox_id, sequence_id) { - inbox_id -> Text, - sequence_id -> BigInt, - state -> Binary, - } -} - -diesel::table! { - consent_records (entity_type, entity) { - entity_type -> Integer, - state -> Integer, - entity -> Text, - } -} - -diesel::table! { - group_intents (id) { - id -> Integer, - kind -> Integer, - group_id -> Binary, - data -> Binary, - state -> Integer, - payload_hash -> Nullable, - post_commit_data -> Nullable, - publish_attempts -> Integer, - staged_commit -> Nullable, - published_in_epoch -> Nullable, - } -} - -diesel::table! { - group_messages (id) { - id -> Binary, - group_id -> Binary, - decrypted_message_bytes -> Binary, - sent_at_ns -> BigInt, - kind -> Integer, - sender_installation_id -> Binary, - sender_inbox_id -> Text, - delivery_status -> Integer, - content_type -> Integer, - version_minor -> Integer, - version_major -> Integer, - authority_id -> Text, - } -} - -diesel::table! { - groups (id) { - id -> Binary, - created_at_ns -> BigInt, - membership_state -> Integer, - installations_last_checked -> BigInt, - added_by_inbox_id -> Text, - welcome_id -> Nullable, - rotated_at_ns -> BigInt, - conversation_type -> Integer, - dm_id -> Nullable, - last_message_ns -> Nullable, - } -} - -diesel::table! { - identity (rowid) { - inbox_id -> Text, - installation_keys -> Binary, - credential_bytes -> Binary, - rowid -> Nullable, - } -} - -diesel::table! { - identity_updates (inbox_id, sequence_id) { - inbox_id -> Text, - sequence_id -> BigInt, - server_timestamp_ns -> BigInt, - payload -> Binary, - } -} - -diesel::table! { - key_package_history (id) { - id -> Integer, - key_package_hash_ref -> Binary, - created_at_ns -> BigInt, - } -} - -diesel::table! { - openmls_key_store (key_bytes) { - key_bytes -> Binary, - value_bytes -> Binary, - } -} - -diesel::table! { - openmls_key_value (version, key_bytes) { - version -> Integer, - key_bytes -> Binary, - value_bytes -> Binary, - } -} - -diesel::table! { - refresh_state (entity_id, entity_kind) { - entity_id -> Binary, - entity_kind -> Integer, - cursor -> BigInt, - } -} - -diesel::table! { - user_preferences (id) { - id -> Integer, - hmac_key -> Nullable, - } -} - -diesel::table! { - wallet_addresses (wallet_address) { - inbox_id -> Text, - wallet_address -> Text, - } -} - -diesel::table! { - conversation_list (id) { + conversation_list (id) { id -> Binary, created_at_ns -> BigInt, membership_state -> Integer, @@ -148,25 +22,5 @@ diesel::table! { version_major -> Nullable, version_minor -> Nullable, authority_id -> Nullable - } + } } - -diesel::joinable!(group_intents -> groups (group_id)); -diesel::joinable!(group_messages -> groups (group_id)); - -diesel::allow_tables_to_appear_in_same_query!( - association_state, - consent_records, - group_intents, - group_messages, - groups, - identity, - identity_updates, - key_package_history, - openmls_key_store, - openmls_key_value, - refresh_state, - user_preferences, - wallet_addresses, - conversation_list -); diff --git a/xmtp_mls/src/storage/encrypted_store/schema_gen.rs b/xmtp_mls/src/storage/encrypted_store/schema_gen.rs new file mode 100644 index 000000000..82a76b76a --- /dev/null +++ b/xmtp_mls/src/storage/encrypted_store/schema_gen.rs @@ -0,0 +1,146 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + association_state (inbox_id, sequence_id) { + inbox_id -> Text, + sequence_id -> BigInt, + state -> Binary, + } +} + +diesel::table! { + consent_records (entity_type, entity) { + entity_type -> Integer, + state -> Integer, + entity -> Text, + } +} + +diesel::table! { + group_intents (id) { + id -> Integer, + kind -> Integer, + group_id -> Binary, + data -> Binary, + state -> Integer, + payload_hash -> Nullable, + post_commit_data -> Nullable, + publish_attempts -> Integer, + staged_commit -> Nullable, + published_in_epoch -> Nullable, + } +} + +diesel::table! { + group_messages (id) { + id -> Binary, + group_id -> Binary, + decrypted_message_bytes -> Binary, + sent_at_ns -> BigInt, + kind -> Integer, + sender_installation_id -> Binary, + sender_inbox_id -> Text, + delivery_status -> Integer, + content_type -> Integer, + version_minor -> Integer, + version_major -> Integer, + authority_id -> Text, + } +} + +diesel::table! { + groups (id) { + id -> Binary, + created_at_ns -> BigInt, + membership_state -> Integer, + installations_last_checked -> BigInt, + added_by_inbox_id -> Text, + welcome_id -> Nullable, + rotated_at_ns -> BigInt, + conversation_type -> Integer, + dm_id -> Nullable, + last_message_ns -> Nullable, + } +} + +diesel::table! { + identity (rowid) { + inbox_id -> Text, + installation_keys -> Binary, + credential_bytes -> Binary, + rowid -> Nullable, + } +} + +diesel::table! { + identity_updates (inbox_id, sequence_id) { + inbox_id -> Text, + sequence_id -> BigInt, + server_timestamp_ns -> BigInt, + payload -> Binary, + } +} + +diesel::table! { + key_package_history (id) { + id -> Integer, + key_package_hash_ref -> Binary, + created_at_ns -> BigInt, + } +} + +diesel::table! { + openmls_key_store (key_bytes) { + key_bytes -> Binary, + value_bytes -> Binary, + } +} + +diesel::table! { + openmls_key_value (version, key_bytes) { + version -> Integer, + key_bytes -> Binary, + value_bytes -> Binary, + } +} + +diesel::table! { + refresh_state (entity_id, entity_kind) { + entity_id -> Binary, + entity_kind -> Integer, + cursor -> BigInt, + } +} + +diesel::table! { + user_preferences (id) { + id -> Integer, + hmac_key -> Nullable, + } +} + +diesel::table! { + wallet_addresses (wallet_address) { + inbox_id -> Text, + wallet_address -> Text, + } +} + +diesel::joinable!(group_intents -> groups (group_id)); +diesel::joinable!(group_messages -> groups (group_id)); + +diesel::allow_tables_to_appear_in_same_query!( + association_state, + consent_records, + group_intents, + group_messages, + groups, + identity, + identity_updates, + key_package_history, + openmls_key_store, + openmls_key_value, + refresh_state, + user_preferences, + wallet_addresses, +); From 70ac2b403d4af195d0a2b053f1c45dcef450431e Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 17:31:03 -0500 Subject: [PATCH 33/36] fix --- xmtp_mls/src/storage/encrypted_store/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/src/storage/encrypted_store/schema.rs b/xmtp_mls/src/storage/encrypted_store/schema.rs index 5d2f68c30..92b5dace1 100644 --- a/xmtp_mls/src/storage/encrypted_store/schema.rs +++ b/xmtp_mls/src/storage/encrypted_store/schema.rs @@ -8,7 +8,7 @@ diesel::table! { installations_last_checked -> BigInt, added_by_inbox_id -> Text, welcome_id -> Nullable, - dm_inbox_id -> Nullable, + dm_id -> Nullable, rotated_at_ns -> BigInt, conversation_type -> Integer, message_id -> Nullable, From ab0acf1eb0c3e303b0fd7abee9b176cccf482d3d Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 17:40:34 -0500 Subject: [PATCH 34/36] lint --- xmtp_mls/src/storage/encrypted_store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index 6dfe637c2..b50f09650 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -665,7 +665,7 @@ pub(crate) mod tests { .raw_query(|conn| groups::table.load::(conn)) .unwrap(); assert_eq!(groups.len(), 1); - assert_eq!(&*groups[0].dm_id.as_ref().unwrap(), "dm:98765:inbox_id"); + assert_eq!(&**groups[0].dm_id.as_ref().unwrap(), "dm:98765:inbox_id"); } #[tokio::test] From f37527462cb1fe5bc1710aafd55d225b097e3155 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 17:50:37 -0500 Subject: [PATCH 35/36] add option to include duplicate dms --- bindings_ffi/src/mls.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index 2da7652f3..1bcfd7699 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -592,15 +592,19 @@ pub struct FfiListConversationsOptions { pub created_before_ns: Option, pub limit: Option, pub consent_state: Option, + pub include_duplicate_dms: bool, } impl From for GroupQueryArgs { fn from(opts: FfiListConversationsOptions) -> GroupQueryArgs { - GroupQueryArgs::default() - .maybe_created_before_ns(opts.created_before_ns) - .maybe_created_after_ns(opts.created_after_ns) - .maybe_limit(opts.limit) - .maybe_consent_state(opts.consent_state.map(Into::into)) + GroupQueryArgs { + created_before_ns: opts.created_before_ns, + created_after_ns: opts.created_after_ns, + limit: opts.limit, + consent_state: opts.consent_state.map(Into::into), + include_duplicate_dms: opts.include_duplicate_dms, + ..Default::default() + } } } From 04ad4eda20fa5a09ca6d1b75f1cf7982e5ff2996 Mon Sep 17 00:00:00 2001 From: Dakota Brink Date: Fri, 20 Dec 2024 21:16:03 -0500 Subject: [PATCH 36/36] lint --- bindings_ffi/src/mls.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index fc43d7fdc..06d1f00a1 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -1397,7 +1397,6 @@ impl FfiConversation { content_types: opts .content_types .map(|types| types.into_iter().map(Into::into).collect()), - ..Default::default() })? .into_iter() .map(|msg| msg.into())