diff --git a/crates/torii/grpc/build.rs b/crates/torii/grpc/build.rs index 095d64820b..34f86df1b0 100644 --- a/crates/torii/grpc/build.rs +++ b/crates/torii/grpc/build.rs @@ -1,5 +1,9 @@ +use std::path::PathBuf; + fn main() -> Result<(), Box> { - let target = std::env::var("TARGET").expect("failed to get TARGET environment variable"); + let out_dir = + PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR environment variable not set")); + let target = std::env::var("TARGET").expect("TARGET environment variable not set"); let feature_client = std::env::var("CARGO_FEATURE_CLIENT"); let feature_server = std::env::var("CARGO_FEATURE_SERVER"); @@ -11,11 +15,13 @@ fn main() -> Result<(), Box> { wasm_tonic_build::configure() .build_server(false) .build_client(feature_client.is_ok()) + .file_descriptor_set_path(out_dir.join("world_descriptor.bin")) .compile(&["proto/world.proto"], &["proto"])?; } else { tonic_build::configure() .build_server(feature_server.is_ok()) .build_client(feature_client.is_ok()) + .file_descriptor_set_path(out_dir.join("world_descriptor.bin")) .compile(&["proto/world.proto"], &["proto"])?; } Ok(()) diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs index a143a6788c..c97b250b16 100644 --- a/crates/torii/grpc/src/client.rs +++ b/crates/torii/grpc/src/client.rs @@ -2,12 +2,12 @@ use futures_util::stream::MapOk; use futures_util::{Stream, StreamExt, TryStreamExt}; -use protos::world::{world_client, SubscribeEntitiesRequest}; +use proto::world::{world_client, SubscribeEntitiesRequest}; use starknet::core::types::{FromStrError, StateUpdate}; use starknet_crypto::FieldElement; -use crate::protos::world::{MetadataRequest, SubscribeEntitiesResponse}; -use crate::protos::{self}; +use crate::proto::world::{MetadataRequest, SubscribeEntitiesResponse}; +use crate::proto::{self}; #[derive(Debug, thiserror::Error)] pub enum Error { diff --git a/crates/torii/grpc/src/conversion.rs b/crates/torii/grpc/src/conversion.rs index 0ddbeadb78..987059cfb2 100644 --- a/crates/torii/grpc/src/conversion.rs +++ b/crates/torii/grpc/src/conversion.rs @@ -9,11 +9,11 @@ use starknet::core::types::{ }; use starknet_crypto::FieldElement; -use crate::protos; +use crate::proto; -impl TryFrom for dojo_types::schema::ModelMetadata { +impl TryFrom for dojo_types::schema::ModelMetadata { type Error = FromStrError; - fn try_from(value: protos::types::ModelMetadata) -> Result { + fn try_from(value: proto::types::ModelMetadata) -> Result { let schema: Ty = serde_json::from_slice(&value.schema).unwrap(); let layout: Vec = value.layout.into_iter().map(FieldElement::from).collect(); Ok(Self { @@ -27,9 +27,9 @@ impl TryFrom for dojo_types::schema::ModelMetadata } } -impl TryFrom for dojo_types::WorldMetadata { +impl TryFrom for dojo_types::WorldMetadata { type Error = FromStrError; - fn try_from(value: protos::types::WorldMetadata) -> Result { + fn try_from(value: proto::types::WorldMetadata) -> Result { let models = value .models .into_iter() @@ -46,38 +46,38 @@ impl TryFrom for dojo_types::WorldMetadata { } } -impl From for protos::types::EntityQuery { +impl From for proto::types::EntityQuery { fn from(value: EntityQuery) -> Self { Self { model: value.model, clause: Some(value.clause.into()) } } } -impl From for protos::types::Clause { +impl From for proto::types::Clause { fn from(value: Clause) -> Self { match value { Clause::Keys(clause) => { - Self { clause_type: Some(protos::types::clause::ClauseType::Keys(clause.into())) } + Self { clause_type: Some(proto::types::clause::ClauseType::Keys(clause.into())) } } Clause::Attribute(clause) => Self { - clause_type: Some(protos::types::clause::ClauseType::Attribute(clause.into())), + clause_type: Some(proto::types::clause::ClauseType::Attribute(clause.into())), }, Clause::Composite(clause) => Self { - clause_type: Some(protos::types::clause::ClauseType::Composite(clause.into())), + clause_type: Some(proto::types::clause::ClauseType::Composite(clause.into())), }, } } } -impl From for protos::types::KeysClause { +impl From for proto::types::KeysClause { fn from(value: KeysClause) -> Self { Self { keys: value.keys.iter().map(|k| k.to_bytes_be().into()).collect() } } } -impl TryFrom for KeysClause { +impl TryFrom for KeysClause { type Error = FromByteSliceError; - fn try_from(value: protos::types::KeysClause) -> Result { + fn try_from(value: proto::types::KeysClause) -> Result { let keys = value .keys .into_iter() @@ -88,7 +88,7 @@ impl TryFrom for KeysClause { } } -impl From for protos::types::AttributeClause { +impl From for proto::types::AttributeClause { fn from(value: AttributeClause) -> Self { Self { attribute: value.attribute, @@ -98,7 +98,7 @@ impl From for protos::types::AttributeClause { } } -impl From for protos::types::CompositeClause { +impl From for proto::types::CompositeClause { fn from(value: CompositeClause) -> Self { Self { operator: value.operator as i32, @@ -107,31 +107,31 @@ impl From for protos::types::CompositeClause { } } -impl From for protos::types::Value { +impl From for proto::types::Value { fn from(value: Value) -> Self { match value { Value::String(val) => { - Self { value_type: Some(protos::types::value::ValueType::StringValue(val)) } + Self { value_type: Some(proto::types::value::ValueType::StringValue(val)) } } Value::Int(val) => { - Self { value_type: Some(protos::types::value::ValueType::IntValue(val)) } + Self { value_type: Some(proto::types::value::ValueType::IntValue(val)) } } Value::UInt(val) => { - Self { value_type: Some(protos::types::value::ValueType::UintValue(val)) } + Self { value_type: Some(proto::types::value::ValueType::UintValue(val)) } } Value::Bool(val) => { - Self { value_type: Some(protos::types::value::ValueType::BoolValue(val)) } + Self { value_type: Some(proto::types::value::ValueType::BoolValue(val)) } } Value::Bytes(val) => { - Self { value_type: Some(protos::types::value::ValueType::ByteValue(val)) } + Self { value_type: Some(proto::types::value::ValueType::ByteValue(val)) } } } } } -impl TryFrom for StorageEntry { +impl TryFrom for StorageEntry { type Error = FromStrError; - fn try_from(value: protos::types::StorageEntry) -> Result { + fn try_from(value: proto::types::StorageEntry) -> Result { Ok(Self { key: FieldElement::from_str(&value.key)?, value: FieldElement::from_str(&value.value)?, @@ -139,9 +139,9 @@ impl TryFrom for StorageEntry { } } -impl TryFrom for ContractStorageDiffItem { +impl TryFrom for ContractStorageDiffItem { type Error = FromStrError; - fn try_from(value: protos::types::StorageDiff) -> Result { + fn try_from(value: proto::types::StorageDiff) -> Result { Ok(Self { address: FieldElement::from_str(&value.address)?, storage_entries: value @@ -153,9 +153,9 @@ impl TryFrom for ContractStorageDiffItem { } } -impl TryFrom for StateDiff { +impl TryFrom for StateDiff { type Error = FromStrError; - fn try_from(value: protos::types::EntityDiff) -> Result { + fn try_from(value: proto::types::EntityDiff) -> Result { Ok(Self { nonces: vec![], declared_classes: vec![], @@ -171,9 +171,9 @@ impl TryFrom for StateDiff { } } -impl TryFrom for StateUpdate { +impl TryFrom for StateUpdate { type Error = FromStrError; - fn try_from(value: protos::types::EntityUpdate) -> Result { + fn try_from(value: proto::types::EntityUpdate) -> Result { Ok(Self { new_root: FieldElement::ZERO, old_root: FieldElement::ZERO, diff --git a/crates/torii/grpc/src/lib.rs b/crates/torii/grpc/src/lib.rs index 56afad464c..4b19ce10cb 100644 --- a/crates/torii/grpc/src/lib.rs +++ b/crates/torii/grpc/src/lib.rs @@ -11,9 +11,13 @@ pub mod client; #[cfg(feature = "server")] pub mod server; -pub mod protos { +pub mod proto { pub mod world { tonic::include_proto!("world"); + + #[cfg(feature = "server")] + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + tonic::include_file_descriptor_set!("world_descriptor"); } pub mod types { tonic::include_proto!("types"); diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index e124280bf1..9e491b7e26 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use dojo_types::schema::KeysClause; use futures::Stream; -use protos::world::{ +use proto::world::{ MetadataRequest, MetadataResponse, SubscribeEntitiesRequest, SubscribeEntitiesResponse, }; use sqlx::{Pool, Sqlite}; @@ -26,9 +26,9 @@ use torii_core::error::{Error, ParseError}; use torii_core::model::{parse_sql_model_members, SqlModelMember}; use self::subscription::SubscribeRequest; -use crate::protos::types::clause::ClauseType; -use crate::protos::world::world_server::WorldServer; -use crate::protos::{self}; +use crate::proto::types::clause::ClauseType; +use crate::proto::world::world_server::WorldServer; +use crate::proto::{self}; #[derive(Clone)] pub struct DojoWorld { @@ -58,7 +58,7 @@ impl DojoWorld { } impl DojoWorld { - pub async fn metadata(&self) -> Result { + pub async fn metadata(&self) -> Result { let (world_address, world_class_hash, executor_address, executor_class_hash): ( String, String, @@ -81,7 +81,7 @@ impl DojoWorld { let mut models_metadata = Vec::with_capacity(models.len()); for model in models { let schema = self.model_schema(&model.0).await?; - models_metadata.push(protos::types::ModelMetadata { + models_metadata.push(proto::types::ModelMetadata { name: model.0, class_hash: model.1, packed_size: model.2, @@ -90,8 +90,9 @@ impl DojoWorld { schema: serde_json::to_vec(&schema).unwrap(), }); } + println!("{:?}", world_address); - Ok(protos::types::WorldMetadata { + Ok(proto::types::WorldMetadata { world_address, world_class_hash, executor_address, @@ -112,7 +113,7 @@ impl DojoWorld { Ok(parse_sql_model_members(model, &model_members)) } - pub async fn model_metadata(&self, model: &str) -> Result { + pub async fn model_metadata(&self, model: &str) -> Result { let (name, class_hash, packed_size, unpacked_size, layout): ( String, String, @@ -129,7 +130,7 @@ impl DojoWorld { let schema = self.model_schema(model).await?; let layout = hex::decode(&layout).unwrap(); - Ok(protos::types::ModelMetadata { + Ok(proto::types::ModelMetadata { name, layout, class_hash, @@ -141,8 +142,8 @@ impl DojoWorld { async fn subscribe_entities( &self, - queries: Vec, - ) -> Result>, Error> + queries: Vec, + ) -> Result>, Error> { let mut subs = Vec::with_capacity(queries.len()); for query in queries { @@ -160,7 +161,7 @@ impl DojoWorld { let model = cairo_short_string_to_felt(&query.model) .map_err(ParseError::CairoShortStringToFelt)?; - let protos::types::ModelMetadata { packed_size, .. } = + let proto::types::ModelMetadata { packed_size, .. } = self.model_metadata(&query.model).await?; subs.push(SubscribeRequest { @@ -183,7 +184,7 @@ type SubscribeEntitiesResponseStream = Pin> + Send>>; #[tonic::async_trait] -impl protos::world::world_server::World for DojoWorld { +impl proto::world::world_server::World for DojoWorld { async fn world_metadata( &self, _request: Request, @@ -222,12 +223,18 @@ pub async fn new( let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; + let reflection = tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(proto::world::FILE_DESCRIPTOR_SET) + .build() + .unwrap(); + let world = DojoWorld::new(pool.clone(), block_rx, world_address, provider); let server = WorldServer::new(world); let server_future = Server::builder() // GrpcWeb is over http1 so we must enable it. .accept_http1(true) + .add_service(reflection) .add_service(tonic_web::enable(server)) .serve_with_incoming_shutdown(TcpListenerStream::new(listener), async move { shutdown_rx.recv().await.map_or((), |_| ()) diff --git a/crates/torii/grpc/src/server/subscription.rs b/crates/torii/grpc/src/server/subscription.rs index 5eb9d3176a..4750d69cdd 100644 --- a/crates/torii/grpc/src/server/subscription.rs +++ b/crates/torii/grpc/src/server/subscription.rs @@ -18,7 +18,7 @@ use tokio::sync::RwLock; use tracing::{debug, error, trace}; use super::error::SubscriptionError as Error; -use crate::protos; +use crate::proto; pub struct ModelMetadata { pub name: FieldElement, @@ -34,7 +34,7 @@ pub struct Subscriber { /// The storage addresses that the subscriber is interested in. storage_addresses: HashSet, /// The channel to send the response back to the subscriber. - sender: Sender>, + sender: Sender>, } #[derive(Default)] @@ -46,7 +46,7 @@ impl SubscriberManager { pub(super) async fn add_subscriber( &self, entities: Vec, - ) -> Receiver> { + ) -> Receiver> { let id = rand::thread_rng().gen::(); let (sender, receiver) = channel(1); @@ -139,17 +139,17 @@ where .filter(|entry| sub.storage_addresses.contains(&entry.key)) .map(|entry| { let StorageEntry { key, value } = entry; - protos::types::StorageEntry { + proto::types::StorageEntry { key: format!("{key:#x}"), value: format!("{value:#x}"), } }) - .collect::>(); + .collect::>(); - let entity_update = protos::types::EntityUpdate { + let entity_update = proto::types::EntityUpdate { block_hash: format!("{:#x}", state_update.block_hash), - entity_diff: Some(protos::types::EntityDiff { - storage_diffs: vec![protos::types::StorageDiff { + entity_diff: Some(proto::types::EntityDiff { + storage_diffs: vec![proto::types::StorageDiff { address: format!("{contract_address:#x}"), storage_entries: relevant_storage_entries, }], @@ -157,7 +157,7 @@ where }; let resp = - protos::world::SubscribeEntitiesResponse { entity_update: Some(entity_update) }; + proto::world::SubscribeEntitiesResponse { entity_update: Some(entity_update) }; if sub.sender.send(Ok(resp)).await.is_err() { closed_stream.push(*idx); diff --git a/crates/torii/server/src/proxy.rs b/crates/torii/server/src/proxy.rs index 15cd6b2f6d..7a45528998 100644 --- a/crates/torii/server/src/proxy.rs +++ b/crates/torii/server/src/proxy.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::time::Duration; use http::header::CONTENT_TYPE; -use http::{HeaderName, Method}; +use http::{HeaderName, Method, Uri}; use hyper::client::connect::dns::GaiResolver; use hyper::client::HttpConnector; use hyper::server::conn::AddrStream; @@ -15,6 +15,7 @@ use serde_json::json; use tokio::sync::RwLock; use tower::ServiceBuilder; use tower_http::cors::{AllowOrigin, CorsLayer}; +use tracing::error; const DEFAULT_ALLOW_HEADERS: [&str; 12] = [ "accept", @@ -128,15 +129,18 @@ async fn handle( graphql_addr: Option, req: Request, ) -> Result, Infallible> { - if req.uri().path().starts_with("/grpc") { - if let Some(grpc_addr) = grpc_addr { - let grpc_addr = format!("http://{}", grpc_addr); - return match PROXY_CLIENT.call(client_ip, &grpc_addr, req).await { + if req.uri().path().starts_with("/graphql") { + if let Some(graphql_addr) = graphql_addr { + let graphql_addr = format!("http://{}", graphql_addr); + return match PROXY_CLIENT.call(client_ip, &graphql_addr, req).await { Ok(response) => Ok(response), - Err(_error) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), + Err(_error) => { + error!("{:?}", _error); + Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()) + } }; } else { return Ok(Response::builder() @@ -146,15 +150,28 @@ async fn handle( } } - if req.uri().path().starts_with("/graphql") { - if let Some(graphql_addr) = graphql_addr { - let graphql_addr = format!("http://{}", graphql_addr); - return match PROXY_CLIENT.call(client_ip, &graphql_addr, req).await { + if req.uri().path().starts_with("/grpc") { + if let Some(grpc_addr) = grpc_addr { + let uri = req.uri().clone(); + let (mut parts, body) = req.into_parts(); + parts.uri = Uri::builder() + .scheme("http") + .authority("replace.com") + .path_and_query(uri.path().trim_start_matches("/grpc")) + .build() + .unwrap(); + let req = Request::from_parts(parts, body); + + let grpc_addr = format!("http://{}", grpc_addr); + return match PROXY_CLIENT.call(client_ip, &grpc_addr, req).await { Ok(response) => Ok(response), - Err(_error) => Ok(Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::empty()) - .unwrap()), + Err(_error) => { + error!("{:?}", _error); + Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()) + } }; } else { return Ok(Response::builder()