From 9935c99d41e84858f4ea78c7882d94c2f9fc5a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 19 Apr 2023 16:11:35 +0200 Subject: [PATCH] nym-api: add service provider endpoint and caching --- Cargo.lock | 1 + .../validator-client/src/nym_api/mod.rs | 6 ++++ .../validator-client/src/nym_api/routes.rs | 2 ++ .../nyxd/traits/sp_directory_query_client.rs | 31 +++++++++++++------ .../service-provider-directory/Cargo.toml | 1 + .../src/response.rs | 11 ++++++- .../service-provider-directory/src/types.rs | 9 +++--- nym-api/Cargo.toml | 1 + nym-api/src/nym_contract_cache/cache/data.rs | 4 +++ nym-api/src/nym_contract_cache/cache/mod.rs | 15 ++++++++- .../src/nym_contract_cache/cache/refresher.rs | 5 +++ nym-api/src/nym_contract_cache/mod.rs | 3 +- nym-api/src/nym_contract_cache/routes.rs | 8 +++++ nym-api/src/support/nyxd/mod.rs | 23 +++++++++++++- 14 files changed, 103 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52d5429eb9..71c8178370 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3877,6 +3877,7 @@ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "schemars", "serde", ] diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 10bea35417..f580d03217 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -15,6 +15,7 @@ use nym_api_requests::models::{ }; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; +use nym_service_provider_directory_common::ServiceInfo; use reqwest::Response; use serde::{Deserialize, Serialize}; use url::Url; @@ -480,6 +481,11 @@ impl Client { ) .await } + + pub async fn get_service_providers(&self) -> Result, NymAPIError> { + self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS) + .await + } } // utility function that should solve the double slash problem in validator API forever. diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 8b6bdeca2f..12722757dc 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -32,3 +32,5 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation"; pub const AVG_UPTIME: &str = "avg_uptime"; pub const STAKE_SATURATION: &str = "stake-saturation"; pub const INCLUSION_CHANCE: &str = "inclusion-probability"; + +pub const SERVICE_PROVIDERS: &str = "service-providers"; diff --git a/common/client-libs/validator-client/src/nyxd/traits/sp_directory_query_client.rs b/common/client-libs/validator-client/src/nyxd/traits/sp_directory_query_client.rs index 3a4fb057f5..3d4b469fbe 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/sp_directory_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/sp_directory_query_client.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use cosmrs::AccountId; use nym_contracts_common::ContractBuildInformation; use nym_service_provider_directory_common::{ - msg::QueryMsg as SpQuery, + msg::QueryMsg as SpQueryMsg, response::{ ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse, }, @@ -14,12 +14,12 @@ use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient}; #[async_trait] pub trait SpDirectoryQueryClient { - async fn query_service_provider_contract(&self, query: SpQuery) -> Result + async fn query_service_provider_contract(&self, query: SpQueryMsg) -> Result where for<'a> T: Deserialize<'a>; async fn get_service_config(&self) -> Result { - self.query_service_provider_contract(SpQuery::Config {}) + self.query_service_provider_contract(SpQueryMsg::Config {}) .await } @@ -27,7 +27,7 @@ pub trait SpDirectoryQueryClient { &self, service_id: ServiceId, ) -> Result { - self.query_service_provider_contract(SpQuery::ServiceId { service_id }) + self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id }) .await } @@ -36,7 +36,7 @@ pub trait SpDirectoryQueryClient { start_after: Option, limit: Option, ) -> Result { - self.query_service_provider_contract(SpQuery::All { limit, start_after }) + self.query_service_provider_contract(SpQueryMsg::All { limit, start_after }) .await } @@ -44,7 +44,7 @@ pub trait SpDirectoryQueryClient { &self, announcer: AccountId, ) -> Result { - self.query_service_provider_contract(SpQuery::ByAnnouncer { + self.query_service_provider_contract(SpQueryMsg::ByAnnouncer { announcer: announcer.to_string(), }) .await @@ -54,12 +54,12 @@ pub trait SpDirectoryQueryClient { &self, nym_address: NymAddress, ) -> Result { - self.query_service_provider_contract(SpQuery::ByNymAddress { nym_address }) + self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address }) .await } async fn get_sp_contract_version(&self) -> Result { - self.query_service_provider_contract(SpQuery::GetContractVersion {}) + self.query_service_provider_contract(SpQueryMsg::GetContractVersion {}) .await } @@ -89,7 +89,7 @@ impl SpDirectoryQueryClient for NyxdClient where C: CosmWasmClient + Send + Sync, { - async fn query_service_provider_contract(&self, query: SpQuery) -> Result + async fn query_service_provider_contract(&self, query: SpQueryMsg) -> Result where for<'a> T: Deserialize<'a>, { @@ -105,3 +105,16 @@ where .await } } + +#[async_trait] +impl SpDirectoryQueryClient for crate::Client +where + C: CosmWasmClient + Send + Sync, +{ + async fn query_service_provider_contract(&self, query: SpQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>, + { + self.nyxd.query_service_provider_contract(query).await + } +} diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml index ea78b91586..14dbf4c792 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml +++ b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] cosmwasm-std = { workspace = true } +schemars = "0.8" serde = { workspace = true, default-features = false, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs index 10e400fc6b..b7d4241762 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/response.rs @@ -1,5 +1,6 @@ use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo}; use cosmwasm_std::Coin; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] @@ -9,7 +10,7 @@ pub struct ServiceInfoResponse { pub service: Option, } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct ServicesListResponse { pub services: Vec, @@ -26,6 +27,14 @@ impl ServicesListResponse { } } +impl From<&[ServiceInfo]> for ServicesListResponse { + fn from(services: &[ServiceInfo]) -> Self { + Self { + services: services.to_vec(), + } + } +} + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[serde(rename_all = "snake_case")] pub struct PagedServicesListResponse { diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs index 4539e8f070..faae77263b 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/types.rs @@ -1,12 +1,13 @@ use std::fmt::{Display, Formatter}; use cosmwasm_std::{Addr, Coin}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; /// The directory of services are indexed by [`ServiceId`]. pub type ServiceId = u32; -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)] pub struct Service { /// The address of the service. pub nym_address: NymAddress, @@ -21,7 +22,7 @@ pub struct Service { } /// The types of addresses supported. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum NymAddress { /// String representation of a nym address, which is of the form @@ -51,7 +52,7 @@ impl Display for NymAddress { } /// The type of services provider supported -#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ServiceType { NetworkRequester, @@ -66,7 +67,7 @@ impl std::fmt::Display for ServiceType { } } -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct ServiceInfo { pub service_id: ServiceId, diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 6c57738067..3ce4a6d9b6 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -82,6 +82,7 @@ nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } +nym-service-provider-directory-common = { path = "../common/cosmwasm-smart-contracts/service-provider-directory" } nym-coconut = { path = "../common/nymcoconut" } nym-sphinx = { path = "../common/nymsphinx" } nym-pemstore = { path = "../common/pemstore" } diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs index 2188373585..263bcc13ca 100644 --- a/nym-api/src/nym_contract_cache/cache/data.rs +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -6,6 +6,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeDetails, RewardingParams, }; +use nym_service_provider_directory_common::ServiceInfo; use std::collections::HashSet; pub(crate) struct ValidatorCacheData { @@ -22,6 +23,8 @@ pub(crate) struct ValidatorCacheData { pub(crate) current_interval: Cache>, pub(crate) mix_to_family: Cache>, + + pub(crate) service_providers: Cache>, } impl ValidatorCacheData { @@ -36,6 +39,7 @@ impl ValidatorCacheData { current_interval: Cache::default(), current_reward_params: Cache::default(), mix_to_family: Cache::default(), + service_providers: Cache::default(), } } } diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index cd885f7a5d..96895f3391 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -5,6 +5,7 @@ use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails, RewardingParams, }; +use nym_service_provider_directory_common::ServiceInfo; use rocket::fairing::AdHoc; use std::{ collections::HashSet, @@ -50,6 +51,7 @@ impl NymContractCache { rewarding_params: RewardingParams, current_interval: Interval, mix_to_family: Vec<(IdentityKey, FamilyHead)>, + services: Option>, ) { match time::timeout(Duration::from_millis(100), self.inner.write()).await { Ok(mut cache) => { @@ -59,7 +61,8 @@ impl NymContractCache { cache.active_set.update(active_set); cache.current_reward_params.update(Some(rewarding_params)); cache.current_interval.update(Some(current_interval)); - cache.mix_to_family.update(mix_to_family) + cache.mix_to_family.update(mix_to_family); + cache.service_providers.update(services.unwrap_or_default()); } Err(err) => { error!("{err}"); @@ -286,6 +289,16 @@ impl NymContractCache { self.mixnode_details(mix_id).await.1 } + pub(crate) async fn services(&self) -> Cache> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.service_providers.clone(), + Err(err) => { + error!("{err}"); + Cache::new(Vec::new()) + } + } + } + pub fn initialised(&self) -> bool { self.initialised.load(Ordering::Relaxed) } diff --git a/nym-api/src/nym_contract_cache/cache/refresher.rs b/nym-api/src/nym_contract_cache/cache/refresher.rs index 4f3241a7aa..420c836a1f 100644 --- a/nym-api/src/nym_contract_cache/cache/refresher.rs +++ b/nym-api/src/nym_contract_cache/cache/refresher.rs @@ -4,6 +4,7 @@ use crate::support::caching::CacheNotification; use anyhow::Result; use nym_mixnet_contract_common::{MixId, MixNodeDetails, RewardedSetNodeStatus}; use nym_task::TaskClient; +use nym_validator_client::nyxd::traits::SpDirectoryQueryClient; use std::{collections::HashMap, sync::atomic::Ordering, time::Duration}; use tokio::sync::watch; use tokio::time; @@ -50,6 +51,9 @@ impl NymContractCacheRefresher { let (rewarded_set, active_set) = Self::collect_rewarded_and_active_set_details(&mixnodes, &rewarded_set_map); + // The service providers are optional + let services = self.nyxd_client.get_all_services().await.ok(); + info!( "Updating validator cache. There are {} mixnodes and {} gateways", mixnodes.len(), @@ -65,6 +69,7 @@ impl NymContractCacheRefresher { rewarding_params, current_interval, mix_to_family, + services, ) .await; diff --git a/nym-api/src/nym_contract_cache/mod.rs b/nym-api/src/nym_contract_cache/mod.rs index e3f4ef267c..c9f82be524 100644 --- a/nym-api/src/nym_contract_cache/mod.rs +++ b/nym-api/src/nym_contract_cache/mod.rs @@ -27,7 +27,8 @@ pub(crate) fn nym_contract_cache_routes(settings: &OpenApiSettings) -> (Vec) -> Json> { Json(cache.current_interval().await.value) } + +#[openapi(tag = "contract-cache")] +#[get("/services")] +pub async fn get_services(cache: &State) -> Json { + let services = cache.services().await.value; + Json(services.as_slice().into()) +} diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 6fe78b69a6..fb0ad5f069 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -25,8 +25,11 @@ use nym_mixnet_contract_common::{ CurrentIntervalResponse, EpochStatus, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, MixId, RewardedSetNodeStatus, }; +use nym_service_provider_directory_common::msg::QueryMsg as SpQueryMsg; use nym_validator_client::nyxd::error::NyxdError; -use nym_validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient}; +use nym_validator_client::nyxd::traits::{ + MixnetQueryClient, MixnetSigningClient, SpDirectoryQueryClient, +}; use nym_validator_client::nyxd::{ cosmwasm_client::types::ExecuteResult, traits::{ @@ -497,3 +500,21 @@ impl DkgQueryClient for Client { self.0.read().await.nyxd.query_dkg_contract(query).await } } + +#[async_trait] +impl SpDirectoryQueryClient for Client { + async fn query_service_provider_contract( + &self, + query: SpQueryMsg, + ) -> std::result::Result + where + for<'a> T: Deserialize<'a>, + { + self.0 + .read() + .await + .nyxd + .query_service_provider_contract(query) + .await + } +}