Skip to content

Commit

Permalink
wip: Service definition
Browse files Browse the repository at this point in the history
  • Loading branch information
netrome committed Feb 21, 2025
1 parent c0fabf6 commit 596a0a6
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/proof_system/global_merkle_root/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ version = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
async-graphql = { workspace = true }
axum = { workspace = true }
derive_more = { workspace = true }
fuel-core-global-merkle-root-storage = { workspace = true }
fuel-core-services = { workspace = true }
Expand All @@ -23,6 +24,7 @@ fuel-core-types = { workspace = true, default-features = false, features = [
"alloc",
] }
hex = { workspace = true }
hyper = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
Expand Down
170 changes: 170 additions & 0 deletions crates/proof_system/global_merkle_root/api/src/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use std::{
future::{
Future,
IntoFuture,
},
marker::PhantomData,
net::{
TcpListener,
ToSocketAddrs,
},
pin::Pin,
};

use async_graphql::{
http::GraphiQLSource,
EmptyMutation,
EmptySubscription,
Request,
Response,
};
use axum::{
response::{
ErrorResponse,
Html,
IntoResponse,
},
routing,
Extension,
Json,
Router,
};
use fuel_core_services::{
RunnableService,
RunnableTask,
ServiceRunner,
StateWatcher,
TaskNextAction,
};

use crate::{
ports::GetStateRoot,
schema::{
Query,
Schema,
},
};

pub fn new_service<Storage, Addr>(
storage: Storage,
network_address: Addr,
) -> anyhow::Result<ServiceRunner<StateRootApiService>>
where
Storage: GetStateRoot + Send + Sync + 'static,
Addr: ToSocketAddrs,
{
Ok(ServiceRunner::new(StateRootApiService::new(
storage,
network_address,
)?))
}

pub struct StateRootApiService {
router: Router<hyper::Body>,
listener: TcpListener,
}

impl StateRootApiService {
#[tracing::instrument(skip(storage, network_address))]
fn new<Storage, Addr>(storage: Storage, network_address: Addr) -> anyhow::Result<Self>
where
Storage: GetStateRoot + Send + Sync + 'static,
Addr: ToSocketAddrs,
{
let graphql_endpoint = "/graphql";

let graphql_playground = || render_graphql_playground(graphql_endpoint);

let query = Query::new(storage);
let schema = Schema::build(query, EmptyMutation, EmptySubscription).finish();

let router = Router::<hyper::Body>::new()
.route("/playground", routing::get(graphql_playground))
.route(graphql_endpoint, routing::post(graphql_handler::<Storage>))
.layer(Extension(schema));

let listener = TcpListener::bind(network_address)?;

Ok(Self { router, listener })
}
}

async fn render_graphql_playground(graphql_endpoint: &str) -> impl IntoResponse {
Html(
GraphiQLSource::build()
.endpoint(graphql_endpoint)
.title("Fuel Graphql Playground")
.finish(),
)
}

async fn graphql_handler<Storage>(
schema: Extension<Schema<Storage>>,
request: Json<Request>,
) -> Json<Response>
where
Storage: GetStateRoot + Send + Sync + 'static,
{
schema.execute(request.0).await.into()
}

#[async_trait::async_trait]
impl RunnableService for StateRootApiService {
const NAME: &'static str = "StateRootGraphQL";

type SharedData = ();
type Task = StateRootApiTask;
type TaskParams = ();

fn shared_data(&self) -> Self::SharedData {}

#[tracing::instrument(skip(self, state, _params))]
async fn into_task(
self,
state: &StateWatcher,
_params: Self::TaskParams,
) -> anyhow::Result<Self::Task> {
let mut state = state.clone();

let graceful_shutdown_signal = async move {
state.while_started().await.expect("unexpected termination");
};

let bound_address = self.listener.local_addr()?;
tracing::info!(%bound_address, "listening for GraphQL requests");

let server = Box::pin(
axum::Server::from_tcp(self.listener)?
.serve(self.router.into_make_service())
.with_graceful_shutdown(graceful_shutdown_signal),
);

Ok(StateRootApiTask { server })
}
}

pub struct StateRootApiTask {
server: Pin<Box<dyn Future<Output = hyper::Result<()>> + Send + 'static>>,
}

impl RunnableTask for StateRootApiTask {
async fn run(
&mut self,
watcher: &mut fuel_core_services::StateWatcher,
) -> TaskNextAction {
match self.server.as_mut().await {
Ok(()) => {
// The `axum::Server` has stopped, and so should we
TaskNextAction::Stop
}
Err(error) => {
tracing::error!(%error, "state root axum server returned error");
TaskNextAction::Stop
}
}
}

async fn shutdown(self) -> anyhow::Result<()> {
Ok(())
}
}

0 comments on commit 596a0a6

Please sign in to comment.