Skip to main content

xmtp_db/
errors.rs

1use diesel::result::DatabaseErrorKind;
2use thiserror::Error;
3use xmtp_common::ErrorCode;
4
5use crate::group_intent::GroupIntentError;
6
7use super::sql_key_store::{self, SqlKeyStoreError};
8use xmtp_common::{BoxDynError, RetryableError, retryable};
9use xmtp_proto::types::{Cursor, GroupId, InstallationId};
10
11pub struct Mls;
12
13#[derive(Debug, Error, ErrorCode)]
14pub enum StorageError {
15    /// The database was created before the backend transition. Not retryable.
16    #[error(
17        "This database predates the backend transition. Delete the database file and create a new client."
18    )]
19    PreTransitionDatabase,
20    /// The database uses the previous self-hosted stream format.
21    #[error(
22        "This database uses an older self-hosted format. Keep a backup and create a new client database."
23    )]
24    OldStreamDatabase,
25    /// Durable stream storage rejected an operation or reached a capacity limit.
26    #[error(transparent)]
27    #[error_code(inherit)]
28    Stream(#[from] crate::stream_storage::StreamStorageError),
29    /// Diesel connection error.
30    ///
31    /// Failed to connect to SQLite. Retryable.
32    #[error(transparent)]
33    DieselConnect(#[from] diesel::ConnectionError),
34    /// Diesel result error.
35    ///
36    /// Database query returned an error. May be retryable.
37    #[error(transparent)]
38    DieselResult(#[from] diesel::result::Error),
39    /// Migration error.
40    ///
41    /// Database migration failed. Not retryable.
42    #[error("Error migrating database {0}")]
43    MigrationError(#[from] BoxDynError),
44    /// Not found.
45    ///
46    /// Requested record does not exist. Not retryable.
47    #[error(transparent)]
48    NotFound(#[from] NotFound),
49    /// Duplicate item.
50    ///
51    /// Attempted to insert a duplicate record. Not retryable.
52    #[error(transparent)]
53    Duplicate(DuplicateItem),
54    /// OpenMLS storage error.
55    ///
56    /// OpenMLS key store operation failed. Not retryable.
57    #[error(transparent)]
58    OpenMlsStorage(#[from] SqlKeyStoreError),
59    /// DB deserialization failed.
60    ///
61    /// Failed to deserialize data from database. Not retryable.
62    #[error("failed to deserialize from db")]
63    DbDeserialize,
64    /// DB serialization failed.
65    ///
66    /// Failed to serialize data for database. Not retryable.
67    #[error("failed to serialize for db")]
68    DbSerialize,
69    /// Builder error.
70    ///
71    /// Required fields missing from stored type. Not retryable.
72    #[error("required fields missing from stored db type {0}")]
73    Builder(#[from] derive_builder::UninitializedFieldError),
74    /// Platform storage error.
75    ///
76    /// Platform-specific storage error. May be retryable.
77    #[error(transparent)]
78    Platform(#[from] crate::database::PlatformStorageError),
79    /// Protobuf decode error.
80    ///
81    /// Failed to decode protobuf from database. Not retryable.
82    #[error("decoding from database failed {}", _0)]
83    Prost(#[from] prost::DecodeError),
84    /// Conversion error.
85    ///
86    /// Proto conversion failed. Not retryable.
87    #[error(transparent)]
88    Conversion(#[from] xmtp_proto::ConversionError),
89    /// Connection error.
90    ///
91    /// Database connection error. Retryable.
92    #[error(transparent)]
93    Connection(#[from] crate::ConnectionError),
94    /// Invalid HMAC length.
95    ///
96    /// HMAC key must be 42 bytes. Not retryable.
97    #[error("HMAC key must be 42 bytes")]
98    InvalidHmacLength,
99    /// Group intent error.
100    ///
101    /// Group intent processing failed. May be retryable.
102    #[error(transparent)]
103    GroupIntent(#[from] GroupIntentError),
104}
105
106impl From<std::convert::Infallible> for StorageError {
107    fn from(_: std::convert::Infallible) -> StorageError {
108        // infallible can never fail/occur
109        unreachable!("Infallible conversion should never fail.")
110    }
111}
112
113impl StorageError {
114    #[cfg(target_arch = "wasm32")]
115    pub fn db_needs_connection(&self) -> bool {
116        use crate::PlatformStorageError::Disconnected;
117        matches!(
118            self,
119            Self::Platform(Disconnected)
120                | Self::Connection(crate::ConnectionError::Platform(Disconnected))
121        )
122    }
123
124    #[cfg(not(target_arch = "wasm32"))]
125    pub fn db_needs_connection(&self) -> bool {
126        use crate::PlatformStorageError::{Pool, PoolNeedsConnection};
127        use StorageError::*;
128        // A pool-acquisition failure (`Pool`) is treated like `PoolNeedsConnection`: the
129        // worker drops and restarts on reconnect instead of logging on every poll.
130        matches!(
131            self,
132            Platform(PoolNeedsConnection | Pool(_))
133                | Connection(crate::ConnectionError::Platform(
134                    PoolNeedsConnection | Pool(_),
135                ))
136        )
137    }
138}
139
140#[derive(Error, Debug, ErrorCode)]
141// Monolithic enum for all things lost
142pub enum NotFound {
143    /// The local key package history row no longer exists.
144    #[error("Key package history row {0} not found")]
145    KeyPackageHistory(i32),
146    /// Group with welcome ID not found.
147    ///
148    /// No group matches the welcome ID. Retryable.
149    #[error("group with welcome id {0} not found")]
150    GroupByWelcome(Cursor),
151    /// Group with ID not found.
152    ///
153    /// Group does not exist in local DB. Retryable.
154    #[error("group with id {0} not found")]
155    GroupById(GroupId),
156    /// Installation time for group not found.
157    ///
158    /// Missing installation timestamp. Retryable.
159    #[error("installation time for group {0}")]
160    InstallationTimeForGroup(GroupId),
161    /// Inbox ID for address not found.
162    ///
163    /// Address has no associated inbox. Retryable.
164    #[error("inbox id for address {0} not found")]
165    InboxIdForAddress(String),
166    /// Message ID not found.
167    ///
168    /// Message does not exist in local DB. Retryable.
169    #[error("message id {id} not found", id = hex::encode(_0))]
170    MessageById(Vec<u8>),
171    /// DM by inbox ID not found.
172    ///
173    /// No DM conversation with this inbox. Retryable.
174    #[error("dm by dm_target_inbox_id {0} not found")]
175    DmByInbox(String),
176    /// Intent for ToPublish not found.
177    ///
178    /// Failed to transition intent from ToPublish to Published. Retryable.
179    #[error("intent with id {0} for state Published from ToPublish not found")]
180    IntentForToPublish(i32),
181    /// Intent for Published not found.
182    ///
183    /// Intent with specified ID not in expected state. Retryable.
184    #[error("intent with id {0} for state ToPublish from Published not found")]
185    IntentForPublish(i32),
186    /// Intent for Committed not found.
187    ///
188    /// Failed to transition intent from Published to Committed. Retryable.
189    #[error("intent with id {0} for state Committed from Published not found")]
190    IntentForCommitted(i32),
191    /// Intent by ID not found.
192    ///
193    /// Intent does not exist. Retryable.
194    #[error("Intent with id {0} not found")]
195    IntentById(i32),
196    /// Cipher salt not found.
197    ///
198    /// Database encryption salt missing. Retryable.
199    #[error("Cipher salt for db at [`{0}`] not found")]
200    CipherSalt(String),
201    /// Sync group not found.
202    ///
203    /// No sync group for this installation. Retryable.
204    #[error("Sync Group for installation {0} not found")]
205    SyncGroup(InstallationId),
206    /// Key package reference not found.
207    ///
208    /// Key package handle not in store. Retryable.
209    #[error("Key Package Reference {handle} not found", handle = hex::encode(_0))]
210    KeyPackageReference(Vec<u8>),
211    /// MLS group not found.
212    ///
213    /// OpenMLS group not in local state. Retryable.
214    #[error("MLS Group {0} Not Found")]
215    MlsGroup(GroupId),
216    /// Post-quantum private key not found.
217    ///
218    /// PQ key pair not in store. Retryable.
219    #[error("Post Quantum Key Pair not found")]
220    PostQuantumPrivateKey,
221    /// Key package not found.
222    ///
223    /// Key package not in store. Retryable.
224    #[error("Key Package {kp} not found", kp = hex::encode(_0))]
225    KeyPackage(Vec<u8>),
226}
227
228#[derive(Error, Debug, ErrorCode)]
229#[error_code(internal)]
230pub enum DuplicateItem {
231    /// Duplicate welcome ID.
232    ///
233    /// Welcome ID already exists. Not retryable.
234    #[error("the welcome id {0:?} already exists")]
235    WelcomeId(Option<Cursor>),
236    /// Duplicate commit log public key.
237    ///
238    /// Commit log public key for group already exists. Not retryable.
239    #[error("the commit log public key for group id {id} already exists", id = hex::encode(_0))]
240    CommitLogPublicKey(Vec<u8>),
241}
242
243impl RetryableError for DuplicateItem {
244    fn is_retryable(&self) -> bool {
245        use DuplicateItem::*;
246        match self {
247            WelcomeId(_) => false,
248            CommitLogPublicKey(_) => false,
249        }
250    }
251}
252
253impl RetryableError<Mls> for diesel::result::Error {
254    fn is_retryable(&self) -> bool {
255        use DatabaseErrorKind::*;
256        use diesel::result::Error::*;
257
258        match self {
259            DatabaseError(UniqueViolation, _) => false,
260            DatabaseError(CheckViolation, _) => false,
261            DatabaseError(NotNullViolation, _) => false,
262            // TODO: Figure out the full list of non-retryable errors.
263            // The diesel code has a comment that "this type is not meant to be exhaustively matched"
264            // so best is probably to return true here and map known errors to something else
265            // that is not retryable.
266            _ => true,
267        }
268    }
269}
270
271impl RetryableError for StorageError {
272    fn is_retryable(&self) -> bool {
273        match self {
274            Self::DieselConnect(_) => true,
275            Self::DieselResult(result) => retryable!(result),
276            Self::Duplicate(d) => retryable!(d),
277            Self::OpenMlsStorage(storage) => retryable!(storage),
278            Self::Platform(p) => retryable!(p),
279            Self::Connection(e) => retryable!(e),
280            Self::GroupIntent(e) => retryable!(e),
281            Self::Stream(e) => retryable!(e),
282            Self::PreTransitionDatabase
283            | Self::OldStreamDatabase
284            | Self::MigrationError(_)
285            | Self::Conversion(_)
286            | Self::NotFound(_)
287            | Self::DbDeserialize
288            | Self::DbSerialize
289            | Self::Builder(_)
290            | Self::InvalidHmacLength
291            | Self::Prost(_) => false,
292        }
293    }
294}
295
296impl RetryableError for NotFound {
297    fn is_retryable(&self) -> bool {
298        true
299    }
300}
301
302// OpenMLS KeyStore errors
303impl RetryableError<Mls> for openmls::group::AddMembersError<sql_key_store::SqlKeyStoreError> {
304    fn is_retryable(&self) -> bool {
305        match self {
306            Self::CreateCommitError(commit) => retryable!(commit),
307            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
308            Self::StorageError(storage) => retryable!(storage),
309            Self::GroupStateError(group_state) => retryable!(group_state),
310            _ => false,
311        }
312    }
313}
314
315impl RetryableError<Mls> for openmls::group::CreateCommitError {
316    fn is_retryable(&self) -> bool {
317        false
318    }
319}
320
321impl RetryableError<Mls>
322    for openmls::treesync::LeafNodeUpdateError<sql_key_store::SqlKeyStoreError>
323{
324    fn is_retryable(&self) -> bool {
325        match self {
326            Self::Storage(storage) => retryable!(storage),
327            _ => false,
328        }
329    }
330}
331
332impl RetryableError<Mls> for openmls::key_packages::errors::KeyPackageNewError {
333    fn is_retryable(&self) -> bool {
334        matches!(self, Self::StorageError)
335    }
336}
337
338impl RetryableError<Mls> for openmls::group::RemoveMembersError<sql_key_store::SqlKeyStoreError> {
339    fn is_retryable(&self) -> bool {
340        match self {
341            Self::CreateCommitError(commit) => retryable!(commit),
342            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
343            Self::GroupStateError(group_state) => retryable!(group_state),
344            Self::StorageError(storage) => retryable!(storage),
345            _ => false,
346        }
347    }
348}
349
350impl RetryableError<Mls> for openmls::group::NewGroupError<sql_key_store::SqlKeyStoreError> {
351    fn is_retryable(&self) -> bool {
352        match self {
353            Self::StorageError(storage) => retryable!(storage),
354            _ => false,
355        }
356    }
357}
358
359impl RetryableError<Mls>
360    for openmls::group::UpdateGroupMembershipError<sql_key_store::SqlKeyStoreError>
361{
362    fn is_retryable(&self) -> bool {
363        match self {
364            Self::CreateCommitError(create_commit) => retryable!(create_commit),
365            Self::GroupStateError(group_state) => retryable!(group_state),
366            Self::StorageError(storage) => retryable!(storage),
367            Self::CommitBuilderError(commit_builder) => retryable!(commit_builder),
368        }
369    }
370}
371
372impl RetryableError<Mls> for openmls::prelude::MlsGroupStateError {
373    fn is_retryable(&self) -> bool {
374        false
375    }
376}
377
378impl RetryableError<Mls>
379    for openmls::prelude::CreateGroupContextExtProposalError<sql_key_store::SqlKeyStoreError>
380{
381    fn is_retryable(&self) -> bool {
382        match self {
383            Self::CreateCommitError(create_commit) => retryable!(create_commit),
384            Self::StorageError(storage) => retryable!(storage),
385            _ => false,
386        }
387    }
388}
389
390impl RetryableError<Mls> for openmls::group::SelfUpdateError<sql_key_store::SqlKeyStoreError> {
391    fn is_retryable(&self) -> bool {
392        match self {
393            Self::CreateCommitError(commit) => retryable!(commit),
394            Self::CommitBuilderStageError(commit_builder_stage) => retryable!(commit_builder_stage),
395            Self::GroupStateError(group_state) => retryable!(group_state),
396            Self::StorageError(storage) => retryable!(storage),
397            _ => false,
398        }
399    }
400}
401
402impl RetryableError<Mls>
403    for openmls::group::CommitBuilderStageError<sql_key_store::SqlKeyStoreError>
404{
405    fn is_retryable(&self) -> bool {
406        match self {
407            Self::KeyStoreError(storage) => retryable!(storage),
408            Self::LibraryError(_) => false,
409        }
410    }
411}
412
413impl RetryableError<Mls>
414    for openmls::prelude::CreationFromExternalError<sql_key_store::SqlKeyStoreError>
415{
416    fn is_retryable(&self) -> bool {
417        match self {
418            Self::WriteToStorageError(storage) => retryable!(storage),
419            _ => false,
420        }
421    }
422}
423
424impl RetryableError<Mls>
425    for openmls::group::ProposeAddMemberError<sql_key_store::SqlKeyStoreError>
426{
427    fn is_retryable(&self) -> bool {
428        match self {
429            Self::GroupStateError(group_state) => retryable!(group_state),
430            Self::StorageError(storage) => retryable!(storage),
431            _ => false,
432        }
433    }
434}
435
436impl RetryableError<Mls>
437    for openmls::group::ProposeRemoveMemberError<sql_key_store::SqlKeyStoreError>
438{
439    fn is_retryable(&self) -> bool {
440        match self {
441            Self::GroupStateError(group_state) => retryable!(group_state),
442            Self::StorageError(storage) => retryable!(storage),
443            _ => false,
444        }
445    }
446}
447
448impl RetryableError<Mls>
449    for openmls::group::ProposeSelfUpdateError<sql_key_store::SqlKeyStoreError>
450{
451    fn is_retryable(&self) -> bool {
452        match self {
453            Self::LeafNodeUpdateError(leaf_node_update) => retryable!(leaf_node_update),
454            Self::StorageError(storage) => retryable!(storage),
455            _ => false,
456        }
457    }
458}
459
460impl RetryableError<Mls> for openmls::group::ProposalError<sql_key_store::SqlKeyStoreError> {
461    fn is_retryable(&self) -> bool {
462        match self {
463            Self::ProposeAddMemberError(e) => retryable!(e),
464            Self::ProposeSelfUpdateError(e) => retryable!(e),
465            Self::ProposeRemoveMemberError(e) => retryable!(e),
466            Self::CreateGroupContextExtProposalError(e) => retryable!(e),
467            Self::GroupStateError(group_state) => retryable!(group_state),
468            Self::StorageError(storage) => retryable!(storage),
469            _ => false,
470        }
471    }
472}
473
474impl RetryableError<Mls>
475    for openmls::group::CommitToPendingProposalsError<sql_key_store::SqlKeyStoreError>
476{
477    fn is_retryable(&self) -> bool {
478        match self {
479            Self::CreateCommitError(commit) => retryable!(commit),
480            Self::CommitBuilderStageError(commit_builder_stage) => {
481                retryable!(commit_builder_stage)
482            }
483            Self::GroupStateError(group_state) => retryable!(group_state),
484            Self::StorageError(storage) => retryable!(storage),
485            _ => false,
486        }
487    }
488}
489
490impl RetryableError<Mls> for openmls::prelude::WelcomeError<sql_key_store::SqlKeyStoreError> {
491    fn is_retryable(&self) -> bool {
492        match self {
493            Self::PublicGroupError(creation_err) => retryable!(creation_err),
494            Self::StorageError(storage) => retryable!(storage),
495            _ => false,
496        }
497    }
498}
499
500impl RetryableError<Mls> for openmls::group::MergeCommitError<sql_key_store::SqlKeyStoreError> {
501    fn is_retryable(&self) -> bool {
502        match self {
503            Self::StorageError(storage) => retryable!(storage),
504            Self::LibraryError(_) => false,
505        }
506    }
507}
508
509impl RetryableError<Mls>
510    for openmls::group::MergePendingCommitError<sql_key_store::SqlKeyStoreError>
511{
512    fn is_retryable(&self) -> bool {
513        match self {
514            Self::MlsGroupStateError(err) => retryable!(err),
515            Self::MergeCommitError(err) => retryable!(err),
516        }
517    }
518}
519
520impl RetryableError<Mls>
521    for openmls::prelude::ProcessMessageError<sql_key_store::SqlKeyStoreError>
522{
523    fn is_retryable(&self) -> bool {
524        match self {
525            Self::GroupStateError(err) => retryable!(err),
526            _ => false,
527        }
528    }
529}