diff --git a/Cargo.lock b/Cargo.lock index 08bb928be1..8d8a90100e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3069,6 +3069,7 @@ dependencies = [ "nym-multisig-contract-common", "nym-node-tester-utils", "nym-pemstore", + "nym-service-provider-directory-common", "nym-sphinx", "nym-task", "nym-topology", @@ -3210,6 +3211,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-multisig-contract-common", "nym-network-defaults", + "nym-service-provider-directory-common", "nym-validator-client", "nym-vesting-contract-common", "rand 0.6.5", @@ -3898,6 +3900,7 @@ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "schemars", "serde", ] @@ -4251,6 +4254,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-multisig-contract-common", "nym-network-defaults", + "nym-service-provider-directory-common", "nym-vesting-contract", "nym-vesting-contract-common", "prost 0.10.4", diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 0c7b2004ad..f6327630db 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -18,6 +18,7 @@ nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-c nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } +nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" } nym-vesting-contract = { path = "../../../contracts/vesting" } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } @@ -63,6 +64,10 @@ name = "offline_signing" # (traits would need to be moved around and refactored themselves) required-features = ["nyxd-client"] +[[example]] +name = "query_service_provider_directory" +required-features = ["nyxd-client"] + [features] nyxd-client = [ "async-trait", diff --git a/common/client-libs/validator-client/examples/query_service_provider_directory.rs b/common/client-libs/validator-client/examples/query_service_provider_directory.rs new file mode 100644 index 0000000000..12369b2696 --- /dev/null +++ b/common/client-libs/validator-client/examples/query_service_provider_directory.rs @@ -0,0 +1,43 @@ +use std::str::FromStr; + +use cosmrs::AccountId; +use nym_network_defaults::{setup_env, NymNetworkDetails}; +use nym_service_provider_directory_common::NymAddress; +use nym_validator_client::nyxd::traits::SpDirectoryQueryClient; + +#[tokio::main] +async fn main() { + setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap())); + let network_details = NymNetworkDetails::new_from_env(); + let config = + nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap(); + let client = nym_validator_client::Client::new_query(config).unwrap(); + + let config = client.nyxd.get_service_config().await.unwrap(); + println!("config: {config:?}"); + + let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap(); + println!("services (paged): {services_paged:#?}"); + + let services = client.nyxd.get_all_services().await.unwrap(); + println!("services: {services:#?}"); + + let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap(); + let services_by_announcer = client + .nyxd + .get_services_by_announcer(announcer) + .await + .unwrap(); + println!("services (by announcer): {services_by_announcer:#?}"); + + let nym_address = NymAddress::new("foo.bar@gateway"); + let services_by_nym_address = client + .nyxd + .get_services_by_nym_address(nym_address) + .await + .unwrap(); + assert_eq!(services_by_announcer, services_by_nym_address); + + let service_info = client.nyxd.get_service_info(1).await; + println!("service info: {service_info:#?}"); +} diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index fe7ded3e5b..570d94f986 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -117,7 +117,7 @@ async fn test_nyxd_connection( ); code == 18 } - Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => { + Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => { log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red()); false } diff --git a/common/client-libs/validator-client/src/nym_api/error.rs b/common/client-libs/validator-client/src/nym_api/error.rs index 1c3f7b11a4..34053daeaf 100644 --- a/common/client-libs/validator-client/src/nym_api/error.rs +++ b/common/client-libs/validator-client/src/nym_api/error.rs @@ -9,6 +9,9 @@ pub enum NymAPIError { source: reqwest::Error, }, + #[error("Not found")] + NotFound, + #[error("Request failed with error message - {0}")] GenericRequestFailure(String), 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..a98ce62754 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -15,7 +15,8 @@ use nym_api_requests::models::{ }; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; -use reqwest::Response; +use nym_service_provider_directory_common::ServiceInfo; +use reqwest::{Response, StatusCode}; use serde::{Deserialize, Serialize}; use url::Url; @@ -76,6 +77,8 @@ impl Client { let res = self.send_get_request(path, params).await?; if res.status().is_success() { Ok(res.json().await?) + } else if res.status() == StatusCode::NOT_FOUND { + Err(NymAPIError::NotFound) } else { Err(NymAPIError::GenericRequestFailure(res.text().await?)) } @@ -480,6 +483,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/error.rs b/common/client-libs/validator-client/src/nyxd/error.rs index 2a82773edb..5032193f98 100644 --- a/common/client-libs/validator-client/src/nyxd/error.rs +++ b/common/client-libs/validator-client/src/nyxd/error.rs @@ -21,8 +21,8 @@ use std::{io, time::Duration}; #[derive(Debug, Error)] pub enum NyxdError { - #[error("No contract address is available to perform the call")] - NoContractAddressAvailable, + #[error("No contract address is available to perform the call: {0}")] + NoContractAddressAvailable(String), #[error(transparent)] WalletError(#[from] DirectSecp256k1HdWalletError), diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index a37354f462..f7629abbd2 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -67,6 +67,7 @@ pub struct Config { pub(crate) group_contract_address: Option, pub(crate) multisig_contract_address: Option, pub(crate) coconut_dkg_contract_address: Option, + pub(crate) service_provider_contract_address: Option, // TODO: add this in later commits // pub(crate) gas_price: GasPrice, } @@ -131,6 +132,13 @@ impl Config { details.contracts.coconut_dkg_contract_address.as_ref(), prefix, )?, + service_provider_contract_address: Self::parse_optional_account( + details + .contracts + .service_provider_directory_contract_address + .as_ref(), + prefix, + )?, }) } } @@ -246,6 +254,10 @@ impl NyxdClient { self.config.multisig_contract_address = Some(address); } + pub fn set_service_provider_contract_address(&mut self, address: AccountId) { + self.config.service_provider_contract_address = Some(address); + } + // TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits // note: what unwrap is doing here is just moving a failure that would have normally // occurred in `connect` when attempting to parse an empty address, @@ -304,6 +316,11 @@ impl NyxdClient { self.config.coconut_dkg_contract_address.as_ref().unwrap() } + // The service provider directory contract is optional, so we return an Option not a Result + pub fn service_provider_contract_address(&self) -> Option<&AccountId> { + self.config.service_provider_contract_address.as_ref() + } + pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) { self.simulated_gas_multiplier = multiplier; } diff --git a/common/client-libs/validator-client/src/nyxd/traits/mod.rs b/common/client-libs/validator-client/src/nyxd/traits/mod.rs index de075ff7f8..acac11ca0c 100644 --- a/common/client-libs/validator-client/src/nyxd/traits/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/traits/mod.rs @@ -16,15 +16,20 @@ mod mixnet_signing_client; mod multisig_signing_client; mod vesting_signing_client; +mod sp_directory_query_client; +mod sp_directory_signing_client; + pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient; pub use dkg_query_client::DkgQueryClient; pub use group_query_client::GroupQueryClient; pub use mixnet_query_client::MixnetQueryClient; pub use multisig_query_client::MultisigQueryClient; +pub use sp_directory_query_client::SpDirectoryQueryClient; pub use vesting_query_client::VestingQueryClient; pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; pub use dkg_signing_client::DkgSigningClient; pub use mixnet_signing_client::MixnetSigningClient; pub use multisig_signing_client::MultisigSigningClient; +pub use sp_directory_signing_client::SpDirectorySigningClient; pub use vesting_signing_client::VestingSigningClient; 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 new file mode 100644 index 0000000000..3d4b469fbe --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/traits/sp_directory_query_client.rs @@ -0,0 +1,120 @@ +use async_trait::async_trait; +use cosmrs::AccountId; +use nym_contracts_common::ContractBuildInformation; +use nym_service_provider_directory_common::{ + msg::QueryMsg as SpQueryMsg, + response::{ + ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse, + }, + NymAddress, ServiceId, ServiceInfo, +}; +use serde::Deserialize; + +use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient}; + +#[async_trait] +pub trait SpDirectoryQueryClient { + 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(SpQueryMsg::Config {}) + .await + } + + async fn get_service_info( + &self, + service_id: ServiceId, + ) -> Result { + self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id }) + .await + } + + async fn get_services_paged( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query_service_provider_contract(SpQueryMsg::All { limit, start_after }) + .await + } + + async fn get_services_by_announcer( + &self, + announcer: AccountId, + ) -> Result { + self.query_service_provider_contract(SpQueryMsg::ByAnnouncer { + announcer: announcer.to_string(), + }) + .await + } + + async fn get_services_by_nym_address( + &self, + nym_address: NymAddress, + ) -> Result { + self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address }) + .await + } + + async fn get_sp_contract_version(&self) -> Result { + self.query_service_provider_contract(SpQueryMsg::GetContractVersion {}) + .await + } + + async fn get_all_services(&self) -> Result, NyxdError> { + let mut services = Vec::new(); + let mut start_after = None; + + loop { + let mut paged_response = self.get_services_paged(start_after.take(), None).await?; + + let last_id = paged_response.services.last().map(|serv| serv.service_id); + services.append(&mut paged_response.services); + + if let Some(start_after_res) = last_id { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(services) + } +} + +#[async_trait] +impl SpDirectoryQueryClient for NyxdClient +where + C: CosmWasmClient + Send + Sync, +{ + async fn query_service_provider_contract(&self, query: SpQueryMsg) -> Result + where + for<'a> T: Deserialize<'a>, + { + self.client + .query_contract_smart( + self.service_provider_contract_address().ok_or( + NyxdError::NoContractAddressAvailable( + "service provider directory contract".to_string(), + ), + )?, + &query, + ) + .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/client-libs/validator-client/src/nyxd/traits/sp_directory_signing_client.rs b/common/client-libs/validator-client/src/nyxd/traits/sp_directory_signing_client.rs new file mode 100644 index 0000000000..722097a99b --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/traits/sp_directory_signing_client.rs @@ -0,0 +1,111 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use nym_service_provider_directory_common::{ + msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType, +}; + +use crate::nyxd::{ + coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient, + SigningCosmWasmClient, +}; + +#[async_trait] +pub trait SpDirectorySigningClient { + async fn exectute_service_provider_directory_contract( + &self, + fee: Option, + msg: SpExecuteMsg, + funds: Vec, + ) -> Result; + + async fn announce_service_provider( + &self, + nym_address: NymAddress, + service_type: ServiceType, + deposit: Coin, + fee: Option, + ) -> Result { + self.exectute_service_provider_directory_contract( + fee, + SpExecuteMsg::Announce { + nym_address, + service_type, + }, + vec![deposit], + ) + .await + } + + async fn delete_service_provider_by_id( + &self, + service_id: ServiceId, + fee: Option, + ) -> Result { + self.exectute_service_provider_directory_contract( + fee, + SpExecuteMsg::DeleteId { service_id }, + vec![], + ) + .await + } + + async fn delete_service_provider_by_nym_address( + &self, + nym_address: NymAddress, + fee: Option, + ) -> Result { + self.exectute_service_provider_directory_contract( + fee, + SpExecuteMsg::DeleteNymAddress { nym_address }, + vec![], + ) + .await + } + + async fn update_deposit_required( + &self, + deposit_required: Coin, + fee: Option, + ) -> Result { + self.exectute_service_provider_directory_contract( + fee, + SpExecuteMsg::UpdateDepositRequired { + deposit_required: deposit_required.into(), + }, + vec![], + ) + .await + } +} + +#[async_trait] +impl SpDirectorySigningClient for NyxdClient +where + C: SigningCosmWasmClient + Sync + Send, +{ + async fn exectute_service_provider_directory_contract( + &self, + fee: Option, + msg: SpExecuteMsg, + funds: Vec, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let memo = msg.default_memo(); + self.client + .execute( + self.address(), + self.service_provider_contract_address().ok_or( + NyxdError::NoContractAddressAvailable( + "service provider directory contract".to_string(), + ), + )?, + &msg, + fee, + memo, + funds, + ) + .await + } +} diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index f81ee756c6..3e3a99f712 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -38,3 +38,4 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" } +nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" } diff --git a/common/commands/src/validator/mixnet/operators/mod.rs b/common/commands/src/validator/mixnet/operators/mod.rs index a38479ba65..aa8af7d314 100644 --- a/common/commands/src/validator/mixnet/operators/mod.rs +++ b/common/commands/src/validator/mixnet/operators/mod.rs @@ -5,6 +5,7 @@ use clap::{Args, Subcommand}; pub mod gateway; pub mod mixnode; +pub mod service; #[derive(Debug, Args)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] @@ -19,4 +20,6 @@ pub enum MixnetOperatorsCommands { Mixnode(mixnode::MixnetOperatorsMixnode), /// Manage your gateway Gateway(gateway::MixnetOperatorsGateway), + /// Manage your service + ServiceProvider(service::MixnetOperatorsService), } diff --git a/common/commands/src/validator/mixnet/operators/service/announce.rs b/common/commands/src/validator/mixnet/operators/service/announce.rs new file mode 100644 index 0000000000..178993f203 --- /dev/null +++ b/common/commands/src/validator/mixnet/operators/service/announce.rs @@ -0,0 +1,33 @@ +use clap::Parser; +use log::info; +use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType}; +use nym_validator_client::nyxd::traits::SpDirectorySigningClient; + +use crate::context::SigningClient; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub nym_address: String, + + /// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym') + #[clap(long)] + pub deposit: u128, +} + +pub async fn announce(args: Args, client: SigningClient) { + info!("Annoucing service provider"); + + let nym_address = NymAddress::Address(args.nym_address); + let service_type = ServiceType::NetworkRequester; + + let denom = client.current_chain_details().mix_denom.base.as_str(); + let deposit = Coin::new(args.deposit, denom); + + let res = client + .announce_service_provider(nym_address, service_type, deposit.into(), None) + .await + .expect("Failed to announce service provider"); + + info!("Announced service provider: {res:?}"); +} diff --git a/common/commands/src/validator/mixnet/operators/service/delete.rs b/common/commands/src/validator/mixnet/operators/service/delete.rs new file mode 100644 index 0000000000..eb86f4028f --- /dev/null +++ b/common/commands/src/validator/mixnet/operators/service/delete.rs @@ -0,0 +1,23 @@ +use clap::Parser; +use log::info; +use nym_service_provider_directory_common::ServiceId; +use nym_validator_client::nyxd::traits::SpDirectorySigningClient; + +use crate::context::SigningClient; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(long)] + pub id: ServiceId, +} + +pub async fn delete(args: Args, client: SigningClient) { + info!("Deleting service provider with id {}", args.id); + + let res = client + .delete_service_provider_by_id(args.id, None) + .await + .expect("Failed to delete service provider"); + + info!("Deleted: {res:?}"); +} diff --git a/common/commands/src/validator/mixnet/operators/service/mod.rs b/common/commands/src/validator/mixnet/operators/service/mod.rs new file mode 100644 index 0000000000..4007909404 --- /dev/null +++ b/common/commands/src/validator/mixnet/operators/service/mod.rs @@ -0,0 +1,19 @@ +use clap::{Args, Subcommand}; + +pub mod announce; +pub mod delete; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct MixnetOperatorsService { + #[clap(subcommand)] + pub command: MixnetOperatorsServiceCommands, +} + +#[derive(Debug, Subcommand)] +pub enum MixnetOperatorsServiceCommands { + /// Announce service provider to the world + Announce(announce::Args), + /// Delete entry for service provider from the directory + Delete(delete::Args), +} diff --git a/common/commands/src/validator/mixnet/query/mod.rs b/common/commands/src/validator/mixnet/query/mod.rs index 6d86e6c7b9..79481a728a 100644 --- a/common/commands/src/validator/mixnet/query/mod.rs +++ b/common/commands/src/validator/mixnet/query/mod.rs @@ -5,6 +5,7 @@ use clap::{Args, Subcommand}; pub mod query_all_gateways; pub mod query_all_mixnodes; +pub mod query_all_service_providers; #[derive(Debug, Args)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] @@ -19,4 +20,6 @@ pub enum MixnetQueryCommands { Mixnodes(query_all_mixnodes::Args), /// Query gateways Gateways(query_all_gateways::Args), + /// Query announced service-providers + ServiceProviders(query_all_service_providers::Args), } diff --git a/common/commands/src/validator/mixnet/query/query_all_service_providers.rs b/common/commands/src/validator/mixnet/query/query_all_service_providers.rs new file mode 100644 index 0000000000..d3cb805d38 --- /dev/null +++ b/common/commands/src/validator/mixnet/query/query_all_service_providers.rs @@ -0,0 +1,55 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use comfy_table::Table; +use nym_validator_client::nym_api::error::NymAPIError; + +use crate::context::QueryClientWithNyxd; +use crate::utils::show_error; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap(value_parser)] + #[clap(help = "Optionally, the service provider to display")] + pub nym_address: Option, +} + +pub async fn query(args: Args, client: &QueryClientWithNyxd) { + match client.nym_api.get_service_providers().await { + Ok(res) => { + if let Some(nym_address) = args.nym_address { + let service = res.iter().find(|service| { + service + .service + .nym_address + .to_string() + .eq_ignore_ascii_case(&nym_address) + }); + println!( + "{}", + ::serde_json::to_string_pretty(&service).expect("json formatting error") + ); + } else { + let mut table = Table::new(); + + table.set_header(vec!["Service Id", "Announcer", "Nym Address"]); + for service in res { + table.add_row(vec![ + service.service_id.to_string(), + service.service.announcer.to_string(), + service.service.service_type.to_string(), + service.service.nym_address.to_string(), + ]); + } + + println!("The service providers in the directory are:"); + println!("{table}"); + } + } + Err(NymAPIError::NotFound) => { + println!("nym-api reports no service provider endpoint available"); + } + Err(e) => show_error(e), + } +} 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/lib.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/lib.rs index f456db01aa..4609473c60 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/lib.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/lib.rs @@ -5,3 +5,5 @@ pub mod types; // Re-export all types at the top-level pub use types::*; + +pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction}; diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs b/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs index 8276cdddb2..ba746c3b10 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs +++ b/common/cosmwasm-smart-contracts/service-provider-directory/src/msg.rs @@ -40,6 +40,24 @@ impl ExecuteMsg { pub fn delete_id(service_id: ServiceId) -> Self { ExecuteMsg::DeleteId { service_id } } + + pub fn default_memo(&self) -> String { + match self { + ExecuteMsg::Announce { + nym_address, + service_type, + } => format!("announcing {nym_address} as type {service_type}"), + ExecuteMsg::DeleteId { service_id } => { + format!("deleting service with service id {service_id}") + } + ExecuteMsg::DeleteNymAddress { nym_address } => { + format!("deleting service with nym address {nym_address}") + } + ExecuteMsg::UpdateDepositRequired { deposit_required } => { + format!("updating the deposit required to {deposit_required}") + } + } + } } #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 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/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 58c4b8d250..b966d61ee7 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -3,7 +3,12 @@ use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; use serde::{Deserialize, Serialize}; -use std::{env::var, ops::Not, path::PathBuf}; +use std::{ + env::{var, VarError}, + ffi::OsStr, + ops::Not, + path::PathBuf, +}; use url::Url; pub mod mainnet; @@ -28,6 +33,7 @@ pub struct NymContracts { pub group_contract_address: Option, pub multisig_contract_address: Option, pub coconut_dkg_contract_address: Option, + pub service_provider_directory_contract_address: Option, } // I wanted to use the simpler `NetworkDetails` name, but there's a clash @@ -68,6 +74,14 @@ impl NymNetworkDetails { } pub fn new_from_env() -> Self { + fn get_optional_env>(env: K) -> Option { + match var(env) { + Ok(var) => Some(var), + Err(VarError::NotPresent) => None, + err => panic!("Unable to set: {:?}", err), + } + } + NymNetworkDetails::new_empty() .with_bech32_account_prefix( var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"), @@ -117,6 +131,9 @@ impl NymNetworkDetails { .with_coconut_dkg_contract(Some( var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"), )) + .with_service_provider_directory_contract(get_optional_env( + var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, + )) } pub fn new_mainnet() -> Self { @@ -146,6 +163,9 @@ impl NymNetworkDetails { coconut_dkg_contract_address: parse_optional_str( mainnet::COCONUT_DKG_CONTRACT_ADDRESS, ), + service_provider_directory_contract_address: parse_optional_str( + mainnet::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, + ), }, } } @@ -227,6 +247,15 @@ impl NymNetworkDetails { self.contracts.coconut_dkg_contract_address = contract.map(Into::into); self } + + #[must_use] + pub fn with_service_provider_directory_contract>( + mut self, + contract: Option, + ) -> Self { + self.contracts.service_provider_directory_contract_address = contract.map(Into::into); + self + } } #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 3e37633b3f..8fd3d68407 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -20,6 +20,8 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = + "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 0fd5506ddc..70f2ed79af 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -19,6 +19,8 @@ pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS"; pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS"; pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS"; +pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = + "SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS"; pub const NYXD: &str = "NYXD"; pub const NYM_API: &str = "NYM_API"; diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index a2f6d65e1e..926ef72a2a 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1152,6 +1152,7 @@ name = "nym-service-provider-directory-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "schemars", "serde", ] diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index 474bf8d073..40cafc524b 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -17,6 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2 MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy +SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://qwerty-validator.qa.nymte.ch/" NYM_API="https://qwerty-validator-api.qa.nymte.ch/api" 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 + } +} diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 102a760698..4f9dd8609b 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3578,6 +3578,15 @@ dependencies = [ "pem", ] +[[package]] +name = "nym-service-provider-directory-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "nym-service-providers-common" version = "0.1.0" @@ -3826,6 +3835,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-multisig-contract-common", "nym-network-defaults", + "nym-service-provider-directory-common", "nym-vesting-contract", "nym-vesting-contract-common", "prost", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 659022a32b..22e5d755ad 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2996,6 +2996,15 @@ dependencies = [ "pem", ] +[[package]] +name = "nym-service-provider-directory-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "nym-sphinx-types" version = "0.2.0" @@ -3069,6 +3078,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-multisig-contract-common", "nym-network-defaults", + "nym-service-provider-directory-common", "nym-vesting-contract", "nym-vesting-contract-common", "prost", diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 8a443243e7..57a5e234bb 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -114,6 +114,8 @@ mod sandbox { "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; + pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = + "n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = @@ -149,6 +151,9 @@ mod sandbox { group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), + service_provider_directory_contract_address: parse_optional_str( + SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, + ), }, } } @@ -176,6 +181,8 @@ mod qa { pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; + pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = + "n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = @@ -210,6 +217,9 @@ mod qa { group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS), multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS), coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS), + service_provider_directory_contract_address: parse_optional_str( + SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, + ), }, } } diff --git a/tools/nym-cli/src/validator/mixnet/operators/mod.rs b/tools/nym-cli/src/validator/mixnet/operators/mod.rs index a79b432872..780886e40c 100644 --- a/tools/nym-cli/src/validator/mixnet/operators/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/operators/mod.rs @@ -6,6 +6,7 @@ use nym_network_defaults::NymNetworkDetails; pub(crate) mod gateways; pub(crate) mod mixnodes; +pub(crate) mod services; pub(crate) async fn execute( global_args: ClientArgs, @@ -19,6 +20,7 @@ pub(crate) async fn execute( nym_cli_commands::validator::mixnet::operators::MixnetOperatorsCommands::Mixnode( mixnode, ) => mixnodes::execute(global_args, mixnode, network_details).await?, + nym_cli_commands::validator::mixnet::operators::MixnetOperatorsCommands::ServiceProvider(service) => services::execute(global_args, service, network_details).await?, } Ok(()) } diff --git a/tools/nym-cli/src/validator/mixnet/operators/services/mod.rs b/tools/nym-cli/src/validator/mixnet/operators/services/mod.rs new file mode 100644 index 0000000000..09df9b6a55 --- /dev/null +++ b/tools/nym-cli/src/validator/mixnet/operators/services/mod.rs @@ -0,0 +1,14 @@ +use nym_cli_commands::context::{create_signing_client, ClientArgs}; +use nym_network_defaults::NymNetworkDetails; + +pub(crate) async fn execute( + global_args: ClientArgs, + service: nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsService, + network_details: &NymNetworkDetails, +) -> anyhow::Result<()> { + match service.command { + nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsServiceCommands::Announce(announce) => nym_cli_commands::validator::mixnet::operators::service::announce::announce(announce, create_signing_client(global_args, network_details)?).await, + nym_cli_commands::validator::mixnet::operators::service::MixnetOperatorsServiceCommands::Delete(delete) => nym_cli_commands::validator::mixnet::operators::service::delete::delete(delete, create_signing_client(global_args, network_details)?).await, + } + Ok(()) +} diff --git a/tools/nym-cli/src/validator/mixnet/query/mod.rs b/tools/nym-cli/src/validator/mixnet/query/mod.rs index 8726787fc3..0edc62c9bb 100644 --- a/tools/nym-cli/src/validator/mixnet/query/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/query/mod.rs @@ -23,6 +23,13 @@ pub(crate) async fn execute( ) .await } + nym_cli_commands::validator::mixnet::query::MixnetQueryCommands::ServiceProviders(args) => { + nym_cli_commands::validator::mixnet::query::query_all_service_providers::query( + args, + &create_query_client_with_nym_api(network_details)?, + ) + .await + } } Ok(()) }