Skip to main content

xmtp_mls/groups/
welcome_pointer.rs

1use super::GroupError;
2use xmtp_api_backend::envelope::{EnvelopeError, decode_welcome_message};
3use xmtp_common::{Retry, retry_async};
4use xmtp_proto::{
5    types::{DecryptedWelcomePointer, WelcomeMessageType, WelcomeMessageV1},
6    xmtp::mls::message_contents::WelcomeWrapperAlgorithm,
7};
8
9/// Resolve a welcome pointer from the first envelope on its destination.
10/// Returns none if the destination has no envelope.
11pub async fn resolve_welcome_pointer<Context: crate::context::XmtpSharedContext>(
12    decrypted_welcome_pointer: &DecryptedWelcomePointer,
13    context: &Context,
14) -> Result<Option<WelcomeMessageV1>, GroupError> {
15    let retry = Retry::default();
16    let mut retries = 0;
17    let time_spent = xmtp_common::time::Instant::now();
18    let destination = decrypted_welcome_pointer.destination;
19    let max_bytes = usize::try_from(context.incoming_runtime().policy().max_fetched_bytes)
20        .unwrap_or(usize::MAX);
21
22    tracing::debug!("Resolving welcome pointer for destination {destination}");
23
24    // Can't use retry_async! because we want to return Ok(None) if it isn't resolved.
25    let envelope = loop {
26        let envelope = retry_async!(
27            Retry::default(),
28            (context.api().query_first_welcome(destination, max_bytes))
29        );
30        if let Some(first) = envelope? {
31            break first;
32        }
33        retries += 1;
34        if retries <= retry.retries()
35            && let Some(d) = retry.backoff(retries, time_spent)
36        {
37            tracing::info!(
38                "Welcome pointee not found, backing off for {d:?}... (attempt {})",
39                retries
40            );
41            xmtp_common::time::sleep(d).await;
42        } else {
43            return Ok(None);
44        }
45    };
46    // These failure modes are non-retryable and will end up incrementing
47    // the cursor and will prevent the welcome message from being retried.
48    // No envelope can later appear below the first one, so a first envelope
49    // that does not decode is never replaced.
50    let welcome = decode_welcome_message(envelope).map_err(|error| match error {
51        EnvelopeError::Conversion(error) => GroupError::ConversionError(error),
52        error => xmtp_api::ApiError::from(error).into(),
53    })?;
54    match welcome.variant {
55        // implements: JOIN-056
56        // The envelope decoder already bound installation_key to the destination topic.
57        WelcomeMessageType::V1(v1)
58            if v1.wrapper_algorithm == WelcomeWrapperAlgorithm::SymmetricKey =>
59        {
60            Ok(Some(v1))
61        }
62        WelcomeMessageType::V1(v1) => Err(xmtp_proto::ConversionError::InvalidValue {
63            item: "WelcomePointee.wrapper_algorithm",
64            expected: "SymmetricKey",
65            got: format!("{:?}", v1.wrapper_algorithm),
66        }
67        .into()),
68        WelcomeMessageType::WelcomePointer(_) => {
69            tracing::warn!("Got Another welcome pointer from a welcome pointer. Ignoring.");
70            Err(xmtp_proto::ConversionError::InvalidValue {
71                item: "WelcomeMessage.version",
72                expected: "V1",
73                got: "WelcomePointer".into(),
74            }
75            .into())
76        }
77    }
78}