e5afd54ce0
* Basic storage stub * New models for new node status api * Route handling * Mounting new routes * Missing selective commit * Moved network monitor related files to separate package * Starting to see some sqlx action * Schema updates * Log statement upon finished migration * Removed old diesel related imports * Converted mixnode cache initialisation into a fairing * Moved cache related functionalities to separate package Also defined staging there * Created run method for validator cache + removed unwrap * Removed old node-status-api types and left bunch of todo placeholders in their place * Fixed managing validatorcache * Status reports are starting to get constructed * Submitting some dummy results to the database * Removing duplicate code for generating reports * Removed statuses older than 48h * Initial attempt at trying to obtain reports for all active nodes * Removed duplicates from the full report * Grabbing uptime history * Updating historical uptimes of active nodes * Updated sqlx-data.json * Removed all placeholder foomp owner values * Changed Layer serde behaviour for easier usage * Extended validator api config * Initial (seems working !) integration with network monitor * Added database path configuration to config * Using ValidatorCache in NetworkMonitor * Flag indicating whether validator cache has been initialised * Introduced a locla-only route for reward script to perform daily chores * Flag to save config to a file * Moved spawning of receiving future to run method rather than new * Removed arguments that dont make sense to be configured via CLI * Removed dead code from config file * More dead code removal * Added validator API to CI * Corrected manifest-path arguments * Constructing network monitor by passing config * Combined validator API CI with the main CI file * Using query_as for NodeStatus * Checking if historical uptimes were already calculated on particular day * Making id field NOT NULL * More query_as! action * Updated sqlx-data.json * Removed unused chrono feature * Renamed the migration file * Changed default validator endpoint to point to local validator * Removing unnecessary clone * More appropriate naming * Removed dead code * Lock file updates * Updated network monitor address in contract code * Don't stage node status api if network monitor is disabled * cargo fmt * Updated all license notices to SPDX
264 lines
8.7 KiB
Rust
264 lines
8.7 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use std::time::Duration;
|
|
|
|
pub use crate::error::ValidatorClientError;
|
|
use crate::models::{QueryRequest, QueryResponse};
|
|
use log::error;
|
|
use mixnet_contract::{
|
|
GatewayBond, IdentityKey, LayerDistribution, MixNodeBond, PagedGatewayResponse,
|
|
PagedMixnodeResponse,
|
|
};
|
|
use rand::{seq::SliceRandom, thread_rng};
|
|
use serde::Deserialize;
|
|
use url::Url;
|
|
|
|
mod error;
|
|
mod models;
|
|
pub(crate) mod serde_helpers;
|
|
mod validator_api;
|
|
|
|
// Implement caching with a global hashmap that has two fields, queryresponse and as_at, there is a side process
|
|
pub struct Config {
|
|
initial_rest_servers: Vec<Url>,
|
|
mixnet_contract_address: String,
|
|
mixnode_page_limit: Option<u32>,
|
|
gateway_page_limit: Option<u32>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn new<S: Into<String>>(
|
|
rest_servers_available_base_urls: Vec<String>,
|
|
mixnet_contract_address: S,
|
|
) -> Self {
|
|
let initial_rest_servers = rest_servers_available_base_urls
|
|
.iter()
|
|
.map(|base_url| Url::parse(base_url).expect("Bad validator URL"))
|
|
.collect();
|
|
Config {
|
|
initial_rest_servers,
|
|
mixnet_contract_address: mixnet_contract_address.into(),
|
|
mixnode_page_limit: None,
|
|
gateway_page_limit: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_mixnode_page_limit(mut self, limit: Option<u32>) -> Config {
|
|
self.mixnode_page_limit = limit;
|
|
self
|
|
}
|
|
|
|
pub fn with_gateway_page_limit(mut self, limit: Option<u32>) -> Config {
|
|
self.gateway_page_limit = limit;
|
|
self
|
|
}
|
|
}
|
|
|
|
pub struct Client {
|
|
config: Config,
|
|
// Currently it seems the client is independent of the url hence a single instance seems to be fine
|
|
reqwest_client: reqwest::Client,
|
|
validator_api_client: validator_api::Client,
|
|
}
|
|
|
|
impl Client {
|
|
pub fn new(config: Config) -> Self {
|
|
let reqwest_client = reqwest::Client::new();
|
|
let validator_api_client = validator_api::Client::new();
|
|
|
|
// client is only ever created on process startup, so a panic here is fine as it implies
|
|
// invalid config. And that can only happen if an user was messing with it by themselves.
|
|
if config.initial_rest_servers.is_empty() {
|
|
panic!("no validator servers provided")
|
|
}
|
|
|
|
Client {
|
|
config,
|
|
reqwest_client,
|
|
validator_api_client,
|
|
}
|
|
}
|
|
|
|
pub fn available_validators_rest_urls(&self) -> Vec<Url> {
|
|
self.config.initial_rest_servers.clone()
|
|
}
|
|
|
|
fn base_query_path(&self, url: &str) -> String {
|
|
format!(
|
|
"{}/wasm/contract/{}/smart",
|
|
url, self.config.mixnet_contract_address
|
|
)
|
|
}
|
|
|
|
// async fn latest_block(&self) -> Block {
|
|
// let path = format!("{}/block", self.available_validators_rest_urls[0]);
|
|
// let response = self.reqwest_client.get(path).send().await?.json().await?;
|
|
// }
|
|
|
|
async fn query_validators<T>(
|
|
&self,
|
|
query: String,
|
|
use_validator_api: bool,
|
|
) -> Result<T, ValidatorClientError>
|
|
where
|
|
for<'a> T: Deserialize<'a>,
|
|
{
|
|
let mut failed = 0;
|
|
let sleep_secs = 5;
|
|
// Randomly select a validator to query, keep querying and shuffling until we get a response
|
|
let mut validator_urls = self.available_validators_rest_urls().clone();
|
|
|
|
// This will never exit
|
|
// JS: Shouldn't it have some sort of maximum attempts counter to return an error at some point?
|
|
loop {
|
|
validator_urls.as_mut_slice().shuffle(&mut thread_rng());
|
|
for url in validator_urls.iter() {
|
|
let res = if use_validator_api {
|
|
Ok(self
|
|
.validator_api_client
|
|
.query_validator_api(query.clone(), url)
|
|
.await?)
|
|
} else {
|
|
self.query_validator(query.clone(), url).await
|
|
};
|
|
match res {
|
|
Ok(res) => return Ok(res),
|
|
Err(e) => {
|
|
failed += 1;
|
|
error!("{}", e);
|
|
error!("Total failed requests {}", failed);
|
|
}
|
|
}
|
|
}
|
|
error!(
|
|
"No validators available out of {} attempted! Will try again in {} seconds. Listing all attempted:",
|
|
validator_urls.len(), sleep_secs
|
|
);
|
|
for url in validator_urls.iter() {
|
|
error!("{}", url)
|
|
}
|
|
// Went with only wasm_timer so we can avoid features on the lib, and pulling in tokio
|
|
wasm_timer::Delay::new(Duration::from_secs(sleep_secs)).await?;
|
|
}
|
|
}
|
|
|
|
async fn query_validator<T>(
|
|
&self,
|
|
query: String,
|
|
validator_url: &Url,
|
|
) -> Result<T, ValidatorClientError>
|
|
where
|
|
for<'a> T: Deserialize<'a>,
|
|
{
|
|
let query_url = format!(
|
|
"{}/{}?encoding=base64",
|
|
self.base_query_path(validator_url.as_str()),
|
|
query
|
|
);
|
|
|
|
let query_response: QueryResponse<T> = self
|
|
.reqwest_client
|
|
.get(query_url)
|
|
.send()
|
|
.await?
|
|
.json()
|
|
.await?;
|
|
|
|
match query_response {
|
|
QueryResponse::Ok(smart_res) => Ok(smart_res.result.smart),
|
|
QueryResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)),
|
|
QueryResponse::CodedError(err) => {
|
|
Err(ValidatorClientError::ValidatorError(format!("{}", err)))
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn get_mix_nodes_paged(
|
|
&self,
|
|
start_after: Option<IdentityKey>,
|
|
) -> Result<PagedMixnodeResponse, ValidatorClientError> {
|
|
let query_content_json = serde_json::to_string(&QueryRequest::GetMixNodes {
|
|
limit: self.config.mixnode_page_limit,
|
|
start_after,
|
|
})
|
|
.expect("serde was incorrectly implemented on QueryRequest::GetMixNodes!");
|
|
|
|
// we need to encode our json request
|
|
let query_content = base64::encode(query_content_json);
|
|
|
|
self.query_validators(query_content, false).await
|
|
}
|
|
|
|
pub async fn get_mix_nodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
|
let mut mixnodes = Vec::new();
|
|
let mut start_after = None;
|
|
loop {
|
|
let mut paged_response = self.get_mix_nodes_paged(start_after.take()).await?;
|
|
mixnodes.append(&mut paged_response.nodes);
|
|
|
|
if let Some(start_after_res) = paged_response.start_next_after {
|
|
start_after = Some(start_after_res)
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(mixnodes)
|
|
}
|
|
|
|
pub async fn get_cached_mix_nodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
|
|
let query_content = validator_api::VALIDATOR_API_MIXNODES.to_string();
|
|
self.query_validators(query_content, true).await
|
|
}
|
|
|
|
async fn get_gateways_paged(
|
|
&self,
|
|
start_after: Option<IdentityKey>,
|
|
) -> Result<PagedGatewayResponse, ValidatorClientError> {
|
|
let query_content_json = serde_json::to_string(&QueryRequest::GetGateways {
|
|
limit: self.config.gateway_page_limit,
|
|
start_after,
|
|
})
|
|
.expect("serde was incorrectly implemented on QueryRequest::GetGateways!");
|
|
|
|
// we need to encode our json request
|
|
let query_content = base64::encode(query_content_json);
|
|
|
|
self.query_validators(query_content, false).await
|
|
}
|
|
|
|
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
|
let mut gateways = Vec::new();
|
|
let mut start_after = None;
|
|
loop {
|
|
let mut paged_response = self.get_gateways_paged(start_after.take()).await?;
|
|
gateways.append(&mut paged_response.nodes);
|
|
|
|
if let Some(start_after_res) = paged_response.start_next_after {
|
|
start_after = Some(start_after_res)
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(gateways)
|
|
}
|
|
|
|
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
|
let query_content = validator_api::VALIDATOR_API_GATEWAYS.to_string();
|
|
self.query_validators(query_content, true).await
|
|
}
|
|
|
|
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, ValidatorClientError> {
|
|
// serialization of an empty enum can't fail...
|
|
let query_content_json =
|
|
serde_json::to_string(&QueryRequest::LayerDistribution {}).unwrap();
|
|
|
|
// we need to encode our json request
|
|
let query_content = base64::encode(query_content_json);
|
|
|
|
self.query_validators(query_content, false).await
|
|
}
|
|
}
|