From 8cadb72d7b9d8aeea0ecb6ef3138f0f0cf3b9efa Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:14:44 +0100 Subject: [PATCH 1/2] feat: added openapi documentation and router --- registry/Cargo.lock | 218 +++++++++++++++++++++++++++- registry/Cargo.toml | 6 + registry/config.toml | 9 +- registry/justfile | 8 + registry/src/api/handlers.rs | 165 +++++++++++++++++++++ registry/src/api/mod.rs | 106 +++++--------- registry/src/api/spec.rs | 17 ++- registry/src/main.rs | 5 + registry/src/primitives/mod.rs | 6 +- registry/src/primitives/registry.rs | 27 +++- 10 files changed, 475 insertions(+), 92 deletions(-) create mode 100644 registry/justfile create mode 100644 registry/src/api/handlers.rs diff --git a/registry/Cargo.lock b/registry/Cargo.lock index a664dca..a6d683c 100644 --- a/registry/Cargo.lock +++ b/registry/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1130,9 +1130,13 @@ dependencies = [ "thiserror 2.0.9", "tokio", "tokio-stream", + "tower-http", "tracing", "tracing-subscriber", "url", + "utoipa", + "utoipa-axum", + "utoipa-swagger-ui", ] [[package]] @@ -1347,6 +1351,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -1925,6 +1938,16 @@ dependencies = [ "safe_arith", ] +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -2901,6 +2924,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -2972,6 +3001,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -3923,6 +3962,40 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.95", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2 0.10.8", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4049,6 +4122,15 @@ name = "safe_arith" version = "0.1.0" source = "git+https://github.com/sigp/lighthouse?tag=v6.0.1#0d90135047519f4c2ee586d50e560f7bb2ff9b10" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scale-info" version = "2.11.6" @@ -4369,6 +4451,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -5057,6 +5145,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -5187,6 +5292,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -5262,6 +5373,61 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utoipa" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +dependencies = [ + "indexmap 2.7.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.95", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161166ec520c50144922a625d8bc4925cc801b2dda958ab69878527c0e5c5d61" +dependencies = [ + "axum", + "base64 0.22.1", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + [[package]] name = "valuable" version = "0.1.0" @@ -5295,6 +5461,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5452,6 +5628,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5807,3 +5992,34 @@ dependencies = [ "quote", "syn 2.0.95", ] + +[[package]] +name = "zip" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.7.0", + "memchr", + "thiserror 2.0.9", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] diff --git a/registry/Cargo.toml b/registry/Cargo.toml index e907b97..f6bded9 100644 --- a/registry/Cargo.toml +++ b/registry/Cargo.toml @@ -28,6 +28,7 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt"] } # web reqwest = { version = "0.12", features = ["json", "stream"] } +tower-http = { version = "0.6.2", features = ["cors", "trace", "timeout"] } axum = "0.8.1" # db @@ -42,6 +43,11 @@ beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus", bls = { git = "https://github.com/sigp/lighthouse", tag = "v6.0.1", features = ["supranational"] } sha2 = { version = "0.10", features = ["asm"] } +# docs +utoipa-axum = "0.2.0" +utoipa = { version = "5.3.1", features = ["axum_extras"] } +utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] } + # types url = { version = "2.5.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } diff --git a/registry/config.toml b/registry/config.toml index 6ec4161..9f4b660 100644 --- a/registry/config.toml +++ b/registry/config.toml @@ -7,10 +7,5 @@ db_url = "postgres://boltadmin:$PASSWORD@remotebeast:5412/bolt_registry" # Beacon node connection beacon_url = "http://remotebeast:44400" -# Data sources -# These data sources take precedence over the registry. Their values are canonical. -# -# Lido CCCP through the keysAPI: -[sources.lido] -type = "kapi" -base_url = "http://34.88.187.80:30303/v1/preconfs/lido-bolt/validators" +# Lido keys API +keys_api_url = "http://34.88.187.80:30303/v1/preconfs/lido-bolt/validators" diff --git a/registry/justfile b/registry/justfile new file mode 100644 index 0000000..9ddd4ea --- /dev/null +++ b/registry/justfile @@ -0,0 +1,8 @@ +default: + @just --list --unsorted + +# Run the development server locally +dev loglevel='debug': + @echo "Starting bolt-registry in development mode with loglevel: {{loglevel}}" + RUST_LOG=bolt_registry={{loglevel}} cargo watch -x run + diff --git a/registry/src/api/handlers.rs b/registry/src/api/handlers.rs new file mode 100644 index 0000000..a304a06 --- /dev/null +++ b/registry/src/api/handlers.rs @@ -0,0 +1,165 @@ +use std::sync::Arc; + +use alloy::primitives::Address; +use axum::{extract::{Path, Query, State}, response::IntoResponse, Json}; +use utoipa::OpenApi; + +use crate::primitives::{ + registry::{ + Deregistration, DeregistrationBatch, Lookahead, Operator, + Registration, RegistrationBatch, RegistryEntry + }, + BlsPublicKey, +}; + +use super::{ + DiscoverySpec, + RegistryApi, + ValidatorFilter, + ValidatorSpec, + DISCOVERY_LOOKAHEAD_PATH, + DISCOVERY_OPERATORS_PATH, + DISCOVERY_OPERATOR_PATH, + DISCOVERY_VALIDATORS_PATH, + DISCOVERY_VALIDATOR_PATH, + VALIDATORS_DEREGISTER_PATH, + VALIDATORS_REGISTER_PATH, + VALIDATORS_REGISTRATIONS_PATH, +}; + +#[derive(OpenApi, Debug)] +#[openapi( + info( + title = "Bolt Registry API", + description = "This API provides access to Bolt protocol validators and operators information." + ), + components(schemas( + Registration, + Deregistration, + RegistryEntry, + Operator, + Lookahead, + RegistrationBatch, + DeregistrationBatch, + )), + paths( + register, + deregister, + get_registrations, + get_validators, + get_validator_by_pubkey, + get_operators, + get_operator_by_signer, + get_lookahead, + ) +)] +pub(crate) struct ApiDoc; + +/// Registers a new validator. +#[utoipa::path(post, path = VALIDATORS_REGISTER_PATH, responses( + (status = 200, description = "Success") +))] +pub(crate) async fn register( + State(api): State>, + Json(registration): Json, +) -> impl IntoResponse { + api.register(registration).await +} + +/// Deregisters a validator. +#[utoipa::path(post, path = VALIDATORS_DEREGISTER_PATH, responses( + (status = 200, description = "Success") +))] +pub(crate) async fn deregister( + State(api): State>, + Json(deregistration): Json, +) -> impl IntoResponse { + api.deregister(deregistration).await +} + +/// Gets all validator registrations. +#[utoipa::path(get, path = VALIDATORS_REGISTRATIONS_PATH, responses( + (status = 200, description = "Success", body = Vec) +))] +pub(crate) async fn get_registrations(State(api): State>) -> impl IntoResponse { + api.get_registrations().await.map(Json) +} + +/// Gets all validators. +#[utoipa::path(get, path = DISCOVERY_VALIDATORS_PATH, + params( + ("pubkeys" = Option>, Query, description = "The public keys of the validators to get."), + ("indices" = Option>, Query, description = "The indices of the validators to get."), + ), + responses( + (status = 200, description = "Success", body = Vec), + ) +)] +pub(crate) async fn get_validators( + State(api): State>, + Query(filter): Query, +) -> impl IntoResponse { + match (filter.pubkeys, filter.indices) { + (Some(pubkeys), None) => api.get_validators_by_pubkeys(pubkeys).await.map(Json), + (None, Some(indices)) => api.get_validators_by_indices(indices).await.map(Json), + _ => api.get_validators().await.map(Json), + } +} + +/// Gets a validator by its public key. +#[utoipa::path( + get, + path = DISCOVERY_VALIDATOR_PATH, + params(("pubkey" = BlsPublicKey, description = "The public key of the validator to get.")), + responses( + (status = 200, description = "Success", body = RegistryEntry), + (status = 404, description = "Not Found", body = String, example = "Not found"), + ) +)] +pub(crate) async fn get_validator_by_pubkey( + State(api): State>, + Path(pubkey): Path, +) -> impl IntoResponse { + api.get_validator_by_pubkey(pubkey).await.map(Json) +} + +/// Gets all operators. +#[utoipa::path(get, path = DISCOVERY_OPERATORS_PATH, responses( + (status = 200, description = "Success", body = Vec) +))] +pub(crate) async fn get_operators(State(api): State>) -> impl IntoResponse { + api.get_operators().await.map(Json) +} + +/// Gets an operator by its signer. +#[utoipa::path( + get, + path = DISCOVERY_OPERATOR_PATH, + params(("signer" = String, description = "The address of the operator to get")), + responses( + (status = 200, description = "Success", body = Operator), + (status = 404, description = "Not Found", body = String, example = "Not found"), + ) +)] +pub(crate) async fn get_operator_by_signer( + State(api): State>, + Path(signer): Path
, +) -> impl IntoResponse { + api.get_operator_by_signer(signer).await.map(Json) +} + +/// Gets the lookahead for an epoch. +#[utoipa::path( + get, + path = DISCOVERY_LOOKAHEAD_PATH, + params(("epoch" = u64, description = "The epoch to get the lookahead for.")), + responses( + (status = 200, description = "Success", body = Lookahead), + ) +)] +pub(crate) async fn get_lookahead( + State(api): State>, + Path(epoch): Path, +) -> impl IntoResponse { + api.get_lookahead(epoch).await.map(Json) +} \ No newline at end of file diff --git a/registry/src/api/mod.rs b/registry/src/api/mod.rs index 21e3422..c1f7013 100644 --- a/registry/src/api/mod.rs +++ b/registry/src/api/mod.rs @@ -4,12 +4,8 @@ use std::{io, net::SocketAddr, sync::Arc, time::Duration}; use alloy::primitives::Address; -use axum::{ - extract::{Path, Query, State}, - response::IntoResponse, - routing::{get, post}, - Json, Router, -}; +use axum::routing::{get, post}; +use reqwest::Method; use serde::Deserialize; use tokio::{ net::TcpListener, @@ -19,7 +15,11 @@ use tokio::{ }, task::JoinHandle, }; +use tower_http::{cors::CorsLayer, timeout::TimeoutLayer, trace::TraceLayer}; use tracing::error; +use utoipa::OpenApi; +use utoipa_axum::router::OpenApiRouter; +use utoipa_swagger_ui::SwaggerUi; use crate::primitives::{ registry::{ @@ -28,6 +28,9 @@ use crate::primitives::{ BlsPublicKey, }; +/// API handler functions +mod handlers; + pub(crate) mod actions; use actions::{Action, ActionStream}; @@ -90,16 +93,31 @@ impl RegistryApi { let listen_addr = self.cfg.listen_addr; let state = Arc::new(self); - let router = Router::new() - .route(VALIDATORS_REGISTER_PATH, post(Self::register)) - .route(VALIDATORS_DEREGISTER_PATH, post(Self::deregister)) - .route(VALIDATORS_REGISTRATIONS_PATH, get(Self::get_registrations)) - .route(DISCOVERY_VALIDATORS_PATH, get(Self::get_validators)) - .route(DISCOVERY_VALIDATOR_PATH, get(Self::get_validator_by_pubkey)) - .route(DISCOVERY_OPERATORS_PATH, get(Self::get_operators)) - .route(DISCOVERY_OPERATOR_PATH, get(Self::get_operator_by_signer)) - .route(DISCOVERY_LOOKAHEAD_PATH, get(Self::get_lookahead)) - .with_state(state); + // All API routes are defined here: + let (router, api_docs) = OpenApiRouter::with_openapi(handlers::ApiDoc::openapi()) + .route(VALIDATORS_REGISTER_PATH, post(handlers::register)) + .route(VALIDATORS_DEREGISTER_PATH, post(handlers::deregister)) + .route(VALIDATORS_REGISTRATIONS_PATH, get(handlers::get_registrations)) + .route(DISCOVERY_VALIDATORS_PATH, get(handlers::get_validators)) + .route(DISCOVERY_VALIDATOR_PATH, get(handlers::get_validator_by_pubkey)) + .route(DISCOVERY_OPERATORS_PATH, get(handlers::get_operators)) + .route(DISCOVERY_OPERATOR_PATH, get(handlers::get_operator_by_signer)) + .route(DISCOVERY_LOOKAHEAD_PATH, get(handlers::get_lookahead)) + .with_state(state) + .split_for_parts(); + + // This is the final router that includes the API routes, + // middlewares and the Swagger UI: + let router = router + .layer(TraceLayer::new_for_http()) + .layer(TimeoutLayer::new(spec::MAX_REQUEST_TIMEOUT)) + .layer( + CorsLayer::new() + .allow_origin(tower_http::cors::Any) + .allow_methods([Method::GET, Method::POST]) + .allow_headers(tower_http::cors::Any), + ) + .merge(SwaggerUi::new("/docs").url("/api-docs/openapi.json", api_docs)); let listener = TcpListener::bind(&listen_addr).await?; @@ -110,60 +128,6 @@ impl RegistryApi { })) } - async fn register( - State(api): State>, - Json(registration): Json, - ) -> impl IntoResponse { - api.register(registration).await - } - - async fn deregister( - State(api): State>, - Json(deregistration): Json, - ) -> impl IntoResponse { - api.deregister(deregistration).await - } - - async fn get_registrations(State(api): State>) -> impl IntoResponse { - api.get_registrations().await.map(Json) - } - - async fn get_validators( - State(api): State>, - Query(filter): Query, - ) -> impl IntoResponse { - match (filter.pubkeys, filter.indices) { - (Some(pubkeys), None) => api.get_validators_by_pubkeys(pubkeys).await.map(Json), - (None, Some(indices)) => api.get_validators_by_indices(indices).await.map(Json), - _ => api.get_validators().await.map(Json), - } - } - - async fn get_validator_by_pubkey( - State(api): State>, - Path(pubkey): Path, - ) -> impl IntoResponse { - api.get_validator_by_pubkey(pubkey).await.map(Json) - } - - async fn get_operators(State(api): State>) -> impl IntoResponse { - api.get_operators().await.map(Json) - } - - async fn get_operator_by_signer( - State(api): State>, - Path(signer): Path
, - ) -> impl IntoResponse { - api.get_operator_by_signer(signer).await.map(Json) - } - - async fn get_lookahead( - State(api): State>, - Path(epoch): Path, - ) -> impl IntoResponse { - api.get_lookahead(epoch).await.map(Json) - } - async fn send_action(&self, action: Action) -> Result<(), SendTimeoutError> { self.tx.send_timeout(action, self.cfg.send_timeout).await } @@ -244,7 +208,7 @@ impl spec::DiscoverySpec for RegistryApi { #[tracing::instrument(skip(self))] async fn get_validator_by_pubkey( &self, - pubkey: crate::primitives::BlsPublicKey, + pubkey: BlsPublicKey, ) -> Result { let (tx, rx) = oneshot::channel(); diff --git a/registry/src/api/spec.rs b/registry/src/api/spec.rs index 4ad6d87..917d260 100644 --- a/registry/src/api/spec.rs +++ b/registry/src/api/spec.rs @@ -1,6 +1,8 @@ //! The API specification for the registry, and its errors. Contains 2 sub-specs: [`ValidatorSpec`] //! and [`DiscoverySpec`]. The [`ApiSpec`] trait combines both of these. +use std::time::Duration; + use alloy::primitives::Address; use axum::{http::StatusCode, response::IntoResponse, Json}; use serde::{Deserialize, Serialize}; @@ -20,6 +22,9 @@ use crate::{ }, }; +/// The maximum request timeout for Bolt Registry API. +pub(super) const MAX_REQUEST_TIMEOUT: Duration = Duration::from_secs(30); + // validator endpoints pub(super) const VALIDATORS_REGISTER_PATH: &str = "/registry/v1/validators/register"; pub(super) const VALIDATORS_DEREGISTER_PATH: &str = "/registry/v1/validators/deregister"; @@ -115,11 +120,11 @@ impl IntoResponse for RegistryError { } fn json_error_response(status: StatusCode, message: &str) -> impl IntoResponse { - #[derive(Serialize, Deserialize)] - struct ErrorBody<'a> { - code: u16, - message: &'a str, - } - (status, Json(ErrorBody { code: status.as_u16(), message })).into_response() } + +#[derive(Serialize, Deserialize)] +struct ErrorBody<'a> { + code: u16, + message: &'a str, +} diff --git a/registry/src/main.rs b/registry/src/main.rs index 172cf77..4942105 100644 --- a/registry/src/main.rs +++ b/registry/src/main.rs @@ -1,3 +1,8 @@ +#![doc = include_str!("../README.md")] +#![warn(missing_debug_implementations, missing_docs, rustdoc::all)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + //! Entrypoint for the registry server binary. use client::BeaconClient; diff --git a/registry/src/primitives/mod.rs b/registry/src/primitives/mod.rs index ef18caf..b904790 100644 --- a/registry/src/primitives/mod.rs +++ b/registry/src/primitives/mod.rs @@ -3,11 +3,15 @@ use derive_more::derive::{Deref, DerefMut, From}; use ethereum_consensus::crypto::PublicKey; use serde::{Deserialize, Serialize}; use sha2::{Digest as _, Sha256}; +use utoipa::ToSchema; pub(crate) mod beacon; pub(crate) mod registry; -#[derive(Debug, Clone, Serialize, Deserialize, Deref, DerefMut, From, PartialEq, Eq, Hash)] +#[derive( + Debug, Clone, Serialize, Deserialize, Deref, DerefMut, From, PartialEq, Eq, Hash, ToSchema, +)] +#[schema(value_type = String)] pub(crate) struct BlsPublicKey(bls::PublicKey); impl BlsPublicKey { diff --git a/registry/src/primitives/registry.rs b/registry/src/primitives/registry.rs index d1a7120..cc92fce 100644 --- a/registry/src/primitives/registry.rs +++ b/registry/src/primitives/registry.rs @@ -4,15 +4,17 @@ use alloy::primitives::{Address, U256}; use serde::{Deserialize, Serialize}; use sha2::{Digest as _, Sha256}; use url::Url; +use utoipa::ToSchema; use super::{BlsPublicKey, BlsSignature, Digest}; /// A batch registration of validators. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct RegistrationBatch { /// Validators being registered. pub(crate) validator_pubkeys: Vec, /// Operator that can sign commitments on behalf of the validators. + #[schema(value_type = String)] pub(crate) operator: Address, /// Gas limit reserved for commitments. pub(crate) gas_limit: u64, @@ -21,6 +23,7 @@ pub(crate) struct RegistrationBatch { /// If set to 0, never expires pub(crate) expiry: u64, // UNIX timestamp value in seconds /// Signatures would be: sign(digest(`operator` + `gas_limit` + `expiry`)) + #[schema(value_type = Vec)] pub(crate) signatures: Vec, } @@ -61,30 +64,34 @@ impl RegistrationBatch { } /// A single registration of a validator. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct Registration { /// Validator being registered. pub(crate) validator_pubkey: BlsPublicKey, /// Index of the validator in the beacon chain. pub(crate) validator_index: u64, /// Operator that can sign commitments on behalf of the validator. + #[schema(value_type = String)] pub(crate) operator: Address, /// Gas limit reserved for commitments. pub(crate) gas_limit: u64, /// The expiry of the registration. pub(crate) expiry: u64, /// The BLS signature of the validator on the registration. + #[schema(value_type = Option)] pub(crate) signature: Option, } /// A batch deregistration of validators. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct DeregistrationBatch { /// Validators being de-registered. pub(crate) validator_pubkeys: Vec, /// Not strictly needed, but will determine signature digest. + #[schema(value_type = String)] pub(crate) operator: Address, /// Signatures would be: sign(digest(operator)) + #[schema(value_type = Vec)] pub(crate) signatures: Vec, } @@ -104,31 +111,39 @@ impl DeregistrationBatch { } /// A single deregistration of a validator. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct Deregistration { /// Validator being de-registered. pub(crate) validator_pubkey: BlsPublicKey, /// Operator that can sign commitments on behalf of the validator. + #[schema(value_type = String)] pub(crate) operator: Address, /// The BLS signature of the validator on the de-registration. + #[schema(value_type = String)] pub(crate) signature: BlsSignature, } /// An entry in the validator registry. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct RegistryEntry { pub(crate) validator_pubkey: BlsPublicKey, + #[schema(value_type = String)] pub(crate) operator: Address, pub(crate) gas_limit: u64, + #[schema(value_type = String)] pub(crate) rpc_endpoint: Url, } /// An operator in the registry. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub(crate) struct Operator { + #[schema(value_type = String)] pub(crate) signer: Address, + #[schema(value_type = String)] pub(crate) rpc_endpoint: Url, + #[schema(value_type = Vec)] pub(crate) collateral_tokens: Vec
, + #[schema(value_type = Vec)] pub(crate) collateral_amounts: Vec, } From 6fd6e04a3e855813387849eefec9ee06dd326a6a Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:25:48 +0100 Subject: [PATCH 2/2] chore: add request body --- registry/src/api/handlers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/src/api/handlers.rs b/registry/src/api/handlers.rs index a304a06..116bb73 100644 --- a/registry/src/api/handlers.rs +++ b/registry/src/api/handlers.rs @@ -56,7 +56,7 @@ use super::{ pub(crate) struct ApiDoc; /// Registers a new validator. -#[utoipa::path(post, path = VALIDATORS_REGISTER_PATH, responses( +#[utoipa::path(post, path = VALIDATORS_REGISTER_PATH, request_body = RegistrationBatch, responses( (status = 200, description = "Success") ))] pub(crate) async fn register( @@ -67,7 +67,7 @@ pub(crate) async fn register( } /// Deregisters a validator. -#[utoipa::path(post, path = VALIDATORS_DEREGISTER_PATH, responses( +#[utoipa::path(post, path = VALIDATORS_DEREGISTER_PATH, request_body = DeregistrationBatch, responses( (status = 200, description = "Success") ))] pub(crate) async fn deregister(