1use super::{DbConnection, remote_commit_log::CommitResult, schema::local_commit_log::dsl};
2use crate::{ConnectionExt, impl_store, schema::local_commit_log};
3use diesel::{Insertable, Queryable, prelude::*};
4use xmtp_common::snippet::Snippet;
5use xmtp_proto::xmtp::mls::message_contents::PlaintextCommitLogEntry;
6
7use xmtp_proto::types::GroupId;
8pub enum CommitType {
9 GroupCreation,
10 BackupRestore,
11 Welcome,
12 KeyUpdate,
13 MetadataUpdate,
14 UpdateGroupMembership,
15 UpdateAdminList,
16 UpdatePermission,
17 RemovedFromGroup,
22}
23
24impl std::fmt::Display for CommitType {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 let description = match self {
27 CommitType::GroupCreation => "GroupCreation",
28 CommitType::BackupRestore => "BackupRestore",
29 CommitType::Welcome => "Welcome",
30 CommitType::KeyUpdate => "KeyUpdate",
31 CommitType::MetadataUpdate => "MetadataUpdate",
32 CommitType::UpdateGroupMembership => "UpdateGroupMembership",
33 CommitType::UpdateAdminList => "UpdateAdminList",
34 CommitType::UpdatePermission => "UpdatePermission",
35 CommitType::RemovedFromGroup => "RemovedFromGroup",
36 };
37 write!(f, "{}", description)
38 }
39}
40
41#[derive(Insertable, Debug, Clone)]
42#[diesel(table_name = local_commit_log)]
43pub struct NewLocalCommitLog {
44 pub group_id: GroupId,
45 pub commit_sequence_id: i64,
46 pub last_epoch_authenticator: Vec<u8>,
47 pub commit_result: CommitResult,
48 pub applied_epoch_number: i64,
49 pub applied_epoch_authenticator: Vec<u8>,
50 pub error_message: Option<String>,
51 pub sender_inbox_id: Option<String>,
52 pub sender_installation_id: Option<Vec<u8>>,
53 pub commit_type: Option<String>,
54}
55
56#[derive(Queryable, Clone)]
57#[diesel(table_name = local_commit_log)]
58#[diesel(primary_key(id))]
59pub struct LocalCommitLog {
60 pub rowid: i32,
61 pub group_id: GroupId,
62 pub commit_sequence_id: i64,
63 pub last_epoch_authenticator: Vec<u8>,
64 pub commit_result: CommitResult,
65 pub applied_epoch_number: i64,
66 pub applied_epoch_authenticator: Vec<u8>,
67 pub error_message: Option<String>,
68 pub sender_inbox_id: Option<String>,
69 pub sender_installation_id: Option<Vec<u8>>,
70 pub commit_type: Option<String>,
71}
72
73impl From<&LocalCommitLog> for PlaintextCommitLogEntry {
74 fn from(local_commit_log: &LocalCommitLog) -> Self {
75 PlaintextCommitLogEntry {
76 group_id: local_commit_log.group_id.to_vec(),
77 commit_sequence_id: local_commit_log.commit_sequence_id as u64,
78 last_epoch_authenticator: local_commit_log.last_epoch_authenticator.clone(),
79 commit_result: local_commit_log.commit_result.into(),
80 applied_epoch_number: local_commit_log.applied_epoch_number as u64,
81 applied_epoch_authenticator: local_commit_log.applied_epoch_authenticator.clone(),
82 }
83 }
84}
85
86impl From<CommitResult> for i32 {
87 fn from(commit_result: CommitResult) -> Self {
88 match commit_result {
89 CommitResult::Success => {
90 xmtp_proto::xmtp::mls::message_contents::CommitResult::Applied as i32
91 }
92 CommitResult::WrongEpoch => {
93 xmtp_proto::xmtp::mls::message_contents::CommitResult::WrongEpoch as i32
94 }
95 CommitResult::Undecryptable => {
96 xmtp_proto::xmtp::mls::message_contents::CommitResult::Undecryptable as i32
97 }
98 CommitResult::Invalid => {
99 xmtp_proto::xmtp::mls::message_contents::CommitResult::Invalid as i32
100 }
101 CommitResult::Unknown => {
102 xmtp_proto::xmtp::mls::message_contents::CommitResult::Unspecified as i32
103 }
104 }
105 }
106}
107
108impl_store!(NewLocalCommitLog, local_commit_log);
109
110impl std::fmt::Debug for LocalCommitLog {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(
113 f,
114 "LocalCommitLog {{ rowid: {:?}, group_id {:?}, commit_sequence_id: {:?}, last_epoch_authenticator: {:?}, commit_result: {:?}, error_message: {:?}, applied_epoch_number: {:?}, applied_epoch_authenticator: {:?}, sender_inbox_id: {:?}, sender_installation_id: {:?}, commit_type: {:?} }}",
115 self.rowid,
116 self.group_id.as_slice().snippet(),
117 self.commit_sequence_id,
118 self.last_epoch_authenticator.snippet(),
119 self.commit_result,
120 self.error_message,
121 self.applied_epoch_number,
122 self.applied_epoch_authenticator.snippet(),
123 self.sender_inbox_id.snippet(),
124 self.sender_installation_id.snippet(),
125 self.commit_type
126 )
127 }
128}
129
130pub enum LocalCommitLogOrder {
131 AscendingByRowid,
132 DescendingByRowid,
133}
134
135pub trait QueryLocalCommitLog {
136 fn get_group_logs(
137 &self,
138 group_id: &GroupId,
139 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError>;
140
141 fn get_local_commit_log_after_cursor(
144 &self,
145 group_id: &GroupId,
146 after_cursor: i64,
147 order_by: LocalCommitLogOrder,
148 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError>;
149
150 fn get_latest_log_for_group(
151 &self,
152 group_id: &GroupId,
153 ) -> Result<Option<LocalCommitLog>, crate::ConnectionError>;
154
155 fn get_local_commit_log_cursor(
156 &self,
157 group_id: &GroupId,
158 ) -> Result<Option<i32>, crate::ConnectionError>;
159
160 fn get_latest_chain_start_rowid(
165 &self,
166 group_id: &GroupId,
167 ) -> Result<Option<i32>, crate::ConnectionError>;
168}
169
170impl<T> QueryLocalCommitLog for &T
171where
172 T: QueryLocalCommitLog,
173{
174 fn get_group_logs(
175 &self,
176 group_id: &GroupId,
177 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError> {
178 (**self).get_group_logs(group_id)
179 }
180
181 fn get_local_commit_log_after_cursor(
182 &self,
183 group_id: &GroupId,
184 after_cursor: i64,
185 order_by: LocalCommitLogOrder,
186 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError> {
187 (**self).get_local_commit_log_after_cursor(group_id, after_cursor, order_by)
188 }
189
190 fn get_latest_log_for_group(
191 &self,
192 group_id: &GroupId,
193 ) -> Result<Option<LocalCommitLog>, crate::ConnectionError> {
194 (**self).get_latest_log_for_group(group_id)
195 }
196
197 fn get_local_commit_log_cursor(
198 &self,
199 group_id: &GroupId,
200 ) -> Result<Option<i32>, crate::ConnectionError> {
201 (**self).get_local_commit_log_cursor(group_id)
202 }
203
204 fn get_latest_chain_start_rowid(
205 &self,
206 group_id: &GroupId,
207 ) -> Result<Option<i32>, crate::ConnectionError> {
208 (**self).get_latest_chain_start_rowid(group_id)
209 }
210}
211
212impl<C: ConnectionExt> QueryLocalCommitLog for DbConnection<C> {
213 fn get_group_logs(
214 &self,
215 group_id: &GroupId,
216 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError> {
217 self.raw_query(|db| {
218 dsl::local_commit_log
219 .filter(dsl::group_id.eq(group_id))
220 .order_by(dsl::rowid.asc())
221 .load(db)
222 })
223 }
224
225 fn get_local_commit_log_after_cursor(
228 &self,
229 group_id: &GroupId,
230 after_cursor: i64,
231 order: LocalCommitLogOrder,
232 ) -> Result<Vec<LocalCommitLog>, crate::ConnectionError> {
233 if after_cursor > i32::MAX as i64 {
235 return Err(crate::ConnectionError::Database(
236 diesel::result::Error::QueryBuilderError("Cursor value exceeds i32::MAX".into()),
237 ));
238 }
239 let after_cursor = after_cursor as i32;
240
241 let query = dsl::local_commit_log
242 .filter(dsl::group_id.eq(group_id))
243 .filter(dsl::rowid.gt(after_cursor))
244 .filter(dsl::commit_sequence_id.ne(0));
245
246 self.raw_query(|db| match order {
247 LocalCommitLogOrder::AscendingByRowid => query.order_by(dsl::rowid.asc()).load(db),
248 LocalCommitLogOrder::DescendingByRowid => query.order_by(dsl::rowid.desc()).load(db),
249 })
250 }
251
252 fn get_latest_log_for_group(
253 &self,
254 group_id: &GroupId,
255 ) -> Result<Option<LocalCommitLog>, crate::ConnectionError> {
256 self.raw_query(|db| {
257 dsl::local_commit_log
258 .filter(dsl::group_id.eq(group_id))
259 .order_by(dsl::rowid.desc())
260 .limit(1)
261 .first(db)
262 .optional()
263 })
264 }
265
266 fn get_local_commit_log_cursor(
267 &self,
268 group_id: &GroupId,
269 ) -> Result<Option<i32>, crate::ConnectionError> {
270 let query = dsl::local_commit_log
271 .filter(dsl::group_id.eq(group_id))
272 .select(dsl::rowid)
273 .order(dsl::rowid.desc())
274 .limit(1);
275
276 self.raw_query(|conn| query.first::<i32>(conn).optional())
277 }
278
279 fn get_latest_chain_start_rowid(
280 &self,
281 group_id: &GroupId,
282 ) -> Result<Option<i32>, crate::ConnectionError> {
283 let query = dsl::local_commit_log
284 .filter(dsl::group_id.eq(group_id))
285 .filter(dsl::commit_sequence_id.eq(0))
286 .select(dsl::rowid)
287 .order(dsl::rowid.desc())
288 .limit(1);
289
290 self.raw_query(|conn| query.first::<i32>(conn).optional())
291 }
292}