diff --git a/catalyst-gateway/bin/src/service/api/fragments/index.rs b/catalyst-gateway/bin/src/service/api/fragments/index.rs new file mode 100644 index 00000000000..cd44b22ebd4 --- /dev/null +++ b/catalyst-gateway/bin/src/service/api/fragments/index.rs @@ -0,0 +1,35 @@ +//! Implementation of the GET /fragments endpoint + +use poem_extensions::{response, UniResponse::T200}; +use poem_openapi::payload::Json; + +use crate::service::common::{ + objects::{ + fragments_batch::FragmentsBatch, fragments_processing_summary::FragmentsProcessingSummary, + }, + responses::{ + resp_2xx::OK, + resp_5xx::{ServerError, ServiceUnavailable}, + }, +}; + +/// All responses +pub(crate) type AllResponses = response! { + 200: OK>, + 500: ServerError, + 503: ServiceUnavailable, +}; + +/// # GET /fragments +/// +/// Process a fragments batch. +/// +/// ## Responses +/// +/// * 200 No Content - Service is OK and can keep running. +/// * 500 Server Error - If anything within this function fails unexpectedly. (Possible +/// but unlikely) +/// * 503 Service Unavailable - Service is possibly not running reliably. +pub(crate) async fn endpoint(_fragments_batch: FragmentsBatch) -> AllResponses { + T200(OK(Json(FragmentsProcessingSummary::default()))) +} diff --git a/catalyst-gateway/bin/src/service/api/fragments/mod.rs b/catalyst-gateway/bin/src/service/api/fragments/mod.rs new file mode 100644 index 00000000000..c1e2d8ee3dd --- /dev/null +++ b/catalyst-gateway/bin/src/service/api/fragments/mod.rs @@ -0,0 +1,40 @@ +//! Fragment endpoints + +mod index; +mod statuses; + +use poem_openapi::{param::Query, payload::Json, OpenApi}; + +use self::statuses::FragmentIds; +use crate::service::common::{objects::fragments_batch::FragmentsBatch, tags::ApiTags}; + +/// All Responses +pub(crate) struct FragmentsApi; + +#[OpenApi(prefix_path = "/v1/fragments", tag = "ApiTags::Fragments")] +impl FragmentsApi { + /// Process fragments + /// + /// Posts a fragments batch to be processed. + #[oai(path = "/", method = "post", operation_id = "fragments")] + async fn index( + &self, + /// Batch of fragments to be processed. + Json(fragments_batch): Json, + ) -> index::AllResponses { + index::endpoint(fragments_batch).await + } + + /// Get Fragment Statuses + /// + /// Get statuses of the fragments with the given ids. + #[oai(path = "/statuses", method = "get", operation_id = "fragmentsStatuses")] + async fn statuses( + &self, + /// Comma-separated list of fragment ids for which the statuses will + /// be retrieved. + Query(fragment_ids): Query, + ) -> statuses::AllResponses { + statuses::endpoint(fragment_ids).await + } +} diff --git a/catalyst-gateway/bin/src/service/api/fragments/statuses.rs b/catalyst-gateway/bin/src/service/api/fragments/statuses.rs new file mode 100644 index 00000000000..00f0e1285b4 --- /dev/null +++ b/catalyst-gateway/bin/src/service/api/fragments/statuses.rs @@ -0,0 +1,38 @@ +//! Implementation of the GET /fragments/statuses endpoint + +use std::collections::HashMap; + +use poem_extensions::{response, UniResponse::T200}; +use poem_openapi::payload::Json; + +use crate::service::common::{ + objects::fragment_status::FragmentStatus, + responses::{ + resp_2xx::OK, + resp_5xx::{ServerError, ServiceUnavailable}, + }, +}; + +#[derive(poem_openapi::NewType)] +pub(crate) struct FragmentIds(String); + +/// All responses +pub(crate) type AllResponses = response! { + 200: OK>>, + 500: ServerError, + 503: ServiceUnavailable, +}; + +/// # GET /fragments/statuses +/// +/// Get fragments statuses endpoint. +/// +/// ## Responses +/// +/// * 200 Fragments Statuses - Statuses of the fragments by id. +/// * 500 Server Error - If anything within this function fails unexpectedly. (Possible +/// but unlikely) +/// * 503 Service Unavailable - Service is possibly not running reliably. +pub(crate) async fn endpoint(_fragment_ids: FragmentIds) -> AllResponses { + T200(OK(Json(HashMap::new()))) +} diff --git a/catalyst-gateway/bin/src/service/api/mod.rs b/catalyst-gateway/bin/src/service/api/mod.rs index 6a2828cbb9d..ebdd7b058f3 100644 --- a/catalyst-gateway/bin/src/service/api/mod.rs +++ b/catalyst-gateway/bin/src/service/api/mod.rs @@ -9,8 +9,10 @@ use test_endpoints::TestApi; use v0::V0Api; use v1::V1Api; +use self::fragments::FragmentsApi; use crate::settings::API_URL_PREFIX; +mod fragments; mod health; mod registration; mod test_endpoints; @@ -63,9 +65,26 @@ const TERMS_OF_SERVICE: &str = /// Create the `OpenAPI` definition pub(crate) fn mk_api( hosts: Vec, -) -> OpenApiService<(TestApi, HealthApi, RegistrationApi, V0Api, V1Api), ()> { +) -> OpenApiService< + ( + TestApi, + HealthApi, + RegistrationApi, + V0Api, + V1Api, + FragmentsApi, + ), + (), +> { let mut service = OpenApiService::new( - (TestApi, HealthApi, RegistrationApi, V0Api, V1Api), + ( + TestApi, + HealthApi, + RegistrationApi, + V0Api, + V1Api, + FragmentsApi, + ), API_TITLE, API_VERSION, ) diff --git a/catalyst-gateway/bin/src/service/common/objects/block.rs b/catalyst-gateway/bin/src/service/common/objects/block.rs new file mode 100644 index 00000000000..516bb69359b --- /dev/null +++ b/catalyst-gateway/bin/src/service/common/objects/block.rs @@ -0,0 +1,28 @@ +use poem_openapi::{types::Example, NewType, Object}; + +#[derive(NewType)] +/// Epoch number. +pub(crate) struct Epoch(pub u32); + +#[derive(NewType)] +/// Slot number. +pub(crate) struct Slot(pub u32); + +#[derive(Object)] +#[oai(example = true)] +/// Block time defined as the pair (epoch, slot). +pub(crate) struct BlockDate { + /// Block's epoch. + pub epoch: Epoch, + /// Block's slot number. + pub slot_id: Slot, +} + +impl Example for BlockDate { + fn example() -> Self { + Self { + epoch: Epoch(1), + slot_id: Slot(5), + } + } +} diff --git a/catalyst-gateway/bin/src/service/common/objects/fragment_status.rs b/catalyst-gateway/bin/src/service/common/objects/fragment_status.rs new file mode 100644 index 00000000000..c7a875e3baf --- /dev/null +++ b/catalyst-gateway/bin/src/service/common/objects/fragment_status.rs @@ -0,0 +1,50 @@ +use poem_openapi::{types::Example, Object, Union}; + +use crate::service::common::objects::{block::BlockDate, hash::Hash}; + +#[derive(Object)] +#[oai(example = false)] +/// Fragment is pending. +pub(crate) struct StatusPending; + +#[derive(Object)] +#[oai(example = true)] +/// Fragment was rejected. +pub(crate) struct StatusRejected { + /// Reason the fragment was rejected. + pub reason: String, +} + +impl Example for StatusRejected { + fn example() -> Self { + Self { + reason: "Transaction malformed".to_string(), + } + } +} + +#[derive(Object)] +#[oai(example = true)] +/// Fragment is included in a block. +pub(crate) struct StatusInABlock { + pub date: BlockDate, + pub hash: Hash, +} + +impl Example for StatusInABlock { + fn example() -> Self { + Self { + date: BlockDate::example(), + hash: Hash::example(), + } + } +} + +#[derive(Union)] +#[oai(one_of = true)] +/// Possible fragment statuses. +pub(crate) enum FragmentStatus { + Pending(StatusPending), + Rejected(StatusRejected), + InABlock(StatusInABlock), +} diff --git a/catalyst-gateway/bin/src/service/common/objects/fragments_batch.rs b/catalyst-gateway/bin/src/service/common/objects/fragments_batch.rs new file mode 100644 index 00000000000..f4768199bf7 --- /dev/null +++ b/catalyst-gateway/bin/src/service/common/objects/fragments_batch.rs @@ -0,0 +1,26 @@ +use poem_openapi::{types::Example, NewType, Object}; +use serde::Deserialize; + +#[derive(NewType, Deserialize)] +/// Hex-encoded fragment's bytes. +pub(crate) struct FragmentDef(String); + +#[derive(Object, Deserialize)] +#[oai(example = true)] +/// Batch of hex-encoded fragments. +pub(crate) struct FragmentsBatch { + /// Fragments are processed sequentially. If this is true, processing is + /// stopped after the first error occurs. + pub fail_fast: bool, + /// Array of hex-encoded fragments bytes. + pub fragments: Vec, +} + +impl Example for FragmentsBatch { + fn example() -> Self { + Self { + fail_fast: false, + fragments: vec![], + } + } +} diff --git a/catalyst-gateway/bin/src/service/common/objects/hash.rs b/catalyst-gateway/bin/src/service/common/objects/hash.rs new file mode 100644 index 00000000000..f8891aee49d --- /dev/null +++ b/catalyst-gateway/bin/src/service/common/objects/hash.rs @@ -0,0 +1,17 @@ +use poem_openapi::{types::Example, Object}; + +#[derive(Object)] +#[oai(example = true)] +/// Blake2b256 hash wrapper. +pub(crate) struct Hash { + /// Blake2b256 hash encoded in hex. + pub hash: String, +} + +impl Example for Hash { + fn example() -> Self { + Self { + hash: "928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202".to_string(), + } + } +} diff --git a/catalyst-gateway/bin/src/service/common/objects/mod.rs b/catalyst-gateway/bin/src/service/common/objects/mod.rs index 624d50012c6..0d9cc29c1e5 100644 --- a/catalyst-gateway/bin/src/service/common/objects/mod.rs +++ b/catalyst-gateway/bin/src/service/common/objects/mod.rs @@ -1,8 +1,12 @@ //! This module contains common and re-usable objects. pub(crate) mod account_votes; +pub(crate) mod block; pub(crate) mod delegate_public_key; pub(crate) mod event_id; +pub(crate) mod fragment_status; +pub(crate) mod fragments_batch; pub(crate) mod fragments_processing_summary; +pub(crate) mod hash; pub(crate) mod stake_public_key; pub(crate) mod voter_group_id; pub(crate) mod voter_info; diff --git a/catalyst-gateway/bin/src/service/common/tags.rs b/catalyst-gateway/bin/src/service/common/tags.rs index b32add60e11..0e4f13398b8 100644 --- a/catalyst-gateway/bin/src/service/common/tags.rs +++ b/catalyst-gateway/bin/src/service/common/tags.rs @@ -4,6 +4,8 @@ use poem_openapi::Tags; /// `OpenAPI` Tags #[derive(Tags)] pub(crate) enum ApiTags { + /// Fragment endpoints + Fragments, /// Health Endpoints Health, /// Information relating to Voter Registration, Delegations and Calculated Voting