diff --git a/service/src/database/dips.rs b/service/src/database/dips.rs new file mode 100644 index 00000000..75f3f5df --- /dev/null +++ b/service/src/database/dips.rs @@ -0,0 +1,44 @@ +// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use axum::async_trait; + +use crate::routes::dips::Agreement; + +#[async_trait] +pub trait AgreementStore: Sync + Send { + async fn get_by_signature(&self, signature: String) -> anyhow::Result>; + async fn create_agreement( + &self, + signature: String, + data: Agreement, + ) -> anyhow::Result; + async fn cancel_agreement(&self, signature: String) -> anyhow::Result; +} + +pub struct InMemoryAgreementStore { + pub data: tokio::sync::RwLock>, +} + +#[async_trait] +impl AgreementStore for InMemoryAgreementStore { + async fn get_by_signature(&self, signature: String) -> anyhow::Result> { + Ok(self.data.try_read()?.get(&signature).cloned()) + } + async fn create_agreement( + &self, + signature: String, + agreement: Agreement, + ) -> anyhow::Result { + self.data.try_write()?.insert(signature, agreement.clone()); + + Ok(agreement) + } + async fn cancel_agreement(&self, signature: String) -> anyhow::Result { + self.data.try_write()?.remove(&signature); + + Ok(signature.clone()) + } +} diff --git a/service/src/database.rs b/service/src/database/mod.rs similarity index 99% rename from service/src/database.rs rename to service/src/database/mod.rs index ab0cd80e..cf9b0a03 100644 --- a/service/src/database.rs +++ b/service/src/database/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 +pub mod dips; + use std::time::Duration; use std::{collections::HashSet, str::FromStr}; diff --git a/service/src/routes/dips.rs b/service/src/routes/dips.rs new file mode 100644 index 00000000..3f2a6b18 --- /dev/null +++ b/service/src/routes/dips.rs @@ -0,0 +1,121 @@ +// Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. +// SPDX-License-Identifier: Apache-2.0 + +use std::{str::FromStr, sync::Arc}; + +use anyhow::bail; +use async_graphql::{Context, FieldResult, Object, SimpleObject}; + +use crate::database::dips::AgreementStore; + +pub enum NetworkProtocol { + ArbitrumMainnet, +} + +impl FromStr for NetworkProtocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let p = match s { + "arbitrum-mainnet" => NetworkProtocol::ArbitrumMainnet, + _ => bail!("unknown network protocol"), + }; + + Ok(p) + } +} + +#[derive(SimpleObject, Debug, Clone)] +pub struct Agreement { + signature: String, + data: String, + protocol_network: String, +} + +#[derive(SimpleObject, Debug, Clone)] +pub struct Price { + price_per_block: String, + chain_id: String, + protocol_network: String, +} + +#[derive(Debug)] +pub struct AgreementQuery {} + +#[Object] +impl AgreementQuery { + pub async fn get_agreement<'a>( + &self, + ctx: &'a Context<'_>, + signature: String, + ) -> FieldResult> { + let store: &Arc = ctx.data()?; + + store + .get_by_signature(signature) + .await + .map_err(async_graphql::Error::from) + } + + pub async fn get_price<'a>( + &self, + ctx: &'a Context<'_>, + protocol_network: String, + chain_id: String, + ) -> FieldResult> { + let prices: &Vec = ctx.data()?; + + let p = prices + .iter() + .find(|p| p.protocol_network.eq(&protocol_network) && p.chain_id.eq(&chain_id)); + + Ok(p.cloned()) + } + + pub async fn get_all_prices<'a>(&self, ctx: &'a Context<'_>) -> FieldResult> { + let prices: &Vec = ctx.data()?; + + Ok(prices.clone()) + } +} + +#[derive(Debug)] +pub struct AgreementMutation {} + +#[Object] +impl AgreementMutation { + pub async fn create_agreement<'a>( + &self, + ctx: &'a Context<'_>, + signature: String, + data: String, + protocol_network: String, + ) -> FieldResult { + let store: &Arc = ctx.data()?; + + store + .create_agreement( + signature.clone(), + Agreement { + signature, + data, + protocol_network, + }, + ) + .await + .map_err(async_graphql::Error::from) + } + + pub async fn cancel_agreement<'a>( + &self, + ctx: &'a Context<'_>, + signature: String, + ) -> FieldResult { + let store: &Arc = ctx.data()?; + + store + .cancel_agreement(signature) + .await + .map_err(async_graphql::Error::from) + } +} diff --git a/service/src/routes/mod.rs b/service/src/routes/mod.rs index 9ac12f8f..e65d13db 100644 --- a/service/src/routes/mod.rs +++ b/service/src/routes/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod cost; +pub mod dips; mod status; pub use status::status; diff --git a/service/src/service.rs b/service/src/service.rs index 1203f85c..7253c48b 100644 --- a/service/src/service.rs +++ b/service/src/service.rs @@ -1,12 +1,18 @@ // Copyright 2023-, Edge & Node, GraphOps, and Semiotic Labs. // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; use std::time::Duration; +use std::{collections::HashMap, sync::Arc}; use super::{config::Config, error::SubgraphServiceError, routes}; use anyhow::anyhow; -use axum::{async_trait, routing::post, Json, Router}; +use async_graphql::{EmptySubscription, Schema}; +use async_graphql_axum::GraphQL; +use axum::{ + async_trait, + routing::{post, post_service}, + Json, Router, +}; use indexer_common::indexer_service::http::{ AttestationOutput, IndexerServiceImpl, IndexerServiceResponse, }; @@ -15,8 +21,16 @@ use reqwest::Url; use serde_json::{json, Value}; use sqlx::PgPool; use thegraph_core::DeploymentId; - -use crate::{cli::Cli, database}; +use tokio::sync::RwLock; + +use crate::{ + cli::Cli, + database::{ + self, + dips::{AgreementStore, InMemoryAgreementStore}, + }, + routes::dips::Price, +}; use clap::Parser; use indexer_common::indexer_service::http::{ @@ -173,6 +187,20 @@ pub async fn run() -> anyhow::Result<()> { .clone(), }); + let agreement_store: Arc = Arc::new(InMemoryAgreementStore { + data: RwLock::new(HashMap::new()), + }); + let prices: Vec = vec![]; + + let schema = Schema::build( + routes::dips::AgreementQuery {}, + routes::dips::AgreementMutation {}, + EmptySubscription, + ) + .data(agreement_store) + .data(prices) + .finish(); + IndexerService::run(IndexerServiceOptions { release, config: config.0.clone(), @@ -181,6 +209,7 @@ pub async fn run() -> anyhow::Result<()> { extra_routes: Router::new() .route("/cost", post(routes::cost::cost)) .route("/status", post(routes::status)) + .route("/dips", post_service(GraphQL::new(schema))) .with_state(state), }) .await