diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index 5429baf2b4..849bd32a17 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -109,6 +109,7 @@ jobs: cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR + cp contracts/target/wasm32-unknown-unknown/release/nym_ephemera.wasm $OUTPUT_DIR - name: Deploy branch to CI www continue-on-error: true diff --git a/contracts/ephemera/src/contract.rs b/contracts/ephemera/src/contract.rs index 205a881e7f..0cfa78a410 100644 --- a/contracts/ephemera/src/contract.rs +++ b/contracts/ephemera/src/contract.rs @@ -66,3 +66,32 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result, _env: Env, _msg: MigrateMsg) -> Result { Ok(Default::default()) } + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + + #[test] + fn initialize_contract() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let init_msg = InstantiateMsg { + group_addr: "group_addr".to_string(), + mix_denom: "uatom".to_string(), + }; + + let sender = mock_info("sender", &[]); + let res = instantiate(deps.as_mut(), env, sender, init_msg); + assert!(res.is_ok()); + + let expected_state = State { + group_addr: Cw4Contract::new(Addr::unchecked("group_addr")), + mix_denom: "uatom".to_string(), + }; + let state = STATE.load(deps.as_ref().storage).unwrap(); + assert_eq!(state, expected_state); + } +} diff --git a/contracts/ephemera/src/error.rs b/contracts/ephemera/src/error.rs index 91d4078649..2ee74c6ab0 100644 --- a/contracts/ephemera/src/error.rs +++ b/contracts/ephemera/src/error.rs @@ -13,6 +13,9 @@ pub enum ContractError { #[error("Group contract invalid address '{addr}'")] InvalidGroup { addr: String }, + #[error("This potential ephemera peer is not in the ephemera group")] + Unauthorized, + #[error("This sender is already registered")] AlreadyRegistered, } diff --git a/contracts/ephemera/src/lib.rs b/contracts/ephemera/src/lib.rs index 6ad690d864..fa30231382 100644 --- a/contracts/ephemera/src/lib.rs +++ b/contracts/ephemera/src/lib.rs @@ -5,3 +5,4 @@ pub mod contract; pub mod error; mod peers; mod state; +mod support; diff --git a/contracts/ephemera/src/peers/queries.rs b/contracts/ephemera/src/peers/queries.rs index 1922303943..fc24e6d583 100644 --- a/contracts/ephemera/src/peers/queries.rs +++ b/contracts/ephemera/src/peers/queries.rs @@ -33,3 +33,132 @@ pub fn query_peers_paged( Ok(PagedPeerResponse::new(peers, limit, start_next_after)) } + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::peers::storage::{PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT}; + use crate::support::tests::fixtures::peer_fixture; + use crate::support::tests::helpers::init_contract; + use cosmwasm_std::DepsMut; + + fn fill_peers(deps: DepsMut<'_>, size: usize) { + for n in 0..size { + let peer = peer_fixture(&format!("peer{}", n)); + PEERS + .save(deps.storage, peer.cosmos_address.clone(), &peer) + .unwrap(); + } + } + + fn remove_peers(deps: DepsMut<'_>, size: usize) { + for n in 0..size { + let peer = peer_fixture(&format!("peer{}", n)); + PEERS.remove(deps.storage, peer.cosmos_address); + } + } + + #[test] + fn peers_empty_on_init() { + let deps = init_contract(); + + let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap(); + assert_eq!(0, page1.peers.len() as u32); + } + + #[test] + fn peers_paged_retrieval_obeys_limits() { + let mut deps = init_contract(); + let limit = 2; + + fill_peers(deps.as_mut(), 1000); + + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(limit)).unwrap(); + assert_eq!(limit, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_paged_retrieval_has_default_limit() { + let mut deps = init_contract(); + + fill_peers(deps.as_mut(), 1000); + + // query without explicitly setting a limit + let page1 = query_peers_paged(deps.as_ref(), None, None).unwrap(); + + assert_eq!(PEERS_PAGE_DEFAULT_LIMIT, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_paged_retrieval_has_max_limit() { + let mut deps = init_contract(); + + // query with a crazily high limit in an attempt to use too many resources + let crazy_limit = 1000 * PEERS_PAGE_MAX_LIMIT; + + fill_peers(deps.as_mut(), 1000); + + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = PEERS_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.peers.len() as u32); + + remove_peers(deps.as_mut(), 1000); + } + + #[test] + fn peers_pagination_works() { + let mut deps = init_contract(); + + let per_page = 2; + + fill_peers(deps.as_mut(), 1); + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.peers.len()); + remove_peers(deps.as_mut(), 1); + + fill_peers(deps.as_mut(), 2); + // page1 should have 2 results on it + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.peers.len()); + remove_peers(deps.as_mut(), 2); + + fill_peers(deps.as_mut(), 3); + // page1 still has 2 results + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.peers.len()); + + // retrieving the next page should start after the last key on this page + let start_after = page1.start_next_after.unwrap(); + let page2 = query_peers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.peers.len()); + remove_peers(deps.as_mut(), 3); + + fill_peers(deps.as_mut(), 4); + let page1 = query_peers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let start_after = page1.start_next_after.unwrap(); + let page2 = query_peers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.peers.len()); + remove_peers(deps.as_mut(), 4); + } +} diff --git a/contracts/ephemera/src/peers/transactions.rs b/contracts/ephemera/src/peers/transactions.rs index 5b402d64c9..6aa587bcb2 100644 --- a/contracts/ephemera/src/peers/transactions.rs +++ b/contracts/ephemera/src/peers/transactions.rs @@ -3,6 +3,7 @@ use crate::error::ContractError; use crate::peers::storage::PEERS; +use crate::state::STATE; use cosmwasm_std::{DepsMut, MessageInfo, Response}; use nym_ephemera_common::types::JsonPeerInfo; @@ -12,9 +13,51 @@ pub fn try_register_peer( peer_info: JsonPeerInfo, ) -> Result { if PEERS.may_load(deps.storage, info.sender.clone())?.is_none() { - PEERS.save(deps.storage, info.sender, &peer_info)?; + if STATE + .load(deps.storage)? + .group_addr + .is_voting_member(&deps.querier, &info.sender, None)? + .is_some() + { + PEERS.save(deps.storage, info.sender, &peer_info)?; + Ok(Default::default()) + } else { + Err(ContractError::Unauthorized {}) + } } else { - return Err(ContractError::AlreadyRegistered); + Err(ContractError::AlreadyRegistered) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::support::tests::fixtures::peer_fixture; + use crate::support::tests::helpers; + use crate::support::tests::helpers::GROUP_MEMBERS; + use cosmwasm_std::testing::mock_info; + use cw4::Member; + + #[test] + fn peer_registration() { + let mut deps = helpers::init_contract(); + let peer_info = peer_fixture("owner"); + let info = mock_info("owner", &[]); + + let ret = try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap_err(); + assert_eq!(ret, ContractError::Unauthorized); + + GROUP_MEMBERS.lock().unwrap().push(( + Member { + addr: "owner".to_string(), + weight: 10, + }, + 1, + )); + + try_register_peer(deps.as_mut(), info.clone(), peer_info.clone()).unwrap(); + + let ret = try_register_peer(deps.as_mut(), info, peer_info).unwrap_err(); + assert_eq!(ret, ContractError::AlreadyRegistered); } - Ok(Default::default()) } diff --git a/contracts/ephemera/src/support/mod.rs b/contracts/ephemera/src/support/mod.rs new file mode 100644 index 0000000000..070bbc8eed --- /dev/null +++ b/contracts/ephemera/src/support/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +pub mod tests; diff --git a/contracts/ephemera/src/support/tests/fixtures.rs b/contracts/ephemera/src/support/tests/fixtures.rs new file mode 100644 index 0000000000..bf1f469b6d --- /dev/null +++ b/contracts/ephemera/src/support/tests/fixtures.rs @@ -0,0 +1,15 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::Addr; +use nym_ephemera_common::types::JsonPeerInfo; + +pub const TEST_MIX_DENOM: &str = "unym"; + +pub fn peer_fixture(cosmos_address: &str) -> JsonPeerInfo { + JsonPeerInfo { + cosmos_address: Addr::unchecked(cosmos_address), + ip_address: "127.0.0.1".to_string(), + public_key: "random_key".to_string(), + } +} diff --git a/contracts/ephemera/src/support/tests/helpers.rs b/contracts/ephemera/src/support/tests/helpers.rs new file mode 100644 index 0000000000..df665eba1e --- /dev/null +++ b/contracts/ephemera/src/support/tests/helpers.rs @@ -0,0 +1,74 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::contract::instantiate; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; +use cosmwasm_std::{ + from_binary, to_binary, ContractResult, Empty, MemoryStorage, OwnedDeps, QuerierResult, + SystemResult, WasmQuery, +}; +use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse}; +use lazy_static::lazy_static; +use nym_ephemera_common::msg::InstantiateMsg; +use std::sync::Mutex; + +use super::fixtures::TEST_MIX_DENOM; + +pub const ADMIN_ADDRESS: &str = "admin address"; +pub const GROUP_CONTRACT: &str = "group contract address"; + +lazy_static! { + pub static ref GROUP_MEMBERS: Mutex> = Mutex::new(vec![]); +} + +fn querier_handler(query: &WasmQuery) -> QuerierResult { + let bin = match query { + WasmQuery::Smart { contract_addr, msg } => { + if contract_addr != GROUP_CONTRACT { + panic!("Not supported"); + } + match from_binary(msg) { + Ok(Cw4QueryMsg::Member { addr, at_height }) => { + let weight = GROUP_MEMBERS.lock().unwrap().iter().find_map(|(m, h)| { + if m.addr == addr { + if let Some(height) = at_height { + if height != *h { + return None; + } + } + Some(m.weight) + } else { + None + } + }); + to_binary(&MemberResponse { weight }).unwrap() + } + Ok(Cw4QueryMsg::ListMembers { .. }) => { + let members = GROUP_MEMBERS + .lock() + .unwrap() + .iter() + .map(|m| m.0.clone()) + .collect(); + to_binary(&MemberListResponse { members }).unwrap() + } + _ => panic!("Not supported"), + } + } + _ => panic!("Not supported"), + }; + SystemResult::Ok(ContractResult::Ok(bin)) +} + +pub fn init_contract() -> OwnedDeps> { + let mut deps = mock_dependencies(); + deps.querier.update_wasm(querier_handler); + let msg = InstantiateMsg { + group_addr: GROUP_CONTRACT.to_string(), + mix_denom: TEST_MIX_DENOM.to_string(), + }; + let env = mock_env(); + let info = mock_info(ADMIN_ADDRESS, &[]); + instantiate(deps.as_mut(), env, info, msg).unwrap(); + deps +} diff --git a/contracts/ephemera/src/support/tests/mod.rs b/contracts/ephemera/src/support/tests/mod.rs new file mode 100644 index 0000000000..44f93cb286 --- /dev/null +++ b/contracts/ephemera/src/support/tests/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod fixtures; +pub mod helpers; diff --git a/envs/qa.env b/envs/qa.env index ec2cbffe62..7c7431cc89 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -9,17 +9,19 @@ MIX_DENOM_DISPLAY=nym STAKE_DENOM=unyx STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 -MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8 -VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +MIXNET_CONTRACT_ADDRESS=n10qt8wg0n7z740ssvf3urmvgtjhxpyp74hxqvqt7z226gykuus7eq5u9pvq +VESTING_CONTRACT_ADDRESS=n1vguuxez2h5ekltfj9gjd62fs5k4rl2zy5hfrncasykzw08rezpfstk9xtk BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 -COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw -GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m -MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs -COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs -EPHEMERA_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs -REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq -STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" -NYXD="https://qa-validator.nymtech.net" -NYM_API="https://qa-validator-api.nymtech.net/api" +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1x22q8lfhz7qcvtzs0dakhgx2th64l79kepjujhhxk5x804taeqlqcywn96 +GROUP_CONTRACT_ADDRESS=n1g4xlpqy29m50j5y69reguae328tc9y83l4299pf2wmjn0xczq5js3704ql +MULTISIG_CONTRACT_ADDRESS=n1p54qvfde6mpnqvz3dnpa78x2qyyr5k4sgw9qr97mxjgklc5gze9sv6t964 +COCONUT_DKG_CONTRACT_ADDRESS=n1xqkp8x4gqwjnhemtemc5dqhwll6w6rrgpywvhka7sh8vz8swul9sp3lv3w +EPHEMERA_CONTRACT_ADDRESS=n1fc7nakzuyfn2qzkclafcsc54asamnclg064962lwne40w2lq558qftzjza +REWARDING_VALIDATOR_ADDRESS=n1547eraje66h9mf99fm50mea04n9mrzsc8c23r5 +NAME=n1uz24lsnwxvhep8m3gjec7ev86twlhlqrf5rphlgn3rda3zu048ssjqr5w9 +SERVICE_PROVICER=n1nhdr07kmjns2x8dnp53tdk4qxreze8zdxj6xucyvkdj9tta73rjqa96wps +STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" +NYXD="https://qa-validator.qa.nymte.ch/" +NYM_API="https://qa-nym-api.qa.nymte.ch/api" -DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" \ No newline at end of file +DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" diff --git a/ephemera/src/cli/init.rs b/ephemera/src/cli/init.rs index 5ef3e7fff5..ffbd2bd0df 100644 --- a/ephemera/src/cli/init.rs +++ b/ephemera/src/cli/init.rs @@ -36,8 +36,8 @@ impl Default for MembershipKind { fn default() -> Self { MembershipKind { threshold: None, - all: true, - any: false, + all: false, + any: true, } } } diff --git a/nym-api/src/ephemera/application.rs b/nym-api/src/ephemera/application.rs index d23a8d15d7..27d3510d83 100644 --- a/nym-api/src/ephemera/application.rs +++ b/nym-api/src/ephemera/application.rs @@ -7,15 +7,16 @@ use ephemera::{ }; use log::{debug, error, info}; +use crate::ephemera::client::Client; use crate::ephemera::epoch::Epoch; use crate::ephemera::peers::members::MembersProvider; -use crate::ephemera::peers::NymApiEphemeraPeerInfo; +use crate::ephemera::peers::{NymApiEphemeraPeerInfo, NymPeer}; use crate::ephemera::reward::aggregator::RewardsAggregator; use crate::ephemera::reward::MixnodeToReward; use crate::ephemera::reward::{EphemeraAccess, RewardManager}; use crate::ephemera::Args; use crate::support::nyxd; -use ephemera::crypto::{EphemeraKeypair, Keypair}; +use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair}; use ephemera::ephemera_api::CommandExecutor; use ephemera::{Ephemera, EphemeraStarterInit}; use nym_task::TaskManager; @@ -55,15 +56,39 @@ impl NymApi { ) -> anyhow::Result> { info!("Initializing ephemera ..."); - //Application for Ephemera - let rewards_ephemera_application = - RewardsEphemeraApplication::init(ephemera_config.clone(), nyxd_client.clone()).await?; + let node_info = ephemera_config.node.clone(); + + let keypair = bs58::decode(&node_info.private_key).into_vec().unwrap(); + let keypair = Keypair::from_bytes(&keypair).unwrap(); + let local_peer_id = keypair.public_key().to_base58(); //Members provider for Ephemera - let members_provider = MembersProvider::new(nyxd_client); + let members_provider = MembersProvider::new(nyxd_client.clone()); - let local_peer = rewards_ephemera_application.peer_info.local_peer.clone(); - members_provider.register_peer(local_peer).await?; + let cosmos_address = nyxd_client.client_address().await.to_string(); + let ip_address = format!( + "/ip4/{}/tcp/{}", + ephemera_config.node.ip, ephemera_config.libp2p.port + ); + + if !nyxd_client + .get_ephemera_peers() + .await? + .iter() + .any(|peer_info| peer_info.cosmos_address == cosmos_address) + { + let local_peer = NymPeer::new( + cosmos_address, + ip_address, + keypair.public_key(), + local_peer_id.clone(), + ); + members_provider.register_peer(local_peer).await?; + } + + //Application for Ephemera + let rewards_ephemera_application = + RewardsEphemeraApplication::init(local_peer_id, nyxd_client.clone()).await?; //EPHEMERA let ephemera_builder = EphemeraStarterInit::new(ephemera_config)?; @@ -107,11 +132,11 @@ pub(crate) struct RewardsEphemeraApplication { impl RewardsEphemeraApplication { pub(crate) async fn init( - ephemera_config: Configuration, + local_peer_id: String, nyxd_client: nyxd::Client, ) -> anyhow::Result { let peer_info = match NymApiEphemeraPeerInfo::from_ephemera_dev_cluster_conf( - &ephemera_config, + local_peer_id, nyxd_client, ) .await diff --git a/nym-api/src/ephemera/peers/mod.rs b/nym-api/src/ephemera/peers/mod.rs index 60d88f2a48..0706c51fde 100644 --- a/nym-api/src/ephemera/peers/mod.rs +++ b/nym-api/src/ephemera/peers/mod.rs @@ -5,8 +5,7 @@ use anyhow::anyhow; use crate::ephemera::client::Client; use crate::support::nyxd; -use ephemera::configuration::Configuration; -use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair, PublicKey}; +use ephemera::crypto::PublicKey; pub(crate) type PeerId = String; @@ -38,13 +37,13 @@ impl NymPeer { // Information about other Nym-Apis. pub(crate) struct NymApiEphemeraPeerInfo { - pub(crate) local_peer: NymPeer, + pub(crate) _local_peer: NymPeer, pub(crate) peers: HashMap, } impl NymApiEphemeraPeerInfo { - fn new(local_peer: NymPeer, peers: HashMap) -> Self { - Self { local_peer, peers } + fn new(_local_peer: NymPeer, peers: HashMap) -> Self { + Self { _local_peer, peers } } pub(crate) fn get_peers_count(&self) -> usize { @@ -52,15 +51,9 @@ impl NymApiEphemeraPeerInfo { } pub(crate) async fn from_ephemera_dev_cluster_conf( - conf: &Configuration, + local_peer_id: String, nyxd_client: nyxd::Client, ) -> anyhow::Result { - let node_info = conf.node.clone(); - - let keypair = bs58::decode(&node_info.private_key).into_vec().unwrap(); - let keypair = Keypair::from_bytes(&keypair).unwrap(); - let local_peer_id = keypair.public_key().to_base58(); - let mut peers = HashMap::new(); for peer_info in nyxd_client.get_ephemera_peers().await? { let public_key = PublicKey::from_str(&peer_info.public_key)?; diff --git a/nym-api/src/ephemera/reward/aggregator.rs b/nym-api/src/ephemera/reward/aggregator.rs index 57374d7902..c375650542 100644 --- a/nym-api/src/ephemera/reward/aggregator.rs +++ b/nym-api/src/ephemera/reward/aggregator.rs @@ -13,7 +13,7 @@ impl RewardsAggregator { pub(crate) fn aggregate( &self, all_rewards: Vec>, - ) -> Vec { + ) -> anyhow::Result> { let mut mix_rewards = HashMap::new(); for api_rewards in all_rewards { for mixnode in api_rewards { @@ -29,14 +29,15 @@ impl RewardsAggregator { let mut mean_avg = vec![]; for (mix_id, rewards) in mix_rewards { let sum: Decimal = rewards.iter().map(|r| r.value()).sum(); - let avg = sum / Decimal::raw(rewards.len() as u128); + let avg = sum / Decimal::from_ratio(rewards.len() as u128, 1u128); + let performance = Performance::new(avg)?; mean_avg.push(MixnodeWithPerformance { mix_id, - performance: Performance::new(avg).expect("Decimal average done wrong"), + performance, }); } info!("Mean average rewards: {:?}", mean_avg); - mean_avg + Ok(mean_avg) } } diff --git a/nym-api/src/ephemera/reward/mod.rs b/nym-api/src/ephemera/reward/mod.rs index b3c639283d..42135a7f72 100644 --- a/nym-api/src/ephemera/reward/mod.rs +++ b/nym-api/src/ephemera/reward/mod.rs @@ -259,7 +259,7 @@ where mix_node_rewards.push(mix_node_reward); } - let aggregated_rewards = self.aggregator().aggregate(mix_node_rewards); + let aggregated_rewards = self.aggregator().aggregate(mix_node_rewards)?; debug!("Aggregated rewards: {:?}", aggregated_rewards); Ok(aggregated_rewards) diff --git a/nym-api/src/support/config/template.rs b/nym-api/src/support/config/template.rs index 30d977c97c..8ecfaf25c4 100644 --- a/nym-api/src/support/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -138,10 +138,10 @@ public_key_with_proof_path = '{{ coconut_signer.storage_paths.public_key_with_pr ephemera_config = '{{ ephemera.args.ephemera_config }}' # Interval, in seconds, for polling the Ephemera blocks -block_polling_interval_seconds = '{{ ephemera.args.block_polling_interval_seconds }}' +block_polling_interval_seconds = {{ ephemera.args.block_polling_interval_seconds }} # Maximum number of attempts for polling the Ephemera blocks -block_polling_max_attempts = '{{ ephemera.args.block_polling_max_attempts }}' +block_polling_max_attempts = {{ ephemera.args.block_polling_max_attempts }} [ephemera.args.cmd] @@ -149,35 +149,35 @@ block_polling_max_attempts = '{{ ephemera.args.block_polling_max_attempts }}' ip = '{{ ephemera.args.cmd.ip }}' # The port which Ephemera uses for peer to peer communication -protocol_port = '{{ ephemera.args.cmd.protocol_port }}' +protocol_port = {{ ephemera.args.cmd.protocol_port }} # The port which Ephemera listens on for websocket subscriptions -websocket_port = '{{ ephemera.args.cmd.websocket_port }}' +websocket_port = {{ ephemera.args.cmd.websocket_port }} # The port which Ephemera listens on for http api -http_api_port = '{{ ephemera.args.cmd.http_api_port }}' +http_api_port = {{ ephemera.args.cmd.http_api_port }} # Either this node produces blocks or not -block_producer = '{{ ephemera.args.cmd.block_producer }}' +block_producer = {{ ephemera.args.cmd.block_producer }} # At which interval to produce blocks -block_creation_interval_sec = '{{ ephemera.args.cmd.block_creation_interval_sec }}' +block_creation_interval_sec = {{ ephemera.args.cmd.block_creation_interval_sec }} # When next block is created before previous one is finished, should we repeat it with the same messages -repeat_last_block_messages = '{{ ephemera.args.cmd.repeat_last_block_messages }}' +repeat_last_block_messages = {{ ephemera.args.cmd.repeat_last_block_messages }} # The interval at which Ephemera requests the list of members -members_provider_delay_sec = '{{ ephemera.args.cmd.members_provider_delay_sec }}' +members_provider_delay_sec = {{ ephemera.args.cmd.members_provider_delay_sec }} [ephemera.args.cmd.membership_kind] # Requires the threshold of peers returned by membership provider to be online -threshold = '{{ ephemera.args.cmd.membership_kind.threshold }}' +threshold = {{ ephemera.args.cmd.membership_kind.threshold }} # Requires that all peers returned by membership provider peers to be online -all = '{{ ephemera.args.cmd.membership_kind.all }}' +all = {{ ephemera.args.cmd.membership_kind.all }} # Membership is just all online peers from the set returned by membership provider -any = '{{ ephemera.args.cmd.membership_kind.any }}' +any = {{ ephemera.args.cmd.membership_kind.any }} "#;