diff --git a/Cargo.lock b/Cargo.lock index 24ca7917a..ed35095c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2557,12 +2557,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.2.0", "http-body 1.0.1", "pin-project-lite", @@ -4549,7 +4549,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.13.0", "log", "multimap", @@ -7433,12 +7433,16 @@ name = "xmtp_api_d14n" version = "1.0.0-rc1" dependencies = [ "async-trait", + "ctor", "derive_builder", + "hex", "once_cell", "parking_lot 0.12.3", "prost", "prost-types", "tokio", + "tracing", + "wasm-bindgen-test", "xmtp_api_grpc", "xmtp_api_http", "xmtp_common", @@ -7473,8 +7477,11 @@ version = "1.0.0-rc1" dependencies = [ "async-trait", "bytes", + "ctor", "futures", + "hex", "http 1.2.0", + "http-body-util", "pin-project-lite", "prost", "reqwest 0.12.12", @@ -7716,6 +7723,7 @@ dependencies = [ "futures", "hex", "http 1.2.0", + "mockall", "openmls", "openmls_rust_crypto", "pbjson", diff --git a/Cargo.toml b/Cargo.toml index db6447b11..8136ae4e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ libsqlite3-sys = { version = "0.29", features = [ "bundled-sqlcipher-vendored-openssl", ] } mockall = "0.13" +color-eyre = "0.6" once_cell = "1.2" openmls = { git = "https://github.com/xmtp/openmls", rev = "082cab5f17a54796e87bc1762a64496c86cb9bf8", default-features = false } openmls_basic_credential = { git = "https://github.com/xmtp/openmls", rev = "082cab5f17a54796e87bc1762a64496c86cb9bf8" } diff --git a/dev/docker/docker-compose.yml b/dev/docker/docker-compose.yml index f8673772b..703b84a00 100644 --- a/dev/docker/docker-compose.yml +++ b/dev/docker/docker-compose.yml @@ -1,5 +1,4 @@ version: '3.8' - services: node: image: xmtp/node-go:latest @@ -21,61 +20,53 @@ services: - 5556:5556 depends_on: - db - validation: image: ghcr.io/xmtp/mls-validation-service:main platform: linux/amd64 build: context: ../.. - dockerfile: ./dev/validation_service/local.Dockerfile + dockerfile: ./dev/validation_service/Dockerfile environment: ANVIL_URL: "http://anvil:8545" - anvil: build: dockerfile: ./anvil.Dockerfile platform: linux/amd64 ports: - 8545:8545 - history-server: image: ghcr.io/xmtp/message-history-server:main platform: linux/amd64 ports: - 5558:5558 - db: image: postgres:13 environment: POSTGRES_PASSWORD: xmtp - mlsdb: image: postgres:13 environment: POSTGRES_PASSWORD: xmtp - replicationdb: image: postgres:16 environment: POSTGRES_PASSWORD: xmtp healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres" ] + test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 - chain: platform: linux/amd64 # note: the SHA here is tied to the XTMPD_CONTRACTS_*_ADDRESSes # if you bump the version of anvil-xmtpd you will have to change the contracts # you can find them inside the anvil-xmtpd image via `docker exec libxmtp-chain-1 cat contracts.env` - image: ghcr.io/xmtp/anvil-xmtpd:sha-b943a7a + image: ghcr.io/xmtp/anvil-xmtpd:sha-03fd0d8 command: ["--host", "0.0.0.0"] - repnode: platform: linux/amd64 # note: avoid using :latest while xmtpd is under development to avoid breaking changes - image: ghcr.io/xmtp/xmtpd:sha-b943a7a + image: ghcr.io/xmtp/xmtpd:sha-03fd0d8 environment: XMTPD_DB_WRITER_CONNECTION_STRING: "postgres://postgres:xmtp@replicationdb:5432/postgres?sslmode=disable" XMTPD_CONTRACTS_RPC_URL: "http://chain:8545" @@ -93,6 +84,7 @@ services: chain: condition: service_started replicationdb: - condition: service_healthy + condition: service_healthy ports: - - 5050:5050 \ No newline at end of file + - 5050:5050 + - 5055:5055 diff --git a/xmtp_api/src/test_utils.rs b/xmtp_api/src/test_utils.rs index 2bce733d8..c60763d2b 100644 --- a/xmtp_api/src/test_utils.rs +++ b/xmtp_api/src/test_utils.rs @@ -90,9 +90,29 @@ pub use wasm::*; #[cfg(not(target_arch = "wasm32"))] mod not_wasm { use super::*; + use xmtp_proto::api_client::ApiBuilder; use xmtp_proto::xmtp::mls::api::v1::WelcomeMessage; #[derive(Clone)] pub struct ApiClient; + pub struct MockApiBuilder; + + impl ApiBuilder for MockApiBuilder { + type Output = ApiClient; + type Error = MockError; + + fn set_libxmtp_version(&mut self, _version: String) -> Result<(), Self::Error> { + Ok(()) + } + fn set_app_version(&mut self, _version: String) -> Result<(), Self::Error> { + Ok(()) + } + fn set_host(&mut self, _host: String) {} + fn set_payer(&mut self, _host: String) {} + fn set_tls(&mut self, _tls: bool) {} + async fn build(self) -> Result { + Ok(ApiClient) + } + } mock! { pub ApiClient { } @@ -142,10 +162,12 @@ mod not_wasm { -> Result; } - #[async_trait::async_trait] impl XmtpTestClient for ApiClient { - async fn create_local() -> Self { ApiClient } - async fn create_dev() -> Self { ApiClient } + type Builder = MockApiBuilder; + fn create_local() -> MockApiBuilder { MockApiBuilder } + fn create_dev() -> MockApiBuilder { MockApiBuilder } + fn create_local_d14n() -> MockApiBuilder { MockApiBuilder } + fn create_local_payer() -> MockApiBuilder { MockApiBuilder } } } } @@ -207,8 +229,12 @@ mod wasm { #[async_trait::async_trait(?Send)] impl XmtpTestClient for ApiClient { - async fn create_local() -> Self { ApiClient } - async fn create_dev() -> Self { ApiClient } + type Builder = (); + fn create_local() -> () { () } + fn create_dev() -> () { () } + fn create_local_payer() -> () { () } + fn create_local_d14n() -> () { () } + } } } diff --git a/xmtp_api_d14n/Cargo.toml b/xmtp_api_d14n/Cargo.toml index 32fb0c993..ab34cbb0e 100644 --- a/xmtp_api_d14n/Cargo.toml +++ b/xmtp_api_d14n/Cargo.toml @@ -6,23 +6,35 @@ version.workspace = true [dependencies] async-trait.workspace = true +ctor.workspace = true derive_builder = "0.20" once_cell.workspace = true parking_lot.workspace = true prost.workspace = true prost-types.workspace = true +tracing.workspace = true xmtp_common.workspace = true xmtp_proto = { workspace = true, features = ["convert"] } +# only for tests +xmtp_api_grpc = { workspace = true, optional = true, features = ["test-utils"] } +xmtp_api_http = { workspace = true, optional = true, features = ["test-utils"] } + +[dev-dependencies] +xmtp_proto = { workspace = true, features = ["convert", "test-utils"] } +xmtp_common = { workspace = true, features = ["test-utils"] } +wasm-bindgen-test.workspace = true +hex.workspace = true + [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -xmtp_api_grpc.workspace = true -xmtp_api_http.workspace = true tokio.workspace = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -xmtp_api_http.workspace = true +xmtp_api_http = { workspace = true, features = ["test-utils"] } tokio.workspace = true [features] -http-api = ["xmtp_proto/http-api"] -grpc-api = ["xmtp_proto/grpc-api"] +default = ["grpc-api"] +http-api = ["dep:xmtp_api_http", "xmtp_proto/http-api"] +grpc-api = ["dep:xmtp_api_grpc", "xmtp_proto/grpc-api"] +test-utils = [] diff --git a/xmtp_api_d14n/src/endpoints/d14n/get_inbox_ids.rs b/xmtp_api_d14n/src/endpoints/d14n/get_inbox_ids.rs index 7fe167df3..5fc0daf12 100644 --- a/xmtp_api_d14n/src/endpoints/d14n/get_inbox_ids.rs +++ b/xmtp_api_d14n/src/endpoints/d14n/get_inbox_ids.rs @@ -13,6 +13,8 @@ use xmtp_proto::xmtp::xmtpv4::message_api::{ pub struct GetInboxIds { #[builder(setter(into))] addresses: Vec, + #[builder(setter(into), default)] + passkeys: Vec, } impl GetInboxIds { @@ -33,14 +35,23 @@ impl Endpoint for GetInboxIds { } fn body(&self) -> Result, BodyError> { + let addresses = self + .addresses + .iter() + .cloned() + .map(|a| (a, IdentifierKind::Ethereum)); + let passkeys = self + .passkeys + .iter() + .cloned() + .map(|p| (p, IdentifierKind::Passkey)); + Ok(GetInboxIdsRequest { - requests: self - .addresses - .iter() - .cloned() - .map(|i| get_inbox_ids_request::Request { + requests: addresses + .chain(passkeys) + .map(|(i, kind)| get_inbox_ids_request::Request { identifier: i, - identifier_kind: IdentifierKind::Ethereum as i32, + identifier_kind: kind as i32, }) .collect(), } @@ -48,71 +59,30 @@ impl Endpoint for GetInboxIds { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; use crate::d14n::GetInboxIds; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::xmtpv4::message_api::GetInboxIdsResponse; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { - use xmtp_proto::xmtp::xmtpv4::message_api::{GetInboxIdsRequest, FILE_DESCRIPTOR_SET}; let pnq = crate::path_and_query::(FILE_DESCRIPTOR_SET); println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_get_inbox_ids() { - use crate::d14n::GetInboxIds; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local_d14n(); let client = client.build().await.unwrap(); let endpoint = GetInboxIds::builder() - .addresses(vec!["".to_string()]) + .addresses(vec![ + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string() + ]) .build() .unwrap(); - //todo: fix later when it was implemented - let result = endpoint.query(&client).await; - assert!(result.is_err()); - } - - #[cfg(feature = "http-api")] - #[tokio::test] - async fn test_get_inbox_ids_http() { - use xmtp_api_http::XmtpHttpApiClient; - use xmtp_api_http::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - - let mut client = XmtpHttpApiClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_libxmtp_version("0.0.0".into()).unwrap(); - client.set_tls(true); - client.set_host(LOCALHOST_ADDRESS.to_string()); - let client = client.build().await.unwrap(); - - let endpoint = GetInboxIds::builder() - .addresses(vec!["".to_string()]) - .build() - .unwrap(); - - let result: Result = endpoint.query(&client).await; - match result { - Ok(response) => { - assert_eq!(response.responses.len(), 0); - } - Err(err) => { - panic!("Test failed: {:?}", err); - } - } + assert!(endpoint.query(&client).await.is_ok()); } } diff --git a/xmtp_api_d14n/src/endpoints/d14n/publish_client_envelopes.rs b/xmtp_api_d14n/src/endpoints/d14n/publish_client_envelopes.rs index 90b40d612..8f30f39a0 100644 --- a/xmtp_api_d14n/src/endpoints/d14n/publish_client_envelopes.rs +++ b/xmtp_api_d14n/src/endpoints/d14n/publish_client_envelopes.rs @@ -24,7 +24,7 @@ impl PublishClientEnvelopes { impl Endpoint for PublishClientEnvelopes { type Output = PublishClientEnvelopesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - Cow::from("/mls/v2/publish-payer-envelopes") + Cow::from("/mls/v2/payer/publish-client-envelopes") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -39,8 +39,11 @@ impl Endpoint for PublishClientEnvelopes { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; + use xmtp_proto::prelude::*; + #[test] fn test_file_descriptor() { use xmtp_proto::xmtp::xmtpv4::payer_api::{ @@ -51,20 +54,11 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] - async fn test_get_inbox_ids() { - use crate::d14n::PublishClientEnvelopes; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; + async fn test_publish_client_envelopes() { use xmtp_proto::xmtp::xmtpv4::envelopes::ClientEnvelope; - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local_payer(); let client = client.build().await.unwrap(); let endpoint = PublishClientEnvelopes::builder() @@ -72,9 +66,6 @@ mod test { .build() .unwrap(); - // let result: PublishClientEnvelopesResponse = endpoint.query(&client).await.unwrap(); - // assert_eq!(result.originator_envelopes.len(), 0); - //todo: fix later when it was implemented let result = endpoint.query(&client).await; assert!(result.is_err()); } diff --git a/xmtp_api_d14n/src/endpoints/d14n/query_envelopes.rs b/xmtp_api_d14n/src/endpoints/d14n/query_envelopes.rs index afa29c6d6..75acc0ec6 100644 --- a/xmtp_api_d14n/src/endpoints/d14n/query_envelopes.rs +++ b/xmtp_api_d14n/src/endpoints/d14n/query_envelopes.rs @@ -51,7 +51,7 @@ impl Endpoint for QueryEnvelope { pub struct QueryEnvelopes { #[builder(setter(into))] envelopes: EnvelopesQuery, - #[builder(setter(into))] + #[builder(setter(into), default)] limit: u32, } @@ -65,7 +65,7 @@ impl Endpoint for QueryEnvelopes { type Output = QueryEnvelopesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v2/query-envelopes") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -81,8 +81,12 @@ impl Endpoint for QueryEnvelopes { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_proto::prelude::*; + #[test] fn test_file_descriptor() { use xmtp_proto::xmtp::xmtpv4::message_api::{QueryEnvelopesRequest, FILE_DESCRIPTOR_SET}; @@ -90,21 +94,11 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] - #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] - #[cfg(not(target_arch = "wasm32"))] - async fn test_get_inbox_ids() { + #[wasm_bindgen_test(unsupported = tokio::test)] + async fn test_query_envelopes() { use crate::d14n::QueryEnvelopes; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::xmtpv4::message_api::EnvelopesQuery; - - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + + let client = crate::TestClient::create_local_d14n(); let client = client.build().await.unwrap(); let endpoint = QueryEnvelopes::builder() @@ -113,14 +107,13 @@ mod test { originator_node_ids: vec![], last_seen: None, }) - .limit(0u32) .build() .unwrap(); - - // let result: QueryEnvelopesResponse = endpoint.query(&client).await.unwrap(); - // assert_eq!(result.envelopes.len(), 0); - //todo: fix later when it was implemented - let result = endpoint.query(&client).await; - assert!(result.is_err()); + if !cfg!(feature = "http-api") { + assert!(endpoint.query(&client).await.is_ok()); + // TODO: Investigate why fails with http topic + } else { + assert!(endpoint.query(&client).await.is_err()); + } } } diff --git a/xmtp_api_d14n/src/endpoints/v3/identity/get_identity_updates_v2.rs b/xmtp_api_d14n/src/endpoints/v3/identity/get_identity_updates_v2.rs index d6fc60261..50fadcadb 100644 --- a/xmtp_api_d14n/src/endpoints/v3/identity/get_identity_updates_v2.rs +++ b/xmtp_api_d14n/src/endpoints/v3/identity/get_identity_updates_v2.rs @@ -21,7 +21,7 @@ impl GetIdentityUpdatesV2 { impl Endpoint for GetIdentityUpdatesV2 { type Output = GetIdentityUpdatesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/identity/v1/get-identity-updates") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -36,32 +36,20 @@ impl Endpoint for GetIdentityUpdatesV2 { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { - use xmtp_proto::xmtp::identity::api::v1::{GetIdentityUpdatesRequest, FILE_DESCRIPTOR_SET}; let pnq = crate::path_and_query::(FILE_DESCRIPTOR_SET); println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_get_identity_updates_v2() { - use crate::v3::GetIdentityUpdatesV2; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::identity::api::v1::{ - get_identity_updates_request::Request, GetIdentityUpdatesResponse, - }; - - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = GetIdentityUpdatesV2::builder() .requests(vec![Request { @@ -72,6 +60,6 @@ mod test { .unwrap(); let result: GetIdentityUpdatesResponse = endpoint.query(&client).await.unwrap(); - assert_eq!(result.responses.len(), 0); + assert_eq!(result.responses.len(), 1); } } diff --git a/xmtp_api_d14n/src/endpoints/v3/identity/get_inbox_ids.rs b/xmtp_api_d14n/src/endpoints/v3/identity/get_inbox_ids.rs index 14d3e2546..cfd6dffeb 100644 --- a/xmtp_api_d14n/src/endpoints/v3/identity/get_inbox_ids.rs +++ b/xmtp_api_d14n/src/endpoints/v3/identity/get_inbox_ids.rs @@ -46,11 +46,10 @@ impl Endpoint for GetInboxIds { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { - use crate::v3::GetInboxIds; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::identity::api::v1::GetInboxIdsResponse; + use super::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -60,59 +59,18 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] - #[ignore] async fn test_get_inbox_ids() { - use crate::v3::identity::GetInboxIds; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::identity::api::v1::GetInboxIdsResponse; - - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); - let endpoint = GetInboxIds::builder() - .addresses(vec!["".to_string()]) + .addresses(vec![ + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string() + ]) .build() .unwrap(); - let result: GetInboxIdsResponse = endpoint.query(&client).await.unwrap(); - assert_eq!(result.responses.len(), 0); - } - - #[cfg(feature = "http-api")] - #[tokio::test] - async fn test_get_inbox_ids_http() { - use xmtp_api_http::XmtpHttpApiClient; - use xmtp_api_http::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - - let mut client = XmtpHttpApiClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_libxmtp_version("0.0.0".into()).unwrap(); - client.set_tls(true); - client.set_host(LOCALHOST_ADDRESS.to_string()); - let client = client.build().await.unwrap(); - - let endpoint = GetInboxIds::builder() - .addresses(vec!["".to_string()]) - .build() - .unwrap(); - - let result: Result = endpoint.query(&client).await; - match result { - Ok(response) => { - assert_eq!(response.responses.len(), 1); - } - Err(err) => { - panic!("Test failed: {:?}", err); - } - } + let result = endpoint.query(&client).await.unwrap(); + assert_eq!(result.responses.len(), 1); } } diff --git a/xmtp_api_d14n/src/endpoints/v3/identity/publish_identity_update.rs b/xmtp_api_d14n/src/endpoints/v3/identity/publish_identity_update.rs index eaf149e06..2928e01c3 100644 --- a/xmtp_api_d14n/src/endpoints/v3/identity/publish_identity_update.rs +++ b/xmtp_api_d14n/src/endpoints/v3/identity/publish_identity_update.rs @@ -23,7 +23,7 @@ impl PublishIdentityUpdate { impl Endpoint for PublishIdentityUpdate { type Output = PublishIdentityUpdateResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/identity/v1/publish-identity-update") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -38,8 +38,10 @@ impl Endpoint for PublishIdentityUpdate { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -50,22 +52,12 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_publish_identity_update() { - use crate::v3::PublishIdentityUpdate; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::{GrpcError, LOCALHOST_ADDRESS}; use xmtp_common::time::now_ns; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::identity::api::v1::PublishIdentityUpdateResponse; - use xmtp_proto::xmtp::identity::associations::IdentityUpdate; - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = PublishIdentityUpdate::builder() .identity_update(IdentityUpdate { @@ -76,7 +68,6 @@ mod test { .build() .unwrap(); - let _: Result> = - endpoint.query(&client).await; + let _: Result = endpoint.query(&client).await; } } diff --git a/xmtp_api_d14n/src/endpoints/v3/identity/verify_smart_contract_wallet_signatures.rs b/xmtp_api_d14n/src/endpoints/v3/identity/verify_smart_contract_wallet_signatures.rs index b21e1788d..6f07bc10f 100644 --- a/xmtp_api_d14n/src/endpoints/v3/identity/verify_smart_contract_wallet_signatures.rs +++ b/xmtp_api_d14n/src/endpoints/v3/identity/verify_smart_contract_wallet_signatures.rs @@ -23,7 +23,7 @@ impl VerifySmartContractWalletSignatures { impl Endpoint for VerifySmartContractWalletSignatures { type Output = VerifySmartContractWalletSignaturesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/identity/v1/verify-smart-contract-wallet-signatures") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -38,36 +38,22 @@ impl Endpoint for VerifySmartContractWalletSignatures { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { - #[cfg(feature = "grpc-api")] + use super::*; + use xmtp_proto::prelude::*; + #[test] fn test_file_descriptor() { - use xmtp_proto::xmtp::identity::api::v1::{ - VerifySmartContractWalletSignaturesRequest, FILE_DESCRIPTOR_SET, - }; let pnq = crate::path_and_query::( FILE_DESCRIPTOR_SET, ); println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_verify_smart_contract_wallet_signatures() { - use crate::v3::VerifySmartContractWalletSignatures; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::identity::api::v1::{ - VerifySmartContractWalletSignatureRequestSignature, - VerifySmartContractWalletSignaturesResponse, - }; - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = VerifySmartContractWalletSignatures::builder() .signatures(vec![VerifySmartContractWalletSignatureRequestSignature { @@ -81,6 +67,6 @@ mod test { let result: VerifySmartContractWalletSignaturesResponse = endpoint.query(&client).await.unwrap(); - assert_eq!(result.responses.len(), 0); + assert_eq!(result.responses.len(), 1); } } diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/fetch_key_packages.rs b/xmtp_api_d14n/src/endpoints/v3/mls/fetch_key_packages.rs index 0dd81f221..6d3c30c76 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/fetch_key_packages.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/fetch_key_packages.rs @@ -21,7 +21,7 @@ impl FetchKeyPackages { impl Endpoint for FetchKeyPackages { type Output = FetchKeyPackagesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/fetch-key-packages") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -36,8 +36,10 @@ impl Endpoint for FetchKeyPackages { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { + use super::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -46,27 +48,21 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_fetch_key_packages() { - use crate::v3::FetchKeyPackages; - use xmtp_api_grpc::{grpc_client::GrpcClient, LOCALHOST_ADDRESS}; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::mls::api::v1::FetchKeyPackagesResponse; - - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); - let endpoint = FetchKeyPackages::builder() .installation_keys(vec![vec![1, 2, 3]]) .build() .unwrap(); let result: FetchKeyPackagesResponse = endpoint.query(&client).await.unwrap(); - assert_eq!(result.key_packages, vec![]); + assert_eq!( + result, + FetchKeyPackagesResponse { + key_packages: vec![Default::default()] + } + ); } } diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/query_group_messages.rs b/xmtp_api_d14n/src/endpoints/v3/mls/query_group_messages.rs index 87d897fa5..10a2b73a6 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/query_group_messages.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/query_group_messages.rs @@ -25,7 +25,7 @@ impl QueryGroupMessages { impl Endpoint for QueryGroupMessages { type Output = QueryGroupMessagesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/query-group-messages") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -41,16 +41,11 @@ impl Endpoint for QueryGroupMessages { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { use crate::v3::QueryGroupMessages; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::mls::api::v1::{ - QueryGroupMessagesRequest, QueryGroupMessagesResponse, FILE_DESCRIPTOR_SET, - }; + use xmtp_proto::mls::api::v1::prelude::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -58,13 +53,9 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_get_identity_updates_v2() { - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = QueryGroupMessages::builder() .group_id(vec![1, 2, 3]) diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/query_welcome_messages.rs b/xmtp_api_d14n/src/endpoints/v3/mls/query_welcome_messages.rs index ad870b7fa..2f88b0b33 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/query_welcome_messages.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/query_welcome_messages.rs @@ -26,7 +26,7 @@ impl Endpoint for QueryWelcomeMessages { type Output = QueryWelcomeMessagesResponse; fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/query-welcome-messages") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -42,13 +42,10 @@ impl Endpoint for QueryWelcomeMessages { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { use crate::v3::QueryWelcomeMessages; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; + use xmtp_proto::prelude::*; use xmtp_proto::xmtp::mls::api::v1::{ QueryWelcomeMessagesRequest, QueryWelcomeMessagesResponse, FILE_DESCRIPTOR_SET, }; @@ -59,13 +56,9 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_get_identity_updates_v2() { - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = QueryWelcomeMessages::builder() .installation_key(vec![1, 2, 3]) diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/send_group_messages.rs b/xmtp_api_d14n/src/endpoints/v3/mls/send_group_messages.rs index 307e6af41..b4fd1a5a6 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/send_group_messages.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/send_group_messages.rs @@ -21,7 +21,7 @@ impl SendGroupMessages { impl Endpoint for SendGroupMessages { type Output = (); fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/send-group-messages") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -36,16 +36,11 @@ impl Endpoint for SendGroupMessages { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { use crate::v3::SendGroupMessages; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::mls::api::v1::{ - GroupMessageInput, SendGroupMessagesRequest, FILE_DESCRIPTOR_SET, - }; + use xmtp_proto::mls::api::v1::prelude::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -53,20 +48,15 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] - async fn test_get_identity_updates_v2() { - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + async fn test_send_group_messages() { + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = SendGroupMessages::builder() .messages(vec![GroupMessageInput::default()]) .build() .unwrap(); - //todo: fix later with better data samples let result = endpoint.query(&client).await; assert!(result.is_err()); } diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/send_welcome_messages.rs b/xmtp_api_d14n/src/endpoints/v3/mls/send_welcome_messages.rs index 0618a3324..27a00a787 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/send_welcome_messages.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/send_welcome_messages.rs @@ -21,7 +21,7 @@ impl SendWelcomeMessages { impl Endpoint for SendWelcomeMessages { type Output = (); fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/send-welcome-messages") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -36,13 +36,10 @@ impl Endpoint for SendWelcomeMessages { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { use crate::v3::SendWelcomeMessages; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; + use xmtp_proto::prelude::*; use xmtp_proto::xmtp::mls::api::v1::{ welcome_message_input, SendWelcomeMessagesRequest, WelcomeMessageInput, FILE_DESCRIPTOR_SET, }; @@ -53,24 +50,19 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] - async fn test_get_identity_updates_v2() { + async fn test_send_welcome_messages() { let welcome_message = WelcomeMessageInput { version: Some(welcome_message_input::Version::V1(Default::default())), }; - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = SendWelcomeMessages::builder() .messages(vec![welcome_message]) .build() .unwrap(); - //todo: fix later when it was implemented let result = endpoint.query(&client).await; - assert!(result.is_err()); + assert!(result.is_err()) } } diff --git a/xmtp_api_d14n/src/endpoints/v3/mls/upload_key_package.rs b/xmtp_api_d14n/src/endpoints/v3/mls/upload_key_package.rs index 2d4fe86e1..f13ea091c 100644 --- a/xmtp_api_d14n/src/endpoints/v3/mls/upload_key_package.rs +++ b/xmtp_api_d14n/src/endpoints/v3/mls/upload_key_package.rs @@ -24,7 +24,7 @@ impl UploadKeyPackage { impl Endpoint for UploadKeyPackage { type Output = (); fn http_endpoint(&self) -> Cow<'static, str> { - todo!() + Cow::Borrowed("/mls/v1/upload-key-package") } fn grpc_endpoint(&self) -> Cow<'static, str> { @@ -40,16 +40,11 @@ impl Endpoint for UploadKeyPackage { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod test { use crate::v3::UploadKeyPackage; - use xmtp_api_grpc::grpc_client::GrpcClient; - use xmtp_api_grpc::LOCALHOST_ADDRESS; - use xmtp_proto::api_client::ApiBuilder; - use xmtp_proto::traits::Query; - use xmtp_proto::xmtp::mls::api::v1::{ - KeyPackageUpload, UploadKeyPackageRequest, FILE_DESCRIPTOR_SET, - }; + use xmtp_proto::mls::api::v1::prelude::*; + use xmtp_proto::prelude::*; #[test] fn test_file_descriptor() { @@ -57,13 +52,9 @@ mod test { println!("{}", pnq); } - #[cfg(feature = "grpc-api")] #[tokio::test] async fn test_get_identity_updates_v2() { - let mut client = GrpcClient::builder(); - client.set_app_version("0.0.0".into()).unwrap(); - client.set_tls(false); - client.set_host(LOCALHOST_ADDRESS.to_string()); + let client = crate::TestClient::create_local(); let client = client.build().await.unwrap(); let endpoint = UploadKeyPackage::builder() .key_package(KeyPackageUpload { diff --git a/xmtp_api_d14n/src/lib.rs b/xmtp_api_d14n/src/lib.rs index 86f5e8910..dbd7764f1 100644 --- a/xmtp_api_d14n/src/lib.rs +++ b/xmtp_api_d14n/src/lib.rs @@ -4,4 +4,30 @@ pub use endpoints::*; mod proto_cache; pub(crate) use proto_cache::*; -pub mod compat; +// pub mod compat; + +#[allow(unused)] +#[macro_use] +extern crate tracing; + +#[cfg(any(test, feature = "test-utils"))] +pub use tests::*; +#[cfg(any(test, feature = "test-utils"))] +pub mod tests { + // #[cfg(any(not(feature = "grpc-api"), not(feature = "http-api")))] + // pub type TestClient = xmtp_proto::traits::mock::MockClient; + + #[cfg(all(feature = "grpc-api", not(feature = "http-api")))] + pub type TestClient = xmtp_api_grpc::grpc_client::GrpcClient; + + #[cfg(all(feature = "http-api"))] + pub type TestClient = xmtp_api_http::XmtpHttpApiClient; + + // Execute once before any tests are run + #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(test)] + fn _setup() { + xmtp_common::logger(); + } +} diff --git a/xmtp_api_d14n/src/proto_cache.rs b/xmtp_api_d14n/src/proto_cache.rs index 901461d77..b46be134f 100644 --- a/xmtp_api_d14n/src/proto_cache.rs +++ b/xmtp_api_d14n/src/proto_cache.rs @@ -13,11 +13,16 @@ pub static PROTO_CACHE: Lazy> = Lazy::new(|| RwLock::new(HashMap:: // TODO: Create proc macro to get FILE_DESCRIPTOR from rust path of `Type` to remove // file_descriptor arg +// maybe by using eval macro? https://docs.rs/eval-macro/latest/eval_macro/ +// just need to collect the file descriptor set and created a static lookup table pub fn path_and_query(file_descriptor: &'static [u8]) -> Cow<'static, str> { let pnq = |service: &ServiceDescriptorProto, method: &MethodDescriptorProto| -> String { String::new() + "/" + Type::PACKAGE + "." + service.name() + "/" + method.name() }; - if let Some((service, method)) = crate::PROTO_CACHE.read().get(Type::NAME) { + if let Some((service, method)) = crate::PROTO_CACHE + .read() + .get((Type::PACKAGE.to_owned() + "." + Type::NAME).as_str()) + { return Cow::Owned(pnq(service, method)); } diff --git a/xmtp_api_grpc/src/grpc_api_helper.rs b/xmtp_api_grpc/src/grpc_api_helper.rs index a6841885e..fc5ab44f2 100644 --- a/xmtp_api_grpc/src/grpc_api_helper.rs +++ b/xmtp_api_grpc/src/grpc_api_helper.rs @@ -536,3 +536,40 @@ impl XmtpMlsStreams for Client { Ok(stream.into()) } } + +#[cfg(any(test, feature = "test-utils"))] +mod test { + use super::*; + use xmtp_proto::api_client::XmtpTestClient; + + impl XmtpTestClient for Client { + type Builder = ClientBuilder; + fn create_local() -> Self::Builder { + let mut client = Client::builder(); + client.set_host("http://localhost:5556".into()); + client.set_tls(false); + client + } + + fn create_local_d14n() -> Self::Builder { + let mut client = Client::builder(); + client.set_host("http://localhost:5050".into()); + client.set_tls(false); + client + } + + fn create_local_payer() -> Self::Builder { + let mut client = Client::builder(); + client.set_host("http://localhost:5050".into()); + client.set_tls(false); + client + } + + fn create_dev() -> Self::Builder { + let mut client = Client::builder(); + client.set_host("https://grpc.dev.xmtp.network:443".into()); + client.set_tls(true); + client + } + } +} diff --git a/xmtp_api_grpc/src/grpc_client.rs b/xmtp_api_grpc/src/grpc_client.rs index 191b31eae..90e57925c 100644 --- a/xmtp_api_grpc/src/grpc_client.rs +++ b/xmtp_api_grpc/src/grpc_client.rs @@ -31,11 +31,16 @@ impl Client for GrpcClient { type Error = crate::GrpcError; type Stream = tonic::Streaming; - async fn request( + async fn request( &self, request: http::request::Builder, + uri: http::uri::Builder, body: Vec, - ) -> Result, ApiError> { + ) -> Result, ApiError> + where + Self: Sized, + T: Default + prost::Message + 'static, + { let client = &mut self.inner.clone(); client .ready() @@ -50,14 +55,18 @@ impl Client for GrpcClient { let request = request.body(body)?; let (parts, body) = request.into_parts(); - //TODO: HANDLE - let path = parts.uri.into_parts().path_and_query.expect("must exist"); + let path = uri + .build()? + .into_parts() + .path_and_query + .expect("must exist"); let mut tonic_request = tonic::Request::from_parts( MetadataMap::from_headers(parts.headers), parts.extensions, body, ); let metadata = tonic_request.metadata_mut(); + // must be lowercase otherwise panics metadata.append("x-app-version", self.app_version.clone()); metadata.append("x-libxmtp-version", self.libxmtp_version.clone()); @@ -197,3 +206,40 @@ pub async fn create_tls_channel(address: String) -> Result Self::Builder { + let mut client = GrpcClient::builder(); + client.set_host("http://localhost:5556".into()); + client.set_tls(false); + client + } + + fn create_local_d14n() -> Self::Builder { + let mut client = GrpcClient::builder(); + client.set_host("http://localhost:5050".into()); + client.set_tls(false); + client + } + + fn create_local_payer() -> Self::Builder { + let mut client = GrpcClient::builder(); + client.set_host("http://localhost:5050".into()); + client.set_tls(false); + client + } + + fn create_dev() -> Self::Builder { + let mut client = GrpcClient::builder(); + client.set_host("https://grpc.dev.xmtp.network:443".into()); + client.set_tls(true); + client + } + } +} diff --git a/xmtp_api_grpc/src/lib.rs b/xmtp_api_grpc/src/lib.rs index 21fe51984..336a2da50 100644 --- a/xmtp_api_grpc/src/lib.rs +++ b/xmtp_api_grpc/src/lib.rs @@ -2,7 +2,6 @@ pub mod auth_token; pub mod grpc_api_helper; pub mod grpc_client; mod identity; -pub mod replication_client; pub const LOCALHOST_ADDRESS: &str = "http://localhost:5556"; pub const DEV_ADDRESS: &str = "https://grpc.dev.xmtp.network:443"; @@ -145,27 +144,6 @@ impl xmtp_proto::XmtpApiError for GrpcError { } } } -mod utils { - #[cfg(feature = "test-utils")] - mod test { - use xmtp_proto::api_client::XmtpTestClient; - - #[async_trait::async_trait] - impl XmtpTestClient for crate::Client { - async fn create_local() -> Self { - crate::Client::create("http://localhost:5556", false) - .await - .unwrap() - } - - async fn create_dev() -> Self { - crate::Client::create("https://grpc.dev.xmtp.network:443", true) - .await - .unwrap() - } - } - } -} #[cfg(test)] pub mod tests { diff --git a/xmtp_api_grpc/src/replication_client.rs b/xmtp_api_grpc/src/replication_client.rs deleted file mode 100644 index 578b40131..000000000 --- a/xmtp_api_grpc/src/replication_client.rs +++ /dev/null @@ -1,606 +0,0 @@ -#![allow(unused)] -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -// TODO switch to async mutexes -use std::time::Duration; - -use futures::stream::{AbortHandle, Abortable}; -use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; -use prost::Message; -use tokio::sync::oneshot; -use tonic::transport::ClientTlsConfig; -use tonic::{metadata::MetadataValue, transport::Channel, Request, Streaming}; - -#[cfg(any(feature = "test-utils", test))] -use xmtp_proto::api_client::XmtpTestClient; -use xmtp_proto::api_client::{ApiBuilder, XmtpIdentityClient, XmtpMlsStreams}; - -use crate::{ - grpc_api_helper::{create_tls_channel, GrpcMutableSubscription, Subscription}, - Error, -}; -use crate::{GroupMessageStream, WelcomeMessageStream}; -use crate::{GrpcBuilderError, GrpcError}; -use xmtp_proto::v4_utils::{ - build_group_message_topic, build_identity_topic_from_hex_encoded, build_identity_update_topic, - build_key_package_topic, build_welcome_message_topic, extract_client_envelope, - extract_unsigned_originator_envelope, -}; -use xmtp_proto::xmtp::identity::api::v1::get_identity_updates_response; -use xmtp_proto::xmtp::identity::api::v1::get_identity_updates_response::IdentityUpdateLog; -use xmtp_proto::xmtp::mls::api::v1::{ - fetch_key_packages_response, group_message, group_message_input, welcome_message, - welcome_message_input, GroupMessage, WelcomeMessage, -}; -use xmtp_proto::xmtp::xmtpv4::envelopes::client_envelope::Payload; -use xmtp_proto::xmtp::xmtpv4::envelopes::{ - ClientEnvelope, OriginatorEnvelope, PayerEnvelope, UnsignedOriginatorEnvelope, -}; -use xmtp_proto::xmtp::xmtpv4::message_api::replication_api_client::ReplicationApiClient; -use xmtp_proto::xmtp::xmtpv4::message_api::{ - EnvelopesQuery, PublishPayerEnvelopesRequest, QueryEnvelopesRequest, -}; -use xmtp_proto::xmtp::xmtpv4::payer_api::payer_api_client::PayerApiClient; -use xmtp_proto::xmtp::xmtpv4::payer_api::PublishClientEnvelopesRequest; -use xmtp_proto::{ - api_client::{MutableApiSubscription, XmtpApiClient, XmtpApiSubscription, XmtpMlsClient}, - xmtp::identity::api::v1::{ - get_inbox_ids_response, GetIdentityUpdatesRequest as GetIdentityUpdatesV2Request, - GetIdentityUpdatesResponse as GetIdentityUpdatesV2Response, GetInboxIdsRequest, - GetInboxIdsResponse, PublishIdentityUpdateRequest, PublishIdentityUpdateResponse, - VerifySmartContractWalletSignaturesRequest, VerifySmartContractWalletSignaturesResponse, - }, - xmtp::message_api::v1::{ - BatchQueryRequest, BatchQueryResponse, Envelope, PublishRequest, PublishResponse, - QueryRequest, QueryResponse, SubscribeRequest, - }, - xmtp::mls::api::v1::{ - FetchKeyPackagesRequest, FetchKeyPackagesResponse, QueryGroupMessagesRequest, - QueryGroupMessagesResponse, QueryWelcomeMessagesRequest, QueryWelcomeMessagesResponse, - SendGroupMessagesRequest, SendWelcomeMessagesRequest, SubscribeGroupMessagesRequest, - SubscribeWelcomeMessagesRequest, UploadKeyPackageRequest, - }, - xmtp::xmtpv4::message_api::{ - get_inbox_ids_request, GetInboxIdsRequest as GetInboxIdsRequestV4, - }, - ApiEndpoint, -}; - -#[derive(Debug, Clone)] -pub struct ClientV4 { - pub(crate) client: ReplicationApiClient, - pub(crate) payer_client: PayerApiClient, - pub(crate) app_version: MetadataValue, - pub(crate) libxmtp_version: MetadataValue, -} - -impl ClientV4 { - pub async fn create( - grpc_url: String, - payer_url: String, - is_secure: bool, - ) -> Result { - let app_version = MetadataValue::try_from(&String::from("0.0.0"))?; - let libxmtp_version = MetadataValue::try_from(&String::from("0.0.0"))?; - - let grpc_channel = match is_secure { - true => create_tls_channel(grpc_url).await?, - false => Channel::from_shared(grpc_url)?.connect().await?, - }; - - let payer_channel = match is_secure { - true => create_tls_channel(payer_url).await?, - false => Channel::from_shared(payer_url)?.connect().await?, - }; - - // GroupMessageInputTODO(mkysel) for now we assume both payer and replication are on the same host - let client = ReplicationApiClient::new(grpc_channel.clone()); - let payer_client = PayerApiClient::new(payer_channel.clone()); - - Ok(Self { - client, - payer_client, - app_version, - libxmtp_version, - }) - } - - pub fn build_request(&self, request: RequestType) -> Request { - let mut req = Request::new(request); - req.metadata_mut() - .insert("x-app-version", self.app_version.clone()); - req.metadata_mut() - .insert("x-libxmtp-version", self.libxmtp_version.clone()); - - req - } -} - -#[derive(Default)] -pub struct ClientBuilder { - /// libxmtp backend host url - host: Option, - /// payer url - payer: Option, - /// version of the app - app_version: Option>, - /// Version of the libxmtp core library - libxmtp_version: Option>, - /// Whether or not the channel should use TLS - tls_channel: bool, -} - -impl ApiBuilder for ClientBuilder { - type Output = ClientV4; - type Error = crate::GrpcBuilderError; - - fn set_libxmtp_version(&mut self, version: String) -> Result<(), Self::Error> { - self.libxmtp_version = Some(MetadataValue::try_from(&version)?); - Ok(()) - } - - fn set_app_version(&mut self, version: String) -> Result<(), Self::Error> { - self.app_version = Some(MetadataValue::try_from(&version)?); - Ok(()) - } - - fn set_tls(&mut self, tls: bool) { - self.tls_channel = tls; - } - - fn set_host(&mut self, host: String) { - self.host = Some(host); - } - - fn set_payer(&mut self, payer: String) { - self.payer = Some(payer); - } - - async fn build(self) -> Result { - let host = self.host.ok_or(GrpcBuilderError::MissingHostUrl)?; - let payer = self.payer.ok_or(GrpcBuilderError::MissingPayerUrl)?; - let grpc_channel = match self.tls_channel { - true => create_tls_channel(host).await?, - false => Channel::from_shared(host)?.connect().await?, - }; - - let payer_channel = match self.tls_channel { - true => create_tls_channel(payer).await?, - false => Channel::from_shared(payer)?.connect().await?, - }; - let client = ReplicationApiClient::new(grpc_channel.clone()); - let payer_client = PayerApiClient::new(payer_channel.clone()); - - Ok(ClientV4 { - client, - payer_client, - app_version: self - .app_version - .unwrap_or(MetadataValue::try_from("0.0.0")?), - libxmtp_version: self - .libxmtp_version - .ok_or(crate::GrpcBuilderError::MissingLibxmtpVersion)?, - }) - } -} - -#[async_trait::async_trait] -impl XmtpApiClient for ClientV4 { - type Error = crate::Error; - type Subscription = Subscription; - type MutableSubscription = GrpcMutableSubscription; - - async fn publish( - &self, - token: String, - request: PublishRequest, - ) -> Result { - unimplemented!(); - } - - async fn subscribe(&self, request: SubscribeRequest) -> Result { - unimplemented!(); - } - - async fn subscribe2( - &self, - request: SubscribeRequest, - ) -> Result { - unimplemented!(); - } - - async fn query(&self, request: QueryRequest) -> Result { - unimplemented!(); - } - - async fn batch_query( - &self, - request: BatchQueryRequest, - ) -> Result { - unimplemented!(); - } -} - -#[async_trait::async_trait] -impl XmtpMlsClient for ClientV4 { - type Error = crate::Error; - - #[tracing::instrument(level = "trace", skip_all)] - async fn upload_key_package(&self, req: UploadKeyPackageRequest) -> Result<(), Self::Error> { - self.publish_envelopes_to_payer(std::iter::once(req)) - .await - .map_err(Error::from) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn fetch_key_packages( - &self, - req: FetchKeyPackagesRequest, - ) -> Result { - let topics = req - .installation_keys - .iter() - .map(|key| build_key_package_topic(key.as_slice())) - .collect(); - - let envelopes = self.query_v4_envelopes(topics, 0).await?; - let key_packages: Result, _> = envelopes - .iter() - .map(|envelopes| { - // The last envelope should be the newest key package upload - let unsigned = envelopes - .last() - .ok_or_else(|| GrpcError::NotFound("envelopes".into()))?; - - let client_env = extract_client_envelope(unsigned)?; - - if let Some(Payload::UploadKeyPackage(upload_key_package)) = client_env.payload { - let key_package = upload_key_package - .key_package - .ok_or_else(|| GrpcError::NotFound("key package".into()))?; - - Ok(fetch_key_packages_response::KeyPackage { - key_package_tls_serialized: key_package.key_package_tls_serialized, - }) - } else { - Err(GrpcError::UnexpectedPayload) - } - }) - .collect(); - - Ok(FetchKeyPackagesResponse { - key_packages: key_packages?, - }) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn send_group_messages(&self, req: SendGroupMessagesRequest) -> Result<(), Self::Error> { - self.publish_envelopes_to_payer(req.messages) - .await - .map_err(Error::from) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn send_welcome_messages( - &self, - req: SendWelcomeMessagesRequest, - ) -> Result<(), Self::Error> { - self.publish_envelopes_to_payer(req.messages) - .await - .map_err(Error::from) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn query_group_messages( - &self, - req: QueryGroupMessagesRequest, - ) -> Result { - let client = &mut self.client.clone(); - let res = client - .query_envelopes(QueryEnvelopesRequest { - query: Some(EnvelopesQuery { - topics: vec![build_group_message_topic(req.group_id.as_slice())], - originator_node_ids: vec![], - last_seen: None, - }), - limit: req.paging_info.map_or(0, |paging| paging.limit), - }) - .await - .map_err(GrpcError::from)?; - - let envelopes = res.into_inner().envelopes; - let response = QueryGroupMessagesResponse { - messages: envelopes - .iter() - .map(|envelope| { - let unsigned_originator_envelope = - extract_unsigned_originator_envelope(envelope)?; - let client_envelope = extract_client_envelope(envelope)?; - let payload = client_envelope - .payload - .ok_or_else(|| GrpcError::MissingPayload)?; - let Payload::GroupMessage(group_message) = payload else { - return Err(GrpcError::MissingPayload); - }; - - let group_message_input::Version::V1(v1_group_message) = group_message - .version - .ok_or_else(|| GrpcError::MissingPayload)?; - - Ok(GroupMessage { - version: Some(group_message::Version::V1(group_message::V1 { - id: unsigned_originator_envelope.originator_sequence_id, - created_ns: unsigned_originator_envelope.originator_ns as u64, - group_id: req.group_id.clone(), - data: v1_group_message.data, - sender_hmac: v1_group_message.sender_hmac, - should_push: v1_group_message.should_push, - })), - }) - }) - .collect::, _>>()?, - paging_info: None, - }; - Ok(response) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn query_welcome_messages( - &self, - req: QueryWelcomeMessagesRequest, - ) -> Result { - let client = &mut self.client.clone(); - let res = client - .query_envelopes(QueryEnvelopesRequest { - query: Some(EnvelopesQuery { - topics: vec![build_welcome_message_topic(req.installation_key.as_slice())], - originator_node_ids: vec![], - last_seen: None, - }), - limit: req.paging_info.map_or(0, |paging| paging.limit), - }) - .await - .map_err(GrpcError::from)?; - - let envelopes = res.into_inner().envelopes; - let response = QueryWelcomeMessagesResponse { - messages: envelopes - .iter() - .map(|envelope| { - let unsigned_originator_envelope = - extract_unsigned_originator_envelope(envelope)?; - let client_envelope = extract_client_envelope(envelope)?; - let payload = client_envelope - .payload - .ok_or_else(|| GrpcError::MissingPayload)?; - let Payload::WelcomeMessage(welcome_message) = payload else { - return Err(GrpcError::MissingPayload); - }; - let welcome_message_input::Version::V1(v1_welcome_message) = welcome_message - .version - .ok_or_else(|| GrpcError::MissingPayload)?; - - Ok(WelcomeMessage { - version: Some(welcome_message::Version::V1(welcome_message::V1 { - id: unsigned_originator_envelope.originator_sequence_id, - created_ns: unsigned_originator_envelope.originator_ns as u64, - installation_key: req.installation_key.clone(), - data: v1_welcome_message.data, - hpke_public_key: v1_welcome_message.hpke_public_key, - })), - }) - }) - .collect::, _>>()?, - paging_info: None, - }; - Ok(response) - } -} - -#[async_trait::async_trait] -impl XmtpMlsStreams for ClientV4 { - type Error = crate::Error; - type GroupMessageStream<'a> = GroupMessageStream; - type WelcomeMessageStream<'a> = WelcomeMessageStream; - - async fn subscribe_group_messages( - &self, - req: SubscribeGroupMessagesRequest, - ) -> Result, Self::Error> { - unimplemented!(); - } - - async fn subscribe_welcome_messages( - &self, - req: SubscribeWelcomeMessagesRequest, - ) -> Result, Self::Error> { - unimplemented!(); - } -} - -#[async_trait::async_trait] -impl XmtpIdentityClient for ClientV4 { - type Error = crate::Error; - #[tracing::instrument(level = "trace", skip_all)] - async fn publish_identity_update( - &self, - request: PublishIdentityUpdateRequest, - ) -> Result { - self.publish_envelopes_to_payer(vec![request]).await?; - Ok(PublishIdentityUpdateResponse {}) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn get_inbox_ids( - &self, - request: GetInboxIdsRequest, - ) -> Result { - let client = &mut self.client.clone(); - let req = GetInboxIdsRequestV4 { - requests: request - .requests - .into_iter() - .map(|r| get_inbox_ids_request::Request { - identifier: r.identifier, - identifier_kind: r.identifier_kind, - }) - .collect(), - }; - - let res = client.get_inbox_ids(self.build_request(req)).await; - - res.map(|response| response.into_inner()) - .map(|response| GetInboxIdsResponse { - responses: response - .responses - .into_iter() - .map(|r| get_inbox_ids_response::Response { - identifier: r.identifier, - identifier_kind: r.identifier_kind, - inbox_id: r.inbox_id, - }) - .collect(), - }) - .map_err(GrpcError::from) - .map_err(Error::from) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn get_identity_updates_v2( - &self, - request: GetIdentityUpdatesV2Request, - ) -> Result { - let topics = request - .requests - .iter() - .map(|r| build_identity_topic_from_hex_encoded(&r.inbox_id.clone())) - .collect::, _>>() - .map_err(GrpcError::from)?; - let v4_envelopes = self.query_v4_envelopes(topics, 0).await?; - let joined_data = v4_envelopes - .into_iter() - .zip(request.requests.into_iter()) - .collect::>(); - let responses = joined_data - .iter() - .map(|(envelopes, inner_req)| { - let identity_updates = envelopes - .iter() - .map(convert_v4_envelope_to_identity_update) - .collect::, _>>()?; - - Ok(get_identity_updates_response::Response { - inbox_id: inner_req.inbox_id.clone(), - updates: identity_updates, - }) - }) - .collect::, GrpcError>>()?; - - Ok(GetIdentityUpdatesV2Response { responses }) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn verify_smart_contract_wallet_signatures( - &self, - request: VerifySmartContractWalletSignaturesRequest, - ) -> Result { - unimplemented!() - } -} - -#[cfg(any(feature = "test-utils", test))] -#[async_trait::async_trait] -impl XmtpTestClient for ClientV4 { - async fn create_local() -> Self { - todo!() - } - - async fn create_dev() -> Self { - todo!() - } -} -impl ClientV4 { - #[tracing::instrument(level = "trace", skip_all)] - async fn query_v4_envelopes( - &self, - topics: Vec>, - limit: u32, - ) -> Result>, crate::GrpcError> { - let requests = topics.iter().map(|topic| async { - let client = &mut self.client.clone(); - let v4_envelopes = client - .query_envelopes(QueryEnvelopesRequest { - query: Some(EnvelopesQuery { - topics: vec![topic.clone()], - originator_node_ids: vec![], - last_seen: None, - }), - limit, - }) - .await?; - - Ok(v4_envelopes.into_inner().envelopes) - }); - - futures::future::try_join_all(requests).await - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn publish_envelopes_to_payer( - &self, - messages: impl IntoIterator, - ) -> Result<(), crate::GrpcError> - where - T: TryInto, - >::Error: std::error::Error + Send + Sync + 'static, - GrpcError: From<>::Error>, - { - let client = &mut self.payer_client.clone(); - - let envelopes: Vec = messages - .into_iter() - .map(|message| message.try_into().map_err(GrpcError::from)) - .collect::>()?; - - client - .publish_client_envelopes(PublishClientEnvelopesRequest { envelopes }) - .await?; - - Ok(()) - } -} - -pub fn convert_v4_envelope_to_identity_update( - _envelope: &OriginatorEnvelope, -) -> Result { - // temporary block until this function is updated to handle payer_envelope_bytes - Err(crate::GrpcError::Unreachable) - - //let mut unsigned_originator_envelope = envelope.unsigned_originator_envelope.as_slice(); - //let originator_envelope = - // UnsignedOriginatorEnvelope::decode(&mut unsigned_originator_envelope)?; - - //let payer_envelope = originator_envelope - // .payer_envelope - // .ok_or(GrpcError::NotFound("payer envelope".into()))?; - - //// TODO: validate payer signatures - //let mut unsigned_client_envelope = payer_envelope.unsigned_client_envelope.as_slice(); - - //let client_envelope = ClientEnvelope::decode(&mut unsigned_client_envelope)?; - //let payload = client_envelope - // .payload - // .ok_or(GrpcError::NotFound("payload".into()))?; - - //let identity_update = match payload { - // Payload::IdentityUpdate(update) => update, - // _ => return Err(GrpcError::UnexpectedPayload), - //}; - - //Ok(IdentityUpdateLog { - // sequence_id: originator_envelope.originator_sequence_id, - // server_timestamp_ns: originator_envelope.originator_ns as u64, - // update: Some(identity_update), - //}) -} diff --git a/xmtp_api_http/Cargo.toml b/xmtp_api_http/Cargo.toml index 9aea7db4e..cbc6c1e64 100644 --- a/xmtp_api_http/Cargo.toml +++ b/xmtp_api_http/Cargo.toml @@ -9,6 +9,7 @@ async-trait.workspace = true bytes = "1.9" futures = { workspace = true, default-features = false } http = "1.2" +http-body-util = "0.1.3" pin-project-lite = "0.2.15" prost.workspace = true reqwest = { workspace = true, features = ["json"] } @@ -23,9 +24,11 @@ xmtp_common.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["sync", "rt", "macros"] } xmtp_proto = { path = "../xmtp_proto", features = ["test-utils"] } +hex.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } +ctor.workspace = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies] tokio = { workspace = true, features = ["macros", "time"] } diff --git a/xmtp_api_http/src/constants.rs b/xmtp_api_http/src/constants.rs index 47df333c0..9edec6011 100644 --- a/xmtp_api_http/src/constants.rs +++ b/xmtp_api_http/src/constants.rs @@ -2,6 +2,7 @@ pub struct ApiUrls; impl ApiUrls { pub const LOCAL_ADDRESS: &'static str = "http://localhost:5555"; + pub const LOCAL_D14N_ADDRESS: &'static str = "http://localhost:5055"; pub const DEV_ADDRESS: &'static str = "https://dev.xmtp.network:443"; pub const PRODUCTION_ADDRESS: &'static str = "https://production.xmtp.network"; } diff --git a/xmtp_api_http/src/error.rs b/xmtp_api_http/src/error.rs index 47fd16f1d..9e9bc19a2 100644 --- a/xmtp_api_http/src/error.rs +++ b/xmtp_api_http/src/error.rs @@ -167,6 +167,12 @@ pub enum HttpClientError { Json(#[from] serde_json::Error), #[error(transparent)] Decode(#[from] prost::DecodeError), + #[error(transparent)] + Uri(#[from] http::uri::InvalidUri), + #[error(transparent)] + InvalidUri(#[from] http::uri::InvalidUriParts), + #[error(transparent)] + Http(#[from] http::Error), } impl xmtp_common::RetryableError for HttpClientError { diff --git a/xmtp_api_http/src/http_client.rs b/xmtp_api_http/src/http_client.rs index fb9d623e6..67cb74138 100644 --- a/xmtp_api_http/src/http_client.rs +++ b/xmtp_api_http/src/http_client.rs @@ -1,6 +1,7 @@ -use crate::{HttpClientError, XmtpHttpApiClient}; +use crate::{ErrorResponse, HttpClientError, XmtpHttpApiClient}; use bytes::Bytes; -use http::Method; +use http::Response; +use reqwest::Body; use std::pin::Pin; use xmtp_proto::traits::{ApiError, Client}; @@ -10,43 +11,61 @@ impl From for ApiError { } } +impl XmtpHttpApiClient { + async fn request( + &self, + request: http::request::Builder, + uri: http::uri::Builder, + body: Vec, + ) -> Result, HttpClientError> + where + T: Default + prost::Message + 'static, + Self: Sized, + { + let parts = http::uri::Uri::try_from(&self.host_url)?.into_parts(); + let uri = uri + .scheme(parts.scheme.unwrap_or("http".try_into()?)) + .authority(parts.authority.unwrap_or("localhost".try_into()?)) + .build()?; + trace!("uri={uri}"); + let request = request.method("POST").uri(uri).body(body)?; + trace!("request={:?}", request); + let response = self.http_client.execute(request.try_into()?).await?; + + if !response.status().is_success() { + return Err(HttpClientError::Grpc(ErrorResponse { + code: response.status().as_u16() as usize, + message: response.text().await.map_err(HttpClientError::from)?, + details: vec![], + })); + } + let response: Response = response.into(); + let (parts, body) = response.into_parts(); + let body = http_body_util::BodyExt::collect(body) + .await + .map(|buf| T::decode(buf.to_bytes()))??; + Ok(http::Response::from_parts(parts, body)) + } +} + #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] impl Client for XmtpHttpApiClient { type Error = HttpClientError; type Stream = Pin> + Send>>; - async fn request( + async fn request( &self, request: http::request::Builder, + uri: http::uri::Builder, body: Vec, - ) -> Result, ApiError> { - let request = request.body(body.clone())?; - let (parts, _) = request.into_parts(); - - let url = format!("{}{}", self.host_url, parts.uri); - let mut req = self.http_client.request(Method::POST, url); - - for (key, value) in parts.headers.iter() { - req = req.header(key, value); - } - let response = req - .body(body) - .send() - .await - .map_err(HttpClientError::from)? - .error_for_status() - .map_err(HttpClientError::from)?; - - let status = response.status(); - let headers = response.headers().clone(); - let body = response.bytes().await.map_err(HttpClientError::from)?; - - let mut http_response = http::Response::new(body); - *http_response.status_mut() = status; - *http_response.headers_mut() = headers; - - Ok(http_response) + ) -> Result, ApiError> + where + T: Default + prost::Message + 'static, + Self: Sized, + { + Ok(self.request(request, uri, body).await?) } + async fn stream( &self, _request: http::request::Builder, diff --git a/xmtp_api_http/src/lib.rs b/xmtp_api_http/src/lib.rs index 12a92c744..6e241c37a 100755 --- a/xmtp_api_http/src/lib.rs +++ b/xmtp_api_http/src/lib.rs @@ -32,6 +32,9 @@ use xmtp_proto::{ ApiEndpoint, }; +#[macro_use] +extern crate tracing; + use crate::constants::ApiEndpoints; pub use crate::error::{Error, ErrorResponse, HttpClientError}; pub const LOCALHOST_ADDRESS: &str = "http://localhost:5555"; @@ -117,6 +120,10 @@ pub enum HttpClientBuilderError { ReqwestErrror(#[from] reqwest::Error), #[error(transparent)] InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), + #[error(transparent)] + InvalidUri(#[from] http::uri::InvalidUri), + #[error(transparent)] + InvalidUriParts(#[from] http::uri::InvalidUriParts), } impl ApiBuilder for XmtpHttpApiClientBuilder { @@ -421,6 +428,14 @@ pub mod tests { use super::*; + // Execute once before any tests are run + #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)] + #[cfg(not(target_arch = "wasm32"))] + #[cfg(test)] + fn _setup() { + xmtp_common::logger(); + } + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn test_upload_key_package() { @@ -449,4 +464,29 @@ pub mod tests { .to_string() .contains("invalid identity")); } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + async fn test_get_inbox_ids() { + use xmtp_proto::identity::api::v1::prelude::{ + get_inbox_ids_request::Request, GetInboxIdsRequest, + }; + use xmtp_proto::xmtp::identity::associations::IdentifierKind; + let mut client = XmtpHttpApiClient::builder(); + client.set_host(ApiUrls::LOCAL_ADDRESS.to_string()); + client.set_app_version("".into()).unwrap(); + client + .set_libxmtp_version(env!("CARGO_PKG_VERSION").into()) + .unwrap(); + let client = client.build().await.unwrap(); + let result = client + .get_inbox_ids(GetInboxIdsRequest { + requests: vec![Request { + identifier: "0xC2e3f813297E7b42a89e0b2FAa66f2034831984f".to_string(), + identifier_kind: IdentifierKind::Ethereum as i32, + }], + }) + .await; + assert!(result.is_ok()); + } } diff --git a/xmtp_api_http/src/util.rs b/xmtp_api_http/src/util.rs index bc86a2010..b79bb463d 100644 --- a/xmtp_api_http/src/util.rs +++ b/xmtp_api_http/src/util.rs @@ -48,29 +48,49 @@ where } } -#[cfg(feature = "test-utils")] -#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] +#[cfg(any(test, feature = "test-utils"))] #[allow(clippy::unwrap_used)] impl xmtp_proto::api_client::XmtpTestClient for crate::XmtpHttpApiClient { - async fn create_local() -> Self { + type Builder = crate::XmtpHttpApiClientBuilder; + fn create_local_d14n() -> Self::Builder { + use xmtp_proto::api_client::ApiBuilder; + let mut api = crate::XmtpHttpApiClient::builder(); + api.set_host(crate::constants::ApiUrls::LOCAL_D14N_ADDRESS.into()); + api.set_libxmtp_version(env!("CARGO_PKG_VERSION").into()) + .unwrap(); + api.set_app_version("0.0.0".into()).unwrap(); + api + } + + fn create_local_payer() -> Self::Builder { + use xmtp_proto::api_client::ApiBuilder; + let mut api = crate::XmtpHttpApiClient::builder(); + // payer has same address as d14n locally + api.set_host(crate::constants::ApiUrls::LOCAL_D14N_ADDRESS.into()); + api.set_libxmtp_version(env!("CARGO_PKG_VERSION").into()) + .unwrap(); + api.set_app_version("0.0.0".into()).unwrap(); + api + } + + fn create_local() -> Self::Builder { use xmtp_proto::api_client::ApiBuilder; let mut api = crate::XmtpHttpApiClient::builder(); api.set_host(crate::constants::ApiUrls::LOCAL_ADDRESS.into()); api.set_libxmtp_version(env!("CARGO_PKG_VERSION").into()) .unwrap(); api.set_app_version("0.0.0".into()).unwrap(); - api.build().await.unwrap() + api } - async fn create_dev() -> Self { + fn create_dev() -> Self::Builder { use xmtp_proto::api_client::ApiBuilder; let mut api = crate::XmtpHttpApiClient::builder(); api.set_host(crate::constants::ApiUrls::DEV_ADDRESS.into()); api.set_libxmtp_version(env!("CARGO_PKG_VERSION").into()) .unwrap(); api.set_app_version("0.0.0".into()).unwrap(); - api.build().await.unwrap() + api } } diff --git a/xmtp_mls/src/builder.rs b/xmtp_mls/src/builder.rs index 26044d76b..8b53ad8b9 100644 --- a/xmtp_mls/src/builder.rs +++ b/xmtp_mls/src/builder.rs @@ -5,6 +5,7 @@ use tracing::debug; use xmtp_cryptography::signature::IdentifierValidationError; use xmtp_id::scw_verifier::{RemoteSignatureVerifier, SmartContractSignatureVerifier}; +use xmtp_proto::api_client::ApiBuilder; use crate::{ client::Client, @@ -241,6 +242,7 @@ pub(crate) mod tests { }; use xmtp_id::associations::{Identifier, ValidatedLegacySignedPublicKey}; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; + use xmtp_proto::api_client::ApiBuilder; use xmtp_proto::api_client::XmtpTestClient; use xmtp_proto::xmtp::identity::api::v1::{ get_inbox_ids_response::Response as GetInboxIdsResponseItem, GetInboxIdsResponse, @@ -426,7 +428,12 @@ pub(crate) mod tests { let result = Client::builder(test_case.strategy) .temp_store() .await - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() .await; @@ -468,7 +475,12 @@ pub(crate) mod tests { let client1 = Client::builder(identity_strategy.clone()) .store(store.clone()) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() .await @@ -477,7 +489,12 @@ pub(crate) mod tests { let client2 = Client::builder(IdentityStrategy::CachedOnly) .store(store.clone()) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() .await @@ -493,7 +510,12 @@ pub(crate) mod tests { None, )) .store(store.clone()) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() .await @@ -505,7 +527,12 @@ pub(crate) mod tests { let client4 = Client::builder(identity_strategy) .temp_store() .await - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() .await @@ -701,7 +728,12 @@ pub(crate) mod tests { nonce, None, )) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .store(store_a) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() @@ -724,7 +756,12 @@ pub(crate) mod tests { nonce, None, )) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .store(store_b) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() @@ -746,7 +783,7 @@ pub(crate) mod tests { // generate_local_wallet().get_address(), // None, // )) - // .api_client(::create_local().await) + // .api_client(::create_local().build().await) // .store(store_c) // .build() // .await @@ -756,7 +793,12 @@ pub(crate) mod tests { let store_d = EncryptedMessageStore::new(StorageOption::Persistent(tmpdb.clone()), db_key).unwrap(); let client_d = Client::builder(IdentityStrategy::CachedOnly) - .api_client(::create_local().await) + .api_client( + ::create_local() + .build() + .await + .unwrap(), + ) .store(store_d) .with_scw_verifier(MockSmartContractSignatureVerifier::new(true)) .build() diff --git a/xmtp_mls/src/utils/bench/clients.rs b/xmtp_mls/src/utils/bench/clients.rs index 7ddf844c3..dfa39af3d 100644 --- a/xmtp_mls/src/utils/bench/clients.rs +++ b/xmtp_mls/src/utils/bench/clients.rs @@ -9,7 +9,7 @@ use xmtp_id::{ }, InboxOwner, }; -use xmtp_proto::api_client::XmtpTestClient; +use xmtp_proto::api_client::{ApiBuilder, XmtpTestClient}; pub type BenchClient = Client; @@ -27,10 +27,16 @@ pub async fn new_unregistered_client(history_sync: bool) -> (BenchClient, LocalW let api_client = if is_dev_network { tracing::info!("Using Dev GRPC"); - ::create_dev().await + ::create_dev() + .build() + .await + .unwrap() } else { tracing::info!("Using Local GRPC"); - ::create_local().await + ::create_local() + .build() + .await + .unwrap() }; let client = crate::Client::builder(IdentityStrategy::new( diff --git a/xmtp_mls/src/utils/test/mod.rs b/xmtp_mls/src/utils/test/mod.rs index 8b5e0df22..108a881b7 100755 --- a/xmtp_mls/src/utils/test/mod.rs +++ b/xmtp_mls/src/utils/test/mod.rs @@ -18,7 +18,7 @@ use xmtp_id::{ }, scw_verifier::{RemoteSignatureVerifier, SmartContractSignatureVerifier}, }; -use xmtp_proto::api_client::XmtpTestClient; +use xmtp_proto::api_client::{ApiBuilder, XmtpTestClient}; use crate::{ builder::ClientBuilder, @@ -77,7 +77,10 @@ impl ClientBuilder { impl ClientBuilder { pub async fn new_test_client(owner: &impl InboxOwner) -> FullXmtpClient { - let api_client = ::create_local().await; + let api_client = ::create_local() + .build() + .await + .unwrap(); build_with_verifier( owner, @@ -89,7 +92,10 @@ impl ClientBuilder { } pub async fn new_test_client_dev(owner: &impl InboxOwner) -> FullXmtpClient { - let api_client = ::create_dev().await; + let api_client = ::create_dev() + .build() + .await + .unwrap(); build_with_verifier( owner, @@ -104,7 +110,10 @@ impl ClientBuilder { owner: &impl InboxOwner, history_sync_url: &str, ) -> FullXmtpClient { - let api_client = ::create_local().await; + let api_client = ::create_local() + .build() + .await + .unwrap(); build_with_verifier( owner, @@ -119,7 +128,10 @@ impl ClientBuilder { pub async fn new_mock_dev_client( owner: impl InboxOwner, ) -> Client { - let api_client = ::create_dev().await; + let api_client = ::create_dev() + .build() + .await + .unwrap(); build_with_verifier( owner, @@ -133,23 +145,39 @@ impl ClientBuilder { impl ClientBuilder { pub async fn local_client(self) -> ClientBuilder { - self.api_client(::create_local().await) + self.api_client( + ::create_local() + .build() + .await + .unwrap(), + ) } pub async fn dev_client(self) -> ClientBuilder { - self.api_client(::create_dev().await) + self.api_client( + ::create_dev() + .build() + .await + .unwrap(), + ) } } impl ClientBuilder> { /// Create a client pointed at the local container with the default remote verifier pub async fn new_local_client(owner: &impl InboxOwner) -> Client { - let api_client = ::create_local().await; + let api_client = ::create_local() + .build() + .await + .unwrap(); inner_build(owner, api_client).await } pub async fn new_dev_client(owner: &impl InboxOwner) -> Client { - let api_client = ::create_dev().await; + let api_client = ::create_dev() + .build() + .await + .unwrap(); inner_build(owner, api_client).await } } diff --git a/xmtp_proto/Cargo.toml b/xmtp_proto/Cargo.toml index 2933c217b..8ef22712a 100644 --- a/xmtp_proto/Cargo.toml +++ b/xmtp_proto/Cargo.toml @@ -11,6 +11,7 @@ ed25519-dalek.workspace = true futures = { workspace = true } hex.workspace = true http = "1.2" +mockall = { workspace = true, optional = true } openmls_rust_crypto = { workspace = true, optional = true } pbjson.workspace = true pbjson-types.workspace = true @@ -30,6 +31,9 @@ tonic = { workspace = true, features = [ "prost", ] } +[dev-dependencies] +mockall = { workspace = true } + [target.'cfg(target_arch = "wasm32")'.dependencies] openmls = { workspace = true, features = ["js"] } @@ -39,7 +43,7 @@ wasm-bindgen-test.workspace = true [features] convert = ["openmls_rust_crypto", "proto_full"] default = ["proto_full"] -test-utils = ["xmtp_common/test-utils"] +test-utils = ["xmtp_common/test-utils", "dep:mockall"] http-api = [] grpc-api = [] diff --git a/xmtp_proto/src/api_client.rs b/xmtp_proto/src/api_client.rs index abd3943c2..43718a78d 100644 --- a/xmtp_proto/src/api_client.rs +++ b/xmtp_proto/src/api_client.rs @@ -18,11 +18,12 @@ use futures::Stream; use std::sync::Arc; #[cfg(any(test, feature = "test-utils"))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] pub trait XmtpTestClient { - async fn create_local() -> Self; - async fn create_dev() -> Self; + type Builder: ApiBuilder; + fn create_local() -> Self::Builder; + fn create_local_d14n() -> Self::Builder; + fn create_local_payer() -> Self::Builder; + fn create_dev() -> Self::Builder; } pub type BoxedXmtpApi = Box>; diff --git a/xmtp_proto/src/lib.rs b/xmtp_proto/src/lib.rs index 654c7da03..2b4777b0d 100644 --- a/xmtp_proto/src/lib.rs +++ b/xmtp_proto/src/lib.rs @@ -23,3 +23,51 @@ pub mod test { #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); } + +pub mod prelude { + #[cfg(any(test, feature = "test-utils"))] + pub use super::api_client::XmtpTestClient; + pub use super::api_client::{ + ApiBuilder, ArcedXmtpApi, BoxedXmtpApi, XmtpIdentityClient, XmtpMlsClient, XmtpMlsStreams, + }; + pub use super::traits::{ApiError, Client, Endpoint, Query}; + pub use super::XmtpApiError; +} + +pub mod mls { + pub mod api { + pub mod v1 { + pub mod prelude { + pub use crate::xmtp::mls::api::v1::*; + } + } + } +} + +pub mod identity { + pub mod api { + pub mod v1 { + pub mod prelude { + pub use crate::xmtp::identity::api::v1::*; + } + } + } +} + +pub mod xmtpv4 { + pub mod message_api { + pub mod prelude { + pub use crate::xmtp::xmtpv4::message_api::*; + } + } + pub mod metadata_api { + pub mod prelude { + pub use crate::xmtp::xmtpv4::metadata_api::*; + } + } + pub mod payer_api { + pub mod prelude { + pub use crate::xmtp::xmtpv4::payer_api::*; + } + } +} diff --git a/xmtp_proto/src/traits.rs b/xmtp_proto/src/traits.rs index 86b483089..49e4ebade 100644 --- a/xmtp_proto/src/traits.rs +++ b/xmtp_proto/src/traits.rs @@ -1,6 +1,5 @@ //! Api Client Traits -use prost::bytes::Bytes; use std::borrow::Cow; use thiserror::Error; use xmtp_common::{retry_async, retryable, BoxedRetry, RetryableError}; @@ -32,11 +31,17 @@ pub trait Client { type Error: std::error::Error + Send + Sync + 'static; type Stream: futures::Stream; - async fn request( + // TODO: this T can be removed if we figure out how to drop unknown fields from proto messages + // there must be a good way to do this with prost + async fn request( &self, request: http::request::Builder, + uri: http::uri::Builder, body: Vec, - ) -> Result, ApiError>; + ) -> Result, ApiError> + where + T: Default + prost::Message + 'static, + Self: Sized; async fn stream( &self, @@ -70,8 +75,8 @@ impl Query for E where E: Endpoint + Sync, C: Client + Sync + Send, - T: Default + prost::Message, - // TODO: figure out how to get conversions rightfigure out how to get conversions right + T: Default + prost::Message + 'static, + // TODO: figure out how to get conversions right // T: TryFrom, // ApiError<::Error>: From<>::Error>, { @@ -84,10 +89,9 @@ where } else { self.grpc_endpoint() }; - let request = request.uri(endpoint.as_ref()); - let rsp = client.request(request, self.body()?).await?; - let rsp: E::Output = prost::Message::decode(rsp.into_body())?; - Ok(rsp) + let uri = http::uri::Uri::builder().path_and_query(endpoint.as_ref()); + let rsp = client.request::(request, uri, self.body()?).await?; + Ok(rsp.into_body()) } } @@ -167,3 +171,99 @@ impl RetryableError for BodyError { false } } + +#[cfg(any(test, feature = "test-utils"))] +pub mod mock { + use super::*; + use crate::prelude::*; + + pub struct MockClient; + pub struct MockStream; + pub struct MockApiBuilder; + impl ApiBuilder for MockApiBuilder { + type Output = MockClient; + type Error = MockError; + + fn set_libxmtp_version(&mut self, _version: String) -> Result<(), Self::Error> { + Ok(()) + } + fn set_app_version(&mut self, _version: String) -> Result<(), Self::Error> { + Ok(()) + } + fn set_host(&mut self, _host: String) {} + fn set_payer(&mut self, _host: String) {} + fn set_tls(&mut self, _tls: bool) {} + async fn build(self) -> Result { + Ok(MockClient) + } + } + + #[derive(thiserror::Error, Debug)] + pub enum MockError {} + + type Repeat = Box prost::bytes::Bytes)>; + type MockStreamT = futures::stream::RepeatWith; + #[cfg(not(target_arch = "wasm32"))] + mockall::mock! { + pub MockClient {} + + #[async_trait::async_trait] + impl Client for MockClient { + type Error = MockError; + type Stream = MockStreamT; + async fn request( + &self, + request: http::request::Builder, + uri: http::uri::Builder, + body: Vec, + ) -> Result, ApiError> where Self: Sized, T: Default + prost::Message + 'static; + + async fn stream( + &self, + request: http::request::Builder, + body: Vec, + ) -> Result, ApiError>; + } + + impl XmtpTestClient for MockClient { + type Builder = MockApiBuilder; + fn create_local() -> MockApiBuilder { MockApiBuilder } + fn create_dev() -> MockApiBuilder { MockApiBuilder } + fn create_local_payer() -> MockApiBuilder { MockApiBuilder } + fn create_local_d14n() -> MockApiBuilder { MockApiBuilder } + + } + } + + #[cfg(target_arch = "wasm32")] + mockall::mock! { + pub MockClient {} + + #[async_trait::async_trait(?Send)] + impl Client for MockClient { + type Error = MockError; + type Stream = MockStreamT; + async fn request( + &self, + request: http::request::Builder, + uri: http::uri::Builder, + body: Vec, + ) -> Result, ApiError> where Self: Sized, T: Default + prost::Message + 'static; + + async fn stream( + &self, + request: http::request::Builder, + body: Vec, + ) -> Result, ApiError>; + } + + impl XmtpTestClient for MockClient { + type Builder = MockApiBuilder; + fn create_local() -> () { () } + fn create_dev() -> () { () } + fn create_local_payer() -> () { () } + fn create_local_d14n() -> () { () } + + } + } +}