1use crate::{ApiClientWrapper, ApiError, Result, dyn_err};
3use futures::{StreamExt, TryStreamExt, stream};
4use prost::Message;
5use std::{
6 collections::{HashMap, HashSet, VecDeque},
7 future::Future,
8};
9use tonic::Code;
10use xmtp_common::{
11 ExponentialBackoff, Retry, RetryableError, Strategy, retry_async,
12 time::{Duration, Instant},
13};
14use xmtp_configuration::*;
15use xmtp_proto::{
16 api::grpc_status,
17 api_client::XmtpBackendClient,
18 backend_v1 as wire,
19 types::{
20 CanonicalEnvelope, Cursor, IncomingBatchLimits, OrderedEnvelopeBatch, Topic, TopicCursor,
21 },
22};
23
24pub const MAX_PUBLISH_CHUNKS_IN_FLIGHT: usize = 4;
25pub const MAX_READ_CHUNKS_IN_FLIGHT: usize = 4;
26
27#[derive(Debug)]
29pub struct OrderedQueryPage {
30 pub batches: Vec<OrderedEnvelopeBatch>,
32 pub has_more: bool,
34}
35
36#[derive(Clone, Debug)]
37struct PublishEnvelope {
38 envelope: wire::ClientEnvelope,
39 canonical: CanonicalEnvelope,
40 topic: Topic,
41}
42
43impl PublishEnvelope {
44 fn parse(envelope: wire::ClientEnvelope) -> Result<Self> {
45 let parsed = xmtp_mls_validation::parse_envelope(envelope)?;
46 Ok(Self {
47 envelope: parsed.envelope,
48 canonical: parsed.canonical,
49 topic: parsed.topic,
50 })
51 }
52}
53
54#[derive(Clone, Debug)]
56pub struct PublishUnit {
57 envelopes: Vec<PublishEnvelope>,
58}
59impl PublishUnit {
60 pub fn new_within(
65 envelopes: Vec<wire::ClientEnvelope>,
66 limits: &LimitsConfiguration,
67 ) -> Result<Self> {
68 if envelopes.is_empty() {
69 return Err(ApiError::InvalidRequest("empty publish unit"));
70 }
71 let envelopes = envelopes
72 .into_iter()
73 .map(|envelope| {
74 let envelope = PublishEnvelope::parse(envelope)?;
75 if envelope.canonical.bytes.len() > limits.max_envelope_bytes {
76 return Err(ApiError::EnvelopeTooLarge);
77 }
78 Ok(envelope)
79 })
80 .collect::<Result<Vec<_>>>()?;
81 let unit = Self { envelopes };
82 let mut measure = PublishMeasure::new(limits);
83 measure.add(&unit);
84 if !measure.fits() {
85 return Err(ApiError::UnitTooLarge);
86 }
87 Ok(unit)
88 }
89
90 pub fn single_within(
91 envelope: wire::ClientEnvelope,
92 limits: &LimitsConfiguration,
93 ) -> Result<Self> {
94 Self::new_within(vec![envelope], limits)
95 }
96
97 pub fn new(envelopes: Vec<wire::ClientEnvelope>) -> Result<Self> {
100 Self::new_within(envelopes, &LimitsConfiguration::default())
101 }
102
103 pub fn single(envelope: wire::ClientEnvelope) -> Result<Self> {
104 Self::new(vec![envelope])
105 }
106
107 pub fn for_settlement(envelopes: Vec<wire::ClientEnvelope>) -> Result<Self> {
111 if envelopes.is_empty() {
112 return Err(ApiError::InvalidRequest("empty publish unit"));
113 }
114 let envelopes = envelopes
115 .into_iter()
116 .map(PublishEnvelope::parse)
117 .collect::<Result<Vec<_>>>()?;
118 Ok(Self { envelopes })
119 }
120}
121
122fn routable<'a>(topics: impl IntoIterator<Item = &'a Topic>) -> Result<()> {
125 for topic in topics {
126 Topic::parse(topic)?;
127 }
128 Ok(())
129}
130
131pub(crate) fn request(units: &[PublishUnit]) -> wire::PublishRequest {
132 wire::PublishRequest {
133 envelopes: units
134 .iter()
135 .flat_map(|unit| unit.envelopes.iter().map(|e| e.envelope.clone()))
136 .collect(),
137 }
138}
139struct PublishMeasure<'a> {
141 bytes: usize,
142 envelopes: usize,
143 topics: HashSet<&'a Topic>,
144 limits: &'a LimitsConfiguration,
145}
146impl<'a> PublishMeasure<'a> {
147 fn new(limits: &'a LimitsConfiguration) -> Self {
148 Self {
149 bytes: 0,
150 envelopes: 0,
151 topics: HashSet::new(),
152 limits,
153 }
154 }
155
156 fn add(&mut self, unit: &'a PublishUnit) {
157 for envelope in &unit.envelopes {
158 let len = envelope.canonical.bytes.len();
159 self.bytes += 1 + prost::length_delimiter_len(len) + len;
160 self.envelopes += 1;
161 self.topics.insert(&envelope.topic);
162 }
163 }
164
165 fn fits(&self) -> bool {
166 self.bytes <= self.limits.max_request_bytes
167 && self.envelopes <= MAX_PUBLISH_ENVELOPES
168 && self.topics.len() <= self.limits.max_publish_topics
169 }
170}
171
172pub fn chunk_publish_within<'a>(
177 units: &'a [PublishUnit],
178 limits: &LimitsConfiguration,
179) -> Result<Vec<&'a [PublishUnit]>> {
180 let mut chunks = Vec::new();
181 let mut start = 0;
182 let mut measure = PublishMeasure::new(limits);
183 for (end, unit) in units.iter().enumerate() {
184 measure.add(unit);
185 if !measure.fits() {
186 if start == end {
187 return Err(ApiError::UnitTooLarge);
188 }
189 chunks.push(&units[start..end]);
190 start = end;
191 measure = PublishMeasure::new(limits);
192 measure.add(unit);
193 if !measure.fits() {
194 return Err(ApiError::UnitTooLarge);
195 }
196 }
197 }
198 if start < units.len() {
199 chunks.push(&units[start..]);
200 }
201 Ok(chunks)
202}
203
204pub fn chunk_publish(units: &[PublishUnit]) -> Result<Vec<&[PublishUnit]>> {
206 chunk_publish_within(units, &LimitsConfiguration::default())
207}
208
209const PUBLISH_ERROR_TYPE_URL: &str = "type.googleapis.com/xmtp.backend.v1.PublishError";
211
212pub(crate) fn publish_error_reason(
216 error: &(dyn std::error::Error + 'static),
217) -> Option<wire::publish_error::Reason> {
218 if crate::preflight::failure(error).is_some() {
219 return None;
220 }
221 let status = grpc_status(error).filter(|status| status.code() == Code::InvalidArgument)?;
222 let details = tonic_types::pb::Status::decode(status.details()).ok()?;
223 details
224 .details
225 .iter()
226 .filter(|detail| detail.type_url == PUBLISH_ERROR_TYPE_URL)
227 .find_map(|detail| wire::PublishError::decode(detail.value.as_slice()).ok())
228 .and_then(|error| wire::publish_error::Reason::try_from(error.reason).ok())
229}
230
231pub(crate) fn size_error(error: &(dyn std::error::Error + 'static)) -> bool {
235 if crate::preflight::failure(error).is_some()
236 || xmtp_api_grpc::error::rate_limit_delay(error).is_some()
237 {
238 return false;
239 }
240 let Some(status) = grpc_status(error) else {
241 return false;
242 };
243 match status.code() {
244 Code::OutOfRange | Code::ResourceExhausted => true,
245 Code::InvalidArgument => {
246 publish_error_reason(error) == Some(wire::publish_error::Reason::TooLarge)
247 }
248 _ => false,
249 }
250}
251
252fn publish_size_error(error: &(dyn std::error::Error + 'static)) -> bool {
257 size_error(error) && grpc_status(error).is_none_or(|status| status.code() != Code::OutOfRange)
258}
259
260#[derive(Debug, thiserror::Error)]
262enum CallError<E> {
263 #[error(transparent)]
264 Resize(E),
265 #[error(transparent)]
266 Retry(E),
267}
268impl<E: RetryableError + 'static> RetryableError for CallError<E> {
269 fn is_retryable(&self) -> bool {
270 match self {
271 Self::Resize(_) => false,
272 Self::Retry(error) => {
273 crate::preflight::failure(error).is_none() && error.is_retryable()
274 }
275 }
276 }
277
278 fn rate_limit_delay(&self) -> Option<Duration> {
279 match self {
280 Self::Resize(_) => None,
281 Self::Retry(error) => xmtp_api_grpc::error::rate_limit_delay(error),
282 }
283 }
284}
285
286struct Until<'a> {
289 strategy: &'a ExponentialBackoff,
290 deadline: Option<Instant>,
291}
292
293impl Strategy for Until<'_> {
294 fn backoff(&self, attempts: usize, time_spent: Instant) -> Option<Duration> {
295 self.strategy.backoff(attempts, time_spent)
296 }
297
298 fn backoff_at_least(
299 &self,
300 attempts: usize,
301 time_spent: Instant,
302 floor: Duration,
303 ) -> Option<Duration> {
304 let wait = self
305 .strategy
306 .backoff_at_least(attempts, time_spent, floor)?;
307 self.deadline
308 .is_none_or(|deadline| deadline.saturating_duration_since(Instant::now()) > wait)
309 .then_some(wait)
310 }
311}
312
313impl<C> ApiClientWrapper<C> {
314 pub(crate) async fn retry_call<T, E, F, Fut>(
315 &self,
316 call: F,
317 resize: bool,
318 ) -> std::result::Result<T, E>
319 where
320 E: RetryableError + 'static,
321 F: FnMut() -> Fut,
322 Fut: Future<Output = std::result::Result<T, E>>,
323 {
324 self.retry_call_until(call, resize, None).await
325 }
326
327 pub(crate) async fn retry_call_until<T, E, F, Fut>(
332 &self,
333 mut call: F,
334 resize: bool,
335 deadline: Option<Instant>,
336 ) -> std::result::Result<T, E>
337 where
338 E: RetryableError + 'static,
339 F: FnMut() -> Fut,
340 Fut: Future<Output = std::result::Result<T, E>>,
341 {
342 let retry = Retry::builder()
343 .retries(self.retry_strategy.retries())
344 .with_strategy(Until {
345 strategy: self.retry_strategy.strategy(),
346 deadline,
347 })
348 .build();
349 retry_async!(
350 retry,
351 (async {
352 call().await.map_err(|error| {
353 if resize && size_error(&error) {
354 CallError::Resize(error)
355 } else {
356 CallError::Retry(error)
357 }
358 })
359 })
360 )
361 .map_err(|error| match error {
362 CallError::Resize(error) | CallError::Retry(error) => error,
363 })
364 }
365}
366
367impl<C: XmtpBackendClient> ApiClientWrapper<C> {
368 #[xmtp_common::rpc_span]
370 pub async fn publish_units(&self, units: Vec<PublishUnit>) -> Result<Vec<wire::EnvelopeMeta>> {
371 let chunks = chunk_publish_within(&units, self.limits())?
372 .into_iter()
373 .map(<[_]>::to_vec)
374 .collect::<Vec<_>>();
375 let mut responses: Vec<_> = stream::iter(chunks.into_iter().enumerate().map(
376 |(index, chunk)| async move {
377 let mut metas = Vec::new();
378 self.publish_chunk(&chunk, &mut metas).await?;
379 Ok::<_, ApiError>((index, metas))
380 },
381 ))
382 .buffer_unordered(MAX_PUBLISH_CHUNKS_IN_FLIGHT)
383 .try_collect()
384 .await?;
385 responses.sort_by_key(|(index, _)| *index);
386 Ok(responses.into_iter().flat_map(|(_, metas)| metas).collect())
387 }
388
389 pub async fn publish_units_in_order(
396 &self,
397 units: Vec<PublishUnit>,
398 stored: &mut Vec<wire::EnvelopeMeta>,
399 ) -> Result<()> {
400 for chunk in chunk_publish_within(&units, self.limits())? {
401 self.publish_chunk(chunk, stored).await?;
402 }
403 Ok(())
404 }
405
406 async fn publish_chunk(
410 &self,
411 units: &[PublishUnit],
412 metas: &mut Vec<wire::EnvelopeMeta>,
413 ) -> Result<()> {
414 let mut pending = VecDeque::from([units]);
415 while let Some(units) = pending.pop_front() {
416 let request = request(units);
417 match self
418 .retry_call(|| self.api_client.publish(request.clone()), units.len() > 1)
419 .await
420 {
421 Ok(response) => {
422 let expected: Vec<_> = units.iter().flat_map(|unit| &unit.envelopes).collect();
423 if response.envelope_metas.len() != expected.len() {
424 return Err(ApiError::InvalidResponse("publish metadata count"));
425 }
426 for (meta, envelope) in response.envelope_metas.iter().zip(expected) {
427 let (topic, _, _) =
428 xmtp_api_backend::envelope::metadata(meta, envelope.topic.kind())?;
429 if topic != envelope.topic {
430 return Err(ApiError::InvalidResponse("publish topic"));
431 }
432 }
433 metas.extend(response.envelope_metas);
434 }
435 Err(error) if publish_size_error(&error) && units.len() > 1 => {
436 let (left, right) = units.split_at(units.len() / 2);
437 pending.push_front(right);
438 pending.push_front(left);
439 }
440 Err(error) => return Err(dyn_err(error)),
441 }
442 }
443 Ok(())
444 }
445
446 pub async fn query_ordered_page(
450 &self,
451 cursors: TopicCursor,
452 limit: u32,
453 limits: IncomingBatchLimits,
454 ) -> Result<OrderedQueryPage> {
455 if limit == 0
456 || limit as usize > self.limits().max_query_limit
457 || limits.max_rows == 0
458 || limits.max_bytes == 0
459 {
460 return Err(ApiError::InvalidRequest("query limit"));
461 }
462 routable(cursors.keys())?;
463 let topics: Vec<_> = cursors.into_iter().collect();
464 let mut pending: VecDeque<_> = topics
465 .chunks(self.limits().max_query_topics)
466 .map(|topics| (topics.to_vec(), limit))
467 .collect();
468 let mut page = OrderedQueryPage {
469 batches: Vec::new(),
470 has_more: false,
471 };
472 let mut remaining = limits;
473 while let Some((topics, requested_limit)) = pending.pop_front() {
474 if remaining.max_rows == 0 || remaining.max_bytes == 0 {
475 page.has_more = true;
476 break;
477 }
478 let requested_limit =
479 requested_limit.min(remaining.max_rows.min(u32::MAX as usize) as u32);
480 let request = wire::QueryRequest {
481 queries: topics
482 .iter()
483 .map(|(topic, cursor)| wire::TopicQuery {
484 topic: Some(wire::Topic {
485 topic: topic.cloned_vec(),
486 }),
487 cursor: Some((*cursor).into()),
488 })
489 .collect(),
490 limit: requested_limit,
491 };
492 let response = match self
493 .retry_call(
494 || self.api_client.query_raw_envelopes(request.clone()),
495 requested_limit > 1 || topics.len() > 1,
496 )
497 .await
498 {
499 Ok(response) => response,
500 Err(error) if size_error(&error) && requested_limit > 1 => {
501 pending.push_front((topics, (requested_limit / 2).max(1)));
502 continue;
503 }
504 Err(error) if size_error(&error) && topics.len() > 1 => {
505 let (left, right) = topics.split_at(topics.len() / 2);
506 pending.push_front((right.to_vec(), requested_limit));
507 pending.push_front((left.to_vec(), requested_limit));
508 continue;
509 }
510 Err(error) => return Err(dyn_err(error)),
511 };
512 let has_more = response
513 .continuation
514 .ok_or(ApiError::InvalidResponse("query continuation"))?
515 .has_more;
516 if has_more && response.envelopes.is_empty() {
517 return Err(ApiError::InvalidResponse("query has more without progress"));
518 }
519 if response.envelopes.len() > requested_limit as usize {
520 return Err(ApiError::InvalidResponse("query response limit"));
521 }
522 let mut positions = topics.iter().cloned().collect();
523 let envelopes = xmtp_proto::types::received_envelopes(response.envelopes)
524 .map_err(|error| dyn_err(xmtp_proto::api::ApiClientError::from(error)))?;
525 let batches = match xmtp_api_backend::envelope::ordered_batches(
526 &mut positions,
527 envelopes,
528 remaining,
529 ) {
530 Ok(batches) => batches,
531 Err(xmtp_api_backend::envelope::EnvelopeError::Capacity) if requested_limit > 1 => {
532 pending.push_front((topics, (requested_limit / 2).max(1)));
533 continue;
534 }
535 Err(xmtp_api_backend::envelope::EnvelopeError::Capacity)
536 if !page.batches.is_empty() =>
537 {
538 page.has_more = true;
539 break;
540 }
541 Err(error) => return Err(error.into()),
542 };
543 for batch in &batches {
544 remaining.max_rows -= batch.envelopes.len();
545 remaining.max_bytes -= batch
546 .envelopes
547 .iter()
548 .map(|envelope| envelope.raw().len())
549 .sum::<usize>();
550 }
551 page.has_more |= has_more;
552 page.batches.extend(batches);
553 }
554 Ok(page)
555 }
556
557 pub async fn fold_ordered_pages<E: From<ApiError>>(
562 &self,
563 mut cursors: TopicCursor,
564 limits: IncomingBatchLimits,
565 mut fold: impl FnMut(Vec<OrderedEnvelopeBatch>, &mut TopicCursor) -> std::result::Result<(), E>,
566 ) -> std::result::Result<(), E> {
567 let limit = limits
568 .max_rows
569 .min(self.limits().max_query_limit)
570 .min(u32::MAX as usize) as u32;
571 while !cursors.is_empty() {
572 let page = self
573 .query_ordered_page(cursors.clone(), limit, limits)
574 .await?;
575 for batch in &page.batches {
576 let cursor = batch
577 .envelopes
578 .last()
579 .and_then(|envelope| envelope.meta())
580 .and_then(|meta| meta.cursor.as_ref())
581 .ok_or(ApiError::InvalidResponse("ordered page cursor"))?;
582 cursors.insert(batch.topic.clone(), Cursor(cursor.sequence_id));
583 }
584 fold(page.batches, &mut cursors)?;
585 if !page.has_more {
586 break;
587 }
588 }
589 Ok(())
590 }
591
592 pub async fn newest_topic_cursors(&self, topics: Vec<Topic>) -> Result<TopicCursor> {
596 let mut cursors: TopicCursor = topics
597 .iter()
598 .cloned()
599 .map(|topic| (topic, Cursor(0)))
600 .collect();
601 for result in self.newest(topics, false).await? {
602 let topic = Topic::parse(
603 &result
604 .topic
605 .ok_or(ApiError::InvalidResponse("newest topic"))?
606 .topic,
607 )?;
608 let meta = result
609 .meta
610 .ok_or(ApiError::InvalidResponse("newest metadata"))?;
611 let (_, cursor, _) = xmtp_api_backend::envelope::metadata(&meta, topic.kind())?;
612 cursors.insert(topic, cursor);
613 }
614 Ok(cursors)
615 }
616
617 pub async fn query_all(
620 &self,
621 cursors: TopicCursor,
622 limit: u32,
623 ) -> Result<Vec<wire::ServerEnvelope>> {
624 if limit == 0 || limit as usize > self.limits().max_query_limit {
625 return Err(ApiError::InvalidRequest("query limit"));
626 }
627 routable(cursors.keys())?;
628 let topics: Vec<_> = cursors.into_iter().collect();
629 let results: Vec<Vec<_>> = stream::iter(
630 topics
631 .chunks(self.limits().max_query_topics)
632 .map(<[_]>::to_vec)
633 .collect::<Vec<_>>()
634 .into_iter()
635 .map(|chunk| self.query_chunk(chunk, limit)),
636 )
637 .buffer_unordered(MAX_READ_CHUNKS_IN_FLIGHT)
638 .try_collect()
639 .await?;
640 Ok(results.into_iter().flatten().collect())
641 }
642
643 async fn query_chunk(
644 &self,
645 topics: Vec<(Topic, Cursor)>,
646 limit: u32,
647 ) -> Result<Vec<wire::ServerEnvelope>> {
648 let mut output = Vec::new();
649 self.scan_chunk(topics, limit, |page| {
650 output.extend(page);
651 false
652 })
653 .await?;
654 Ok(output)
655 }
656
657 #[xmtp_common::rpc_span]
665 pub async fn settle_units(
666 &self,
667 units: &[PublishUnit],
668 ) -> Result<Vec<Option<wire::EnvelopeMeta>>> {
669 let envelopes: Vec<_> = units.iter().flat_map(|unit| &unit.envelopes).collect();
670 let mut pending: HashMap<_, Vec<_>> = HashMap::new();
671 for (index, envelope) in envelopes.iter().enumerate() {
672 pending
673 .entry(envelope.canonical.hash.to_vec())
674 .or_default()
675 .push(index);
676 }
677 let mut settled = vec![None; envelopes.len()];
678 let topics: HashSet<_> = envelopes.iter().map(|envelope| &envelope.topic).collect();
679 let topics: Vec<_> = topics
680 .into_iter()
681 .map(|topic| (topic.clone(), Cursor(0)))
682 .collect();
683 let limit = self.limits().max_query_limit as u32;
684 for chunk in topics.chunks(self.limits().max_query_topics) {
685 let mut invalid = false;
686 self.scan_chunk(chunk.to_vec(), limit, |page| {
687 invalid = page.into_iter().any(|stored| {
688 let Some(meta) = stored.meta else { return true };
689 let Ok(hash) = xmtp_api_backend::envelope::message_hash(&meta) else {
690 return true;
691 };
692 let Some(indices) = pending.remove(&hash) else {
693 return false;
694 };
695 let topic = &envelopes[indices[0]].topic;
697 let valid = xmtp_api_backend::envelope::metadata(&meta, topic.kind())
698 .is_ok_and(|(stored_topic, _, _)| stored_topic == *topic);
699 for index in indices {
700 settled[index] = Some(meta.clone());
701 }
702 !valid
703 });
704 invalid || pending.is_empty()
705 })
706 .await?;
707 if invalid {
708 return Err(ApiError::InvalidResponse("settled metadata"));
709 }
710 if pending.is_empty() {
711 break;
712 }
713 }
714 Ok(settled)
715 }
716
717 async fn scan_chunk(
720 &self,
721 topics: Vec<(Topic, Cursor)>,
722 limit: u32,
723 mut visit: impl FnMut(Vec<wire::ServerEnvelope>) -> bool,
724 ) -> Result<()> {
725 let mut pending = VecDeque::from([(topics, limit)]);
726 while let Some((mut topics, mut limit)) = pending.pop_front() {
727 loop {
728 let request = wire::QueryRequest {
729 queries: topics
730 .iter()
731 .map(|(topic, cursor)| wire::TopicQuery {
732 topic: Some(wire::Topic {
733 topic: topic.cloned_vec(),
734 }),
735 cursor: Some((*cursor).into()),
736 })
737 .collect(),
738 limit,
739 };
740 match self
741 .retry_call(
742 || self.api_client.query(request.clone()),
743 limit > 1 || topics.len() > 1,
744 )
745 .await
746 {
747 Ok(response) => {
748 let has_more = response
749 .continuation
750 .ok_or(ApiError::InvalidResponse("query continuation"))?
751 .has_more;
752 let mut cursors: HashMap<_, _> = topics
753 .iter_mut()
754 .map(|(topic, cursor)| (topic.clone(), cursor))
755 .collect();
756 let mut advanced = false;
757 for envelope in &response.envelopes {
758 let meta = envelope
759 .meta
760 .as_ref()
761 .ok_or(ApiError::InvalidResponse("query metadata"))?;
762 let topic = Topic::parse(
763 &meta
764 .topic
765 .as_ref()
766 .ok_or(ApiError::InvalidResponse("query topic"))?
767 .topic,
768 )?;
769 let cursor = cursors
770 .get_mut(&topic)
771 .ok_or(ApiError::InvalidResponse("unrequested query topic"))?;
772 let sequence = meta
773 .cursor
774 .as_ref()
775 .ok_or(ApiError::InvalidResponse("query cursor"))?
776 .sequence_id;
777 if sequence <= cursor.0 || sequence > i64::MAX as u64 {
778 return Err(ApiError::InvalidResponse(
779 "query cursor did not advance",
780 ));
781 }
782 **cursor = Cursor(sequence);
783 advanced = true;
784 }
785 if visit(response.envelopes) {
786 return Ok(());
787 }
788 if !has_more {
789 break;
790 }
791 if !advanced {
792 return Err(ApiError::InvalidResponse(
793 "query has more without progress",
794 ));
795 }
796 }
797 Err(error) if size_error(&error) && limit > 1 => {
798 limit = (limit / 2).max(1);
799 }
800 Err(error) if size_error(&error) && topics.len() > 1 => {
801 let right = topics.split_off(topics.len() / 2);
802 pending.push_front((right, limit));
803 }
804 Err(error) => return Err(dyn_err(error)),
805 }
806 }
807 }
808 Ok(())
809 }
810
811 pub(crate) async fn newest(
812 &self,
813 topics: Vec<Topic>,
814 full: bool,
815 ) -> Result<Vec<wire::query_newest_response::Result>> {
816 routable(&topics)?;
817 let topics: Vec<_> = topics
818 .into_iter()
819 .collect::<HashSet<_>>()
820 .into_iter()
821 .collect();
822 let cap = if full {
823 self.limits().max_newest_full_topics
824 } else {
825 self.limits().max_newest_metadata_topics
826 };
827 let results: Vec<Vec<_>> = stream::iter(
828 topics
829 .chunks(cap)
830 .map(<[_]>::to_vec)
831 .collect::<Vec<_>>()
832 .into_iter()
833 .map(|chunk| async move { self.newest_chunk(&chunk, full).await }),
834 )
835 .buffer_unordered(MAX_READ_CHUNKS_IN_FLIGHT)
836 .try_collect()
837 .await?;
838 Ok(results.into_iter().flatten().collect())
839 }
840
841 async fn newest_chunk(
842 &self,
843 topics: &[Topic],
844 full: bool,
845 ) -> Result<Vec<wire::query_newest_response::Result>> {
846 let mut pending = VecDeque::from([topics]);
847 let mut output = Vec::new();
848 while let Some(topics) = pending.pop_front() {
849 let request = wire::QueryNewestRequest {
850 topics: topics
851 .iter()
852 .map(|topic| wire::Topic {
853 topic: topic.cloned_vec(),
854 })
855 .collect(),
856 include_full_envelope: full,
857 };
858 match self
859 .retry_call(
860 || self.api_client.query_newest(request.clone()),
861 topics.len() > 1,
862 )
863 .await
864 {
865 Ok(response) => {
866 let mut seen = HashSet::new();
867 for result in &response.results {
868 let topic = Topic::parse(
869 &result
870 .topic
871 .as_ref()
872 .ok_or(ApiError::InvalidResponse("newest topic"))?
873 .topic,
874 )?;
875 if !topics.contains(&topic) || !seen.insert(topic.clone()) {
876 return Err(ApiError::InvalidResponse(
877 "unrequested or duplicate newest topic",
878 ));
879 }
880 let meta = result
881 .meta
882 .as_ref()
883 .ok_or(ApiError::InvalidResponse("newest metadata"))?;
884 let (meta_topic, _, _) =
885 xmtp_api_backend::envelope::metadata(meta, topic.kind())?;
886 if meta_topic != topic || (full && result.envelope.is_none()) {
887 return Err(ApiError::InvalidResponse("newest envelope"));
888 }
889 }
890 output.extend(response.results);
891 }
892 Err(error) if size_error(&error) && topics.len() > 1 => {
893 let (left, right) = topics.split_at(topics.len() / 2);
894 pending.push_front(right);
895 pending.push_front(left);
896 }
897 Err(error) => return Err(dyn_err(error)),
898 }
899 }
900 Ok(output)
901 }
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907 use xmtp_mls_validation::test_utils::{
908 GroupMessageKind, group_keys, inline_welcome_envelope, keyed_group_message_envelope,
909 };
910
911 #[xmtp_common::test(unwrap_try = true)]
912 fn running_publish_measure_matches_encoded_mixed_request() {
913 let keys = group_keys(2);
914 let units = [
915 PublishUnit::single(inline_welcome_envelope([1; 32]))?,
916 PublishUnit::new(vec![
917 keyed_group_message_envelope(&keys, GroupMessageKind::Proposal, [0; 127]),
918 keyed_group_message_envelope(&keys, GroupMessageKind::Commit, [0; 16384]),
919 ])?,
920 PublishUnit::single(inline_welcome_envelope([1; 32]))?,
921 PublishUnit::single(inline_welcome_envelope([3; 32]))?,
922 ];
923 let limits = LimitsConfiguration::default();
924 let mut measure = PublishMeasure::new(&limits);
925 for (index, unit) in units.iter().enumerate() {
926 measure.add(unit);
927 assert_eq!(measure.bytes, request(&units[..=index]).encoded_len());
928 }
929 assert_eq!(measure.topics.len(), 3);
930 }
931}