1use crate::{
2 ApiClientWrapper, ApiError, HandleRejection, PublishUnit, Result,
3 chunk::{MAX_READ_CHUNKS_IN_FLIGHT, publish_error_reason},
4 dyn_err,
5};
6use futures::{StreamExt, TryStreamExt, stream};
7use prost::Message;
8use std::collections::{HashMap, HashSet};
9use xmtp_configuration::MAX_SCW_SIGNATURES_PER_REQUEST;
10use xmtp_proto::{
11 api::grpc_status,
12 api_client::XmtpBackendClient,
13 backend_v1 as wire,
14 types::{ApiIdentifier, Cursor, IdentityUpdateLog, IncomingBatchLimits, Topic, TopicCursor},
15 xmtp::identity::associations::{IdentifierKind, IdentityUpdate},
16};
17
18fn handle_rejection(error: &ApiError) -> Option<HandleRejection> {
21 use wire::publish_error::Reason;
22 match publish_error_reason(error)? {
23 Reason::HandleTaken => Some(HandleRejection::Taken),
24 Reason::HandleNotAllowed => Some(HandleRejection::NotAllowed),
25 Reason::HandleTooSoon => Some(HandleRejection::TooSoon),
26 _ => None,
27 }
28}
29
30#[derive(Debug)]
32pub struct GetIdentityUpdatesV2Filter {
33 pub inbox_id: String,
34 pub sequence_id: Option<u64>,
35}
36
37impl<C: XmtpBackendClient> ApiClientWrapper<C> {
38 #[xmtp_common::rpc_span]
39 pub async fn publish_identity_update<U: Into<IdentityUpdate>>(
40 &self,
41 update: U,
42 ) -> Result<Cursor> {
43 let unit = PublishUnit::single_within(
44 wire::ClientEnvelope {
45 payload: Some(wire::client_envelope::Payload::IdentityUpdate(
46 update.into(),
47 )),
48 },
49 self.limits(),
50 )?;
51 match self.publish_units(vec![unit]).await {
52 Ok(metas) => metas
53 .into_iter()
54 .next()
55 .and_then(|meta| meta.cursor)
56 .map(Into::into)
57 .ok_or(ApiError::InvalidResponse("identity publish cursor")),
58 Err(error)
59 if grpc_status(&error)
60 .is_some_and(|status| status.code() == tonic::Code::Aborted) =>
61 {
62 Err(ApiError::IdentityUpdateConflict)
63 }
64 Err(error) => Err(handle_rejection(&error)
65 .map(ApiError::HandleRejected)
66 .unwrap_or(error)),
67 }
68 }
69 #[xmtp_common::rpc_span]
73 pub async fn get_identity_updates_v2(
74 &self,
75 filters: Vec<GetIdentityUpdatesV2Filter>,
76 ) -> Result<HashMap<String, Vec<IdentityUpdateLog>>> {
77 let mut result: HashMap<_, Vec<_>> = filters
78 .iter()
79 .map(|filter| (filter.inbox_id.clone(), Vec::new()))
80 .collect();
81 self.fold_identity_updates(filters, IncomingBatchLimits::STREAM_BATCH, |updates| {
82 for update in updates {
83 result
84 .entry(update.update.inbox_id.clone())
85 .or_default()
86 .push(update);
87 }
88 Ok::<_, ApiError>(())
89 })
90 .await?;
91 Ok(result)
92 }
93
94 #[xmtp_common::rpc_span]
99 pub async fn fold_identity_updates<E: From<ApiError> + std::fmt::Display>(
100 &self,
101 filters: Vec<GetIdentityUpdatesV2Filter>,
102 limits: IncomingBatchLimits,
103 mut fold: impl FnMut(Vec<IdentityUpdateLog>) -> std::result::Result<(), E>,
104 ) -> std::result::Result<(), E> {
105 let mut cursors = TopicCursor::new();
106 for filter in &filters {
107 let bytes =
108 hex::decode(&filter.inbox_id).map_err(|_| ApiError::InvalidRequest("inbox id"))?;
109 let topic = Topic::new_identity_update(bytes);
110 Topic::parse(&topic).map_err(ApiError::from)?;
111 let after = Cursor(filter.sequence_id.unwrap_or(0));
112 cursors
113 .entry(topic)
114 .and_modify(|cursor| *cursor = (*cursor).min(after))
115 .or_insert(after);
116 }
117 let requested: HashSet<_> = filters.iter().map(|filter| &filter.inbox_id).collect();
118 self.fold_ordered_pages(cursors, limits, |batches, _| {
119 let updates = batches
120 .into_iter()
121 .flat_map(|batch| batch.envelopes)
122 .map(|envelope| {
123 let update =
124 xmtp_api_backend::envelope::decode_identity_update(envelope.decode()?)?;
125 if !requested.contains(&update.update.inbox_id) {
126 return Err(ApiError::InvalidResponse("unrequested inbox"));
127 }
128 Ok(update)
129 })
130 .collect::<Result<Vec<_>>>()?;
131 fold(updates)
132 })
133 .await
134 }
135 #[xmtp_common::rpc_span]
137 pub async fn get_inbox_ids(
138 &self,
139 identifiers: Vec<ApiIdentifier>,
140 ) -> Result<Vec<Option<String>>> {
141 if identifiers
142 .iter()
143 .any(|id| id.identifier_kind == IdentifierKind::Unspecified)
144 {
145 return Err(ApiError::InvalidResponse("unspecified identifier kind"));
146 }
147 let requests: Vec<_> = identifiers
148 .chunks(self.limits().max_lookup_identifiers)
149 .map(<[_]>::to_vec)
150 .collect();
151 let mut chunks: Vec<_> = stream::iter(requests.into_iter().enumerate().map(
152 |(index, chunk)| async move {
153 let request = wire::GetInboxIdsRequest {
154 requests: chunk
155 .iter()
156 .map(|id| wire::get_inbox_ids_request::Request {
157 identifier: id.identifier.clone(),
158 identifier_kind: id.identifier_kind as i32,
159 })
160 .collect(),
161 };
162 let response = self
163 .retry_call(|| self.api_client.get_inbox_ids(request.clone()), false)
164 .await
165 .map_err(dyn_err)?;
166 if response.responses.len() != chunk.len() {
167 return Err(ApiError::InvalidResponse("inbox result count"));
168 }
169 let mut values = Vec::with_capacity(chunk.len());
170 for (response, input) in response.responses.into_iter().zip(&chunk) {
171 if IdentifierKind::try_from(response.identifier_kind)
172 .ok()
173 .filter(|kind| *kind != IdentifierKind::Unspecified)
174 != Some(input.identifier_kind)
175 || response.identifier != input.identifier
176 {
177 return Err(ApiError::InvalidResponse("inbox result identity"));
178 }
179 values.push(response.inbox_id);
180 }
181 Ok((index, values))
182 },
183 ))
184 .buffer_unordered(MAX_READ_CHUNKS_IN_FLIGHT)
185 .try_collect()
186 .await?;
187 chunks.sort_by_key(|(index, _)| *index);
188 Ok(chunks.into_iter().flat_map(|(_, values)| values).collect())
189 }
190 #[xmtp_common::rpc_span]
198 pub async fn verify_smart_contract_wallet_signatures(
199 &self,
200 request: wire::VerifySmartContractWalletSignaturesRequest,
201 ) -> Result<wire::VerifySmartContractWalletSignaturesResponse> {
202 let limits = self.limits();
203 let per_request = limits
204 .max_scw_signatures
205 .min(MAX_SCW_SIGNATURES_PER_REQUEST);
206 let requests: Vec<_> =
207 verify_requests(&request.signatures, per_request, limits.max_request_bytes)
208 .into_iter()
209 .map(<[_]>::to_vec)
210 .collect();
211 let chunks: Vec<Vec<_>> = stream::iter(
212 requests
213 .into_iter()
214 .map(|signatures| self.verify_chunk(signatures)),
215 )
216 .buffered(MAX_READ_CHUNKS_IN_FLIGHT)
217 .try_collect()
218 .await?;
219 Ok(wire::VerifySmartContractWalletSignaturesResponse {
220 responses: chunks.into_iter().flatten().collect(),
221 })
222 }
223
224 async fn verify_chunk(
230 &self,
231 signatures: Vec<wire::verify_smart_contract_wallet_signatures_request::Signature>,
232 ) -> Result<Vec<wire::verify_smart_contract_wallet_signatures_response::Response>> {
233 let mut verdicts = Vec::with_capacity(signatures.len());
234 let mut pending = vec![signatures];
236 while let Some(signatures) = pending.pop() {
237 let request = wire::VerifySmartContractWalletSignaturesRequest { signatures };
238 let response = match self
239 .retry_call(
240 || {
241 self.api_client
242 .verify_smart_contract_wallet_signatures(request.clone())
243 },
244 false,
245 )
246 .await
247 {
248 Ok(response) => response,
249 Err(error) if request.signatures.len() > 1 && refused_as_too_large(&error) => {
250 let mut first = request.signatures;
251 let second = first.split_off(first.len() / 2);
252 pending.extend([second, first]);
253 continue;
254 }
255 Err(error) => return Err(dyn_err(error)),
256 };
257 if response.responses.len() != request.signatures.len() {
258 return Err(ApiError::VerdictCount {
259 expected: request.signatures.len(),
260 got: response.responses.len(),
261 });
262 }
263 verdicts.extend(response.responses);
264 }
265 Ok(verdicts)
266 }
267}
268
269fn refused_as_too_large(error: &(dyn std::error::Error + 'static)) -> bool {
274 crate::preflight::failure(error).is_none()
275 && grpc_status(error).is_some_and(|status| {
276 matches!(
277 status.code(),
278 tonic::Code::InvalidArgument | tonic::Code::OutOfRange
279 )
280 })
281}
282
283fn verify_requests(
290 signatures: &[wire::verify_smart_contract_wallet_signatures_request::Signature],
291 per_request: usize,
292 max_request_bytes: usize,
293) -> Vec<&[wire::verify_smart_contract_wallet_signatures_request::Signature]> {
294 let mut requests = Vec::new();
295 let (mut start, mut bytes) = (0, 0);
296 for (end, signature) in signatures.iter().enumerate() {
297 let len = signature.encoded_len();
298 let framed = 1 + prost::length_delimiter_len(len) + len;
299 if end > start && (end - start >= per_request || bytes + framed > max_request_bytes) {
300 requests.push(&signatures[start..end]);
301 (start, bytes) = (end, 0);
302 }
303 bytes += framed;
304 }
305 if start < signatures.len() {
306 requests.push(&signatures[start..]);
307 }
308 requests
309}