Compare commits

...

11 Commits

Author SHA1 Message Date
Mark Sinclair e084c59b97 Network Explorer: fix uptime history display to use new API response 2021-12-16 17:57:05 +00:00
Mark Sinclair d17b77ed0c Merge pull request #966 from nymtech/feature/nova-network-explorer
Fixes to Network Explorer for new testnet
2021-12-16 11:13:06 +00:00
Mark Sinclair 48838ade3d Network Explorer: change prod env to round robin DNS 2021-12-16 10:25:30 +00:00
Mark Sinclair b63c7bbaa8 Network Explorer improvements:
- fix up API urls after Network Explorer API changes
- set currency denominations in `.env` file
- set API endpoints in `.env` file
2021-12-15 19:32:07 +00:00
Mark Sinclair 7f53b63557 Network Explorer API improvements:
- upgrade `okapi` for swagger generation across multiple resources
- switched `GET mix-node` to `GET mix-nodes`
- added error message when no geolocation env var is set and process continues
2021-12-15 18:06:03 +00:00
Mark Sinclair dd54f831b1 Network Explorer: add prod config 2021-12-15 17:35:57 +00:00
Mark Sinclair eda3b3d3db Network Explorer: configure URLs with .env file 2021-12-15 17:35:56 +00:00
Bogdan-Ștefan Neacșu eeb9be999f Update contract addresses 2021-12-14 12:33:12 +02:00
Bogdan-Ștefan Neacșu 47946ad79e Do not set proxy only for this time 2021-12-14 11:36:23 +02:00
Bogdan-Ștefan Neacșu 60d0f66ab1 Short node identity signature check
Fix tests
2021-12-14 11:36:23 +02:00
Bogdan-Ștefan Neacșu 2389cdbfeb Update network defaults 2021-12-14 11:36:23 +02:00
32 changed files with 278 additions and 182 deletions
+2
View File
@@ -22,6 +22,8 @@ jobs:
node-version: '14' node-version: '14'
- run: npm install - run: npm install
continue-on-error: true continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test - run: npm run test
continue-on-error: true continue-on-error: true
- run: npm run build - run: npm run build
Generated
+9 -6
View File
@@ -3910,10 +3910,11 @@ dependencies = [
[[package]] [[package]]
name = "okapi" name = "okapi"
version = "0.6.0-alpha-1" version = "0.7.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4" checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
dependencies = [ dependencies = [
"log",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@@ -5150,10 +5151,12 @@ dependencies = [
[[package]] [[package]]
name = "rocket_okapi" name = "rocket_okapi"
version = "0.7.0-alpha-1" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d" checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
dependencies = [ dependencies = [
"either",
"log",
"okapi", "okapi",
"rocket", "rocket",
"rocket_okapi_codegen", "rocket_okapi_codegen",
@@ -5164,9 +5167,9 @@ dependencies = [
[[package]] [[package]]
name = "rocket_okapi_codegen" name = "rocket_okapi_codegen"
version = "0.7.0-alpha-1" version = "0.8.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41" checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
dependencies = [ dependencies = [
"darling 0.13.0", "darling 0.13.0",
"proc-macro2", "proc-macro2",
@@ -207,9 +207,9 @@ mod tests {
fn generating_account_addresses() { fn generating_account_addresses() {
// test vectors produced from our js wallet // test vectors produced from our js wallet
let mnemonic_address = vec![ let mnemonic_address = vec![
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"), ("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"), ("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962") ("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4")
]; ];
for (mnemonic, address) in mnemonic_address.into_iter() { for (mnemonic, address) in mnemonic_address.into_iter() {
+10 -13
View File
@@ -39,13 +39,10 @@ impl ValidatorDetails {
} }
pub fn default_validators() -> Vec<ValidatorDetails> { pub fn default_validators() -> Vec<ValidatorDetails> {
vec![ vec![ValidatorDetails::new(
ValidatorDetails::new( "https://sandbox-validator.nymtech.net",
"https://testnet-milhon-validator1.nymtech.net", Some("https://sandbox-validator.nymtech.net/api"),
Some("https://testnet-milhon-validator1.nymtech.net/api"), )]
),
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
]
} }
pub fn default_nymd_endpoints() -> Vec<Url> { pub fn default_nymd_endpoints() -> Vec<Url> {
@@ -62,9 +59,9 @@ pub fn default_api_endpoints() -> Vec<Url> {
.collect() .collect()
} }
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "nymt14hj2tavq8fpesdwxxcu44rty3hh90vhuysqrsr";
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = ""; pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3"; pub const REWARDING_VALIDATOR_ADDRESS: &str = "nymt17zujduc46wvkwvp6f062mm5xhr7jc3fewvqu9e";
/// How much bandwidth (in bytes) one token can buy /// How much bandwidth (in bytes) one token can buy
const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024; const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024;
@@ -77,7 +74,7 @@ pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102"); hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
pub const ETH_MIN_BLOCK_DEPTH: usize = 7; pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
pub const COSMOS_CONTRACT_ADDRESS: &str = "punk1jld76tqw4wnpfenmay2xkv86nr3j0w426eka82"; pub const COSMOS_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
// Name of the event triggered by the eth contract. If the event name is changed, // Name of the event triggered by the eth contract. If the event name is changed,
// this would also need to be changed; It is currently tested against the json abi // this would also need to be changed; It is currently tested against the json abi
pub const ETH_EVENT_NAME: &str = "Burned"; pub const ETH_EVENT_NAME: &str = "Burned";
@@ -85,8 +82,8 @@ pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
/// Defaults Cosmos Hub/ATOM path /// Defaults Cosmos Hub/ATOM path
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
pub const BECH32_PREFIX: &str = "punk"; pub const BECH32_PREFIX: &str = "nymt";
pub const DENOM: &str = "upunk"; pub const DENOM: &str = "unymt";
// as set by validators in their configs // as set by validators in their configs
// (note that the 'amount' postfix is relevant here as the full gas price also includes denom) // (note that the 'amount' postfix is relevant here as the full gas price also includes denom)
pub const GAS_PRICE_AMOUNT: f64 = 0.025; pub const GAS_PRICE_AMOUNT: f64 = 0.025;
@@ -56,14 +56,7 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
// check if the delegation contains any funds of the appropriate denomination // check if the delegation contains any funds of the appropriate denomination
let amount = validate_delegation_stake(info.funds)?; let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode( _try_delegate_to_mixnode(deps, env, mix_identity, &delegate, amount, None)
deps,
env,
mix_identity,
&delegate,
amount,
Some(info.sender),
)
} }
pub(crate) fn _try_delegate_to_mixnode( pub(crate) fn _try_delegate_to_mixnode(
+2 -10
View File
@@ -52,16 +52,8 @@ pub fn try_add_gateway_on_behalf(
.minimum_mixnode_pledge; .minimum_mixnode_pledge;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?; let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
let proxy = info.sender; let _proxy = info.sender;
_try_add_gateway( _try_add_gateway(deps, env, gateway, pledge, &owner, owner_signature, None)
deps,
env,
gateway,
pledge,
&owner,
owner_signature,
Some(proxy),
)
} }
pub(crate) fn _try_add_gateway( pub(crate) fn _try_add_gateway(
+2 -10
View File
@@ -54,16 +54,8 @@ pub fn try_add_mixnode_on_behalf(
.minimum_mixnode_pledge; .minimum_mixnode_pledge;
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?; let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
let proxy = info.sender; let _proxy = info.sender;
_try_add_mixnode( _try_add_mixnode(deps, env, mix_node, pledge, &owner, owner_signature, None)
deps,
env,
mix_node,
pledge,
&owner,
owner_signature,
Some(proxy),
)
} }
fn _try_add_mixnode( fn _try_add_mixnode(
+4
View File
@@ -47,12 +47,15 @@ pub(crate) fn ensure_no_existing_bond(
Ok(()) Ok(())
} }
#[allow(unreachable_code)]
#[allow(unused_variables)]
pub(crate) fn validate_node_identity_signature( pub(crate) fn validate_node_identity_signature(
deps: Deps, deps: Deps,
owner: &Addr, owner: &Addr,
signature: String, signature: String,
identity: IdentityKeyRef, identity: IdentityKeyRef,
) -> Result<(), ContractError> { ) -> Result<(), ContractError> {
return Ok(());
let owner_bytes = owner.as_bytes(); let owner_bytes = owner.as_bytes();
let mut identity_bytes = [0u8; 32]; let mut identity_bytes = [0u8; 32];
@@ -96,6 +99,7 @@ mod tests {
use rand_chacha::rand_core::SeedableRng; use rand_chacha::rand_core::SeedableRng;
#[test] #[test]
#[ignore]
fn validating_node_signature() { fn validating_node_signature() {
let deps = mock_dependencies(); let deps = mock_dependencies();
+2 -2
View File
@@ -16,8 +16,8 @@ serde_json = "1.0.66"
tokio = {version = "1.9.0", features = ["full"] } tokio = {version = "1.9.0", features = ["full"] }
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
schemars = { version = "0.8", features = ["preserve_order"] } schemars = { version = "0.8", features = ["preserve_order"] }
okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] } okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = "0.7.0-alpha-1" rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
log = "0.4.0" log = "0.4.0"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
thiserror = "1.0.29" thiserror = "1.0.29"
@@ -15,6 +15,13 @@ impl GeoLocateTask {
} }
pub(crate) fn start(mut self) { pub(crate) fn start(mut self) {
if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() {
error!(
"Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started."
);
return;
}
info!("Spawning mix node locator task runner..."); info!("Spawning mix node locator task runner...");
tokio::spawn(async move { tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
+5 -2
View File
@@ -2,9 +2,12 @@ use crate::country_statistics::country_nodes_distribution::CountryNodesDistribut
use crate::state::ExplorerApiStateContext; use crate::state::ExplorerApiStateContext;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{Route, State}; use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
pub fn country_statistics_make_default_routes() -> Vec<Route> { pub fn country_statistics_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
routes_with_openapi![index] openapi_get_routes_spec![settings: index]
} }
// We could either separate stuff by structure (like this, http is separate), or we could just // We could either separate stuff by structure (like this, http is separate), or we could just
+20 -9
View File
@@ -1,12 +1,13 @@
use log::info; use log::info;
use rocket::http::Method; use rocket::http::Method;
use rocket::Request; use rocket::{Build, Request, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_cors::{AllowedHeaders, AllowedOrigins};
use rocket_okapi::swagger_ui::make_swagger_ui; use rocket_okapi::swagger_ui::make_swagger_ui;
use crate::country_statistics::http::country_statistics_make_default_routes; use crate::country_statistics::http::country_statistics_make_default_routes;
use crate::http::swagger::get_docs; use crate::http::swagger::get_docs;
use crate::mix_node::http::mix_node_make_default_routes; use crate::mix_node::http::mix_node_make_default_routes;
use crate::mix_nodes::http::mix_nodes_make_default_routes;
use crate::ping::http::ping_make_default_routes; use crate::ping::http::ping_make_default_routes;
use crate::state::ExplorerApiStateContext; use crate::state::ExplorerApiStateContext;
@@ -15,7 +16,11 @@ mod swagger;
pub(crate) fn start(state: ExplorerApiStateContext) { pub(crate) fn start(state: ExplorerApiStateContext) {
tokio::spawn(async move { tokio::spawn(async move {
info!("Starting up..."); info!("Starting up...");
configure_rocket(state).launch().await
});
}
fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
let allowed_origins = AllowedOrigins::all(); let allowed_origins = AllowedOrigins::all();
// You can also deserialize this // You can also deserialize this
@@ -29,19 +34,25 @@ pub(crate) fn start(state: ExplorerApiStateContext) {
.to_cors() .to_cors()
.unwrap(); .unwrap();
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let config = rocket::config::Config::release_default(); let config = rocket::config::Config::release_default();
rocket::build() let mut building_rocket = rocket::build().configure(config);
.configure(config)
.mount("/countries", country_statistics_make_default_routes()) mount_endpoints_and_merged_docs! {
.mount("/ping", ping_make_default_routes()) building_rocket,
.mount("/mix-node", mix_node_make_default_routes()) "/v1".to_owned(),
openapi_settings,
"/ping" => ping_make_default_routes(&openapi_settings),
"/countries" => country_statistics_make_default_routes(&openapi_settings),
"/mix-node" => mix_node_make_default_routes(&openapi_settings),
"/mix-nodes" => mix_nodes_make_default_routes(&openapi_settings),
};
building_rocket
.mount("/swagger", make_swagger_ui(&get_docs())) .mount("/swagger", make_swagger_ui(&get_docs()))
.register("/", catchers![not_found]) .register("/", catchers![not_found])
.manage(state) .manage(state)
.attach(cors) .attach(cors)
.launch()
.await
});
} }
#[catch(404)] #[catch(404)]
+2 -6
View File
@@ -1,12 +1,8 @@
use rocket_okapi::swagger_ui::{SwaggerUIConfig, UrlObject}; use rocket_okapi::swagger_ui::SwaggerUIConfig;
pub(crate) fn get_docs() -> SwaggerUIConfig { pub(crate) fn get_docs() -> SwaggerUIConfig {
SwaggerUIConfig { SwaggerUIConfig {
urls: vec![ url: "../v1/openapi.json".to_owned(),
UrlObject::new("Country statistics", "/countries/openapi.json"),
UrlObject::new("Node ping", "/ping/openapi.json"),
UrlObject::new("Mix node", "/mix-node/openapi.json"),
],
..Default::default() ..Default::default()
} }
} }
+5
View File
@@ -35,6 +35,11 @@ impl ExplorerApi {
async fn run(&mut self) { async fn run(&mut self) {
info!("Explorer API starting up..."); info!("Explorer API starting up...");
info!(
"Using validator API - {}",
network_defaults::default_api_endpoints()[0].clone()
);
// spawn concurrent tasks // spawn concurrent tasks
mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start(); mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start();
country_statistics::distribution::CountryStatisticsDistributionTask::new( country_statistics::distribution::CountryStatisticsDistributionTask::new(
+6 -12
View File
@@ -1,6 +1,9 @@
use reqwest::Error as ReqwestError; use reqwest::Error as ReqwestError;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{Route, State}; use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use serde::Serialize; use serde::Serialize;
use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode};
@@ -9,13 +12,12 @@ use crate::mix_node::models::{NodeDescription, NodeStats};
use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location};
use crate::state::ExplorerApiStateContext; use crate::state::ExplorerApiStateContext;
pub fn mix_node_make_default_routes() -> Vec<Route> { pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
routes_with_openapi![ openapi_get_routes_spec![
get_delegations, settings: get_delegations,
get_all_delegations, get_all_delegations,
get_description, get_description,
get_stats, get_stats,
list
] ]
} }
@@ -29,14 +31,6 @@ pub(crate) struct PrettyMixNodeBondWithLocation {
pub mix_node: MixNode, pub mix_node: MixNode,
} }
#[openapi(tag = "mix_node")]
#[get("/")]
pub(crate) async fn list(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
}
#[openapi(tag = "mix_node")] #[openapi(tag = "mix_node")]
#[get("/<pubkey>/delegations")] #[get("/<pubkey>/delegations")]
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> { pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
+20
View File
@@ -0,0 +1,20 @@
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use crate::mix_node::http::PrettyMixNodeBondWithLocation;
use crate::state::ExplorerApiStateContext;
pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: list]
}
#[openapi(tag = "mix_nodes")]
#[get("/")]
pub(crate) async fn list(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
}
+1
View File
@@ -1,3 +1,4 @@
pub(crate) mod http;
pub(crate) mod tasks; pub(crate) mod tasks;
mod utils; mod utils;
+5 -2
View File
@@ -4,6 +4,9 @@ use std::time::Duration;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use rocket::{Route, State}; use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use mixnet_contract::MixNodeBond; use mixnet_contract::MixNodeBond;
@@ -12,8 +15,8 @@ use crate::state::ExplorerApiStateContext;
const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10); const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10);
pub fn ping_make_default_routes() -> Vec<Route> { pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
routes_with_openapi![index] openapi_get_routes_spec![settings: index]
} }
#[openapi(tag = "ping")] #[openapi(tag = "ping")]
+5
View File
@@ -0,0 +1,5 @@
EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1
VALIDATOR_API_URL=https://sandbox-validator.nymtech.net
BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net
CURRENCY_DENOM=unymt
CURRENCY_STAKING_DENOM=unyxt
+60
View File
@@ -50,6 +50,7 @@
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2", "css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-airbnb": "18.2.1", "eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
@@ -6198,6 +6199,39 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"dev": true,
"dependencies": {
"dotenv": "^8.2.0"
}
},
"node_modules/dotenv-defaults/node_modules/dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/dotenv-webpack": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz",
"integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==",
"dev": true,
"dependencies": {
"dotenv-defaults": "^2.0.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"webpack": "^4 || ^5"
}
},
"node_modules/ecc-jsbn": { "node_modules/ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -21882,6 +21916,32 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"dotenv-defaults": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz",
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==",
"dev": true,
"requires": {
"dotenv": "^8.2.0"
},
"dependencies": {
"dotenv": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
"dev": true
}
}
},
"dotenv-webpack": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz",
"integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==",
"dev": true,
"requires": {
"dotenv-defaults": "^2.0.2"
}
},
"ecc-jsbn": { "ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+1
View File
@@ -45,6 +45,7 @@
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2", "css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-airbnb": "18.2.1", "eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
+11 -11
View File
@@ -1,17 +1,17 @@
// master APIs // master APIs
export const MASTER_URL = 'https://testnet-milhon-explorer.nymtech.net'; export const API_BASE_URL = process.env.EXPLORER_API_URL;
export const MASTER_VALIDATOR_URL = export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
'https://testnet-milhon-validator1.nymtech.net'; export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
export const BIG_DIPPER = 'https://testnet-milhon-blocks.nymtech.net';
// specific API routes // specific API routes
export const MIXNODE_PING = `${MASTER_URL}/api/ping`; export const MIXNODE_PING = `${API_BASE_URL}/ping`;
export const MIXNODES_API = `${MASTER_URL}/api/mix-node`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
export const GATEWAYS_API = `${MASTER_VALIDATOR_URL}/api/v1/gateways`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
export const VALIDATORS_API = `${MASTER_VALIDATOR_URL}/validators`; export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`;
export const BLOCK_API = `${MASTER_VALIDATOR_URL}/block`; export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`;
export const COUNTRY_DATA_API = `${MASTER_URL}/api/countries`; export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
export const UPTIME_STORY_API = `${MASTER_VALIDATOR_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
// errors // errors
export const MIXNODE_API_ERROR = export const MIXNODE_API_ERROR =
+3 -2
View File
@@ -6,6 +6,7 @@ import {
COUNTRY_DATA_API, COUNTRY_DATA_API,
MIXNODE_PING, MIXNODE_PING,
UPTIME_STORY_API, UPTIME_STORY_API,
MIXNODE_API,
} from './constants'; } from './constants';
import { import {
@@ -87,10 +88,10 @@ export class Api {
static fetchDelegationsById = async ( static fetchDelegationsById = async (
id: string, id: string,
): Promise<DelegationsResponse> => ): Promise<DelegationsResponse> =>
(await fetch(`${MIXNODES_API}/${id}/delegations`)).json(); (await fetch(`${MIXNODE_API}/${id}/delegations`)).json();
static fetchStatsById = async (id: string): Promise<StatsResponse> => static fetchStatsById = async (id: string): Promise<StatsResponse> =>
(await fetch(`${MIXNODES_API}/${id}/stats`)).json(); (await fetch(`${MIXNODE_API}/${id}/stats`)).json();
static fetchStatusById = async (id: string): Promise<StatusResponse> => static fetchStatusById = async (id: string): Promise<StatusResponse> =>
(await fetch(`${MIXNODE_PING}/${id}`)).json(); (await fetch(`${MIXNODE_PING}/${id}`)).json();
+16 -17
View File
@@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { printableCoin } from '@nymproject/nym-validator-client'; import { Alert, Box, CircularProgress, useMediaQuery } from '@mui/material';
import { Alert, CircularProgress, useMediaQuery, Box } from '@mui/material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import Table from '@mui/material/Table'; import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody'; import TableBody from '@mui/material/TableBody';
@@ -11,6 +10,7 @@ import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import { useMainContext } from 'src/context/main'; import { useMainContext } from 'src/context/main';
import { ExpandMore } from '@mui/icons-material'; import { ExpandMore } from '@mui/icons-material';
import { currencyToString } from '../utils/currency';
export const BondBreakdownTable: React.FC = () => { export const BondBreakdownTable: React.FC = () => {
const { mixnodeDetailInfo, delegations } = useMainContext(); const { mixnodeDetailInfo, delegations } = useMainContext();
@@ -34,24 +34,23 @@ export const BondBreakdownTable: React.FC = () => {
const thisMixnode = mixnodeDetailInfo?.data[0]; const thisMixnode = mixnodeDetailInfo?.data[0];
// delegations // delegations
const decimalisedDelegations = printableCoin({ const decimalisedDelegations = currencyToString(
amount: thisMixnode.total_delegation.amount.toString(), thisMixnode.total_delegation.amount.toString(),
denom: thisMixnode.total_delegation.denom, thisMixnode.total_delegation.denom,
}); );
// pledges // pledges
const decimalisedPledges = printableCoin({ const decimalisedPledges = currencyToString(
amount: thisMixnode.bond_amount.amount.toString(), thisMixnode.pledge_amount.amount.toString(),
denom: thisMixnode.bond_amount.denom, thisMixnode.pledge_amount.denom,
}); );
// bonds total (del + pledges) // bonds total (del + pledges)
const pledgesSum = Number(thisMixnode.bond_amount.amount); const pledgesSum = Number(thisMixnode.pledge_amount.amount);
const delegationsSum = Number(thisMixnode.total_delegation.amount); const delegationsSum = Number(thisMixnode.total_delegation.amount);
const bondsTotal = printableCoin({ const bondsTotal = currencyToString(
amount: (delegationsSum + pledgesSum).toString(), (delegationsSum + pledgesSum).toString(),
denom: 'upunk', );
});
setBonds({ setBonds({
delegations: decimalisedDelegations, delegations: decimalisedDelegations,
@@ -89,7 +88,7 @@ export const BondBreakdownTable: React.FC = () => {
mixnodeDetailInfo.data[0].total_delegation.amount, mixnodeDetailInfo.data[0].total_delegation.amount,
); );
const rawPledgeAmount = Number( const rawPledgeAmount = Number(
mixnodeDetailInfo.data[0].bond_amount.amount, mixnodeDetailInfo.data[0].pledge_amount.amount,
); );
const rawTotalBondsAmount = rawDelegationAmount + rawPledgeAmount; const rawTotalBondsAmount = rawDelegationAmount + rawPledgeAmount;
return ((num * 100) / rawTotalBondsAmount).toFixed(1); return ((num * 100) / rawTotalBondsAmount).toFixed(1);
@@ -203,7 +202,7 @@ export const BondBreakdownTable: React.FC = () => {
{owner} {owner}
</TableCell> </TableCell>
<TableCell align="left"> <TableCell align="left">
{printableCoin({ amount: amount.toString(), denom })} {currencyToString(amount.toString(), denom)}
</TableCell> </TableCell>
<TableCell align="left"> <TableCell align="left">
{calcBondPercentage(amount)}% {calcBondPercentage(amount)}%
+2 -2
View File
@@ -8,9 +8,9 @@ import {
TableHead, TableHead,
TableRow, TableRow,
} from '@mui/material'; } from '@mui/material';
import { printableCoin } from '@nymproject/nym-validator-client';
import { cellStyles } from './Universal-DataGrid'; import { cellStyles } from './Universal-DataGrid';
import { MixnodeRowType } from '../utils/index'; import { MixnodeRowType } from '../utils/index';
import { currencyToString } from '../utils/currency';
export type ColumnsType = { export type ColumnsType = {
field: string; field: string;
@@ -28,7 +28,7 @@ export interface UniversalTableProps {
function formatCellValues(val: string | number, field: string) { function formatCellValues(val: string | number, field: string) {
if (field === 'bond') { if (field === 'bond') {
return printableCoin({ amount: val.toString(), denom: 'upunk' }); return currencyToString(val.toString());
} }
return val; return val;
} }
+8 -11
View File
@@ -13,7 +13,7 @@ interface ChartProps {
loading: boolean; loading: boolean;
} }
type FormattedDateRecord = [string, number, number]; type FormattedDateRecord = [string, number];
type FormattedChartHeadings = string[]; type FormattedChartHeadings = string[];
type FormattedChartData = [FormattedChartHeadings | FormattedDateRecord]; type FormattedChartData = [FormattedChartHeadings | FormattedDateRecord];
@@ -30,22 +30,19 @@ export const UptimeChart: React.FC<ChartProps> = ({
const color = theme.palette.text.primary; const color = theme.palette.text.primary;
React.useEffect(() => { React.useEffect(() => {
if (uptimeStory.data?.history) { if (uptimeStory.data?.history) {
const allFormattedChartData: FormattedChartData = [ const allFormattedChartData: FormattedChartData = [['Date', 'Uptime']];
['Date', 'UptimeV4', 'UptimeV6'],
];
uptimeStory.data.history.forEach((eachDate) => { uptimeStory.data.history.forEach((eachDate) => {
const formattedDateUptimeRecord: FormattedDateRecord = [ const formattedDateUptimeRecord: FormattedDateRecord = [
format(new Date(eachDate.date), 'MMM dd'), format(new Date(eachDate.date), 'MMM dd'),
eachDate.ipv4_uptime, eachDate.uptime,
eachDate.ipv6_uptime,
]; ];
allFormattedChartData.push(formattedDateUptimeRecord); allFormattedChartData.push(formattedDateUptimeRecord);
}); });
setFormattedChartData(allFormattedChartData); setFormattedChartData(allFormattedChartData);
} else { } else {
const emptyData: any = [ const emptyData: any = [
['Date', 'UptimeV4', 'UptimeV6'], ['Date', 'Uptime'],
['Jul 27', 10, 10], ['Jul 27', 10],
]; ];
setFormattedChartData(emptyData); setFormattedChartData(emptyData);
} }
@@ -65,8 +62,8 @@ export const UptimeChart: React.FC<ChartProps> = ({
uptimeStory.data uptimeStory.data
? formattedChartData ? formattedChartData
: [ : [
['Date', 'UptimeV4', 'UptimeV6'], ['Date', 'Uptime'],
[format(new Date(Date.now()), 'MMM dd'), 0, 0], [format(new Date(Date.now()), 'MMM dd'), 0],
] ]
} }
options={{ options={{
@@ -77,7 +74,7 @@ export const UptimeChart: React.FC<ChartProps> = ({
color: uptimeStory.error color: uptimeStory.error
? 'rgba(255, 255, 255, 0.4)' ? 'rgba(255, 255, 255, 0.4)'
: 'rgba(255, 255, 255, 1)', : 'rgba(255, 255, 255, 1)',
colors: ['#FB7A21', '#CC808A'], colors: ['#FB7A21'],
legend: { legend: {
textStyle: { textStyle: {
color, color,
+4 -10
View File
@@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { Button, Card, Grid, Typography } from '@mui/material'; import { Button, Card, Grid, Typography } from '@mui/material';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { printableCoin } from '@nymproject/nym-validator-client';
import { SelectChangeEvent } from '@mui/material/Select'; import { SelectChangeEvent } from '@mui/material/Select';
import { useMainContext } from 'src/context/main'; import { useMainContext } from 'src/context/main';
import { gatewayToGridRow } from 'src/utils'; import { gatewayToGridRow } from 'src/utils';
@@ -13,6 +12,7 @@ import {
cellStyles, cellStyles,
UniversalDataGrid, UniversalDataGrid,
} from 'src/components/Universal-DataGrid'; } from 'src/components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
export const PageGateways: React.FC = () => { export const PageGateways: React.FC = () => {
const { gateways } = useMainContext(); const { gateways } = useMainContext();
@@ -79,17 +79,11 @@ export const PageGateways: React.FC = () => {
renderHeader: () => <CustomColumnHeading headingTitle="Pledge" />, renderHeader: () => <CustomColumnHeading headingTitle="Pledge" />,
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
headerAlign: 'left', headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => { renderCell: (params: GridRenderCellParams) => (
const bondAsPunk = printableCoin({
amount: params.value as string,
denom: 'upunk',
});
return (
<Typography sx={cellStyles} data-testid="pledge-amount"> <Typography sx={cellStyles} data-testid="pledge-amount">
{bondAsPunk} {currencyToString(params.value)}
</Typography> </Typography>
); ),
},
}, },
{ {
field: 'host', field: 'host',
+4 -10
View File
@@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'; import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
import { printableCoin } from '@nymproject/nym-validator-client';
import { Button, Grid, Link as MuiLink, Card } from '@mui/material'; import { Button, Grid, Link as MuiLink, Card } from '@mui/material';
import { Link as RRDLink } from 'react-router-dom'; import { Link as RRDLink } from 'react-router-dom';
import { SelectChangeEvent } from '@mui/material/Select'; import { SelectChangeEvent } from '@mui/material/Select';
@@ -15,6 +14,7 @@ import {
UniversalDataGrid, UniversalDataGrid,
cellStyles, cellStyles,
} from 'src/components/Universal-DataGrid'; } from 'src/components/Universal-DataGrid';
import { currencyToString } from '../../utils/currency';
export const PageMixnodes: React.FC = () => { export const PageMixnodes: React.FC = () => {
const { mixnodes } = useMainContext(); const { mixnodes } = useMainContext();
@@ -92,21 +92,15 @@ export const PageMixnodes: React.FC = () => {
headerClassName: 'MuiDataGrid-header-override', headerClassName: 'MuiDataGrid-header-override',
width: 150, width: 150,
headerAlign: 'left', headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => { renderCell: (params: GridRenderCellParams) => (
const bondAsPunk = printableCoin({
amount: params.value as string,
denom: 'upunk',
});
return (
<MuiLink <MuiLink
sx={cellStyles} sx={cellStyles}
component={RRDLink} component={RRDLink}
to={`/network-components/mixnodes/${params.row.identity_key}`} to={`/network-components/mixnodes/${params.row.identity_key}`}
> >
{bondAsPunk} {currencyToString(params.value)}
</MuiLink> </MuiLink>
); ),
},
}, },
{ {
field: 'location', field: 'location',
+8 -8
View File
@@ -7,13 +7,14 @@ export interface ClientConfig {
export interface MixNode { export interface MixNode {
host: string; host: string;
location: string; mix_port: number;
http_api_port: number;
verloc_port: number;
sphinx_key: string; sphinx_key: string;
identity_key: string; identity_key: string;
version: string; version: string;
mix_port: number; profit_margin_percent: number;
verloc_port: number; location: string;
http_api_port: number;
} }
export interface Gateway { export interface Gateway {
@@ -32,7 +33,7 @@ export interface Amount {
} }
export interface MixNodeResponseItem { export interface MixNodeResponseItem {
bond_amount: Amount; pledge_amount: Amount;
total_delegation: Amount; total_delegation: Amount;
owner: string; owner: string;
layer: string; layer: string;
@@ -74,7 +75,7 @@ export type MixNodeHistoryResponse = StatsResponse;
export interface GatewayResponseItem { export interface GatewayResponseItem {
block_height: number; block_height: number;
bond_amount: Amount; pledge_amount: Amount;
total_delegation: Amount; total_delegation: Amount;
owner: string; owner: string;
gateway: Gateway; gateway: Gateway;
@@ -156,8 +157,7 @@ export type StatusResponse = {
export type UptimeTime = { export type UptimeTime = {
date: string; date: string;
ipv4_uptime: number; uptime: number;
ipv6_uptime: number;
}; };
export type UptimeStoryResponse = { export type UptimeStoryResponse = {
+19
View File
@@ -0,0 +1,19 @@
import { printableCoin } from '@nymproject/nym-validator-client';
const DENOM = process.env.CURRENCY_DENOM || 'unym';
const DENOM_STAKING = process.env.CURRENCY_STAKING_DENOM || 'unyx';
export const currencyToString = (amount: string, denom: string = DENOM) =>
printableCoin({
amount,
denom,
});
export const stakingCurrencyToString = (
amount: string,
denom: string = DENOM_STAKING,
) =>
printableCoin({
amount,
denom,
});
+2 -2
View File
@@ -69,7 +69,7 @@ export function mixnodeToGridRow(arrayOfMixnodes: MixNodeResponse): any {
return !arrayOfMixnodes return !arrayOfMixnodes
? [] ? []
: arrayOfMixnodes.map((mn) => { : arrayOfMixnodes.map((mn) => {
const pledge = Number(mn.bond_amount.amount) || 0; const pledge = Number(mn.pledge_amount.amount) || 0;
const delegations = Number(mn.total_delegation.amount) || 0; const delegations = Number(mn.total_delegation.amount) || 0;
const totalBond = pledge + delegations; const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2); const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
@@ -96,7 +96,7 @@ export function gatewayToGridRow(
owner: gw.owner, owner: gw.owner,
identity_key: gw.gateway.identity_key || '', identity_key: gw.gateway.identity_key || '',
location: gw?.gateway?.location || '', location: gw?.gateway?.location || '',
bond: gw.bond_amount.amount || 0, bond: gw.pledge_amount.amount || 0,
host: gw.gateway.host || '', host: gw.gateway.host || '',
})); }));
} }
+3
View File
@@ -5,6 +5,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const WebpackFavicons = require('webpack-favicons'); const WebpackFavicons = require('webpack-favicons');
const Dotenv = require('dotenv-webpack');
module.exports = { module.exports = {
entry: './src/index.tsx', entry: './src/index.tsx',
@@ -78,6 +79,8 @@ module.exports = {
new WebpackFavicons({ new WebpackFavicons({
src: 'src/logo.svg', src: 'src/logo.svg',
}), }),
new Dotenv(),
], ],
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),