Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(endpoints): Add 'POST /v0/message' endpoint #95

Merged
merged 8 commits into from
Oct 30, 2023
6 changes: 4 additions & 2 deletions catalyst-gateway/bin/src/service/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ use health::HealthApi;
use poem_openapi::{ContactObject, LicenseObject, OpenApiService, ServerObject};
use registration::RegistrationApi;
use test_endpoints::TestApi;
use v0::V0Api;

mod health;
mod registration;
mod test_endpoints;
mod v0;

/// The name of the API
const API_TITLE: &str = "Catalyst Data Service";
Expand Down Expand Up @@ -58,9 +60,9 @@ const TERMS_OF_SERVICE: &str =
/// Create the `OpenAPI` definition
pub(crate) fn mk_api(
hosts: Vec<String>,
) -> OpenApiService<(TestApi, HealthApi, RegistrationApi), ()> {
) -> OpenApiService<(TestApi, HealthApi, V0Api, RegistrationApi), ()> {
let mut service = OpenApiService::new(
(TestApi, HealthApi, RegistrationApi),
(TestApi, HealthApi, V0Api, RegistrationApi),
API_TITLE,
API_VERSION,
)
Expand Down
44 changes: 44 additions & 0 deletions catalyst-gateway/bin/src/service/api/v0/message_post.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Implementation of the POST /message endpoint

use crate::service::common::{
objects::fragments_processing_summary::FragmentsProcessingSummary,
responses::{
resp_2xx::OK,
resp_4xx::BadRequest,
resp_5xx::{ServerError, ServiceUnavailable},
},
};
use poem_extensions::response;
use poem_extensions::UniResponse::T200;
use poem_openapi::payload::{Binary, Json};

/// All responses
pub(crate) type AllResponses = response! {
200: OK<Json<FragmentsProcessingSummary>>,
400: BadRequest<Json<FragmentsProcessingSummary>>,
500: ServerError,
503: ServiceUnavailable,
};

/// # POST /message
///
/// Message post endpoint.
///
/// So, it will always just return 200.
///
/// In theory it can also return 503 is the service has some startup processing
/// to complete before it is ready to serve requests.
///
/// ## Responses
///
/// * 200 JSON Fragments Processing Summary - Contains information about accepted and rejected
/// fragments.
/// * 400 JSON Fragments Processing Summary - Contains information about accepted and rejected
/// fragments.
/// * 500 Server Error - If anything within this function fails unexpectedly. (Possible but unlikely)
/// * 503 Service Unavailable - Service has not started, do not send other requests.
#[allow(clippy::unused_async)]
pub(crate) async fn endpoint(_message: Binary<Vec<u8>>) -> AllResponses {
// otherwise everything seems to be A-OK
T200(OK(Json(FragmentsProcessingSummary::default())))
}
59 changes: 59 additions & 0 deletions catalyst-gateway/bin/src/service/api/v0/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! `v0` Endpoints
//!
use crate::service::common::tags::ApiTags;
use poem_openapi::{payload::Binary, OpenApi};

mod message_post;

/// `v0` API Endpoints
pub(crate) struct V0Api;

#[OpenApi(prefix_path = "/v0")]
impl V0Api {
#[oai(
path = "/message",
method = "post",
operation_id = "Message",
tag = "ApiTags::Message"
)]
/// Posts a signed transaction.
async fn message_post(&self, message: Binary<Vec<u8>>) -> message_post::AllResponses {
message_post::endpoint(message).await
}
}

/// Need to setup and run a test event db instance
/// To do it you can use the following commands:
/// Prepare docker images
/// ```
/// TODO
/// ```
/// Run event-db container
/// ```
/// TODO
/// ```
/// Also need establish `EVENT_DB_URL` env variable with the following value
/// ```
/// EVENT_DB_URL="postgres://catalyst-event-dev:CHANGE_ME@localhost/CatalystEventDev"
/// ```
/// [readme](https://github.com/input-output-hk/catalyst-voices/blob/main/catalyst-gateway/event-db/Readme.md)

#[cfg(test)]
mod tests {
use crate::{service::poem_service::tests::mk_test_app, state::State};
use poem::http::StatusCode;
use std::sync::Arc;

#[tokio::test]
async fn v0_test() {
let state = Arc::new(State::new(None).await.unwrap());
let app = mk_test_app(&state);

let resp = app
.post("/api/v0/message")
.body("00000000000".to_string())
.send()
.await;
resp.assert_status(StatusCode::BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Define information about fragments that were processed.

use poem_openapi::{types::Example, Enum, NewType, Object};

#[derive(NewType)]
#[oai(example = true)]
/// Unique ID of a fragment.
///
/// A fragment is the binary representation of a signed transaction.
/// The fragment ID is the hex-encoded representation of 32 bytes.
pub(crate) struct FragmentId(String);

impl Example for FragmentId {
fn example() -> Self {
Self("7db6f91f3c92c0aef7b3dd497e9ea275229d2ab4dba6a1b30ce6b32db9c9c3b2".into())
}
}
#[derive(Enum)]
/// The reason for which a fragment was rejected.
pub(crate) enum ReasonRejected {
/// This fragment was already processed by the node.
FragmentAlreadyInLog,
/// This fragment failed validation.
FragmentInvalid,
/// One of the previous fragments was rejected and `fail_fast` is enabled.
PreviousFragmentInvalid,
/// One of the mempools rejected this fragment due to reaching capacity limit.
PoolOverflow,
}

#[derive(Object)]
#[oai(example = true)]
/// Information about a rejected fragment.
pub(crate) struct RejectedFragment {
#[oai(rename = "id")]
#[oai(validator(max_length = 64, min_length = 64, pattern = "[0-9a-f]{64}"))]
/// The ID of the rejected fragment.
///
/// Currently, the hex encoded bytes that represent the fragment ID. In the
/// future, this might change to including the prefix "0x".
fragment_id: FragmentId,
Mr-Leshiy marked this conversation as resolved.
Show resolved Hide resolved
/// The number of the pool that caused this error.
pool_number: usize,
/// The reason why this fragment was rejected.
reason: ReasonRejected,
}

impl Example for RejectedFragment {
fn example() -> Self {
Self {
fragment_id: FragmentId::example(),
pool_number: 1,
reason: ReasonRejected::FragmentAlreadyInLog,
}
}
}

#[derive(Object, Default)]
#[oai(example = true)]
/// Information about whether a message was accepted or rejected.
pub(crate) struct FragmentsProcessingSummary {
/// IDs of accepted fragments.
accepted: Vec<FragmentId>,
/// Detailed information about rejected fragments.
rejected: Vec<RejectedFragment>,
}

impl Example for FragmentsProcessingSummary {
fn example() -> Self {
Self {
accepted: vec![FragmentId::example()],
rejected: vec![RejectedFragment::example()],
}
}
}
1 change: 1 addition & 0 deletions catalyst-gateway/bin/src/service/common/objects/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! This module contains common and re-usable objects.
pub(crate) mod delegate_public_key;
pub(crate) mod event_id;
pub(crate) mod fragments_processing_summary;
pub(crate) mod stake_public_key;
pub(crate) mod voter_group_id;
pub(crate) mod voter_info;
Expand Down
2 changes: 2 additions & 0 deletions catalyst-gateway/bin/src/service/common/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use poem_openapi::Tags;
pub(crate) enum ApiTags {
/// Health Endpoints
Health,
/// Message Endpoints
Message,
/// Information relating to Voter Registration, Delegations and Calculated Voting Power.
Registration,
/// Test Endpoints (Not part of the API)
Expand Down