xmtp_mls_identity/identity_updates/
state_store.rs1use super::IdentityUpdateError;
5use crate::identity_updates::IdentityContext;
6use xmtp_db::{
7 StorageError, TransactionOutcome, identity_update::StoredIdentityUpdate, prelude::*,
8};
9use xmtp_id::{
10 associations::{
11 AssociationError, AssociationState, get_state, unverified::UnverifiedIdentityUpdate,
12 verify_updates,
13 },
14 scw_verifier::SmartContractSignatureVerifier,
15};
16
17use crate::identity_ext::IdentityExt;
18
19pub(super) enum ResolvedState {
22 Cached(AssociationState),
23 Computed {
24 sequence_id: i64,
25 state: AssociationState,
26 },
27}
28
29pub(super) async fn resolve_association_state(
33 conn: &impl DbQuery,
34 inbox_id: &str,
35 to_sequence_id: Option<i64>,
36 scw_verifier: &impl SmartContractSignatureVerifier,
37) -> Result<ResolvedState, IdentityUpdateError> {
38 let updates = conn.get_identity_updates(inbox_id, None, to_sequence_id)?;
39 let last_sequence_id = updates
40 .last()
41 .ok_or::<IdentityUpdateError>(AssociationError::MissingIdentityUpdate.into())?
42 .sequence_id;
43 if let Some(to_sequence_id) = to_sequence_id
44 && to_sequence_id != last_sequence_id
45 {
46 return Err(AssociationError::MissingIdentityUpdate.into());
47 }
48
49 if let Some(association_state) = conn.read_from_cache(inbox_id, last_sequence_id)? {
50 return Ok(ResolvedState::Cached(
51 association_state.try_into().map_err(StorageError::from)?,
52 ));
53 }
54
55 let unverified_updates = updates
56 .into_iter()
57 .map(StoredIdentityUpdate::to_unverified)
59 .collect::<Result<Vec<UnverifiedIdentityUpdate>, AssociationError>>()?;
60 let updates = verify_updates(unverified_updates, scw_verifier).await?;
61
62 Ok(ResolvedState::Computed {
63 sequence_id: last_sequence_id,
64 state: get_state(updates)?,
65 })
66}
67
68pub async fn get_association_state_for_client(
72 context: &impl IdentityContext,
73 conn: &impl DbQuery,
74 inbox_id: &str,
75 to_sequence_id: Option<i64>,
76) -> Result<AssociationState, IdentityUpdateError> {
77 let verifier = context.scw_verifier();
78 match resolve_association_state(conn, inbox_id, to_sequence_id, &verifier).await? {
79 ResolvedState::Cached(state) => Ok(state),
80 ResolvedState::Computed { sequence_id, state } => {
81 store_association_state(context, sequence_id, &state)?;
82 Ok(state)
83 }
84 }
85}
86
87pub fn store_association_state(
93 context: &impl IdentityContext,
94 sequence_id: i64,
95 state: &AssociationState,
96) -> Result<(), IdentityUpdateError> {
97 use xmtp_events::{ClientEvent, EventWriter};
98 let inbox_id = state.inbox_id();
99 xmtp_mls_core::state_tx::state_write_with_events(
100 context.mls_storage(),
101 context.events(),
102 |tx, events| {
103 let storage = tx.storage();
104 let db = storage.db();
105 let held = db
106 .newest_cached_states(&[inbox_id])?
107 .remove(inbox_id)
108 .map(|(held_sequence, held)| {
109 AssociationState::try_from(held).map(|held| (held_sequence, held))
110 })
111 .transpose()
112 .map_err(StorageError::from)?;
113 db.write_to_cache(inbox_id.to_owned(), sequence_id, state.clone().into())?;
114 if let Some(change) = handle_change(held.as_ref(), sequence_id, state) {
115 events.emit(Some(ClientEvent::HandleChanged(change)), None);
116 }
117 Ok::<_, StorageError>(TransactionOutcome::Continue(()))
118 },
119 )?
120 .into_continued();
121 Ok(())
122}
123
124fn handle_change(
129 held: Option<&(i64, AssociationState)>,
130 sequence_id: i64,
131 state: &AssociationState,
132) -> Option<xmtp_events::HandleChanged> {
133 let (held_sequence, held) = held?;
134 if sequence_id <= *held_sequence {
135 return None;
136 }
137 let handle = state.display_handle()?;
138 (held.display_handle() != Some(handle)).then(|| xmtp_events::HandleChanged {
139 inbox_id: state.inbox_id().to_owned(),
140 handle: handle.to_string(),
141 })
142}