diff --git a/.github/workflows/test-webassembly.yml b/.github/workflows/test-webassembly.yml index c77c0a7c1..24cabc60d 100644 --- a/.github/workflows/test-webassembly.yml +++ b/.github/workflows/test-webassembly.yml @@ -16,7 +16,7 @@ on: - "rust-toolchain" env: CARGO_TERM_COLOR: always - WASM_BINDGEN_TEST_TIMEOUT: 120 + WASM_BINDGEN_TEST_TIMEOUT: 180 WASM_BINDGEN_TEST_ONLY_WEB: 1 WASM_BINDGEN_SPLIT_LINKED_MODULES: 1 jobs: @@ -35,7 +35,7 @@ jobs: - name: Start Docker containers run: dev/up - name: Build WebAssembly Packages - run: cargo build --tests --release --target wasm32-unknown-unknown -p xmtp_id -p xmtp_mls -p xmtp_api_http -p xmtp_cryptography + run: cargo build --tests --release --target wasm32-unknown-unknown -p xmtp_id -p xmtp_mls -p xmtp_api_http -p xmtp_cryptography -p xmtp_common - name: test with chrome run: | cargo test --release --target wasm32-unknown-unknown -p xmtp_mls -p xmtp_id -p xmtp_api_http -p xmtp_cryptography -- \ diff --git a/Cargo.lock b/Cargo.lock index 96b598356..7cf24af0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,7 @@ dependencies = [ "tracing", "tracing-subscriber", "xmtp_api_grpc", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -485,6 +486,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-bindgen-test", "xmtp_api_http", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -3517,6 +3519,7 @@ dependencies = [ "tracing-subscriber", "vergen", "warp", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", @@ -6830,21 +6833,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.76" @@ -7294,6 +7282,7 @@ dependencies = [ "valuable", "valuable-serde", "xmtp_api_grpc", + "xmtp_common", "xmtp_content_types", "xmtp_cryptography", "xmtp_id", @@ -7301,6 +7290,29 @@ dependencies = [ "xmtp_proto", ] +[[package]] +name = "xmtp_common" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "futures", + "getrandom", + "gloo-timers 0.3.0", + "js-sys", + "parking_lot 0.12.3", + "rand", + "thiserror 2.0.6", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-wasm", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", + "web-time", + "xmtp_cryptography", +] + [[package]] name = "xmtp_content_types" version = "0.1.0" @@ -7309,6 +7321,7 @@ dependencies = [ "rand", "thiserror 2.0.6", "tonic", + "xmtp_common", "xmtp_proto", ] @@ -7366,7 +7379,8 @@ dependencies = [ "tracing", "url", "wasm-bindgen-test", - "wasm-timer", + "web-time", + "xmtp_common", "xmtp_cryptography", "xmtp_proto", "xmtp_v2", @@ -7429,11 +7443,10 @@ dependencies = [ "trait-variant", "wasm-bindgen-futures", "wasm-bindgen-test", - "wasm-timer", "web-sys", - "web-time", "xmtp_api_grpc", "xmtp_api_http", + "xmtp_common", "xmtp_content_types", "xmtp_cryptography", "xmtp_id", @@ -7457,6 +7470,7 @@ dependencies = [ "tonic", "tracing", "wasm-bindgen-test", + "xmtp_common", ] [[package]] @@ -7504,6 +7518,7 @@ dependencies = [ "uniffi", "uuid 1.11.0", "xmtp_api_grpc", + "xmtp_common", "xmtp_cryptography", "xmtp_id", "xmtp_mls", diff --git a/Cargo.toml b/Cargo.toml index c72428bfd..0dde776cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "xtask", "xmtp_debug", "xmtp_content_types", + "common" ] # Make the feature resolver explicit. @@ -104,6 +105,7 @@ xmtp_id = { path = "xmtp_id" } xmtp_mls = { path = "xmtp_mls" } xmtp_proto = { path = "xmtp_proto" } xmtp_content_types = { path = "xmtp_content_types" } +xmtp_common = { path = "common" } [profile.dev] # Disabling debug info speeds up builds a bunch, diff --git a/bindings_ffi/Cargo.toml b/bindings_ffi/Cargo.toml index 938ab78b8..7e44b3498 100644 --- a/bindings_ffi/Cargo.toml +++ b/bindings_ffi/Cargo.toml @@ -22,6 +22,7 @@ xmtp_mls = { path = "../xmtp_mls" } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } xmtp_user_preferences = { path = "../xmtp_user_preferences" } xmtp_v2 = { path = "../xmtp_v2" } +xmtp_common.workspace = true [target.'cfg(target_os = "android")'.dependencies] paranoid-android = "0.2" diff --git a/bindings_ffi/src/mls.rs b/bindings_ffi/src/mls.rs index b3a3b74b3..a9ae8232b 100644 --- a/bindings_ffi/src/mls.rs +++ b/bindings_ffi/src/mls.rs @@ -4,6 +4,7 @@ use crate::{FfiSubscribeError, GenericError}; use std::{collections::HashMap, convert::TryInto, sync::Arc}; use tokio::sync::Mutex; use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::verify_signed_with_public_context; use xmtp_id::scw_verifier::RemoteSignatureVerifier; use xmtp_id::{ @@ -37,7 +38,6 @@ use xmtp_mls::{ GroupMetadataOptions, MlsGroup, PreconfiguredPolicies, UpdateAdminListType, }, identity::IdentityStrategy, - retry::Retry, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, group::GroupQueryArgs, @@ -1827,9 +1827,7 @@ mod tests { FfiPermissionPolicySet, FfiPermissionUpdateType, FfiSubscribeError, }; use ethers::utils::hex; - use rand::distributions::{Alphanumeric, DistString}; use std::{ - env, sync::{ atomic::{AtomicU32, Ordering}, Arc, Mutex, @@ -1837,13 +1835,14 @@ mod tests { time::{Duration, Instant}, }; use tokio::{sync::Notify, time::error::Elapsed}; + use xmtp_common::tmp_path; + use xmtp_common::{wait_for_eq, wait_for_ok}; use xmtp_cryptography::{signature::RecoverableSignature, utils::rng}; use xmtp_id::associations::{ generate_inbox_id, unverified::{UnverifiedRecoverableEcdsaSignature, UnverifiedSignature}, }; use xmtp_mls::{ - api::test_utils::{wait_for_eq, wait_for_ok}, groups::{scoped_client::LocalScopedGroupClient, GroupError}, storage::EncryptionKey, InboxOwner, @@ -1974,15 +1973,6 @@ mod tests { } } - pub fn rand_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 24) - } - - pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", env::temp_dir().to_str().unwrap(), db_name) - } - fn static_enc_key() -> EncryptionKey { [2u8; 32] } @@ -4160,7 +4150,9 @@ mod tests { let _ = wait_for_ok(|| async { alix_a.inner_client.get_sync_group(&alix_a_conn) }).await; let alix_b = new_test_client_with_wallet_and_history(wallet).await; - wait_for_eq(|| async { alix_b.inner_client.identity().is_ready() }, true).await; + wait_for_eq(|| async { alix_b.inner_client.identity().is_ready() }, true) + .await + .unwrap(); let bo = new_test_client_with_history().await; @@ -4179,7 +4171,8 @@ mod tests { }, 2, ) - .await; + .await + .unwrap(); // check that they have the same sync group let sync_group_a = wait_for_ok(|| async { alix_a.conversations().get_sync_group() }) diff --git a/bindings_node/Cargo.toml b/bindings_node/Cargo.toml index aa1038961..81729585c 100644 --- a/bindings_node/Cargo.toml +++ b/bindings_node/Cargo.toml @@ -30,6 +30,7 @@ xmtp_cryptography = { path = "../xmtp_cryptography" } xmtp_id = { path = "../xmtp_id" } xmtp_mls = { path = "../xmtp_mls" } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } +xmtp_common.workspace = true [build-dependencies] napi-build = "2.0.1" diff --git a/bindings_node/src/inbox_id.rs b/bindings_node/src/inbox_id.rs index b9a4d34a9..5b91cb9ea 100644 --- a/bindings_node/src/inbox_id.rs +++ b/bindings_node/src/inbox_id.rs @@ -4,10 +4,10 @@ use napi::bindgen_prelude::Uint8Array; use napi_derive::napi; use std::sync::Arc; use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id; use xmtp_id::associations::MemberIdentifier; use xmtp_mls::api::ApiClientWrapper; -use xmtp_mls::retry::Retry; #[napi] pub async fn get_inbox_id_for_address( diff --git a/bindings_wasm/Cargo.toml b/bindings_wasm/Cargo.toml index 65f79084b..20e988091 100644 --- a/bindings_wasm/Cargo.toml +++ b/bindings_wasm/Cargo.toml @@ -19,6 +19,7 @@ xmtp_api_http = { path = "../xmtp_api_http" } xmtp_cryptography = { path = "../xmtp_cryptography" } xmtp_id = { path = "../xmtp_id" } xmtp_mls = { path = "../xmtp_mls", features = ["test-utils", "http-api"] } +xmtp_common.workspace = true xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } tracing-web = "0.1" tracing.workspace = true diff --git a/bindings_wasm/src/inbox_id.rs b/bindings_wasm/src/inbox_id.rs index 1aec357c4..d0b046f3d 100644 --- a/bindings_wasm/src/inbox_id.rs +++ b/bindings_wasm/src/inbox_id.rs @@ -1,8 +1,8 @@ use wasm_bindgen::prelude::{wasm_bindgen, JsError}; use xmtp_api_http::XmtpHttpApiClient; +use xmtp_common::retry::Retry; use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id; use xmtp_mls::api::ApiClientWrapper; -use xmtp_mls::retry::Retry; #[wasm_bindgen(js_name = getInboxIdForAddress)] pub async fn get_inbox_id_for_address( diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 000000000..76b836916 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "xmtp_common" +edition = "2021" +version.workspace = true +license.workspace = true + +[dependencies] +web-time.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["time"] } +rand = "0.8" +futures.workspace = true +xmtp_cryptography.workspace = true + +parking_lot = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "ansi", "json"], optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true, features = ["js"] } +gloo-timers = { workspace = true, features = ["futures"] } +tracing-wasm = { version = "0.2", optional = true } +console_error_panic_hook = { version = "0.1", optional = true } +js-sys.workspace = true +web-sys = { workspace = true, features = ["Window"] } +wasm-bindgen-futures.workspace = true + +[dev-dependencies] +thiserror.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +tokio = { workspace = true, features = ["time", "macros", "rt", "sync"]} +wasm-bindgen-test.workspace = true + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +tokio = { workspace = true, features = ["time", "macros", "rt-multi-thread", "sync"]} + +[features] +test-utils = ["dep:parking_lot", "dep:tracing-subscriber", "dep:tracing-wasm", "dep:console_error_panic_hook"] diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 000000000..a198962ad --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,47 @@ +//! Common types shared among all XMTP Crates + +mod macros; + +#[cfg(feature = "test-utils")] +mod test; +#[cfg(feature = "test-utils")] +pub use test::*; + +pub mod retry; +pub use retry::*; + +/// Global Marker trait for WebAssembly +#[cfg(target_arch = "wasm32")] +pub trait Wasm {} +#[cfg(target_arch = "wasm32")] +impl Wasm for T {} + +pub mod time; + +use rand::{ + distributions::{Alphanumeric, DistString}, + RngCore, +}; +use xmtp_cryptography::utils as crypto_utils; + +pub fn rand_string() -> String { + Alphanumeric.sample_string(&mut crypto_utils::rng(), N) +} + +pub fn rand_array() -> [u8; N] { + let mut buffer = [0u8; N]; + crypto_utils::rng().fill_bytes(&mut buffer); + buffer +} + +/// Yield back control to the async runtime +#[cfg(not(target_arch = "wasm32"))] +pub async fn yield_() { + tokio::task::yield_now().await +} + +/// Yield back control to the async runtime +#[cfg(target_arch = "wasm32")] +pub async fn yield_() { + time::sleep(crate::time::Duration::from_millis(1)).await; +} diff --git a/common/src/macros.rs b/common/src/macros.rs new file mode 100644 index 000000000..9ebf42790 --- /dev/null +++ b/common/src/macros.rs @@ -0,0 +1,24 @@ +/// Turn the `Result` into an `Option`, logging the error with `tracing::error` and +/// returning `None` if the value matches on Result::Err(). +/// Optionally pass a message as the second argument. +#[macro_export] +macro_rules! optify { + ( $e: expr ) => { + match $e { + Ok(v) => Some(v), + Err(e) => { + tracing::error!("{}", e); + None + } + } + }; + ( $e: expr, $msg: tt ) => { + match $e { + Ok(v) => Some(v), + Err(e) => { + tracing::error!("{}: {:?}", $msg, e); + None + } + } + }; +} diff --git a/xmtp_mls/src/retry.rs b/common/src/retry.rs similarity index 94% rename from xmtp_mls/src/retry.rs rename to common/src/retry.rs index 6e5aca993..3088d1dc6 100644 --- a/xmtp_mls/src/retry.rs +++ b/common/src/retry.rs @@ -18,9 +18,11 @@ use rand::Rng; +pub struct NotSpecialized; + /// Specifies which errors are retryable. /// All Errors are not retryable by-default. -pub trait RetryableError: std::error::Error { +pub trait RetryableError: std::error::Error { fn is_retryable(&self) -> bool; } @@ -76,7 +78,7 @@ pub struct RetryBuilder { /// /// # Example /// ``` -/// use xmtp_mls::retry::RetryBuilder; +/// use xmtp_common::retry::RetryBuilder; /// /// RetryBuilder::default() /// .retries(5) @@ -121,7 +123,7 @@ impl Retry { /// Retry but for an async context /// ``` -/// use xmtp_mls::{retry_async, retry::{RetryableError, Retry}}; +/// use xmtp_common::{retry_async, retry::{RetryableError, Retry}}; /// use thiserror::Error; /// use tokio::sync::mpsc; /// @@ -183,7 +185,7 @@ macro_rules! retry_async { e.to_string() ); attempts += 1; - $crate::sleep($retry.duration(attempts)).await; + $crate::time::sleep($retry.duration(attempts)).await; } else { tracing::info!("error is not retryable. {:?}:{}", e, e); break Err(e); @@ -207,13 +209,6 @@ macro_rules! retryable { }}; } -// network errors should generally be retryable, unless there's a bug in our code -impl RetryableError for xmtp_proto::Error { - fn is_retryable(&self) -> bool { - true - } -} - #[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] @@ -313,7 +308,7 @@ pub(crate) mod tests { return Ok(()); } // do some work - crate::sleep(core::time::Duration::from_nanos(100)).await; + crate::time::sleep(core::time::Duration::from_nanos(100)).await; Err(SomeError::ARetryableError) } @@ -339,7 +334,7 @@ pub(crate) mod tests { } *data += 1; // do some work - crate::sleep(core::time::Duration::from_nanos(100)).await; + crate::time::sleep(core::time::Duration::from_nanos(100)).await; Err(SomeError::ARetryableError) } diff --git a/common/src/test.rs b/common/src/test.rs new file mode 100644 index 000000000..c11c692cb --- /dev/null +++ b/common/src/test.rs @@ -0,0 +1,183 @@ +//! Common Test Utilites +use rand::{ + distributions::{Alphanumeric, DistString}, + seq::IteratorRandom, + Rng, +}; +use std::{future::Future, sync::OnceLock}; +use xmtp_cryptography::utils as crypto_utils; + +#[cfg(not(target_arch = "wasm32"))] +pub mod traced_test; +#[cfg(not(target_arch = "wasm32"))] +pub use traced_test::TestWriter; + +use crate::time::Expired; + +mod macros; + +static INIT: OnceLock<()> = OnceLock::new(); + +/// A simple test logger that defaults to the INFO level +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub fn logger() { + use tracing_subscriber::{ + fmt::{self, format}, + layer::SubscriberExt, + util::SubscriberInitExt, + EnvFilter, Layer, + }; + + INIT.get_or_init(|| { + let structured = std::env::var("STRUCTURED"); + let is_structured = matches!(structured, Ok(s) if s == "true" || s == "1"); + + let filter = || { + EnvFilter::builder() + .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) + .from_env_lossy() + }; + + tracing_subscriber::registry() + // structured JSON logger only if STRUCTURED=true + .with(is_structured.then(|| { + tracing_subscriber::fmt::layer() + .json() + .flatten_event(true) + .with_level(true) + .with_filter(filter()) + })) + // default logger + .with((!is_structured).then(|| { + fmt::layer() + .compact() + .fmt_fields({ + format::debug_fn(move |writer, field, value| { + if field.name() == "message" { + write!(writer, "{:?}", value)?; + } + Ok(()) + }) + }) + .with_filter(filter()) + })) + .init(); + }); +} + +/// A simple test logger that defaults to the INFO level +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +pub fn logger() { + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + use tracing_subscriber::EnvFilter; + + INIT.get_or_init(|| { + let filter = EnvFilter::builder() + .with_default_directive(tracing::metadata::LevelFilter::DEBUG.into()) + .from_env_lossy(); + + tracing_subscriber::registry() + .with(tracing_wasm::WASMLayer::default()) + .with(filter) + .init(); + + console_error_panic_hook::set_once(); + }); +} + +pub fn rand_hexstring() -> String { + let mut rng = crypto_utils::rng(); + let hex_chars = "0123456789abcdef"; + let v: String = (0..40) + .map(|_| hex_chars.chars().choose(&mut rng).unwrap()) + .collect(); + + format!("0x{}", v) +} + +pub fn rand_account_address() -> String { + Alphanumeric.sample_string(&mut crypto_utils::rng(), 42) +} + +pub fn rand_vec() -> Vec { + crate::rand_array::().to_vec() +} + +pub fn rand_u64() -> u64 { + crypto_utils::rng().gen() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn tmp_path() -> String { + let db_name = crate::rand_string::<24>(); + format!("{}/{}.db3", std::env::temp_dir().to_str().unwrap(), db_name) +} + +#[cfg(target_arch = "wasm32")] +pub fn tmp_path() -> String { + let db_name = crate::rand_string::<24>(); + format!("{}/{}.db3", "test_db", db_name) +} + +pub fn rand_time() -> i64 { + let mut rng = rand::thread_rng(); + rng.gen_range(0..1_000_000_000) +} + +pub async fn wait_for_some(f: F) -> Option +where + F: Fn() -> Fut, + Fut: Future>, +{ + crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + if let Some(r) = f().await { + return r; + } else { + crate::yield_().await; + } + } + }) + .await + .ok() +} + +pub async fn wait_for_ok(f: F) -> Result +where + F: Fn() -> Fut, + Fut: Future>, +{ + crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + if let Ok(r) = f().await { + return r; + } else { + crate::yield_().await; + } + } + }) + .await +} + +pub async fn wait_for_eq(f: F, expected: T) -> Result<(), Expired> +where + F: Fn() -> Fut, + Fut: Future, + T: std::fmt::Debug + PartialEq, +{ + let result = crate::time::timeout(crate::time::Duration::from_secs(20), async { + loop { + let result = f().await; + if expected == result { + return result; + } else { + crate::yield_().await; + } + } + }) + .await?; + + assert_eq!(expected, result); + Ok(()) +} diff --git a/common/src/test/macros.rs b/common/src/test/macros.rs new file mode 100644 index 000000000..e07087d78 --- /dev/null +++ b/common/src/test/macros.rs @@ -0,0 +1,49 @@ +/// wrapper over assert!(matches!()) for Errors +/// assert_err!(fun(), StorageError::Explosion) +/// +/// or the message variant, +/// assert_err!(fun(), StorageError::Explosion, "the storage did not explode"); +#[macro_export] +macro_rules! assert_err { + ( $x:expr , $y:pat $(,)? ) => { + assert!(matches!($x, Err($y))) + }; + + ( $x:expr, $y:pat $(,)?, $($msg:tt)+) => {{ + assert!(matches!($x, Err($y)), $($msg)+) + }} + } + +/// wrapper over assert! macros for Ok's +/// +/// Make sure something is Ok(_) without caring about return value. +/// assert_ok!(fun()); +/// +/// Against an expected value, e.g Ok(true) +/// assert_ok!(fun(), true); +/// +/// or the message variant, +/// assert_ok!(fun(), Ok(_), "the storage is not ok"); +#[macro_export] +macro_rules! assert_ok { + + ( $e:expr ) => { + assert_ok!($e,) + }; + + ( $e:expr, ) => {{ + use std::result::Result::*; + match $e { + Ok(v) => v, + Err(e) => panic!("assertion failed: Err({:?})", e), + } + }}; + + ( $x:expr , $y:expr $(,)? ) => { + assert_eq!($x, Ok($y.into())); + }; + + ( $x:expr, $y:expr $(,)?, $($msg:tt)+) => {{ + assert_eq!($x, Ok($y.into()), $($msg)+); + }} + } diff --git a/xmtp_mls/src/utils/test/traced_test.rs b/common/src/test/traced_test.rs similarity index 72% rename from xmtp_mls/src/utils/test/traced_test.rs rename to common/src/test/traced_test.rs index fea217199..eb208d220 100644 --- a/xmtp_mls/src/utils/test/traced_test.rs +++ b/common/src/test/traced_test.rs @@ -1,3 +1,4 @@ +/// Tests that can assert on tracing logs in a tokio threaded context use std::{io, sync::Arc}; use parking_lot::Mutex; @@ -58,8 +59,9 @@ impl fmt::MakeWriter<'_> for TestWriter { self.clone() } } - +/* /// Only works with current-thread +#[inline] pub fn traced_test(f: impl Fn() -> Fut) where Fut: futures::Future, @@ -88,6 +90,39 @@ where buf.clear(); }); } +*/ + +#[macro_export] +macro_rules! traced_test { + ( $f:expr ) => {{ + use tracing_subscriber::fmt; + use $crate::traced_test::TestWriter; + + $crate::traced_test::LOG_BUFFER.with(|buf| { + let rt = tokio::runtime::Builder::new_current_thread() + .thread_name("tracing-test") + .enable_time() + .enable_io() + .build() + .unwrap(); + buf.clear(); + + let subscriber = fmt::Subscriber::builder() + .with_env_filter(format!("{}=debug", env!("CARGO_PKG_NAME"))) + .with_writer(buf.clone()) + .with_level(true) + .with_ansi(false) + .finish(); + + let dispatch = tracing::Dispatch::new(subscriber); + tracing::dispatcher::with_default(&dispatch, || { + rt.block_on($f); + }); + + buf.clear(); + }); + }}; +} /// macro that can assert logs in tests. /// Note: tests that use this must be used in `traced_test` function @@ -95,7 +130,7 @@ where #[macro_export] macro_rules! assert_logged { ( $search:expr , $occurrences:expr ) => { - $crate::utils::test::traced_test::LOG_BUFFER.with(|buf| { + $crate::traced_test::LOG_BUFFER.with(|buf| { let lines = { buf.flush(); buf.as_string() diff --git a/common/src/time.rs b/common/src/time.rs new file mode 100644 index 000000000..8b411a826 --- /dev/null +++ b/common/src/time.rs @@ -0,0 +1,99 @@ +//! Time primitives for native and WebAssembly + +use std::fmt; + +#[derive(Debug)] +pub struct Expired; + +impl std::error::Error for Expired { + fn description(&self) -> &str { + "Timer duration expired" + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Expired { + fn from(_: tokio::time::error::Elapsed) -> Expired { + Expired + } +} + +impl fmt::Display for Expired { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + write!(f, "timer duration expired") + } +} + +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +#[cfg(all(target_family = "wasm", target_os = "unknown"))] +pub use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +fn duration_since_epoch() -> Duration { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") +} + +pub fn now_ns() -> i64 { + duration_since_epoch().as_nanos() as i64 +} + +pub fn now_secs() -> i64 { + duration_since_epoch().as_secs() as i64 +} + +#[cfg(target_arch = "wasm32")] +pub async fn timeout(duration: Duration, future: F) -> Result +where + F: std::future::IntoFuture, +{ + use futures::future::Either::*; + let timeout = gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32); + let future = future.into_future(); + futures::pin_mut!(future); + match futures::future::select(timeout, future).await { + Left(_) => Err(Expired), + Right((value, _)) => Ok(value), + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub async fn timeout(duration: Duration, future: F) -> Result +where + F: std::future::IntoFuture, +{ + tokio::time::timeout(duration, future) + .await + .map_err(Into::into) +} + +// WASM Shims +#[cfg(target_arch = "wasm32")] +#[doc(hidden)] +pub async fn sleep(duration: Duration) { + use js_sys::wasm_bindgen::JsCast; + use js_sys::wasm_bindgen::UnwrapThrowExt; + use web_sys::WorkerGlobalScope; + + let mut cb = |resolve: js_sys::Function, _reject: js_sys::Function| { + let worker = js_sys::global() + .dyn_into::() + .expect("xmtp_mls should always act in worker in browser"); + + worker + .set_timeout_with_callback_and_timeout_and_arguments_0( + &resolve, + duration.as_millis() as i32, + ) + .expect("Failed to call set_timeout"); + }; + let p = js_sys::Promise::new(&mut cb); + wasm_bindgen_futures::JsFuture::from(p).await.unwrap_throw(); +} + +#[cfg(not(target_arch = "wasm32"))] +#[doc(hidden)] +pub async fn sleep(duration: Duration) { + tokio::time::sleep(duration).await +} diff --git a/dev/wasm b/dev/wasm deleted file mode 100755 index 717bdf8b4..000000000 --- a/dev/wasm +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -eou pipefail - -if [[ "${OSTYPE}" == "darwin"* ]]; then - if ! wasm-pack --version &>/dev/null; then cargo install wasm-pack; fi -fi - -pushd bindings_wasm/ > /dev/null - -wasm-pack build --target nodejs --out-dir pkg # pkg is the default - -popd > /dev/null diff --git a/examples/cli/Cargo.toml b/examples/cli/Cargo.toml index 830931bce..cff9a9200 100644 --- a/examples/cli/Cargo.toml +++ b/examples/cli/Cargo.toml @@ -43,3 +43,4 @@ xmtp_content_types = { path = "../../xmtp_content_types" } xmtp_id = { path = "../../xmtp_id" } xmtp_mls = { path = "../../xmtp_mls" } xmtp_proto = { path = "../../xmtp_proto", features = ["proto_full"] } +xmtp_common.workspace = true diff --git a/examples/cli/cli-client.rs b/examples/cli/cli-client.rs index ec285b1d9..a30060380 100755 --- a/examples/cli/cli-client.rs +++ b/examples/cli/cli-client.rs @@ -32,6 +32,7 @@ use tracing_subscriber::{ use valuable::Valuable; use xmtp_api_grpc::grpc_api_helper::Client as ClientV3; use xmtp_api_grpc::replication_client::ClientV4; +use xmtp_common::time::now_ns; use xmtp_content_types::{text::TextCodec, ContentCodec}; use xmtp_cryptography::{ signature::{RecoverableSignature, SignatureError}, @@ -54,7 +55,6 @@ use xmtp_mls::{ group_message::StoredGroupMessage, EncryptedMessageStore, EncryptionKey, StorageError, StorageOption, }, - utils::time::now_ns, InboxOwner, }; use xmtp_proto::xmtp::mls::message_contents::DeviceSyncKind; diff --git a/mls_validation_service/Cargo.toml b/mls_validation_service/Cargo.toml index fd6e37edd..e5aa6dbb7 100644 --- a/mls_validation_service/Cargo.toml +++ b/mls_validation_service/Cargo.toml @@ -36,6 +36,7 @@ ethers.workspace = true rand = { workspace = true } xmtp_id = { workspace = true, features = ["test-utils"] } xmtp_mls = { workspace = true, features = ["test-utils"] } +xmtp_common = { workspace = true, features = ["test-utils"]} [features] test-utils = ["xmtp_id/test-utils"] diff --git a/mls_validation_service/src/handlers.rs b/mls_validation_service/src/handlers.rs index ef96904dd..abf159685 100644 --- a/mls_validation_service/src/handlers.rs +++ b/mls_validation_service/src/handlers.rs @@ -328,11 +328,12 @@ mod tests { prelude::{tls_codec::Serialize, Credential as OpenMlsCredential, CredentialWithKey}, }; use openmls_rust_crypto::OpenMlsRustCrypto; + use xmtp_common::{rand_string, rand_u64}; use xmtp_cryptography::XmtpInstallationCredential; use xmtp_id::{ associations::{ generate_inbox_id, - test_utils::{rand_string, rand_u64, MockSmartContractSignatureVerifier}, + test_utils::MockSmartContractSignatureVerifier, unverified::{UnverifiedAction, UnverifiedIdentityUpdate}, }, is_smart_contract, @@ -399,7 +400,7 @@ mod tests { #[tokio::test] #[should_panic] async fn test_get_association_state() { - let account_address = rand_string(); + let account_address = rand_string::<24>(); let nonce = rand_u64(); let inbox_id = generate_inbox_id(&account_address, &nonce).unwrap(); let update = UnverifiedIdentityUpdate::new_test( diff --git a/xmtp_api_http/src/util.rs b/xmtp_api_http/src/util.rs index 55e4ff3fa..8a839fc56 100644 --- a/xmtp_api_http/src/util.rs +++ b/xmtp_api_http/src/util.rs @@ -76,7 +76,6 @@ pub fn create_grpc_stream_inner< http_client: reqwest::Client, ) -> impl Stream> { async_stream::stream! { - tracing::info!("Spawning grpc http stream"); let request = http_client .post(endpoint) .json(&request) diff --git a/xmtp_content_types/Cargo.toml b/xmtp_content_types/Cargo.toml index 4d0a2b2ee..2b7c506d1 100644 --- a/xmtp_content_types/Cargo.toml +++ b/xmtp_content_types/Cargo.toml @@ -11,6 +11,10 @@ rand = { workspace = true } # XMTP/Local xmtp_proto = { workspace = true, features = ["convert"] } +xmtp_common = { workspace = true } + +[dev-dependencies] +xmtp_common = { workspace = true, features = ['test-utils'] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tonic = { version = "0.12", features = ["transport"] } diff --git a/xmtp_content_types/src/group_updated.rs b/xmtp_content_types/src/group_updated.rs index 7aa797ee0..2ab08917a 100644 --- a/xmtp_content_types/src/group_updated.rs +++ b/xmtp_content_types/src/group_updated.rs @@ -50,20 +50,18 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use xmtp_proto::xmtp::mls::message_contents::{group_updated::Inbox, GroupUpdated}; - - use crate::test_utils::rand_string; - use super::*; + use xmtp_common::rand_string; + use xmtp_proto::xmtp::mls::message_contents::{group_updated::Inbox, GroupUpdated}; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_encode_decode() { let new_member = Inbox { - inbox_id: rand_string(), + inbox_id: rand_string::<24>(), }; let data = GroupUpdated { - initiated_by_inbox_id: rand_string(), + initiated_by_inbox_id: rand_string::<24>(), added_inboxes: vec![new_member.clone()], removed_inboxes: vec![], metadata_field_changes: vec![], diff --git a/xmtp_content_types/src/lib.rs b/xmtp_content_types/src/lib.rs index b882891a1..04a7a3fb9 100644 --- a/xmtp_content_types/src/lib.rs +++ b/xmtp_content_types/src/lib.rs @@ -1,7 +1,5 @@ pub mod group_updated; pub mod membership_change; -#[cfg(test)] -mod test_utils; pub mod text; use thiserror::Error; diff --git a/xmtp_content_types/src/membership_change.rs b/xmtp_content_types/src/membership_change.rs index b83483806..14401bbb8 100644 --- a/xmtp_content_types/src/membership_change.rs +++ b/xmtp_content_types/src/membership_change.rs @@ -52,18 +52,16 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use xmtp_proto::xmtp::mls::message_contents::MembershipChange; - - use crate::test_utils::{rand_string, rand_vec}; - use super::*; + use xmtp_common::{rand_string, rand_vec}; + use xmtp_proto::xmtp::mls::message_contents::MembershipChange; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_encode_decode() { let new_member = MembershipChange { - installation_ids: vec![rand_vec()], - account_address: rand_string(), + installation_ids: vec![rand_vec::<24>()], + account_address: rand_string::<24>(), initiated_by_account_address: "".to_string(), }; let data = GroupMembershipChanges { diff --git a/xmtp_content_types/src/test_utils.rs b/xmtp_content_types/src/test_utils.rs deleted file mode 100644 index dead0c7e6..000000000 --- a/xmtp_content_types/src/test_utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[cfg(test)] -use rand::{ - distributions::{Alphanumeric, DistString}, - Rng, -}; - -#[cfg(test)] -pub(crate) fn rand_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 24) -} - -#[cfg(test)] -pub(crate) fn rand_vec() -> Vec { - rand::thread_rng().gen::<[u8; 24]>().to_vec() -} diff --git a/xmtp_id/Cargo.toml b/xmtp_id/Cargo.toml index 3488b23d3..2b6d44c91 100644 --- a/xmtp_id/Cargo.toml +++ b/xmtp_id/Cargo.toml @@ -27,8 +27,9 @@ tokio = { workspace = true, features = ["macros"] } tracing.workspace = true xmtp_cryptography.workspace = true xmtp_proto = { workspace = true, features = ["proto_full"] } -wasm-timer.workspace = true +web-time.workspace = true ethers = { workspace = true, features = ["rustls"] } +xmtp_common.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] openmls = { workspace = true, features = ["js"] } @@ -40,10 +41,11 @@ openmls.workspace = true [dev-dependencies] xmtp_v2 = { path = "../xmtp_v2" } ed25519-dalek = { workspace = true, features = ["digest", "rand_core"] } +xmtp_common = { workspace = true, features = ["test-utils"] } +wasm-bindgen-test.workspace = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test.workspace = true gloo-timers.workspace = true [features] -test-utils = [] +test-utils = ["xmtp_common/test-utils"] diff --git a/xmtp_id/src/associations/builder.rs b/xmtp_id/src/associations/builder.rs index 43c0d166a..5426cd8b7 100644 --- a/xmtp_id/src/associations/builder.rs +++ b/xmtp_id/src/associations/builder.rs @@ -4,8 +4,9 @@ use std::collections::HashMap; -use crate::{scw_verifier::SmartContractSignatureVerifier, utils::now_ns}; +use crate::scw_verifier::SmartContractSignatureVerifier; use thiserror::Error; +use xmtp_common::time::now_ns; use super::{ unsigned_actions::{ diff --git a/xmtp_id/src/associations/member.rs b/xmtp_id/src/associations/member.rs index 40b162e73..f23e19101 100644 --- a/xmtp_id/src/associations/member.rs +++ b/xmtp_id/src/associations/member.rs @@ -165,15 +165,12 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::test_utils; - use super::*; - - use test_utils::rand_string; + use xmtp_common::rand_hexstring; impl Default for MemberIdentifier { fn default() -> Self { - MemberIdentifier::Address(rand_string()) + MemberIdentifier::Address(rand_hexstring()) } } diff --git a/xmtp_id/src/associations/mod.rs b/xmtp_id/src/associations/mod.rs index 7cd29ba17..ca6e6e7d3 100644 --- a/xmtp_id/src/associations/mod.rs +++ b/xmtp_id/src/associations/mod.rs @@ -42,11 +42,11 @@ pub fn get_state>( #[cfg(any(test, feature = "test-utils"))] pub mod test_defaults { use self::{ - test_utils::{rand_string, rand_u64, rand_vec}, unverified::{UnverifiedAction, UnverifiedIdentityUpdate}, verified_signature::VerifiedSignature, }; use super::*; + use xmtp_common::{rand_hexstring, rand_u64, rand_vec}; impl IdentityUpdate { pub fn new_test(actions: Vec, inbox_id: String) -> Self { @@ -62,19 +62,19 @@ pub mod test_defaults { impl Default for AddAssociation { fn default() -> Self { - let existing_member = rand_string(); - let new_member = rand_vec(); + let existing_member = rand_hexstring(); + let new_member = rand_vec::<32>(); Self { existing_member_signature: VerifiedSignature::new( existing_member.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), new_member_signature: VerifiedSignature::new( new_member.clone().into(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member.into(), @@ -85,14 +85,14 @@ pub mod test_defaults { // Default will create an inbox with a ERC-191 signature impl Default for CreateInbox { fn default() -> Self { - let signer = rand_string(); + let signer = rand_hexstring(); Self { nonce: rand_u64(), account_address: signer.clone(), initial_address_signature: VerifiedSignature::new( signer.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), } @@ -101,15 +101,15 @@ pub mod test_defaults { impl Default for RevokeAssociation { fn default() -> Self { - let signer = rand_string(); + let signer = rand_hexstring(); Self { recovery_address_signature: VerifiedSignature::new( signer.into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), - revoked_member: rand_string().into(), + revoked_member: rand_hexstring().into(), } } } @@ -119,10 +119,11 @@ pub mod test_defaults { pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; - use self::test_utils::{rand_string, rand_vec}; use super::*; use crate::associations::verified_signature::VerifiedSignature; + use xmtp_common::{rand_hexstring, rand_vec}; pub fn new_test_inbox() -> AssociationState { let create_request = CreateInbox::default(); @@ -144,7 +145,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( initial_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -157,7 +158,7 @@ pub(crate) mod tests { .unwrap() } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_create_inbox() { let create_request = CreateInbox::default(); let inbox_id = @@ -172,11 +173,11 @@ pub(crate) mod tests { assert!(existing_entity.identifier.eq(&account_address.into())); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_and_add_separately() { let initial_state = new_test_inbox(); let inbox_id = initial_state.inbox_id().to_string(); - let new_installation_identifier: MemberIdentifier = rand_vec().into(); + let new_installation_identifier: MemberIdentifier = rand_vec::<32>().into(); let first_member: MemberIdentifier = initial_state.recovery_address().clone().into(); let update = Action::AddAssociation(AddAssociation { @@ -184,13 +185,13 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( new_installation_identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( first_member.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -206,24 +207,24 @@ pub(crate) mod tests { assert_eq!(new_member.added_by_entity, Some(first_member)); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_and_add_together() { let create_action = CreateInbox::default(); let account_address = create_action.account_address.clone(); let inbox_id = generate_inbox_id(&account_address, &create_action.nonce).unwrap(); - let new_member_identifier: MemberIdentifier = rand_vec().into(); + let new_member_identifier: MemberIdentifier = rand_vec::<32>().into(); let add_action = AddAssociation { existing_member_signature: VerifiedSignature::new( account_address.clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), // Add an installation ID new_member_signature: VerifiedSignature::new( new_member_identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member_identifier.clone(), @@ -243,9 +244,9 @@ pub(crate) mod tests { ); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn create_from_legacy_key() { - let member_identifier: MemberIdentifier = rand_string().into(); + let member_identifier: MemberIdentifier = rand_hexstring().into(); let create_action = CreateInbox { nonce: 0, account_address: member_identifier.to_string(), @@ -282,7 +283,7 @@ pub(crate) mod tests { assert!(matches!(update_result, Err(AssociationError::Replay))); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn add_wallet_from_installation_key() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -293,19 +294,19 @@ pub(crate) mod tests { .unwrap() .identifier; - let new_wallet_address: MemberIdentifier = rand_string().into(); + let new_wallet_address: MemberIdentifier = rand_hexstring().into(); let add_association = Action::AddAssociation(AddAssociation { new_member_identifier: new_wallet_address.clone(), new_member_signature: VerifiedSignature::new( new_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( installation_id.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -318,13 +319,13 @@ pub(crate) mod tests { assert_eq!(new_state.members().len(), 3); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_invalid_signature_on_create() { // Creates a signature with the wrong signer let bad_signature = VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ); let action = CreateInbox { @@ -334,7 +335,7 @@ pub(crate) mod tests { let state_result = get_state(vec![IdentityUpdate::new_test( vec![Action::CreateInbox(action)], - rand_string(), + rand_hexstring(), )]); assert!(state_result.is_err()); @@ -344,15 +345,15 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_invalid_signature_on_update() { let initial_state = new_test_inbox(); let inbox_id = initial_state.inbox_id().to_string(); // Signature is from a random address let bad_signature = VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ); @@ -376,7 +377,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -392,7 +393,7 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_if_signer_not_existing_member() { let create_inbox = CreateInbox::default(); let inbox_id = @@ -402,9 +403,9 @@ pub(crate) mod tests { let update = Action::AddAssociation(AddAssociation { // Existing member signature is coming from a random wallet existing_member_signature: VerifiedSignature::new( - rand_string().into(), + rand_hexstring().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -420,26 +421,26 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn reject_if_installation_adding_installation() { let existing_state = new_test_inbox_with_installation(); let inbox_id = existing_state.inbox_id().to_string(); let existing_installations = existing_state.members_by_kind(MemberKind::Installation); let existing_installation = existing_installations.first().unwrap(); - let new_installation_id: MemberIdentifier = rand_vec().into(); + let new_installation_id: MemberIdentifier = rand_vec::<32>().into(); let update = Action::AddAssociation(AddAssociation { existing_member_signature: VerifiedSignature::new( existing_installation.identifier.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_installation_id.clone(), new_member_signature: VerifiedSignature::new( new_installation_id.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -457,7 +458,7 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -471,7 +472,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: installation_id.clone(), @@ -485,7 +486,7 @@ pub(crate) mod tests { assert!(new_state.get(&installation_id).is_none()); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke_children() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); @@ -500,7 +501,7 @@ pub(crate) mod tests { existing_member_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), ..Default::default() @@ -517,7 +518,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: wallet_address.clone(), @@ -532,7 +533,7 @@ pub(crate) mod tests { assert_eq!(new_state.members().len(), 0); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn revoke_and_re_add() { let initial_state = new_test_inbox(); let wallet_address = initial_state @@ -544,19 +545,19 @@ pub(crate) mod tests { let inbox_id = initial_state.inbox_id().to_string(); - let second_wallet_address: MemberIdentifier = rand_string().into(); + let second_wallet_address: MemberIdentifier = rand_hexstring().into(); let add_second_wallet = Action::AddAssociation(AddAssociation { new_member_identifier: second_wallet_address.clone(), new_member_signature: VerifiedSignature::new( second_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -565,7 +566,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: second_wallet_address.clone(), @@ -586,13 +587,13 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( second_wallet_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), existing_member_signature: VerifiedSignature::new( wallet_address, SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -605,19 +606,19 @@ pub(crate) mod tests { assert_eq!(state_after_re_add.members().len(), 2); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn change_recovery_address() { let initial_state = new_test_inbox_with_installation(); let inbox_id = initial_state.inbox_id().to_string(); let initial_recovery_address: MemberIdentifier = initial_state.recovery_address().clone().into(); - let new_recovery_address = rand_string(); + let new_recovery_address = rand_hexstring(); let update_recovery = Action::ChangeRecoveryAddress(ChangeRecoveryAddress { new_recovery_address: new_recovery_address.clone(), recovery_address_signature: VerifiedSignature::new( initial_state.recovery_address().clone().into(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), }); @@ -633,7 +634,7 @@ pub(crate) mod tests { recovery_address_signature: VerifiedSignature::new( initial_recovery_address.clone(), SignatureKind::Erc191, - rand_vec(), + rand_vec::<32>(), None, ), revoked_member: initial_recovery_address.clone(), @@ -650,14 +651,14 @@ pub(crate) mod tests { )); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn scw_signature_binding() { let initial_chain_id: u64 = 1; - let signer = rand_string(); + let signer = rand_hexstring(); let initial_address_signature = VerifiedSignature::new( signer.clone().into(), SignatureKind::Erc1271, - rand_vec(), + rand_vec::<32>(), Some(initial_chain_id), ); let action = CreateInbox { @@ -675,13 +676,13 @@ pub(crate) mod tests { let inbox_id = initial_state.inbox_id(); let new_chain_id: u64 = 2; - let new_member: MemberIdentifier = rand_vec().into(); + let new_member: MemberIdentifier = rand_vec::<32>().into(); // A signature from the same account address but on a different chain ID let existing_member_sig = VerifiedSignature::new( signer.clone().into(), SignatureKind::Erc1271, - rand_vec(), + rand_vec::<32>(), Some(new_chain_id), ); @@ -691,7 +692,7 @@ pub(crate) mod tests { new_member_signature: VerifiedSignature::new( new_member.clone(), SignatureKind::InstallationKey, - rand_vec(), + rand_vec::<32>(), None, ), new_member_identifier: new_member.clone(), @@ -702,7 +703,7 @@ pub(crate) mod tests { }), Action::ChangeRecoveryAddress(ChangeRecoveryAddress { recovery_address_signature: existing_member_sig.clone(), - new_recovery_address: rand_string(), + new_recovery_address: rand_hexstring(), }), ]; diff --git a/xmtp_id/src/associations/serialization.rs b/xmtp_id/src/associations/serialization.rs index 15c5ca2b5..41e76a0d7 100644 --- a/xmtp_id/src/associations/serialization.rs +++ b/xmtp_id/src/associations/serialization.rs @@ -581,21 +581,19 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::{ - hashes::generate_inbox_id, - test_utils::{rand_string, rand_u64, rand_vec}, - }; + use crate::associations::hashes::generate_inbox_id; + use xmtp_common::{rand_hexstring, rand_u64, rand_vec}; use super::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn test_round_trip_unverified() { - let account_address = rand_string(); + let account_address = rand_hexstring(); let nonce = rand_u64(); let inbox_id = generate_inbox_id(&account_address, &nonce).unwrap(); let client_timestamp_ns = rand_u64(); - let signature_bytes = rand_vec(); + let signature_bytes = rand_vec::<32>(); let identity_update = UnverifiedIdentityUpdate::new( inbox_id, @@ -616,7 +614,7 @@ pub(crate) mod tests { 4, 5, 6, ]), unsigned_action: UnsignedAddAssociation { - new_member_identifier: rand_string().into(), + new_member_identifier: rand_hexstring().into(), }, }), UnverifiedAction::ChangeRecoveryAddress(UnverifiedChangeRecoveryAddress { @@ -624,7 +622,7 @@ pub(crate) mod tests { 7, 8, 9, ]), unsigned_action: UnsignedChangeRecoveryAddress { - new_recovery_address: rand_string(), + new_recovery_address: rand_hexstring(), }, }), UnverifiedAction::RevokeAssociation(UnverifiedRevokeAssociation { @@ -632,7 +630,7 @@ pub(crate) mod tests { 10, 11, 12, ]), unsigned_action: UnsignedRevokeAssociation { - revoked_member: rand_string().into(), + revoked_member: rand_hexstring().into(), }, }), ], diff --git a/xmtp_id/src/associations/signature.rs b/xmtp_id/src/associations/signature.rs index 863e0044e..90eb98a4d 100644 --- a/xmtp_id/src/associations/signature.rs +++ b/xmtp_id/src/associations/signature.rs @@ -296,8 +296,9 @@ mod tests { use ethers::core::k256::{ecdsa::Signature as K256Signature, elliptic_curve::scalar::IsHigh}; use ethers::signers::{LocalWallet, Signer}; use rand::thread_rng; + use wasm_bindgen_test::wasm_bindgen_test; - #[tokio::test] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_to_lower_s() { // Create a test wallet let wallet = LocalWallet::new(&mut thread_rng()); @@ -334,7 +335,7 @@ mod tests { assert!(!is_high, "Normalized signature should have low-s value"); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_invalid_signature() { // Test with invalid signature bytes let invalid_sig = vec![0u8; 65]; diff --git a/xmtp_id/src/associations/state.rs b/xmtp_id/src/associations/state.rs index a9339be88..d26569e1b 100644 --- a/xmtp_id/src/associations/state.rs +++ b/xmtp_id/src/associations/state.rs @@ -204,14 +204,14 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::test_utils::rand_string; + use xmtp_common::rand_hexstring; use super::*; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn can_add_remove() { - let starting_state = AssociationState::new(rand_string(), 0, None).unwrap(); + let starting_state = AssociationState::new(rand_hexstring(), 0, None).unwrap(); let new_entity = Member::default(); let with_add = starting_state.add(new_entity.clone()); assert!(with_add.get(&new_entity.identifier).is_some()); @@ -221,7 +221,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn can_diff() { - let starting_state = AssociationState::new(rand_string(), 0, None).unwrap(); + let starting_state = AssociationState::new(rand_hexstring(), 0, None).unwrap(); let entity_1 = Member::default(); let entity_2 = Member::default(); let entity_3 = Member::default(); diff --git a/xmtp_id/src/associations/test_utils.rs b/xmtp_id/src/associations/test_utils.rs index 4ab83734d..2f0db1704 100644 --- a/xmtp_id/src/associations/test_utils.rs +++ b/xmtp_id/src/associations/test_utils.rs @@ -11,32 +11,9 @@ use ethers::{ signers::{LocalWallet, Signer}, types::Bytes, }; -use rand::Rng; use xmtp_cryptography::basic_credential::XmtpInstallationCredential; use xmtp_cryptography::CredentialSign; -pub fn rand_string() -> String { - let hex_chars = "0123456789abcdef"; - let v: String = (0..40) - .map(|_| { - let idx = rand::thread_rng().gen_range(0..hex_chars.len()); - hex_chars.chars().nth(idx).unwrap() - }) - .collect(); - - format!("0x{}", v) -} - -pub fn rand_u64() -> u64 { - rand::thread_rng().gen() -} - -pub fn rand_vec() -> Vec { - let mut buf = [0u8; 32]; - rand::thread_rng().fill(&mut buf[..]); - buf.to_vec() -} - #[derive(Debug, Clone)] pub struct MockSmartContractSignatureVerifier { is_valid_signature: bool, diff --git a/xmtp_id/src/associations/unverified.rs b/xmtp_id/src/associations/unverified.rs index a1ac2c849..64f198e0e 100644 --- a/xmtp_id/src/associations/unverified.rs +++ b/xmtp_id/src/associations/unverified.rs @@ -409,9 +409,8 @@ mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::associations::{ - generate_inbox_id, test_utils::rand_string, unsigned_actions::UnsignedCreateInbox, - }; + use crate::associations::{generate_inbox_id, unsigned_actions::UnsignedCreateInbox}; + use xmtp_common::rand_hexstring; use super::{ UnverifiedAction, UnverifiedCreateInbox, UnverifiedIdentityUpdate, @@ -421,7 +420,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), test)] fn create_identity_update() { - let account_address = rand_string(); + let account_address = rand_hexstring(); let nonce = 1; let update = UnverifiedIdentityUpdate { inbox_id: generate_inbox_id(account_address.as_str(), &nonce).unwrap(), diff --git a/xmtp_id/src/associations/verified_signature.rs b/xmtp_id/src/associations/verified_signature.rs index 3b30dc6e0..255fc3972 100644 --- a/xmtp_id/src/associations/verified_signature.rs +++ b/xmtp_id/src/associations/verified_signature.rs @@ -175,15 +175,15 @@ mod tests { use super::*; use crate::{ associations::{ - sign_with_legacy_key, - test_utils::{rand_string, MockSmartContractSignatureVerifier}, - verified_signature::VerifiedSignature, - InstallationKeyContext, MemberIdentifier, SignatureKind, + sign_with_legacy_key, test_utils::MockSmartContractSignatureVerifier, + verified_signature::VerifiedSignature, InstallationKeyContext, MemberIdentifier, + SignatureKind, }, InboxOwner, }; use ethers::signers::{LocalWallet, Signer}; use prost::Message; + use xmtp_common::rand_hexstring; use xmtp_cryptography::{CredentialSign, XmtpInstallationCredential}; use xmtp_proto::xmtp::message_contents::{ signature::Union as SignatureUnion, signed_private_key, @@ -367,7 +367,7 @@ mod tests { async fn test_smart_contract_wallet() { let mock_verifier = MockSmartContractSignatureVerifier::new(true); let chain_id: u64 = 24; - let account_address = rand_string(); + let account_address = rand_hexstring(); let account_id = AccountId::new(format!("eip155:{chain_id}"), account_address.clone()); let signature_text = "test_smart_contract_wallet_signature"; let signature_bytes = &[1, 2, 3]; diff --git a/xmtp_id/src/utils/mod.rs b/xmtp_id/src/utils/mod.rs index 0acaf32ef..ec59e09c8 100644 --- a/xmtp_id/src/utils/mod.rs +++ b/xmtp_id/src/utils/mod.rs @@ -1,13 +1,2 @@ -use wasm_timer::{SystemTime, UNIX_EPOCH}; #[cfg(any(test, feature = "test-utils"))] pub mod test; - -pub const NS_IN_SEC: i64 = 1_000_000_000; - -pub fn now_ns() -> i64 { - let now = SystemTime::now(); - - now.duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as i64 -} diff --git a/xmtp_mls/Cargo.toml b/xmtp_mls/Cargo.toml index e9d16676d..ef9d49670 100644 --- a/xmtp_mls/Cargo.toml +++ b/xmtp_mls/Cargo.toml @@ -40,6 +40,7 @@ test-utils = [ "xmtp_api_grpc/test-utils", "dep:const_format", "mockall", + "xmtp_common/test-utils" ] update-schema = ["toml"] @@ -68,9 +69,8 @@ tokio-stream = { version = "0.1", default-features = false, features = [ ] } tracing.workspace = true trait-variant.workspace = true -wasm-timer.workspace = true -web-time.workspace = true zeroize.workspace = true +xmtp_common.workspace = true # XMTP/Local xmtp_content_types = { path = "../xmtp_content_types" } @@ -149,6 +149,8 @@ mockall = "0.13.1" openmls_basic_credential.workspace = true xmtp_id = { path = "../xmtp_id", features = ["test-utils"] } xmtp_proto = { workspace = true, features = ["test-utils"] } +xmtp_common = { workspace = true, features = ["test-utils"]} +wasm-bindgen-test.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] ctor.workspace = true diff --git a/xmtp_mls/benches/crypto.rs b/xmtp_mls/benches/crypto.rs index 17306e01b..c4f338ba7 100644 --- a/xmtp_mls/benches/crypto.rs +++ b/xmtp_mls/benches/crypto.rs @@ -46,7 +46,6 @@ fn bench_encrypt_welcome(c: &mut Criterion) { let keypair = crypto .derive_hpke_keypair(CIPHERSUITE.hpke_config(), ikm.as_slice()) .unwrap(); - let mut payload = vec![0; size]; OsRng.fill_bytes(payload.as_mut_slice()); (payload, keypair.public) diff --git a/xmtp_mls/src/api/identity.rs b/xmtp_mls/src/api/identity.rs index 02e6be91c..f2ef21a75 100644 --- a/xmtp_mls/src/api/identity.rs +++ b/xmtp_mls/src/api/identity.rs @@ -151,8 +151,9 @@ pub(crate) mod tests { use super::super::test_utils::*; use super::GetIdentityUpdatesV2Filter; - use crate::{api::ApiClientWrapper, retry::Retry}; - use xmtp_id::associations::{test_utils::rand_string, unverified::UnverifiedIdentityUpdate}; + use crate::api::ApiClientWrapper; + use xmtp_common::{rand_hexstring, Retry}; + use xmtp_id::associations::unverified::UnverifiedIdentityUpdate; use xmtp_proto::xmtp::identity::api::v1::{ get_identity_updates_response::{ IdentityUpdateLog, Response as GetIdentityUpdatesResponseItem, @@ -173,7 +174,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn publish_identity_update() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let identity_update = create_identity_update(inbox_id.clone()); mock_api @@ -191,7 +192,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_identity_update_v2() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let inbox_id_clone = inbox_id.clone(); let inbox_id_clone_2 = inbox_id.clone(); mock_api @@ -238,10 +239,10 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn get_inbox_ids() { let mut mock_api = MockApiClient::new(); - let inbox_id = rand_string(); + let inbox_id = rand_hexstring(); let inbox_id_clone = inbox_id.clone(); let inbox_id_clone_2 = inbox_id.clone(); - let address = rand_string(); + let address = rand_hexstring(); let address_clone = address.clone(); let address_clone_2 = address.clone(); diff --git a/xmtp_mls/src/api/mls.rs b/xmtp_mls/src/api/mls.rs index a319f7737..3994cd8fa 100644 --- a/xmtp_mls/src/api/mls.rs +++ b/xmtp_mls/src/api/mls.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; use super::ApiClientWrapper; -use crate::{retry_async, XmtpApi}; +use crate::XmtpApi; +use xmtp_common::retry_async; use xmtp_proto::api_client::XmtpMlsStreams; use xmtp_proto::xmtp::mls::api::v1::{ subscribe_group_messages_request::Filter as GroupFilterProto, diff --git a/xmtp_mls/src/api/mod.rs b/xmtp_mls/src/api/mod.rs index 8cd71039d..e9515058d 100644 --- a/xmtp_mls/src/api/mod.rs +++ b/xmtp_mls/src/api/mod.rs @@ -5,11 +5,9 @@ pub mod test_utils; use std::sync::Arc; -use crate::{ - retry::{Retry, RetryableError}, - XmtpApi, -}; +use crate::XmtpApi; use thiserror::Error; +use xmtp_common::{Retry, RetryableError}; use xmtp_id::{associations::DeserializationError as AssociationDeserializationError, InboxId}; use xmtp_proto::Error as ApiError; diff --git a/xmtp_mls/src/api/test_utils.rs b/xmtp_mls/src/api/test_utils.rs index 087877624..829166507 100644 --- a/xmtp_mls/src/api/test_utils.rs +++ b/xmtp_mls/src/api/test_utils.rs @@ -1,6 +1,3 @@ -use std::{future::Future, time::Duration}; -use web_time::Instant; - use mockall::mock; use xmtp_proto::{ api_client::{ClientWithMetadata, XmtpIdentityClient, XmtpMlsClient, XmtpMlsStreams}, @@ -179,55 +176,3 @@ mod wasm { } } } - -pub async fn wait_for_some(f: F) -> Option -where - F: Fn() -> Fut, - Fut: Future>, -{ - let start = Instant::now(); - while start.elapsed() < Duration::from_secs(3) { - let result = f().await; - if result.is_some() { - return result; - } - crate::sleep(Duration::from_millis(100)).await; - } - None -} - -pub async fn wait_for_ok(f: F) -> Result -where - F: Fn() -> Fut, - Fut: Future>, -{ - let start = Instant::now(); - let mut result = f().await; - while start.elapsed() < Duration::from_secs(3) { - if result.is_ok() { - return result; - } - crate::sleep(Duration::from_millis(100)).await; - result = f().await; - } - result -} - -pub async fn wait_for_eq(f: F, expected: T) -where - F: Fn() -> Fut, - Fut: Future, - T: std::fmt::Debug + PartialEq, -{ - let start = Instant::now(); - let mut result = f().await; - while start.elapsed() < Duration::from_secs(3) { - if result == expected { - break; - } - crate::sleep(Duration::from_millis(100)).await; - result = f().await; - } - - assert_eq!(expected, result); -} diff --git a/xmtp_mls/src/builder.rs b/xmtp_mls/src/builder.rs index 997a92ce8..f8411744e 100644 --- a/xmtp_mls/src/builder.rs +++ b/xmtp_mls/src/builder.rs @@ -11,10 +11,10 @@ use crate::{ client::Client, identity::{Identity, IdentityStrategy}, identity_updates::load_identity_updates, - retry::Retry, storage::EncryptedMessageStore, StorageError, XmtpApi, XmtpOpenMlsProvider, }; +use xmtp_common::Retry; #[derive(Error, Debug)] pub enum ClientBuilderError { @@ -230,24 +230,22 @@ pub(crate) mod tests { use crate::api::ApiClientWrapper; use crate::builder::ClientBuilderError; use crate::identity::IdentityError; - use crate::retry::Retry; use crate::utils::test::TestClient; use crate::XmtpApi; - use crate::{ - api::test_utils::*, identity::Identity, storage::identity::StoredIdentity, - utils::test::rand_vec, Store, - }; + use crate::{api::test_utils::*, identity::Identity, storage::identity::StoredIdentity, Store}; + use xmtp_common::{rand_vec, tmp_path, Retry}; use openmls::credentials::{Credential, CredentialType}; use prost::Message; + use xmtp_common::rand_u64; use xmtp_cryptography::utils::{generate_local_wallet, rng}; use xmtp_cryptography::XmtpInstallationCredential; + use xmtp_id::associations::generate_inbox_id; use xmtp_id::associations::test_utils::MockSmartContractSignatureVerifier; use xmtp_id::associations::unverified::{ UnverifiedRecoverableEcdsaSignature, UnverifiedSignature, }; use xmtp_id::associations::ValidatedLegacySignedPublicKey; - use xmtp_id::associations::{generate_inbox_id, test_utils::rand_u64}; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::api_client::XmtpTestClient; use xmtp_proto::xmtp::identity::api::v1::{ @@ -263,7 +261,6 @@ pub(crate) mod tests { use super::{ClientBuilder, IdentityStrategy}; use crate::{ storage::{EncryptedMessageStore, StorageOption}, - utils::test::tmp_path, Client, InboxOwner, }; @@ -627,7 +624,7 @@ pub(crate) mod tests { let stored: StoredIdentity = (&Identity { inbox_id: inbox_id.clone(), installation_keys: XmtpInstallationCredential::new(), - credential: Credential::new(CredentialType::Basic, rand_vec()), + credential: Credential::new(CredentialType::Basic, rand_vec::<24>()), signature_request: None, is_ready: AtomicBool::new(true), }) @@ -664,7 +661,7 @@ pub(crate) mod tests { let stored: StoredIdentity = (&Identity { inbox_id: stored_inbox_id.clone(), installation_keys: Default::default(), - credential: Credential::new(CredentialType::Basic, rand_vec()), + credential: Credential::new(CredentialType::Basic, rand_vec::<24>()), signature_request: None, is_ready: AtomicBool::new(true), }) diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index 819356dc4..e293188a7 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -40,8 +40,6 @@ use crate::{ identity_updates::{load_identity_updates, IdentityUpdateError}, intents::ProcessIntentError, mutex_registry::MutexRegistry, - retry::Retry, - retry_async, retryable, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, db_connection::DbConnection, @@ -57,6 +55,7 @@ use crate::{ xmtp_openmls_provider::XmtpOpenMlsProvider, Fetch, Store, XmtpApi, }; +use xmtp_common::{retry_async, retryable, Retry}; /// Enum representing the network the Client is connected to #[derive(Clone, Copy, Default, Debug)] @@ -112,7 +111,7 @@ impl From for ClientError { } } -impl crate::retry::RetryableError for ClientError { +impl xmtp_common::RetryableError for ClientError { fn is_retryable(&self) -> bool { match self { ClientError::Group(group_error) => retryable!(group_error), @@ -441,9 +440,9 @@ where .into_iter() .map(UserPreferenceUpdate::ConsentUpdate) .collect(); - self.local_events - .send(LocalEvents::OutgoingPreferenceUpdates(records)) - .map_err(|e| ClientError::Generic(e.to_string()))?; + let _ = self + .local_events + .send(LocalEvents::OutgoingPreferenceUpdates(records)); } Ok(()) @@ -689,7 +688,7 @@ where .await?; self.apply_signature_request(signature_request).await?; - + self.identity().set_ready(); Ok(()) } diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index 3f9536371..5e5a840a7 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -1,11 +1,9 @@ use super::{GroupError, MlsGroup}; use crate::configuration::NS_IN_HOUR; -use crate::retry::{Retry, RetryableError}; use crate::storage::group::{ConversationType, GroupQueryArgs}; use crate::storage::group_message::MsgQueryArgs; use crate::storage::DbConnection; use crate::subscriptions::{LocalEvents, StreamMessages, SubscribeError, SyncMessage}; -use crate::utils::time::now_ns; use crate::xmtp_openmls_provider::XmtpOpenMlsProvider; use crate::{ client::ClientError, @@ -15,9 +13,8 @@ use crate::{ group_message::{GroupMessageKind, StoredGroupMessage}, StorageError, }, - Client, + Client, Store, }; -use crate::{retry_async, Store}; use aes_gcm::aead::generic_array::GenericArray; use aes_gcm::{ aead::{Aead, KeyInit}, @@ -25,16 +22,14 @@ use aes_gcm::{ }; use futures::{Stream, StreamExt}; use preference_sync::UserPreferenceUpdate; -use rand::{ - distributions::{Alphanumeric, DistString}, - Rng, RngCore, -}; +use rand::{Rng, RngCore}; use serde::{Deserialize, Serialize}; use std::pin::Pin; -use std::time::Duration; use thiserror::Error; use tokio::sync::OnceCell; use tracing::{instrument, warn}; +use xmtp_common::time::{now_ns, Duration}; +use xmtp_common::{retry_async, Retry, RetryableError}; use xmtp_cryptography::utils as crypto_utils; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::api_client::trait_impls::XmtpApi; @@ -154,9 +149,9 @@ where V: SmartContractSignatureVerifier + 'static, { async fn run(&mut self) -> Result<(), DeviceSyncError> { - // Wait for the identity to be ready before doing anything + // Wait for the identity to be ready & verified before doing anything while !self.client.identity().is_ready() { - crate::sleep(Duration::from_millis(200)).await; + xmtp_common::yield_().await } self.sync_init().await?; @@ -332,17 +327,22 @@ where let inbox_id = self.client.inbox_id().to_string(); let installation_id = hex::encode(self.client.installation_public_key()); while let Err(err) = self.run().await { + tracing::info!("Running worker.."); match err { DeviceSyncError::Client(ClientError::Storage( StorageError::PoolNeedsConnection, )) => { - tracing::warn!("Pool disconnected. task will restart on reconnect"); + tracing::warn!( + inbox_id, + installation_id, + "Pool disconnected. task will restart on reconnect" + ); break; } _ => { tracing::error!(inbox_id, installation_id, "sync worker error {err}"); // Wait 2 seconds before restarting. - crate::sleep(Duration::from_secs(2)).await; + xmtp_common::time::sleep(Duration::from_secs(2)).await; } } } @@ -615,7 +615,7 @@ where tracing::info!( inbox_id = self.inbox_id(), installation_id = hex::encode(self.installation_public_key()), - "Using upload url {upload_url}" + "Using upload url {upload_url}", ); let response = reqwest::Client::new() @@ -858,14 +858,11 @@ impl TryFrom for DeviceSyncKeyType { } pub(super) fn new_request_id() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), ENC_KEY_SIZE) + xmtp_common::rand_string::() } pub(super) fn generate_nonce() -> [u8; NONCE_SIZE] { - let mut nonce = [0u8; NONCE_SIZE]; - let mut rng = crypto_utils::rng(); - rng.fill_bytes(&mut nonce); - nonce + xmtp_common::rand_array::() } pub(super) fn new_pin() -> String { diff --git a/xmtp_mls/src/groups/device_sync/consent_sync.rs b/xmtp_mls/src/groups/device_sync/consent_sync.rs index d327923fe..965fdb770 100644 --- a/xmtp_mls/src/groups/device_sync/consent_sync.rs +++ b/xmtp_mls/src/groups/device_sync/consent_sync.rs @@ -50,33 +50,41 @@ where } } -#[cfg(all(not(target_arch = "wasm32"), test))] +#[cfg(test)] pub(crate) mod tests { + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; + const HISTORY_SERVER_HOST: &str = "localhost"; const HISTORY_SERVER_PORT: u16 = 5558; - use std::time::{Duration, Instant}; + use xmtp_common::{ + assert_ok, + time::{Duration, Instant}, + }; use super::*; use crate::{ - assert_ok, builder::ClientBuilder, - groups::scoped_client::LocalScopedGroupClient, + groups::scoped_client::ScopedGroupClient, storage::consent_record::{ConsentState, ConsentType}, utils::test::wait_for_min_intents, }; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] async fn test_consent_sync() { + xmtp_common::logger(); let history_sync_url = format!("http://{}:{}", HISTORY_SERVER_HOST, HISTORY_SERVER_PORT); - let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; let amal_a_provider = amal_a.mls_provider().unwrap(); let amal_a_conn = amal_a_provider.conn_ref(); + wait_for_min_intents(amal_a_conn, 3).await; // create an alix installation and consent with alix let alix_wallet = generate_local_wallet(); @@ -93,12 +101,12 @@ pub(crate) mod tests { // Create a second installation for amal with sync. let amal_b = ClientBuilder::new_test_client_with_history(&wallet, &history_sync_url).await; + let amal_b_provider = amal_b.mls_provider().unwrap(); let amal_b_conn = amal_b_provider.conn_ref(); let consent_records_b = amal_b.syncable_consent_records(amal_b_conn).unwrap(); assert_eq!(consent_records_b.len(), 0); - - // make sure amal's worker has time to sync + // make sure amal's workers have time to sync // 3 Intents: // 1.) UpdateGroupMembership Intent for new sync group // 2.) Device Sync Request @@ -120,30 +128,25 @@ pub(crate) mod tests { // Have amal_a receive the message (and auto-process) let amal_a_sync_group = amal_a.get_sync_group(amal_a_conn).unwrap(); assert_ok!(amal_a_sync_group.sync_with_conn(&amal_a_provider).await); - - // Wait for up to 3 seconds for the reply on amal_b (usually is almost instant) - let start = Instant::now(); - let mut reply = None; - while reply.is_none() { - reply = amal_b + xmtp_common::wait_for_some(|| async { + amal_b .get_latest_sync_reply(&amal_b_provider, DeviceSyncKind::Consent) .await - .unwrap(); - if start.elapsed() > Duration::from_secs(3) { - panic!("Did not receive sync reply."); - } - } - - // Wait up to 3 seconds for sync to process (typically is almost instant) - let mut consent_b = 0; - let start = Instant::now(); - while consent_b != consent_a { - consent_b = amal_b.syncable_consent_records(amal_b_conn).unwrap().len(); - - if start.elapsed() > Duration::from_secs(3) { - panic!("Consent sync did not work. Consent: {consent_b}/{consent_a}"); - } - } + .unwrap() + }) + .await + .unwrap(); + + // Wait up to 20 seconds for sync to process (typically is almost instant) + xmtp_common::wait_for_eq( + || { + let consent_b = amal_b.syncable_consent_records(amal_b_conn).unwrap().len(); + futures::future::ready(consent_b != consent_a) + }, + true, + ) + .await + .unwrap(); // Test consent streaming let amal_b_sync_group = amal_b.get_sync_group(amal_b_conn).unwrap(); diff --git a/xmtp_mls/src/groups/device_sync/message_sync.rs b/xmtp_mls/src/groups/device_sync/message_sync.rs index 66e63e261..053dea448 100644 --- a/xmtp_mls/src/groups/device_sync/message_sync.rs +++ b/xmtp_mls/src/groups/device_sync/message_sync.rs @@ -42,24 +42,25 @@ where } } -#[cfg(all(not(target_arch = "wasm32"), test))] +#[cfg(test)] pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; use super::*; + use crate::{ - api::test_utils::wait_for_some, - assert_ok, builder::ClientBuilder, groups::GroupMetadataOptions, utils::test::{wait_for_min_intents, HISTORY_SYNC_URL}, }; - use std::time::{Duration, Instant}; + use xmtp_common::{assert_ok, wait_for_some}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] + #[cfg_attr(target_family = "wasm", ignore)] async fn test_message_history_sync() { let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; @@ -123,35 +124,30 @@ pub(crate) mod tests { let amal_a_sync_group = amal_a.get_sync_group(amal_a_conn).unwrap(); assert_ok!(amal_a_sync_group.sync_with_conn(&amal_a_provider).await); - // Wait for up to 3 seconds for the reply on amal_b (usually is almost instant) - let start = Instant::now(); - let mut reply = None; - while reply.is_none() { - reply = amal_b + xmtp_common::wait_for_some(|| async { + amal_b .get_latest_sync_reply(&amal_b_provider, DeviceSyncKind::MessageHistory) .await - .unwrap(); - if start.elapsed() > Duration::from_secs(3) { - panic!("Did not receive sync reply."); - } - } - - // Wait up to 3 seconds for sync to process (typically is almost instant) - let [mut groups_a, mut groups_b, mut messages_a, mut messages_b] = [0; 4]; - let start = Instant::now(); - while groups_a != groups_b || messages_a != messages_b { - groups_a = amal_a.syncable_groups(amal_a_conn).unwrap().len(); - groups_b = amal_b.syncable_groups(amal_b_conn).unwrap().len(); - messages_a = amal_a.syncable_messages(amal_a_conn).unwrap().len(); - messages_b = amal_b.syncable_messages(amal_b_conn).unwrap().len(); - - if start.elapsed() > Duration::from_secs(3) { - panic!("Message sync did not work. Groups: {groups_a}/{groups_b} | Messages: {messages_a}/{messages_b}"); - } - } + .unwrap() + }) + .await + .unwrap(); + + xmtp_common::wait_for_eq( + || { + let groups_a = amal_a.syncable_groups(amal_a_conn).unwrap().len(); + let groups_b = amal_b.syncable_groups(amal_b_conn).unwrap().len(); + let messages_a = amal_a.syncable_messages(amal_a_conn).unwrap().len(); + let messages_b = amal_b.syncable_messages(amal_b_conn).unwrap().len(); + futures::future::ready(groups_a != groups_b || messages_a != messages_b) + }, + true, + ) + .await + .unwrap(); } - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_sync_continues_during_db_disconnect() { let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; @@ -213,35 +209,7 @@ pub(crate) mod tests { assert_ne!(old_group_id, new_group_id); } - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn disconnect_does_not_effect_init() { - let wallet = generate_local_wallet(); - let amal_a = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; - - let amal_a_provider = amal_a.mls_provider().unwrap(); - let amal_a_conn = amal_a_provider.conn_ref(); - - //release db conn right after creating client, not giving the worker time to do initial - //sync - amal_a.release_db_connection().unwrap(); - - let sync_group = amal_a.get_sync_group(amal_a_conn); - crate::assert_err!(sync_group, GroupError::GroupNotFound); - - amal_a.reconnect_db().unwrap(); - - // make sure amal's worker has time to sync - // 3 Intents: - // 1.) Sync Group Creation - // 2.) Device Sync Request - // 3.) MessageHistory Sync Request - wait_for_min_intents(amal_a_conn, 3).await; - tracing::info!("Waiting for intents published"); - let sync_group = amal_a.get_sync_group(amal_a_conn); - assert!(sync_group.is_ok()); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_prepare_groups_to_sync() { let wallet = generate_local_wallet(); let amal_a = ClientBuilder::new_test_client(&wallet).await; @@ -258,7 +226,7 @@ pub(crate) mod tests { assert_eq!(result.len(), 2); } - #[tokio::test] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_externals_cant_join_sync_group() { let wallet = generate_local_wallet(); let amal = ClientBuilder::new_test_client_with_history(&wallet, HISTORY_SYNC_URL).await; @@ -295,20 +263,20 @@ pub(crate) mod tests { assert!(result.is_err()); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_pin() { let pin = new_pin(); assert!(pin.chars().all(|c| c.is_numeric())); assert_eq!(pin.len(), 4); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_request_id() { let request_id = new_request_id(); assert_eq!(request_id.len(), ENC_KEY_SIZE); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_new_key() { let sig_key = DeviceSyncKeyType::new_aes_256_gcm_key(); let enc_key = DeviceSyncKeyType::new_aes_256_gcm_key(); @@ -318,7 +286,7 @@ pub(crate) mod tests { assert_ne!(sig_key, enc_key); } - #[test] + #[wasm_bindgen_test(unsupported = test)] fn test_generate_nonce() { let nonce_1 = generate_nonce(); let nonce_2 = generate_nonce(); diff --git a/xmtp_mls/src/groups/device_sync/preference_sync.rs b/xmtp_mls/src/groups/device_sync/preference_sync.rs index fa9c48628..f677eee4e 100644 --- a/xmtp_mls/src/groups/device_sync/preference_sync.rs +++ b/xmtp_mls/src/groups/device_sync/preference_sync.rs @@ -32,9 +32,9 @@ impl TryInto for UserPreferenceUpdate { #[cfg(test)] mod tests { - use crate::storage::consent_record::{ConsentState, ConsentType}; - use super::*; + use crate::storage::consent_record::{ConsentState, ConsentType}; + use wasm_bindgen_test::wasm_bindgen_test; #[derive(Serialize, Deserialize, Clone)] #[repr(i32)] @@ -42,8 +42,7 @@ mod tests { ConsentUpdate(StoredConsentRecord) = 1, } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 1))] async fn test_can_deserialize_between_versions() { let consent_record = StoredConsentRecord { entity: "hello there".to_string(), diff --git a/xmtp_mls/src/groups/group_permissions.rs b/xmtp_mls/src/groups/group_permissions.rs index 62aedf158..1a1b04594 100644 --- a/xmtp_mls/src/groups/group_permissions.rs +++ b/xmtp_mls/src/groups/group_permissions.rs @@ -1275,19 +1275,17 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::{ - groups::{ - group_metadata::DmMembers, group_mutable_metadata::MetadataField, - validated_commit::MutableMetadataChanges, - }, - utils::test::{rand_string, rand_vec}, + use crate::groups::{ + group_metadata::DmMembers, group_mutable_metadata::MetadataField, + validated_commit::MutableMetadataChanges, }; + use xmtp_common::{rand_string, rand_vec}; use super::*; fn build_change(inbox_id: Option, is_admin: bool, is_super_admin: bool) -> Inbox { Inbox { - inbox_id: inbox_id.unwrap_or(rand_string()), + inbox_id: inbox_id.unwrap_or(rand_string::<24>()), is_creator: is_super_admin, is_super_admin, is_admin, @@ -1302,8 +1300,8 @@ pub(crate) mod tests { is_super_admin: bool, ) -> CommitParticipant { CommitParticipant { - inbox_id: inbox_id.unwrap_or(rand_string()), - installation_id: installation_id.unwrap_or_else(rand_vec), + inbox_id: inbox_id.unwrap_or(rand_string::<24>()), + installation_id: installation_id.unwrap_or_else(rand_vec::<24>), is_creator: is_super_admin, is_admin, is_super_admin, @@ -1344,7 +1342,13 @@ pub(crate) mod tests { let field_changes = metadata_fields_changed .unwrap_or_default() .into_iter() - .map(|field| MetadataFieldChange::new(field, Some(rand_string()), Some(rand_string()))) + .map(|field| { + MetadataFieldChange::new( + field, + Some(rand_string::<24>()), + Some(rand_string::<24>()), + ) + }) .collect(); let dm_members = if let Some(dm_target_inbox_id) = dm_target_inbox_id { diff --git a/xmtp_mls/src/groups/intents.rs b/xmtp_mls/src/groups/intents.rs index b3faf11b4..d756f204a 100644 --- a/xmtp_mls/src/groups/intents.rs +++ b/xmtp_mls/src/groups/intents.rs @@ -95,7 +95,7 @@ impl MlsGroup { fn maybe_insert_key_update_intent(&self, conn: &DbConnection) -> Result<(), GroupError> { let last_rotated_at_ns = conn.get_rotated_at_ns(self.group_id.clone())?; - let now_ns = crate::utils::time::now_ns(); + let now_ns = xmtp_common::time::now_ns(); let elapsed_ns = now_ns - last_rotated_at_ns; if elapsed_ns > GROUP_KEY_ROTATION_INTERVAL_NS { self.queue_intent_with_conn(conn, IntentKind::KeyUpdate, vec![])?; diff --git a/xmtp_mls/src/groups/mls_sync.rs b/xmtp_mls/src/groups/mls_sync.rs index a613551f8..954041dde 100644 --- a/xmtp_mls/src/groups/mls_sync.rs +++ b/xmtp_mls/src/groups/mls_sync.rs @@ -13,13 +13,12 @@ use crate::{ GRPC_DATA_LIMIT, HMAC_SALT, MAX_GROUP_SIZE, MAX_INTENT_PUBLISH_ATTEMPTS, MAX_PAST_EPOCHS, SYNC_UPDATE_INSTALLATIONS_INTERVAL_NS, }, + groups::device_sync::DeviceSyncContent, groups::{intents::UpdateMetadataIntentData, validated_commit::ValidatedCommit}, hpke::{encrypt_welcome, HpkeError}, identity::{parse_credential, IdentityError}, identity_updates::load_identity_updates, intents::ProcessIntentError, - retry::{Retry, RetryableError}, - retry_async, storage::{ db_connection::DbConnection, group_intent::{IntentKind, IntentState, StoredGroupIntent, ID}, @@ -31,11 +30,11 @@ use crate::{ StorageError, }, subscriptions::LocalEvents, + subscriptions::SyncMessage, utils::{hash::sha256, id::calculate_message_id, time::hmac_epoch}, xmtp_openmls_provider::XmtpOpenMlsProvider, Delete, Fetch, StoreOrIgnore, }; -use crate::{groups::device_sync::DeviceSyncContent, subscriptions::SyncMessage}; use futures::future::try_join_all; use hkdf::Hkdf; use hmac::{Hmac, Mac}; @@ -63,6 +62,8 @@ use std::{ ops::RangeInclusive, }; use thiserror::Error; +use tracing::debug; +use xmtp_common::{retry_async, Retry, RetryableError}; use xmtp_content_types::{group_updated::GroupUpdatedCodec, CodecError, ContentCodec}; use xmtp_id::{InboxId, InboxIdRef}; use xmtp_proto::xmtp::mls::{ @@ -131,7 +132,7 @@ pub enum GroupMessageProcessingError { AssociationDeserialization(#[from] xmtp_id::associations::DeserializationError), } -impl crate::retry::RetryableError for GroupMessageProcessingError { +impl RetryableError for GroupMessageProcessingError { fn is_retryable(&self) -> bool { match self { Self::Diesel(err) => err.is_retryable(), @@ -1221,7 +1222,7 @@ where None => SYNC_UPDATE_INSTALLATIONS_INTERVAL_NS, }; - let now_ns = crate::utils::time::now_ns(); + let now_ns = xmtp_common::time::now_ns(); let last_ns = provider .conn_ref() .get_installations_time_checked(self.group_id.clone())?; diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index c8e19e933..9213c38ef 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -59,6 +59,7 @@ use self::{ validated_commit::CommitValidationError, }; use std::{collections::HashSet, sync::Arc}; +use xmtp_common::time::now_ns; use xmtp_cryptography::signature::{sanitize_evm_addresses, AddressValidationError}; use xmtp_id::{InboxId, InboxIdRef}; use xmtp_proto::xmtp::mls::{ @@ -84,7 +85,6 @@ use crate::{ identity::{parse_credential, IdentityError}, identity_updates::{load_identity_updates, InstallationDiffError}, intents::ProcessIntentError, - retry::RetryableError, storage::{ consent_record::{ConsentState, ConsentType, StoredConsentRecord}, db_connection::DbConnection, @@ -94,10 +94,11 @@ use crate::{ sql_key_store, StorageError, }, subscriptions::{LocalEventError, LocalEvents}, - utils::{id::calculate_message_id, time::now_ns}, + utils::id::calculate_message_id, xmtp_openmls_provider::XmtpOpenMlsProvider, Store, }; +use xmtp_common::retry::RetryableError; #[derive(Debug, Error)] pub enum GroupError { @@ -1609,13 +1610,14 @@ pub(crate) mod tests { use openmls::prelude::Member; use prost::Message; use std::sync::Arc; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_common::assert_err; use xmtp_content_types::{group_updated::GroupUpdatedCodec, ContentCodec}; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_proto::xmtp::mls::api::v1::group_message::Version; use xmtp_proto::xmtp::mls::message_contents::EncodedContent; use crate::{ - assert_err, builder::ClientBuilder, groups::{ build_dm_protected_metadata_extension, build_mutable_metadata_extension_default, @@ -1706,8 +1708,7 @@ pub(crate) mod tests { .unwrap(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_send_message() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -1724,8 +1725,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 2); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_receive_self_message() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -1778,8 +1778,7 @@ pub(crate) mod tests { } // Test members function from non group creator - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_members_func_from_non_creator() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1835,8 +1834,7 @@ pub(crate) mod tests { // Amal and Bola will both try and add Charlie from the same epoch. // The group should resolve to a consistent state - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_member_conflict() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1935,8 +1933,9 @@ pub(crate) mod tests { #[cfg(not(target_arch = "wasm32"))] fn test_create_from_welcome_validation() { use crate::groups::{build_group_membership_extension, group_membership::GroupMembership}; - use crate::{assert_logged, utils::test::traced_test}; - traced_test(|| async { + use xmtp_common::assert_logged; + xmtp_common::traced_test!(async { + tracing::info!("TEST"); let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1970,8 +1969,7 @@ pub(crate) mod tests { }); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_inbox() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let client_2 = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -1995,8 +1993,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_invalid_member() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let group = client @@ -2008,8 +2005,7 @@ pub(crate) mod tests { assert!(result.is_err()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_unregistered_member() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let unconnected_wallet_address = generate_local_wallet().get_address(); @@ -2021,8 +2017,7 @@ pub(crate) mod tests { assert!(result.is_err()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_remove_inbox() { let client_1 = ClientBuilder::new_test_client(&generate_local_wallet()).await; // Add another client onto the network @@ -2060,8 +2055,7 @@ pub(crate) mod tests { assert_eq!(messages.len(), 2); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_key_update() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_client = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2101,8 +2095,7 @@ pub(crate) mod tests { assert_eq!(bola_messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_post_commit() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let client_2 = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2125,8 +2118,7 @@ pub(crate) mod tests { assert_eq!(welcome_messages.len(), 1); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_remove_by_account_address() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = &generate_local_wallet(); @@ -2174,8 +2166,7 @@ pub(crate) mod tests { .unwrap()) } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_removed_members_cannot_send_message_to_others() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = &generate_local_wallet(); @@ -2236,8 +2227,7 @@ pub(crate) mod tests { assert!(amal_messages.is_empty()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_add_missing_installations() { // Setup for test let amal_wallet = generate_local_wallet(); @@ -2270,11 +2260,7 @@ pub(crate) mod tests { assert_eq!(num_members, 3); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_self_resolve_epoch_mismatch() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2324,8 +2310,7 @@ pub(crate) mod tests { assert!(expected_latest_message.eq(&dave_latest_message.decrypted_message_bytes)); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_permissions() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2351,8 +2336,7 @@ pub(crate) mod tests { .is_err(),); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_options() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2394,8 +2378,7 @@ pub(crate) mod tests { assert_eq!(amal_group_pinned_frame_url, "pinned frame"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] #[ignore] async fn test_max_limit_add() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2420,8 +2403,7 @@ pub(crate) mod tests { .is_err(),); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_mutable_data() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2513,8 +2495,7 @@ pub(crate) mod tests { assert_eq!(bola_group_name, "New Group Name 1"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_update_group_image_url_square() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2552,8 +2533,7 @@ pub(crate) mod tests { assert_eq!(amal_group_image_url, "a url"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_update_group_pinned_frame_url() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2591,8 +2571,7 @@ pub(crate) mod tests { assert_eq!(amal_group_pinned_frame_url, "a frame url"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_mutable_data_group_permissions() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = generate_local_wallet(); @@ -2681,8 +2660,7 @@ pub(crate) mod tests { assert_eq!(amal_group_name, "New Group Name 2"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_admin_list_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola_wallet = generate_local_wallet(); @@ -2801,8 +2779,7 @@ pub(crate) mod tests { .expect_err("expected err"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_super_admin_list_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -2912,8 +2889,7 @@ pub(crate) mod tests { .expect_err("expected err"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_group_members_permission_level_update() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3010,8 +2986,7 @@ pub(crate) mod tests { assert_eq!(count_member, 0, "no members have no admin status"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_staged_welcome() { // Create Clients let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3054,8 +3029,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_read_group_creator_inbox_id() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let policy_set = Some(PreconfiguredPolicies::AllMembers.to_policy_set()); @@ -3081,8 +3055,7 @@ pub(crate) mod tests { assert_eq!(protected_metadata.creator_inbox_id, amal.inbox_id()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_update_gce_after_failed_commit() { // Step 1: Amal creates a group let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3150,8 +3123,7 @@ pub(crate) mod tests { assert_eq!(bola_group_name, "Name Update 2"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_can_update_permissions_after_group_creation() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let policy_set = Some(PreconfiguredPolicies::AdminsOnly.to_policy_set()); @@ -3216,8 +3188,7 @@ pub(crate) mod tests { assert_eq!(members.len(), 3); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_optimistic_send() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bola_wallet = generate_local_wallet(); @@ -3304,8 +3275,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_dm_creation() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3372,8 +3342,7 @@ pub(crate) mod tests { assert!(!is_bola_super_admin); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn process_messages_abort_on_retryable_error() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3427,8 +3396,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn skip_already_processed_messages() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3480,11 +3448,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn test_parallel_syncs() { let wallet = generate_local_wallet(); let alix1 = Arc::new(ClientBuilder::new_test_client(&wallet).await); @@ -3586,11 +3550,7 @@ pub(crate) mod tests { * We need to be safe even in situations where there are multiple * intents that do the same thing, leading to conflicts */ - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn add_missing_installs_reentrancy() { let wallet = generate_local_wallet(); let alix1 = ClientBuilder::new_test_client(&wallet).await; @@ -3667,11 +3627,7 @@ pub(crate) mod tests { .any(|m| m.decrypted_message_bytes == "hi from alix1".as_bytes())); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 5))] async fn respect_allow_epoch_increment() { let wallet = generate_local_wallet(); let client = ClientBuilder::new_test_client(&wallet).await; @@ -3711,8 +3667,7 @@ pub(crate) mod tests { ); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_get_and_set_consent() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -3771,8 +3726,7 @@ pub(crate) mod tests { assert_eq!(caro_group.consent_state().unwrap(), ConsentState::Allowed); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] // TODO(rich): Generalize the test once fixed - test messages that are 0, 1, 2, 3, 4, 5 epochs behind async fn test_max_past_epochs() { // Create group with two members @@ -3831,8 +3785,7 @@ pub(crate) mod tests { assert_eq!(alix_messages.len(), 3); // Fails here, 2 != 3 } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn test_validate_dm_group() { let client = ClientBuilder::new_test_client(&generate_local_wallet()).await; let added_by_inbox = "added_by_inbox_id"; diff --git a/xmtp_mls/src/groups/scoped_client.rs b/xmtp_mls/src/groups/scoped_client.rs index 557761f6e..e745114a1 100644 --- a/xmtp_mls/src/groups/scoped_client.rs +++ b/xmtp_mls/src/groups/scoped_client.rs @@ -17,7 +17,7 @@ use xmtp_id::{ }; use xmtp_proto::{api_client::trait_impls::XmtpApi, xmtp::mls::api::v1::GroupMessage}; -#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(ScopedGroupClient: Send ))] +#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(ScopedGroupClient: Send))] #[cfg(not(target_arch = "wasm32"))] #[allow(unused)] pub trait LocalScopedGroupClient: Send + Sync + Sized { diff --git a/xmtp_mls/src/groups/subscriptions.rs b/xmtp_mls/src/groups/subscriptions.rs index fbaaa20d6..eb354fe3c 100644 --- a/xmtp_mls/src/groups/subscriptions.rs +++ b/xmtp_mls/src/groups/subscriptions.rs @@ -15,8 +15,8 @@ use crate::storage::StorageError; use crate::subscriptions::MessagesStreamInfo; use crate::subscriptions::SubscribeError; use crate::XmtpOpenMlsProvider; -use crate::{retry::Retry, retry_async}; use prost::Message; +use xmtp_common::{retry_async, Retry}; use xmtp_proto::xmtp::mls::api::v1::GroupMessage; impl MlsGroup { @@ -263,6 +263,8 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; + use super::*; use tokio_stream::wrappers::UnboundedReceiverStream; use xmtp_cryptography::utils::generate_local_wallet; @@ -273,11 +275,7 @@ pub(crate) mod tests { }; use futures::StreamExt; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 1) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_decode_group_message_bytes() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -312,11 +310,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_messages() { let amal = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bola = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -369,11 +363,7 @@ pub(crate) mod tests { assert_eq!(second_val.decrypted_message_bytes, "goodbye".as_bytes()); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_multiple() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let group = Arc::new( @@ -411,11 +401,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 5) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_subscribe_membership_changes() { let amal = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bola = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -443,7 +429,7 @@ pub(crate) mod tests { // just to make sure stream is started let _ = start_rx.await; // Adding in a sleep, since the HTTP API client may acknowledge requests before they are ready - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; amal_group .add_members_by_inbox_id(&[bola.inbox_id()]) diff --git a/xmtp_mls/src/groups/validated_commit.rs b/xmtp_mls/src/groups/validated_commit.rs index 45cb5cc75..f13557236 100644 --- a/xmtp_mls/src/groups/validated_commit.rs +++ b/xmtp_mls/src/groups/validated_commit.rs @@ -24,10 +24,9 @@ use xmtp_proto::xmtp::{ use crate::{ configuration::GROUP_MEMBERSHIP_EXTENSION_ID, identity_updates::{InstallationDiff, InstallationDiffError}, - retry::RetryableError, - retryable, storage::db_connection::DbConnection, }; +use xmtp_common::{retry::RetryableError, retryable}; use super::{ group_membership::{GroupMembership, MembershipDiff}, diff --git a/xmtp_mls/src/hpke.rs b/xmtp_mls/src/hpke.rs index 19906a351..cf664043a 100644 --- a/xmtp_mls/src/hpke.rs +++ b/xmtp_mls/src/hpke.rs @@ -1,7 +1,5 @@ use crate::{ configuration::{CIPHERSUITE, WELCOME_HPKE_LABEL}, - retry::RetryableError, - retryable, storage::sql_key_store::{SqlKeyStoreError, KEY_PACKAGE_REFERENCES}, xmtp_openmls_provider::XmtpOpenMlsProvider, }; @@ -17,6 +15,7 @@ use openmls_rust_crypto::RustCrypto; use openmls_traits::OpenMlsProvider; use openmls_traits::{storage::StorageProvider, types::HpkeCiphertext}; use thiserror::Error; +use xmtp_common::{retryable, RetryableError}; #[derive(Debug, Error)] pub enum HpkeError { diff --git a/xmtp_mls/src/identity.rs b/xmtp_mls/src/identity.rs index d80ebf852..10a05344a 100644 --- a/xmtp_mls/src/identity.rs +++ b/xmtp_mls/src/identity.rs @@ -1,7 +1,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::configuration::GROUP_PERMISSIONS_EXTENSION_ID; -use crate::retry::RetryableError; use crate::storage::db_connection::DbConnection; use crate::storage::identity::StoredIdentity; use crate::storage::sql_key_store::{SqlKeyStore, SqlKeyStoreError, KEY_PACKAGE_REFERENCES}; @@ -10,9 +9,8 @@ use crate::{ configuration::{CIPHERSUITE, GROUP_MEMBERSHIP_EXTENSION_ID, MUTABLE_METADATA_EXTENSION_ID}, storage::StorageError, xmtp_openmls_provider::XmtpOpenMlsProvider, - XmtpApi, + Fetch, Store, XmtpApi, }; -use crate::{retryable, Fetch, Store}; use openmls::prelude::hash_ref::HashReference; use openmls::{ credentials::{errors::BasicCredentialError, BasicCredential, CredentialWithKey}, @@ -30,6 +28,7 @@ use prost::Message; use thiserror::Error; use tracing::debug; use tracing::info; +use xmtp_common::{retryable, RetryableError}; use xmtp_cryptography::{CredentialSign, XmtpInstallationCredential}; use xmtp_id::associations::unverified::UnverifiedSignature; use xmtp_id::associations::{AssociationError, InstallationKeyContext, PublicContext}; @@ -423,11 +422,14 @@ impl Identity { conn.get_latest_sequence_id_for_inbox(self.inbox_id.as_str()) } - #[allow(dead_code)] pub fn is_ready(&self) -> bool { self.is_ready.load(Ordering::SeqCst) } + pub(crate) fn set_ready(&self) { + self.is_ready.store(true, Ordering::SeqCst) + } + pub fn signature_request(&self) -> Option { self.signature_request.clone() } @@ -524,8 +526,6 @@ impl Identity { } self.rotate_key_package(provider, api_client).await?; - self.is_ready.store(true, Ordering::SeqCst); - Ok(StoredIdentity::try_from(self)?.store(provider.conn_ref())?) } diff --git a/xmtp_mls/src/identity_updates.rs b/xmtp_mls/src/identity_updates.rs index e4a6cef10..822c00bac 100644 --- a/xmtp_mls/src/identity_updates.rs +++ b/xmtp_mls/src/identity_updates.rs @@ -1,11 +1,8 @@ -use crate::{ - retry::{Retry, RetryableError}, - retry_async, retryable, - storage::association_state::StoredAssociationState, -}; +use crate::storage::association_state::StoredAssociationState; use futures::future::try_join_all; use std::collections::{HashMap, HashSet}; use thiserror::Error; +use xmtp_common::{retry_async, retryable, Retry, RetryableError}; use xmtp_cryptography::CredentialSign; use xmtp_id::{ associations::{ @@ -593,9 +590,10 @@ pub(crate) mod tests { builder::ClientBuilder, groups::group_membership::GroupMembership, storage::{db_connection::DbConnection, identity_update::StoredIdentityUpdate}, - utils::test::{rand_vec, FullXmtpClient}, + utils::test::FullXmtpClient, Client, XmtpApi, }; + use xmtp_common::rand_vec; use super::{is_member_of_association_state, load_identity_updates}; @@ -620,7 +618,7 @@ pub(crate) mod tests { fn insert_identity_update(conn: &DbConnection, inbox_id: &str, sequence_id: i64) { let identity_update = - StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, 0, rand_vec()); + StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, 0, rand_vec::<24>()); conn.insert_or_ignore_identity_updates(&[identity_update]) .expect("insert should succeed"); @@ -730,8 +728,8 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg(not(target_arch = "wasm32"))] fn cache_association_state() { - use crate::{assert_logged, utils::test::traced_test}; - traced_test(|| async { + use xmtp_common::assert_logged; + xmtp_common::traced_test!(async { let wallet = generate_local_wallet(); let wallet_2 = generate_local_wallet(); let wallet_address = wallet.get_address(); diff --git a/xmtp_mls/src/intents.rs b/xmtp_mls/src/intents.rs index 6664152cf..87adbc539 100644 --- a/xmtp_mls/src/intents.rs +++ b/xmtp_mls/src/intents.rs @@ -8,8 +8,8 @@ //! Intents are written to local storage (SQLite), before being published to the delivery service via gRPC. An //! intent is fully resolved (success or failure) once it -use crate::retry::RetryableError; use thiserror::Error; +use xmtp_common::RetryableError; #[derive(Debug, Error)] pub enum ProcessIntentError { diff --git a/xmtp_mls/src/lib.rs b/xmtp_mls/src/lib.rs index 71d2ede3f..d287fd676 100644 --- a/xmtp_mls/src/lib.rs +++ b/xmtp_mls/src/lib.rs @@ -11,7 +11,6 @@ pub mod identity; pub mod identity_updates; mod intents; mod mutex_registry; -pub mod retry; pub mod storage; mod stream_handles; pub mod subscriptions; @@ -27,15 +26,6 @@ pub use xmtp_openmls_provider::XmtpOpenMlsProvider; pub use xmtp_id::InboxOwner; pub use xmtp_proto::api_client::trait_impls::*; -#[macro_use] -extern crate tracing; - -/// Global Marker trait for WebAssembly -#[cfg(target_arch = "wasm32")] -pub trait Wasm {} -#[cfg(target_arch = "wasm32")] -impl Wasm for T {} - /// Inserts a model to the underlying data store, erroring if it already exists pub trait Store { fn store(&self, into: &StorageConnection) -> Result<(), StorageError>; @@ -78,138 +68,12 @@ pub use stream_handles::{ spawn, AbortHandle, GenericStreamHandle, StreamHandle, StreamHandleError, }; -#[cfg(target_arch = "wasm32")] -#[doc(hidden)] -pub async fn sleep(duration: core::time::Duration) { - gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32).await; -} - -#[cfg(not(target_arch = "wasm32"))] -#[doc(hidden)] -pub async fn sleep(duration: core::time::Duration) { - tokio::time::sleep(duration).await -} - -/// Turn the `Result` into an `Option`, logging the error with `tracing::error` and -/// returning `None` if the value matches on Result::Err(). -/// Optionally pass a message as the second argument. -#[macro_export] -macro_rules! optify { - ( $e: expr ) => { - match $e { - Ok(v) => Some(v), - Err(e) => { - tracing::error!("{}", e); - None - } - } - }; - ( $e: expr, $msg: tt ) => { - match $e { - Ok(v) => Some(v), - Err(e) => { - tracing::error!("{}: {:?}", $msg, e); - None - } - } - }; -} - #[cfg(test)] pub(crate) mod tests { // Execute once before any tests are run #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)] #[cfg(not(target_arch = "wasm32"))] fn _setup() { - use tracing_subscriber::{ - fmt::{self, format}, - layer::SubscriberExt, - util::SubscriberInitExt, - EnvFilter, Layer, - }; - - let structured = std::env::var("STRUCTURED"); - let is_structured = matches!(structured, Ok(s) if s == "true" || s == "1"); - - let filter = || { - EnvFilter::builder() - .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) - .from_env_lossy() - }; - - tracing_subscriber::registry() - // structured JSON logger - .with(is_structured.then(|| { - tracing_subscriber::fmt::layer() - .json() - .flatten_event(true) - .with_level(true) - .with_filter(filter()) - })) - // default logger - .with((!is_structured).then(|| { - fmt::layer() - .compact() - .fmt_fields({ - format::debug_fn(move |writer, field, value| { - if field.name() == "message" { - write!(writer, "{:?}", value)?; - } - Ok(()) - }) - }) - .with_filter(filter()) - })) - .init(); - } - - /// wrapper over assert!(matches!()) for Errors - /// assert_err!(fun(), StorageError::Explosion) - /// - /// or the message variant, - /// assert_err!(fun(), StorageError::Explosion, "the storage did not explode"); - #[macro_export] - macro_rules! assert_err { - ( $x:expr , $y:pat $(,)? ) => { - assert!(matches!($x, Err($y))) - }; - - ( $x:expr, $y:pat $(,)?, $($msg:tt)+) => {{ - assert!(matches!($x, Err($y)), $($msg)+) - }} - } - - /// wrapper over assert! macros for Ok's - /// - /// Make sure something is Ok(_) without caring about return value. - /// assert_ok!(fun()); - /// - /// Against an expected value, e.g Ok(true) - /// assert_ok!(fun(), true); - /// - /// or the message variant, - /// assert_ok!(fun(), Ok(_), "the storage is not ok"); - #[macro_export] - macro_rules! assert_ok { - - ( $e:expr ) => { - assert_ok!($e,) - }; - - ( $e:expr, ) => {{ - use std::result::Result::*; - match $e { - Ok(v) => v, - Err(e) => panic!("assertion failed: Err({:?})", e), - } - }}; - - ( $x:expr , $y:expr $(,)? ) => { - assert_eq!($x, Ok($y.into())); - }; - - ( $x:expr, $y:expr $(,)?, $($msg:tt)+) => {{ - assert_eq!($x, Ok($y.into()), $($msg)+); - }} + xmtp_common::logger() } } diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index 63906099e..010eab713 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -388,7 +388,7 @@ impl DbConnection { /// Updates the 'last time checked' we checked for new installations. pub fn update_rotated_at_ns(&self, group_id: Vec) -> Result<(), StorageError> { self.raw_query(|conn| { - let now = crate::utils::time::now_ns(); + let now = xmtp_common::time::now_ns(); diesel::update(dsl::groups.find(&group_id)) .set(dsl::rotated_at_ns.eq(now)) .execute(conn) @@ -416,7 +416,7 @@ impl DbConnection { /// Updates the 'last time checked' we checked for new installations. pub fn update_installations_time_checked(&self, group_id: Vec) -> Result<(), StorageError> { self.raw_query(|conn| { - let now = crate::utils::time::now_ns(); + let now = xmtp_common::time::now_ns(); diesel::update(dsl::groups.find(&group_id)) .set(dsl::installations_last_checked.eq(now)) .execute(conn) @@ -548,18 +548,17 @@ pub(crate) mod tests { use super::*; use crate::{ - assert_ok, storage::{ consent_record::{ConsentType, StoredConsentRecord}, encrypted_store::{schema::groups::dsl::groups, tests::with_connection}, }, - utils::{test::rand_vec, time::now_ns}, Fetch, Store, }; + use xmtp_common::{assert_ok, rand_vec, time::now_ns}; /// Generate a test group pub fn generate_group(state: Option) -> StoredGroup { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); StoredGroup::new( @@ -586,7 +585,7 @@ pub(crate) mod tests { /// Generate a test dm group pub fn generate_dm(state: Option) -> StoredGroup { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = state.unwrap_or(GroupMembershipState::Allowed); let dm_inbox_id = Some("placeholder_inbox_id".to_string()); @@ -774,7 +773,7 @@ pub(crate) mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_new_sync_group() { with_connection(|conn| { - let id = rand_vec(); + let id = rand_vec::<24>(); let created_at_ns = now_ns(); let membership_state = GroupMembershipState::Allowed; diff --git a/xmtp_mls/src/storage/encrypted_store/group_intent.rs b/xmtp_mls/src/storage/encrypted_store/group_intent.rs index a45a818c3..2cc872a3c 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_intent.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_intent.rs @@ -400,9 +400,9 @@ pub(crate) mod tests { group::{GroupMembershipState, StoredGroup}, tests::with_connection, }, - utils::test::rand_vec, Fetch, Store, }; + use xmtp_common::rand_vec; fn insert_group(conn: &DbConnection, group_id: Vec) { let group = StoredGroup::new( @@ -445,8 +445,8 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_and_fetch() { - let group_id = rand_vec(); - let data = rand_vec(); + let group_id = rand_vec::<24>(); + let data = rand_vec::<24>(); let kind = IntentKind::UpdateGroupMembership; let state = IntentState::ToPublish; @@ -479,25 +479,25 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_query() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); let test_intents: Vec = vec![ NewGroupIntent::new_test( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::ToPublish, ), NewGroupIntent::new_test( IntentKind::KeyUpdate, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::Published, ), NewGroupIntent::new_test( IntentKind::KeyUpdate, group_id.clone(), - rand_vec(), + rand_vec::<24>(), IntentState::Committed, ), ]; @@ -559,7 +559,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn find_by_payload_hash() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -568,7 +568,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -577,8 +577,8 @@ pub(crate) mod tests { let intent = find_first_intent(conn, group_id.clone()); // Set the payload hash - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -602,7 +602,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_happy_path_state_transitions() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -611,7 +611,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -619,8 +619,8 @@ pub(crate) mod tests { let mut intent = find_first_intent(conn, group_id.clone()); // Set to published - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -648,7 +648,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_republish_state_transition() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -657,7 +657,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -665,8 +665,8 @@ pub(crate) mod tests { let mut intent = find_first_intent(conn, group_id.clone()); // Set to published - let payload_hash = rand_vec(); - let post_commit_data = rand_vec(); + let payload_hash = rand_vec::<24>(); + let post_commit_data = rand_vec::<24>(); conn.set_group_intent_published( intent.id, payload_hash.clone(), @@ -693,7 +693,7 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_invalid_state_transition() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); @@ -702,7 +702,7 @@ pub(crate) mod tests { NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); @@ -729,13 +729,13 @@ pub(crate) mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_increment_publish_attempts() { - let group_id = rand_vec(); + let group_id = rand_vec::<24>(); with_connection(|conn| { insert_group(conn, group_id.clone()); NewGroupIntent::new( IntentKind::UpdateGroupMembership, group_id.clone(), - rand_vec(), + rand_vec::<24>(), ) .store(conn) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/group_message.rs b/xmtp_mls/src/storage/encrypted_store/group_message.rs index 7976a030c..743800d79 100644 --- a/xmtp_mls/src/storage/encrypted_store/group_message.rs +++ b/xmtp_mls/src/storage/encrypted_store/group_message.rs @@ -284,11 +284,11 @@ pub(crate) mod tests { use super::*; use crate::{ - assert_err, assert_ok, storage::encrypted_store::{group::tests::generate_group, tests::with_connection}, - utils::test::{rand_time, rand_vec}, Store, }; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_common::{assert_err, assert_ok, rand_time, rand_vec}; fn generate_message( kind: Option, @@ -296,19 +296,18 @@ pub(crate) mod tests { sent_at_ns: Option, ) -> StoredGroupMessage { StoredGroupMessage { - id: rand_vec(), - group_id: group_id.map(<[u8]>::to_vec).unwrap_or(rand_vec()), - decrypted_message_bytes: rand_vec(), + id: rand_vec::<24>(), + group_id: group_id.map(<[u8]>::to_vec).unwrap_or(rand_vec::<24>()), + decrypted_message_bytes: rand_vec::<24>(), sent_at_ns: sent_at_ns.unwrap_or(rand_time()), - sender_installation_id: rand_vec(), + sender_installation_id: rand_vec::<24>(), sender_inbox_id: "0x0".to_string(), kind: kind.unwrap_or(GroupMessageKind::Application), delivery_status: DeliveryStatus::Unpublished, } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_does_not_error_on_empty_messages() { with_connection(|conn| { let id = vec![0x0]; @@ -317,8 +316,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages() { with_connection(|conn| { let group = generate_group(None); @@ -334,8 +332,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_cannot_insert_message_without_group() { use diesel::result::{DatabaseErrorKind::ForeignKeyViolation, Error::DatabaseError}; @@ -349,8 +346,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_many_messages() { use crate::storage::encrypted_store::schema::group_messages::dsl; @@ -385,8 +381,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages_by_time() { with_connection(|conn| { let group = generate_group(None); @@ -423,8 +418,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_gets_messages_by_kind() { with_connection(|conn| { let group = generate_group(None); @@ -471,8 +465,7 @@ pub(crate) mod tests { .await } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn it_orders_messages_by_sent() { with_connection(|conn| { let group = generate_group(None); diff --git a/xmtp_mls/src/storage/encrypted_store/identity.rs b/xmtp_mls/src/storage/encrypted_store/identity.rs index 4051036c5..f6f973b8f 100644 --- a/xmtp_mls/src/storage/encrypted_store/identity.rs +++ b/xmtp_mls/src/storage/encrypted_store/identity.rs @@ -71,7 +71,8 @@ pub(crate) mod tests { super::{EncryptedMessageStore, StorageOption}, StoredIdentity, }; - use crate::{utils::test::rand_vec, Store}; + use crate::Store; + use xmtp_common::rand_vec; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] @@ -84,12 +85,12 @@ pub(crate) mod tests { .unwrap(); let conn = &store.conn().unwrap(); - StoredIdentity::new("".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); let duplicate_insertion = - StoredIdentity::new("".to_string(), rand_vec(), rand_vec()).store(conn); + StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>()).store(conn); assert!(duplicate_insertion.is_err()); } } diff --git a/xmtp_mls/src/storage/encrypted_store/identity_update.rs b/xmtp_mls/src/storage/encrypted_store/identity_update.rs index 74eb878d3..1c5c6b564 100644 --- a/xmtp_mls/src/storage/encrypted_store/identity_update.rs +++ b/xmtp_mls/src/storage/encrypted_store/identity_update.rs @@ -134,16 +134,18 @@ pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); - use crate::{ - storage::encrypted_store::tests::with_connection, - utils::test::{rand_time, rand_vec}, - Store, - }; + use crate::{storage::encrypted_store::tests::with_connection, Store}; + use xmtp_common::{rand_time, rand_vec}; use super::*; fn build_update(inbox_id: &str, sequence_id: i64) -> StoredIdentityUpdate { - StoredIdentityUpdate::new(inbox_id.to_string(), sequence_id, rand_time(), rand_vec()) + StoredIdentityUpdate::new( + inbox_id.to_string(), + sequence_id, + rand_time(), + rand_vec::<24>(), + ) } #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] diff --git a/xmtp_mls/src/storage/encrypted_store/key_package_history.rs b/xmtp_mls/src/storage/encrypted_store/key_package_history.rs index 7947abb0a..9e43243c0 100644 --- a/xmtp_mls/src/storage/encrypted_store/key_package_history.rs +++ b/xmtp_mls/src/storage/encrypted_store/key_package_history.rs @@ -1,7 +1,8 @@ use diesel::prelude::*; use super::{db_connection::DbConnection, schema::key_package_history, StorageError}; -use crate::{impl_store_or_ignore, utils::time::now_ns, StoreOrIgnore}; +use crate::{impl_store_or_ignore, StoreOrIgnore}; +use xmtp_common::time::now_ns; #[derive(Insertable, Debug, Clone)] #[diesel(table_name = key_package_history)] @@ -78,7 +79,8 @@ impl DbConnection { #[cfg(test)] mod tests { - use crate::{storage::encrypted_store::tests::with_connection, utils::test::rand_vec}; + use crate::storage::encrypted_store::tests::with_connection; + use xmtp_common::rand_vec; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); @@ -86,7 +88,7 @@ mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_key_package_history_entry() { with_connection(|conn| { - let hash_ref = rand_vec(); + let hash_ref = rand_vec::<24>(); let new_entry = conn .store_key_package_history_entry(hash_ref.clone()) .unwrap(); @@ -100,9 +102,9 @@ mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_store_multiple() { with_connection(|conn| { - let hash_ref1 = rand_vec(); - let hash_ref2 = rand_vec(); - let hash_ref3 = rand_vec(); + let hash_ref1 = rand_vec::<24>(); + let hash_ref2 = rand_vec::<24>(); + let hash_ref3 = rand_vec::<24>(); conn.store_key_package_history_entry(hash_ref1.clone()) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index 9646a7611..c7172dd5b 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -472,6 +472,7 @@ where pub(crate) mod tests { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + use wasm_bindgen_test::wasm_bindgen_test; use super::*; use crate::{ @@ -479,9 +480,9 @@ pub(crate) mod tests { group::{GroupMembershipState, StoredGroup}, identity::StoredIdentity, }, - utils::test::{rand_vec, tmp_path}, Fetch, Store, StreamHandle as _, XmtpOpenMlsProvider, }; + use xmtp_common::{rand_vec, tmp_path}; /// Test harness that loads an Ephemeral store. pub async fn with_connection(fun: F) -> R @@ -510,8 +511,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn ephemeral_store() { let store = EncryptedMessageStore::new( StorageOption::Ephemeral, @@ -522,7 +522,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -530,8 +530,7 @@ pub(crate) mod tests { assert_eq!(fetched_identity.inbox_id, inbox_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn persistent_store() { let db_path = tmp_path(); { @@ -544,7 +543,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -553,10 +552,8 @@ pub(crate) mod tests { } EncryptedMessageStore::remove_db_files(db_path) } - - // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn releases_db_lock() { let db_path = tmp_path(); { @@ -569,7 +566,7 @@ pub(crate) mod tests { let conn = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn) .unwrap(); @@ -588,8 +585,7 @@ pub(crate) mod tests { EncryptedMessageStore::remove_db_files(db_path) } - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - #[cfg(not(target_arch = "wasm32"))] + #[tokio::test] async fn mismatched_encryption_key() { let mut enc_key = [1u8; 32]; @@ -601,9 +597,13 @@ pub(crate) mod tests { .await .unwrap(); - StoredIdentity::new("dummy_address".to_string(), rand_vec(), rand_vec()) - .store(&store.conn().unwrap()) - .unwrap(); + StoredIdentity::new( + "dummy_address".to_string(), + rand_vec::<24>(), + rand_vec::<24>(), + ) + .store(&store.conn().unwrap()) + .unwrap(); } // Drop it enc_key[3] = 145; // Alter the enc_key @@ -618,8 +618,7 @@ pub(crate) mod tests { EncryptedMessageStore::remove_db_files(db_path) } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[wasm_bindgen_test(unsupported = tokio::test)] async fn encrypted_db_with_multiple_connections() { let db_path = tmp_path(); { @@ -632,7 +631,7 @@ pub(crate) mod tests { let conn1 = &store.conn().unwrap(); let inbox_id = "inbox_id"; - StoredIdentity::new(inbox_id.to_string(), rand_vec(), rand_vec()) + StoredIdentity::new(inbox_id.to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); @@ -670,7 +669,7 @@ pub(crate) mod tests { let handle = std::thread::spawn(move || { store_pointer.transaction(&provider, |provider| { let conn1 = provider.conn_ref(); - StoredIdentity::new("correct".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("correct".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); // wait for second transaction to start @@ -738,7 +737,7 @@ pub(crate) mod tests { store_pointer .transaction_async(&provider, |provider| async move { let conn1 = provider.conn_ref(); - StoredIdentity::new("crab".to_string(), rand_vec(), rand_vec()) + StoredIdentity::new("crab".to_string(), rand_vec::<24>(), rand_vec::<24>()) .store(conn1) .unwrap(); diff --git a/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs b/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs index 25dfb379f..fe9350b26 100644 --- a/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs +++ b/xmtp_mls/src/storage/encrypted_store/sqlcipher_connection.rs @@ -304,9 +304,10 @@ fn pragma_plaintext_header() -> impl Display { #[cfg(test)] mod tests { - use crate::{storage::EncryptedMessageStore, utils::test::tmp_path}; + use crate::storage::EncryptedMessageStore; use diesel_migrations::MigrationHarness; use std::fs::File; + use xmtp_common::tmp_path; use super::*; const SQLITE3_PLAINTEXT_HEADER: &str = "SQLite format 3\0"; diff --git a/xmtp_mls/src/storage/encrypted_store/wasm.rs b/xmtp_mls/src/storage/encrypted_store/wasm.rs index 2d551c8a9..7cbc05fc0 100644 --- a/xmtp_mls/src/storage/encrypted_store/wasm.rs +++ b/xmtp_mls/src/storage/encrypted_store/wasm.rs @@ -50,13 +50,11 @@ impl XmtpDb for WasmDb { Ok(()) } - #[allow(unreachable_code)] fn release_connection(&self) -> Result<(), StorageError> { - unimplemented!(); + Ok(()) } - #[allow(unreachable_code)] fn reconnect(&self) -> Result<(), StorageError> { - unimplemented!(); + Ok(()) } } diff --git a/xmtp_mls/src/storage/errors.rs b/xmtp_mls/src/storage/errors.rs index f109b0248..de25850ab 100644 --- a/xmtp_mls/src/storage/errors.rs +++ b/xmtp_mls/src/storage/errors.rs @@ -3,9 +3,11 @@ use std::sync::PoisonError; use diesel::result::DatabaseErrorKind; use thiserror::Error; -use crate::{groups::intents::IntentError, retry::RetryableError, retryable}; - use super::sql_key_store; +use crate::groups::intents::IntentError; +use xmtp_common::{retryable, RetryableError}; + +pub struct Mls; #[derive(Debug, Error)] pub enum StorageError { @@ -66,7 +68,7 @@ impl From> for StorageError { } } -impl RetryableError for diesel::result::Error { +impl RetryableError for diesel::result::Error { fn is_retryable(&self) -> bool { use diesel::result::Error::*; use DatabaseErrorKind::*; @@ -101,7 +103,7 @@ impl RetryableError for StorageError { } // OpenMLS KeyStore errors -impl RetryableError for openmls::group::AddMembersError { +impl RetryableError for openmls::group::AddMembersError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -112,7 +114,7 @@ impl RetryableError for openmls::group::AddMembersError { +impl RetryableError for openmls::group::CreateCommitError { fn is_retryable(&self) -> bool { match self { Self::KeyStoreError(storage) => retryable!(storage), @@ -122,7 +124,9 @@ impl RetryableError for openmls::group::CreateCommitError { +impl RetryableError + for openmls::treesync::LeafNodeUpdateError +{ fn is_retryable(&self) -> bool { match self { Self::Storage(storage) => retryable!(storage), @@ -131,13 +135,13 @@ impl RetryableError for openmls::treesync::LeafNodeUpdateError for openmls::key_packages::errors::KeyPackageNewError { fn is_retryable(&self) -> bool { matches!(self, Self::StorageError) } } -impl RetryableError for openmls::group::RemoveMembersError { +impl RetryableError for openmls::group::RemoveMembersError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -148,7 +152,7 @@ impl RetryableError for openmls::group::RemoveMembersError { +impl RetryableError for openmls::group::NewGroupError { fn is_retryable(&self) -> bool { match self { Self::StorageError(storage) => retryable!(storage), @@ -157,7 +161,7 @@ impl RetryableError for openmls::group::NewGroupError for openmls::group::UpdateGroupMembershipError { fn is_retryable(&self) -> bool { @@ -170,13 +174,13 @@ impl RetryableError } } -impl RetryableError for openmls::prelude::MlsGroupStateError { +impl RetryableError for openmls::prelude::MlsGroupStateError { fn is_retryable(&self) -> bool { false } } -impl RetryableError +impl RetryableError for openmls::prelude::CreateGroupContextExtProposalError { fn is_retryable(&self) -> bool { @@ -188,7 +192,7 @@ impl RetryableError } } -impl RetryableError for openmls::group::SelfUpdateError { +impl RetryableError for openmls::group::SelfUpdateError { fn is_retryable(&self) -> bool { match self { Self::CreateCommitError(commit) => retryable!(commit), @@ -199,7 +203,7 @@ impl RetryableError for openmls::group::SelfUpdateError for openmls::prelude::CreationFromExternalError { fn is_retryable(&self) -> bool { @@ -210,7 +214,7 @@ impl RetryableError } } -impl RetryableError for openmls::prelude::WelcomeError { +impl RetryableError for openmls::prelude::WelcomeError { fn is_retryable(&self) -> bool { match self { Self::PublicGroupError(creation_err) => retryable!(creation_err), @@ -220,7 +224,7 @@ impl RetryableError for openmls::prelude::WelcomeError { +impl RetryableError for openmls::group::MergeCommitError { fn is_retryable(&self) -> bool { match self { Self::StorageError(storage) => retryable!(storage), @@ -229,7 +233,9 @@ impl RetryableError for openmls::group::MergeCommitError { +impl RetryableError + for openmls::group::MergePendingCommitError +{ fn is_retryable(&self) -> bool { match self { Self::MlsGroupStateError(err) => retryable!(err), @@ -238,7 +244,7 @@ impl RetryableError for openmls::group::MergePendingCommitError for openmls::prelude::ProcessMessageError { fn is_retryable(&self) -> bool { match self { Self::GroupStateError(err) => retryable!(err), diff --git a/xmtp_mls/src/storage/mod.rs b/xmtp_mls/src/storage/mod.rs index c1ecc7ed6..d3c525897 100644 --- a/xmtp_mls/src/storage/mod.rs +++ b/xmtp_mls/src/storage/mod.rs @@ -13,3 +13,95 @@ pub async fn init_sqlite() { } #[cfg(not(target_arch = "wasm32"))] pub async fn init_sqlite() {} + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_util { + #![allow(clippy::unwrap_used)] + use super::*; + use diesel::{connection::LoadConnection, deserialize::FromSqlRow, sql_query, RunQueryDsl}; + impl DbConnection { + /// Create a new table and register triggers for tracking column updates + pub fn register_triggers(&self) { + tracing::info!("Registering triggers"); + let queries = vec![ + r#" + CREATE TABLE test_metadata ( + intents_created INT DEFAULT 0, + intents_published INT DEFAULT 0, + intents_deleted INT DEFAULT 0, + rowid integer PRIMARY KEY CHECK (rowid = 1) -- There can only be one meta + ); + "#, + r#"CREATE TRIGGER intents_created_tracking AFTER INSERT on group_intents + BEGIN + UPDATE test_metadata SET intents_created = intents_created + 1; + END;"#, + r#"CREATE TRIGGER intents_published_tracking AFTER UPDATE OF state ON group_intents + FOR EACH ROW + WHEN NEW.state = 2 AND OLD.state !=2 + BEGIN + UPDATE test_metadata SET intents_published = intents_published + 1; + END;"#, + r#"CREATE TRIGGER intents_deleted_tracking AFTER DELETE ON group_intents + FOR EACH ROW + BEGIN + UPDATE test_metadata SET intents_deleted = intents_deleted + 1; + END;"#, + r#"INSERT INTO test_metadata ( + intents_created, + intents_deleted, + intents_published + ) VALUES (0, 0,0);"#, + ]; + + for query in queries { + let query = diesel::sql_query(query); + let _ = self.raw_query(|conn| query.execute(conn)).unwrap(); + } + } + + pub fn intents_published(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query( + "SELECT intents_published FROM test_metadata WHERE rowid = 1", + )) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + + pub fn intents_deleted(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query("SELECT intents_deleted FROM test_metadata")) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + + pub fn intents_created(&self) -> i32 { + self.raw_query(|conn| { + let mut row = conn + .load(sql_query("SELECT intents_created FROM test_metadata")) + .unwrap(); + let row = row.next().unwrap().unwrap(); + Ok::<_, StorageError>( + >::build_from_row(&row) + .unwrap(), + ) + }) + .unwrap() + } + } +} diff --git a/xmtp_mls/src/storage/sql_key_store.rs b/xmtp_mls/src/storage/sql_key_store.rs index ae9d2d797..1ad7aa674 100644 --- a/xmtp_mls/src/storage/sql_key_store.rs +++ b/xmtp_mls/src/storage/sql_key_store.rs @@ -1,4 +1,4 @@ -use crate::{retry::RetryableError, retryable}; +use xmtp_common::{retryable, RetryableError}; use super::encrypted_store::db_connection::DbConnectionPrivate; use bincode; @@ -1041,9 +1041,9 @@ pub(crate) mod tests { use crate::{ configuration::CIPHERSUITE, storage::{sql_key_store::SqlKeyStoreError, EncryptedMessageStore, StorageOption}, - utils::test::tmp_path, xmtp_openmls_provider::XmtpOpenMlsProvider, }; + use xmtp_common::tmp_path; #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] diff --git a/xmtp_mls/src/stream_handles.rs b/xmtp_mls/src/stream_handles.rs index 869b3dbc7..a42af78e0 100644 --- a/xmtp_mls/src/stream_handles.rs +++ b/xmtp_mls/src/stream_handles.rs @@ -1,6 +1,6 @@ //! Consistent Stream behavior between WebAssembly and Native utilizing `tokio::task::spawn` in native and //! `wasm_bindgen_futures::spawn` for web. -use futures::{Future, FutureExt}; +use futures::FutureExt; #[cfg(target_arch = "wasm32")] pub type GenericStreamHandle = dyn StreamHandle; @@ -72,6 +72,12 @@ pub use wasm::*; #[cfg(target_arch = "wasm32")] mod wasm { + use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use futures::future::Either; use super::*; @@ -150,32 +156,62 @@ mod wasm { F::Output: 'static, { let (res_tx, res_rx) = tokio::sync::oneshot::channel(); - let (closer_tx, mut closer_rx) = tokio::sync::mpsc::channel::<()>(1); + let (closer_tx, closer_rx) = tokio::sync::mpsc::channel::<()>(1); + let closer_handle = CloserHandle::new(closer_rx); let handle = WasmStreamHandle { result: res_rx, closer: closer_tx, ready, }; - + tracing::info!("Spawning local task on web executor"); wasm_bindgen_futures::spawn_local(async move { - let recv = closer_rx.recv(); - futures::pin_mut!(recv); + futures::pin_mut!(closer_handle); futures::pin_mut!(future); - let value = match futures::future::select(recv, future).await { - Either::Left((_, _)) => Err(StreamHandleError::StreamClosed), - Either::Right((v, _)) => Ok(v), + let value = match futures::future::select(closer_handle, future).await { + Either::Left((_, _)) => { + tracing::warn!("stream closed"); + Err(StreamHandleError::StreamClosed) + } + Either::Right((v, _)) => { + tracing::debug!("Future ended with value"); + Ok(v) + } }; let _ = res_tx.send(value); + tracing::info!("spawned local future closing"); }); handle } + + struct CloserHandle(tokio::sync::mpsc::Receiver<()>); + + impl CloserHandle { + fn new(receiver: tokio::sync::mpsc::Receiver<()>) -> Self { + Self(receiver) + } + } + + impl Future for CloserHandle { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.0.poll_recv(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(_)) => Poll::Ready(()), + // if the channel is closed, the task has detached and must + // be kept alive for the duration for the program + Poll::Ready(None) => Poll::Pending, + } + } + } } #[cfg(not(target_arch = "wasm32"))] mod native { use super::*; + use std::future::Future; use tokio::task::JoinHandle; pub struct TokioStreamHandle { diff --git a/xmtp_mls/src/subscriptions.rs b/xmtp_mls/src/subscriptions.rs index 94b93c537..d285050c2 100644 --- a/xmtp_mls/src/subscriptions.rs +++ b/xmtp_mls/src/subscriptions.rs @@ -17,8 +17,6 @@ use crate::{ mls_sync::GroupMessageProcessingError, scoped_client::ScopedGroupClient as _, subscriptions, GroupError, MlsGroup, }, - retry::{Retry, RetryableError}, - retry_async, retryable, storage::{ consent_record::StoredConsentRecord, group::{ConversationType, GroupQueryArgs, StoredGroup}, @@ -28,6 +26,7 @@ use crate::{ Client, XmtpApi, XmtpOpenMlsProvider, }; use thiserror::Error; +use xmtp_common::{retry_async, retryable, Retry, RetryableError}; #[derive(Debug, Error)] pub enum LocalEventError { @@ -130,7 +129,7 @@ where #[instrument(level = "trace", skip_all)] fn stream_sync_messages(self) -> impl Stream, SubscribeError>> { BroadcastStream::new(self).filter_map(|event| async { - crate::optify!(event, "Missed message due to event queue lag") + xmtp_common::optify!(event, "Missed message due to event queue lag") .and_then(LocalEvents::sync_filter) .map(Result::Ok) }) @@ -140,7 +139,7 @@ where self, ) -> impl Stream, SubscribeError>> { BroadcastStream::new(self).filter_map(|event| async { - crate::optify!(event, "Missed message due to event queue lag") + xmtp_common::optify!(event, "Missed message due to event queue lag") .and_then(LocalEvents::consent_filter) .map(Result::Ok) }) @@ -309,7 +308,7 @@ where let event_queue = tokio_stream::wrappers::BroadcastStream::new(self.local_events.subscribe()) .filter_map(|event| async { - crate::optify!(event, "Missed messages due to event queue lag") + xmtp_common::optify!(event, "Missed messages due to event queue lag") .and_then(LocalEvents::group_filter) .map(Result::Ok) }) @@ -545,11 +544,11 @@ pub(crate) mod tests { atomic::{AtomicU64, Ordering}, Arc, }; + use wasm_bindgen_test::wasm_bindgen_test; use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "current_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_welcomes() { let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bob = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -584,12 +583,9 @@ pub(crate) mod tests { assert_eq!(bob_received_groups.group_id, group_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_messages() { + xmtp_common::logger(); let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bob = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -597,7 +593,6 @@ pub(crate) mod tests { .create_group(None, GroupMetadataOptions::default()) .unwrap(); - // let mut bob_stream = bob.stream_conversations().await.unwrap()warning: unused implementer of `futures::Future` that must be used; alice_group .add_members_by_inbox_id(&[bob.inbox_id()]) .await @@ -619,26 +614,25 @@ pub(crate) mod tests { notify_ptr.notify_one(); } }); - let mut stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); + let stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); + // let stream = alice_group.stream().await.unwrap(); + futures::pin_mut!(stream); bob_group.send_message(b"hello").await.unwrap(); - notify.wait_for_delivery().await.unwrap(); + tracing::debug!("Bob Sent Message!, waiting for delivery"); + // notify.wait_for_delivery().await.unwrap(); let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello"); bob_group.send_message(b"hello2").await.unwrap(); - notify.wait_for_delivery().await.unwrap(); + // notify.wait_for_delivery().await.unwrap(); let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello2"); // assert_eq!(bob_received_groups.group_id, alice_bob_group.group_id); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_all_messages_unchanging_group_list() { let alix = ClientBuilder::new_test_client(&generate_local_wallet()).await; let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -659,7 +653,7 @@ pub(crate) mod tests { .add_members_by_inbox_id(&[caro.inbox_id()]) .await .unwrap(); - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; let messages: Arc>> = Arc::new(Mutex::new(Vec::new())); let messages_clone = messages.clone(); @@ -695,11 +689,7 @@ pub(crate) mod tests { assert_eq!(messages[3].decrypted_message_bytes, b"fourth"); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] async fn test_stream_all_messages_changing_group_list() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = ClientBuilder::new_test_client(&generate_local_wallet()).await; @@ -782,7 +772,7 @@ pub(crate) mod tests { .send_message("should not show up".as_bytes()) .await .unwrap(); - crate::sleep(core::time::Duration::from_millis(100)).await; + xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; let messages = messages.lock(); @@ -790,11 +780,7 @@ pub(crate) mod tests { } #[ignore] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr( - not(target_arch = "wasm32"), - tokio::test(flavor = "multi_thread", worker_threads = 10) - )] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_stream_all_messages_does_not_lose_messages() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let caro = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -827,7 +813,7 @@ pub(crate) mod tests { crate::spawn(None, async move { for _ in 0..50 { alix_group_pointer.send_message(b"spam").await.unwrap(); - crate::sleep(core::time::Duration::from_micros(200)).await; + xmtp_common::time::sleep(core::time::Duration::from_micros(200)).await; } }); @@ -859,8 +845,7 @@ pub(crate) mod tests { } } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_self_group_creation() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -921,8 +906,7 @@ pub(crate) mod tests { closer.end(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_dm_streaming() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -1055,8 +1039,7 @@ pub(crate) mod tests { closer.end(); } - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test(flavor = "multi_thread"))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread"))] async fn test_dm_stream_all_messages() { let alix = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); let bo = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); diff --git a/xmtp_mls/src/utils/mod.rs b/xmtp_mls/src/utils/mod.rs index a81bba777..357410b65 100644 --- a/xmtp_mls/src/utils/mod.rs +++ b/xmtp_mls/src/utils/mod.rs @@ -8,30 +8,11 @@ pub mod hash { } pub mod time { - use std::time::Duration; - - use wasm_timer::{SystemTime, UNIX_EPOCH}; - - pub const NS_IN_SEC: i64 = 1_000_000_000; const SECS_IN_30_DAYS: i64 = 60 * 60 * 24 * 30; - fn duration_since_epoch() -> Duration { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - } - - pub fn now_ns() -> i64 { - duration_since_epoch().as_nanos() as i64 - } - - pub fn now_secs() -> i64 { - duration_since_epoch().as_secs() as i64 - } - /// Current hmac epoch. HMAC keys change every 30 days pub fn hmac_epoch() -> i64 { - now_secs() / SECS_IN_30_DAYS + xmtp_common::time::now_secs() / SECS_IN_30_DAYS } } @@ -56,34 +37,3 @@ pub mod id { hex::encode(group_id) } } - -#[cfg(any( - all(target_arch = "wasm32", feature = "test-utils"), - all(test, target_arch = "wasm32") -))] -pub mod wasm { - use tokio::sync::OnceCell; - static INIT: OnceCell<()> = OnceCell::const_new(); - - /// can be used to debug wasm tests - /// normal tracing logs are output to the browser console - pub async fn init() { - use web_sys::console; - - INIT.get_or_init(|| async { - console::log_1(&"INIT".into()); - let config = tracing_wasm::WASMLayerConfigBuilder::default() - .set_console_config(tracing_wasm::ConsoleConfig::ReportWithoutConsoleColor) - .build(); - tracing_wasm::set_as_global_default_with_config(config); - console_error_panic_hook::set_once(); - diesel_wasm_sqlite::init_sqlite().await; - }) - .await; - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub mod wasm { - pub async fn init() {} -} diff --git a/xmtp_mls/src/utils/test/mod.rs b/xmtp_mls/src/utils/test/mod.rs index 7067f0537..ff44452b2 100755 --- a/xmtp_mls/src/utils/test/mod.rs +++ b/xmtp_mls/src/utils/test/mod.rs @@ -1,19 +1,7 @@ #![allow(clippy::unwrap_used)] -use crate::storage::DbConnection; -use crate::{ - builder::ClientBuilder, - identity::IdentityStrategy, - storage::{EncryptedMessageStore, StorageOption}, - types::Address, - Client, InboxOwner, XmtpApi, -}; -use rand::{ - distributions::{Alphanumeric, DistString}, - Rng, RngCore, -}; use std::sync::Arc; -use tokio::{sync::Notify, time::error::Elapsed}; +use tokio::sync::Notify; use xmtp_id::{ associations::{ generate_inbox_id, @@ -24,10 +12,12 @@ use xmtp_id::{ }; use xmtp_proto::api_client::XmtpTestClient; -#[cfg(not(target_arch = "wasm32"))] -pub mod traced_test; -#[cfg(not(target_arch = "wasm32"))] -pub use traced_test::traced_test; +use crate::{ + builder::ClientBuilder, + identity::IdentityStrategy, + storage::{DbConnection, EncryptedMessageStore, StorageOption}, + Client, InboxOwner, XmtpApi, +}; pub type FullXmtpClient = Client; @@ -45,40 +35,9 @@ use xmtp_api_http::XmtpHttpApiClient; #[cfg(any(feature = "http-api", target_arch = "wasm32"))] pub type TestClient = XmtpHttpApiClient; -pub fn rand_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 24) -} - -pub fn rand_account_address() -> Address { - Alphanumeric.sample_string(&mut rand::thread_rng(), 42) -} - -pub fn rand_vec() -> Vec { - rand::thread_rng().gen::<[u8; 24]>().to_vec() -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", std::env::temp_dir().to_str().unwrap(), db_name) -} - -#[cfg(target_arch = "wasm32")] -pub fn tmp_path() -> String { - let db_name = rand_string(); - format!("{}/{}.db3", "test_db", db_name) -} - -pub fn rand_time() -> i64 { - let mut rng = rand::thread_rng(); - rng.gen_range(0..1_000_000_000) -} - impl EncryptedMessageStore { pub fn generate_enc_key() -> [u8; 32] { - let mut key = [0u8; 32]; - xmtp_cryptography::utils::rng().fill_bytes(&mut key[..]); - key + xmtp_common::rand_array::<32>() } #[cfg(not(target_arch = "wasm32"))] @@ -97,8 +56,7 @@ impl EncryptedMessageStore { impl ClientBuilder { pub async fn temp_store(self) -> Self { - let tmpdb = tmp_path(); - tracing::info!("Opening Database at [{}]", tmpdb); + let tmpdb = xmtp_common::tmp_path(); self.store( EncryptedMessageStore::new( StorageOption::Persistent(tmpdb), @@ -197,7 +155,8 @@ where .build() .await .unwrap(); - + let conn = client.store().conn().unwrap(); + conn.register_triggers(); register_client(&client, owner).await; client @@ -232,7 +191,8 @@ where } let client = builder.build_with_verifier().await.unwrap(); - + let conn = client.store().conn().unwrap(); + conn.register_triggers(); register_client(&client, owner).await; client @@ -254,8 +214,8 @@ impl Delivery { } } - pub async fn wait_for_delivery(&self) -> Result<(), Elapsed> { - tokio::time::timeout(self.timeout, async { self.notify.notified().await }).await + pub async fn wait_for_delivery(&self) -> Result<(), xmtp_common::time::Expired> { + xmtp_common::time::timeout(self.timeout, async { self.notify.notified().await }).await } pub fn notify_one(&self) { @@ -295,50 +255,12 @@ pub async fn register_client( client.register_identity(signature_request).await.unwrap(); } -/// waits for all intents to finish -/// TODO: Should wrap with a timeout -pub async fn wait_for_all_intents_published(conn: &DbConnection) { - use crate::storage::group_intent::IntentState; - use crate::storage::group_intent::StoredGroupIntent; - use crate::storage::schema::group_intents::dsl; - use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; - - let mut query = dsl::group_intents.into_boxed(); - let states = vec![IntentState::ToPublish]; - query = query.filter(dsl::state.eq_any(states)); - query = query.order(dsl::id.asc()); - - let intents = conn - .raw_query(|conn| query.load::(conn)) - .unwrap(); - - tracing::info!("{} intents left", intents.len()); - if intents.is_empty() { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - Box::pin(wait_for_all_intents_published(conn)).await - } -} - -/// wait for a minimum amount of intent +/// wait for a minimum amount of intents to be published /// TODO: Should wrap with a timeout pub async fn wait_for_min_intents(conn: &DbConnection, n: usize) { - use crate::storage::group_intent::IntentState; - use crate::storage::group_intent::StoredGroupIntent; - use crate::storage::schema::group_intents::dsl; - use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; - - let mut query = dsl::group_intents.into_boxed(); - let states = vec![IntentState::Published]; - query = query.filter(dsl::state.eq_any(states)); - query = query.order(dsl::id.asc()); - - let intents = conn - .raw_query(|conn| query.load::(conn)) - .unwrap(); - - tracing::info!("{} intents left", intents.len()); - if intents.len() < n { - tokio::time::sleep(std::time::Duration::from_millis(200)).await; - Box::pin(wait_for_min_intents(conn, n - intents.len())).await + let mut published = conn.intents_published() as usize; + while published < n { + xmtp_common::yield_().await; + published = conn.intents_published() as usize; } } diff --git a/xmtp_proto/Cargo.toml b/xmtp_proto/Cargo.toml index d4bb67c97..9e5ddf30f 100644 --- a/xmtp_proto/Cargo.toml +++ b/xmtp_proto/Cargo.toml @@ -6,7 +6,6 @@ license.workspace = true [dependencies] futures = { workspace = true } -openmls = { workspace = true, optional = true } pbjson-types.workspace = true pbjson.workspace = true prost = { workspace = true, features = ["prost-derive"] } @@ -15,17 +14,22 @@ async-trait = "0.1" hex.workspace = true openmls_rust_crypto = { workspace = true, optional = true } tracing.workspace = true +xmtp_common.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tonic = { workspace = true } +tonic = { workspace = true, features = ["codegen", "server", "channel", "prost"] } +openmls = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +openmls = { workspace = true, features = ["js"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test.workspace = true [features] -convert = ["openmls", "openmls_rust_crypto", "proto_full"] +convert = ["openmls_rust_crypto", "proto_full"] default = [] -test-utils = [] +test-utils = ["xmtp_common/test-utils"] # @@protoc_deletion_point(features) # This section is automatically generated by protoc-gen-prost-crate. diff --git a/xmtp_proto/src/api_client.rs b/xmtp_proto/src/api_client.rs index 03afb8df3..75cd72fbb 100644 --- a/xmtp_proto/src/api_client.rs +++ b/xmtp_proto/src/api_client.rs @@ -128,12 +128,6 @@ where } } -/// Global Marker trait for WebAssembly -#[cfg(target_arch = "wasm32")] -pub trait Wasm {} -#[cfg(target_arch = "wasm32")] -impl Wasm for T {} - // Wasm futures don't have `Send` or `Sync` bounds. #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] diff --git a/xmtp_proto/src/error.rs b/xmtp_proto/src/error.rs index d8109b9b8..8a574c785 100644 --- a/xmtp_proto/src/error.rs +++ b/xmtp_proto/src/error.rs @@ -35,6 +35,13 @@ pub struct Error { source: Option, } +// network errors should generally be retryable, unless there's a bug in our code +impl xmtp_common::RetryableError for Error { + fn is_retryable(&self) -> bool { + true + } +} + impl Error { pub fn new(kind: ErrorKind) -> Self { Self { kind, source: None }