xmtp_mls/groups/mls_sync/
receive.rs1use super::*;
4
5impl<Context> MlsGroup<Context>
6where
7 Context: XmtpSharedContext,
8{
9 #[tracing::instrument(skip_all, level = "trace")]
11 pub async fn receive(&self) -> Result<ProcessSummary, GroupError> {
12 use xmtp_db::delivery::{DeliveryScope, QueryDelivery};
13 let db = self.context.db();
14 let mut position = db.current_delivery_cursor()?;
15 crate::subscriptions::barrier::receive_through_current(
16 &self.context,
17 vec![xmtp_proto::types::Topic::new_group_message(self.group_id)],
18 )
19 .await?;
20 let upper = db.current_delivery_cursor()?;
21 let settings = self.context.incoming_runtime().policy();
22 let mut summary = ProcessSummary::default();
23 loop {
24 let rows = db.replay_delivery_messages_bounded(
25 position,
26 &DeliveryScope::Groups(vec![self.group_id]),
27 xmtp_common::time::now_ns(),
28 settings.max_local_read_rows,
29 settings.max_local_read_bytes,
30 )?;
31 if rows.is_empty() {
32 break;
33 }
34 let mut reached_upper = false;
35 for row in rows {
36 if row.cursor.delivery_sequence > upper.delivery_sequence {
37 reached_upper = true;
38 break;
39 }
40 position = row.cursor;
41 let message = row.message;
42 summary.add_id(message.cursor());
43 summary.add(MessageIdentifier {
44 cursor: message.cursor(),
45 group_id: message.group_id,
46 created_ns: chrono::DateTime::from_timestamp_nanos(message.sent_at_ns),
47 previously_processed: false,
48 internal_id: Some(message.id),
49 group_context: None,
50 intent_kind: None,
51 });
52 }
53 if reached_upper || position.delivery_sequence >= upper.delivery_sequence {
54 break;
55 }
56 }
57 Ok(summary)
58 }
59
60 #[tracing::instrument(skip_all, level = "trace")]
61 pub(super) fn maybe_update_cursor(
62 &self,
63 db: &impl DbQuery,
64 message: &xmtp_proto::types::GroupMessage,
65 ) -> Result<bool, StorageError> {
66 let updated = db.update_cursor(
67 message.group_id,
68 xmtp_db::refresh_state::EntityKind::ApplicationMessage,
69 message.cursor,
70 )?;
71 if updated {
72 log_event!(
73 Event::GroupCursorUpdate,
74 self.context.installation_id(),
75 group_id = message.group_id.as_slice(),
76 cursor = message.cursor.0,
77 );
78 } else {
79 tracing::debug!("no cursor update required");
80 }
81 Ok(updated)
82 }
83
84 pub(super) fn save_transcript_message(
85 &self,
86 validated_commit: ValidatedCommit,
87 timestamp_ns: u64,
88 cursor: Cursor,
89 storage: &impl XmtpMlsStorageProvider,
90 ) -> Result<Option<(StoredGroupMessage, GroupUpdated)>, GroupMessageProcessingError> {
91 if validated_commit.is_empty() {
92 return Ok(None);
93 }
94 let sender_installation_id = validated_commit.actor_installation_id();
95 let sender_inbox_id = validated_commit.actor_inbox_id();
96
97 let pending_remove_users = &storage.db().get_pending_remove_users(&self.group_id)?;
98 let payload: GroupUpdated = validated_commit.into_with(pending_remove_users);
99 tracing::info!("Storing transcript message");
100 let encoded_payload = GroupUpdatedCodec::encode(payload.clone())?;
101 let mut encoded_payload_bytes = Vec::new();
102 encoded_payload.encode(&mut encoded_payload_bytes)?;
103
104 let message_id = calculate_message_id(
105 self.group_id,
106 encoded_payload_bytes.as_slice(),
107 ×tamp_ns.to_string(),
108 );
109 let content_type = encoded_payload.r#type.unwrap_or_else(|| {
110 tracing::warn!("Missing content type in encoded payload, using default values");
111 xmtp_proto::xmtp::mls::message_contents::ContentTypeId {
113 authority_id: "unknown".to_string(),
114 type_id: "unknown".to_string(),
115 version_major: 0,
116 version_minor: 0,
117 }
118 });
119
120 self.handle_metadata_update_from_commit(&payload.metadata_field_changes, storage)?;
121
122 if self.update_already_exists(&payload, storage)? {
124 return Ok(None);
125 }
126
127 let msg = StoredGroupMessage {
128 id: message_id,
129 group_id: self.group_id,
130 decrypted_message_bytes: encoded_payload_bytes,
131 sent_at_ns: timestamp_ns as i64,
132 kind: GroupMessageKind::MembershipChange,
133 sender_installation_id,
134 sender_inbox_id,
135 delivery_status: DeliveryStatus::Published,
136 content_type: content_type.type_id.into(),
137 version_major: content_type.version_major as i32,
138 version_minor: content_type.version_minor as i32,
139 authority_id: content_type.authority_id.to_string(),
140 reference_id: None,
141 sequence_id: cursor.0 as i64,
142 envelope_hash: None,
143 expiry_ns: None,
144 expire_at_ns: None,
145 inserted_at_ns: 0, should_push: true,
147 idempotency_key: timestamp_ns.to_string(),
149 };
150
151 msg.store_or_ignore(&storage.db())?;
152 Ok(Some((msg, payload)))
153 }
154
155 fn update_already_exists(
156 &self,
157 payload: &GroupUpdated,
158 storage: &impl XmtpMlsStorageProvider,
159 ) -> Result<bool, GroupMessageProcessingError> {
160 if self.dm_id.is_none() || payload.added_inboxes.is_empty() {
161 return Ok(false);
164 }
165
166 let mut deduper = GroupUpdateDeduper::default();
167 let mut inserted_after_ns = None;
168 let mut msgs;
169 loop {
170 msgs = self.find_messages_v2_with_conn(
173 &MsgQueryArgs {
174 content_types: Some(vec![ContentType::GroupUpdated]),
175 inserted_after_ns,
176 limit: Some(100),
177 ..Default::default()
178 },
179 storage.db(),
180 )?;
181
182 let Some(msg) = msgs.last() else {
183 break;
184 };
185 inserted_after_ns = Some(msg.metadata.inserted_at_ns);
186
187 for msg in msgs {
188 let MessageBody::GroupUpdated(update) = msg.content else {
189 continue;
190 };
191
192 deduper.consume(&update);
193 }
194 }
195
196 Ok(deduper.is_dupe(payload))
197 }
198}