Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5102df3bd3 | |||
| 7d0d5cb81e | |||
| dcc0ef0e47 | |||
| df93356a23 | |||
| b80f9de18d |
Generated
+36
@@ -6294,6 +6294,23 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-storage-plus",
|
||||
"cw2",
|
||||
"nym-contracts-common",
|
||||
"nym-contracts-common-testing",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-vesting-contract-common",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.6.0"
|
||||
@@ -6813,6 +6830,25 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-performance-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-controllers",
|
||||
"cw-storage-plus",
|
||||
"cw2",
|
||||
"nym-contracts-common",
|
||||
"nym-contracts-common-testing",
|
||||
"nym-crypto",
|
||||
"nym-mixnet-contract",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-performance-contract-common",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-performance-contract-common"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -34,6 +34,7 @@ members = [
|
||||
"common/nym-common",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"contracts/performance",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/contracts-common-testing",
|
||||
"common/cosmwasm-smart-contracts/easy_addr",
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ pub use nym_performance_contract_common::{
|
||||
EpochMeasurementsPagedResponse, EpochNodePerformance, EpochPerformancePagedResponse,
|
||||
FullHistoricalPerformancePagedResponse, HistoricalPerformance, LastSubmission,
|
||||
NetworkMonitorInformation, NetworkMonitorsPagedResponse, NodeId, NodeMeasurement,
|
||||
NodeMeasurementsResponse, NodePerformance, NodePerformancePagedResponse,
|
||||
NodeMeasurementsPerKindResponse, NodePerformance, NodePerformancePagedResponse,
|
||||
NodePerformanceResponse, RetiredNetworkMonitor, RetiredNetworkMonitorsPagedResponse,
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ pub trait PerformanceQueryClient {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
node_id: NodeId,
|
||||
) -> Result<NodeMeasurementsResponse, NyxdError> {
|
||||
) -> Result<NodeMeasurementsPerKindResponse, NyxdError> {
|
||||
self.query_performance_contract(PerformanceQueryMsg::NodeMeasurements { epoch_id, node_id })
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ pub mod storage_keys {
|
||||
pub const AUTHORISED_COUNT: &str = "authorised-count";
|
||||
pub const AUTHORISED: &str = "authorised";
|
||||
pub const RETIRED: &str = "retired";
|
||||
pub const PERFORMANCE_RESULTS: &str = "performance-results";
|
||||
pub const PERFORMANCE_RESULTS_PER_KIND: &str = "performance-results-per-kind";
|
||||
pub const PERFORMANCE_DEFINED_KINDS: &str = "performance-defined-kinds";
|
||||
|
||||
pub const SUBMISSION_METADATA: &str = "submission-metadata";
|
||||
}
|
||||
|
||||
@@ -23,6 +23,15 @@ pub enum NymPerformanceContractError {
|
||||
#[error("{address} is not an authorised network monitor")]
|
||||
NotAuthorised { address: Addr },
|
||||
|
||||
#[error("{kind} not a valid measurement kind")]
|
||||
UnsupportedMeasurementKind { kind: String },
|
||||
|
||||
#[error("Measurement {kind} already defined")]
|
||||
MeasurementAlreadyDefined { kind: String },
|
||||
|
||||
#[error("Invalid input: {0}")]
|
||||
InvalidInput(String),
|
||||
|
||||
#[error(
|
||||
"attempted to submit performance data for epoch {epoch_id} and node {node_id} whilst last submitted was {last_epoch_id} for node {last_node_id}"
|
||||
)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use cosmwasm_schema::cw_serde;
|
||||
use crate::types::{
|
||||
EpochMeasurementsPagedResponse, EpochPerformancePagedResponse,
|
||||
FullHistoricalPerformancePagedResponse, LastSubmission, NetworkMonitorResponse,
|
||||
NetworkMonitorsPagedResponse, NodeMeasurementsResponse, NodePerformancePagedResponse,
|
||||
NetworkMonitorsPagedResponse, NodeMeasurementsPerKindResponse, NodePerformancePagedResponse,
|
||||
NodePerformanceResponse, RetiredNetworkMonitorsPagedResponse,
|
||||
};
|
||||
|
||||
@@ -35,6 +35,14 @@ pub enum ExecuteMsg {
|
||||
data: Vec<NodePerformance>,
|
||||
},
|
||||
|
||||
/// Measurement kind needs to be defined by the admin before measurements of
|
||||
/// that kind can be submitted.
|
||||
DefineMeasurementKind { measurement_kind: String },
|
||||
|
||||
/// After this action is done, measurements of this kind aren't returned on the API anymore
|
||||
/// New measurements of this kind cannot be submitted
|
||||
RetireMeasurementKind { measurement_kind: String },
|
||||
|
||||
/// Attempt to authorise new network monitor for submitting performance data
|
||||
AuthoriseNetworkMonitor { address: String },
|
||||
|
||||
@@ -69,9 +77,16 @@ pub enum QueryMsg {
|
||||
limit: Option<u32>,
|
||||
},
|
||||
|
||||
/// Returns all submitted measurements for the particular node
|
||||
/// Returns all measurements of a specific kind for the particular node
|
||||
#[cfg_attr(feature = "schema", returns(NodeMeasurementsResponse))]
|
||||
NodeMeasurements { epoch_id: EpochId, node_id: NodeId },
|
||||
NodeMeasurements {
|
||||
epoch_id: EpochId,
|
||||
node_id: NodeId,
|
||||
kind: String,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(NodeMeasurementsResponse))]
|
||||
AllNodeMeasurements { epoch_id: EpochId, node_id: NodeId },
|
||||
|
||||
/// Returns (paged) measurements for particular epoch
|
||||
#[cfg_attr(feature = "schema", returns(EpochMeasurementsPagedResponse))]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Env, Timestamp};
|
||||
use nym_contracts_common::Percent;
|
||||
@@ -49,11 +51,13 @@ pub struct RetiredNetworkMonitor {
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct NodePerformance {
|
||||
#[serde(rename = "n")]
|
||||
pub node_id: NodeId,
|
||||
|
||||
#[serde(rename = "m")]
|
||||
pub measurement_kind: MeasurementKind,
|
||||
|
||||
// note: value is rounded to 2 decimal places.
|
||||
#[serde(rename = "p")]
|
||||
pub performance: Percent,
|
||||
@@ -97,25 +101,35 @@ impl NodeResults {
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &[Percent] {
|
||||
&self.0
|
||||
&self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
pub type MeasurementKind = String;
|
||||
|
||||
/// maps measurement kind to the value of that measurement for a node
|
||||
/// (present only if measured)
|
||||
#[cw_serde]
|
||||
pub struct NodePerformanceResponse {
|
||||
pub performance: Option<Percent>,
|
||||
pub performance: HashMap<MeasurementKind, Percent>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct NodeMeasurementsResponse {
|
||||
pub struct NodeMeasurementsPerKindResponse {
|
||||
pub measurements: Option<NodeResults>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct AllNodeMeasurementsResponse {
|
||||
// Option is used because if a measurement has been defined, that doesn't
|
||||
// mean the node had actually been measured at the time of the query
|
||||
pub measurements: HashMap<MeasurementKind, Option<NodeResults>>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct EpochNodePerformance {
|
||||
pub epoch: EpochId,
|
||||
pub performance: Option<Percent>,
|
||||
pub performance: HashMap<MeasurementKind, Percent>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -133,24 +147,23 @@ pub struct EpochPerformancePagedResponse {
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct NodeMeasurement {
|
||||
pub struct NodeMeasurements {
|
||||
pub node_id: NodeId,
|
||||
pub measurements: NodeResults,
|
||||
pub measurements_per_kind: HashMap<String, NodeResults>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct EpochMeasurementsPagedResponse {
|
||||
pub epoch_id: EpochId,
|
||||
pub measurements: Vec<NodeMeasurement>,
|
||||
pub measurements: Vec<NodeMeasurements>,
|
||||
pub start_next_after: Option<NodeId>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct HistoricalPerformance {
|
||||
pub epoch_id: EpochId,
|
||||
pub node_id: NodeId,
|
||||
pub performance: Percent,
|
||||
pub performance: HashMap<MeasurementKind, Percent>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
@@ -187,11 +200,14 @@ pub struct RemoveEpochMeasurementsResponse {
|
||||
pub additional_entries_to_remove_remaining: bool,
|
||||
}
|
||||
|
||||
/// return details about submissions: whether they were accepted, or why they
|
||||
/// were rejected
|
||||
#[cw_serde]
|
||||
#[derive(Default)]
|
||||
pub struct BatchSubmissionResult {
|
||||
pub accepted_scores: u64,
|
||||
pub non_existent_nodes: Vec<NodeId>,
|
||||
pub non_existent_measurement_kind: Vec<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::queries::{
|
||||
query_admin, query_epoch_measurements_paged, query_epoch_performance_paged,
|
||||
query_full_historical_performance_paged, query_last_submission, query_network_monitor_details,
|
||||
query_network_monitors_paged, query_node_measurements, query_node_performance,
|
||||
query_node_performance_paged, query_retired_network_monitors_paged,
|
||||
query_admin, query_all_node_measurements, query_epoch_measurements_paged,
|
||||
query_epoch_performance_paged, query_full_historical_performance_paged, query_last_submission,
|
||||
query_network_monitor_details, query_network_monitors_paged, query_node_measurements_for_kind,
|
||||
query_node_performance, query_node_performance_paged, query_retired_network_monitors_paged,
|
||||
};
|
||||
use crate::storage::NYM_PERFORMANCE_CONTRACT_STORAGE;
|
||||
use crate::transactions::{
|
||||
try_authorise_network_monitor, try_batch_submit_performance_results,
|
||||
try_remove_epoch_measurements, try_remove_node_measurements, try_retire_network_monitor,
|
||||
try_submit_performance_results, try_update_contract_admin,
|
||||
try_define_measurement_kind, try_remove_epoch_measurements, try_remove_node_measurements,
|
||||
try_retire_measurement_kind, try_retire_network_monitor, try_submit_performance_results,
|
||||
try_update_contract_admin,
|
||||
};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response,
|
||||
Binary, Deps, DepsMut, Env, MessageInfo, Response, entry_point, to_json_binary,
|
||||
};
|
||||
use nym_contracts_common::set_build_information;
|
||||
use nym_performance_contract_common::{
|
||||
@@ -62,12 +63,20 @@ pub fn execute(
|
||||
ExecuteMsg::BatchSubmit { epoch, data } => {
|
||||
try_batch_submit_performance_results(deps, env, info, epoch, data)
|
||||
}
|
||||
ExecuteMsg::DefineMeasurementKind { measurement_kind } => {
|
||||
try_define_measurement_kind(deps, &info.sender, measurement_kind)
|
||||
}
|
||||
ExecuteMsg::RetireMeasurementKind { measurement_kind } => {
|
||||
try_retire_measurement_kind(deps, &info.sender, measurement_kind)
|
||||
}
|
||||
ExecuteMsg::AuthoriseNetworkMonitor { address } => {
|
||||
try_authorise_network_monitor(deps, env, info, address)
|
||||
}
|
||||
ExecuteMsg::RetireNetworkMonitor { address } => {
|
||||
try_retire_network_monitor(deps, env, info, address)
|
||||
}
|
||||
// TODO dz removing measurement for only a certain node shouldn't be allowed
|
||||
// remove this message and corresponding path
|
||||
ExecuteMsg::RemoveNodeMeasurements { epoch_id, node_id } => {
|
||||
try_remove_node_measurements(deps, info, epoch_id, node_id)
|
||||
}
|
||||
@@ -116,9 +125,17 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, NymPerformance
|
||||
QueryMsg::RetiredNetworkMonitorsPaged { start_after, limit } => Ok(to_json_binary(
|
||||
&query_retired_network_monitors_paged(deps, start_after, limit)?,
|
||||
)?),
|
||||
QueryMsg::NodeMeasurements { epoch_id, node_id } => Ok(to_json_binary(
|
||||
&query_node_measurements(deps, epoch_id, node_id)?,
|
||||
QueryMsg::NodeMeasurements {
|
||||
epoch_id,
|
||||
node_id,
|
||||
kind,
|
||||
} => Ok(to_json_binary(&query_node_measurements_for_kind(
|
||||
deps, epoch_id, node_id, kind,
|
||||
)?)?),
|
||||
QueryMsg::AllNodeMeasurements { epoch_id, node_id } => Ok(to_json_binary(
|
||||
&query_all_node_measurements(deps, epoch_id, node_id)?,
|
||||
)?),
|
||||
|
||||
QueryMsg::EpochMeasurementsPaged {
|
||||
epoch_id,
|
||||
start_after,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{from_json, Binary, CustomQuery, QuerierWrapper, StdError, StdResult};
|
||||
use cosmwasm_std::{Binary, CustomQuery, QuerierWrapper, StdError, StdResult, from_json};
|
||||
use cw_storage_plus::{Key, Namespace, Path, PrimaryKey};
|
||||
use nym_mixnet_contract_common::{Interval, NymNodeBond};
|
||||
use nym_performance_contract_common::{EpochId, NodeId};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,35 +3,35 @@
|
||||
|
||||
use crate::contract::{execute, instantiate, migrate, query};
|
||||
use crate::helpers::MixnetContractQuerier;
|
||||
use crate::storage::NYM_PERFORMANCE_CONTRACT_STORAGE;
|
||||
use cosmwasm_std::testing::{message_info, mock_env, MockApi};
|
||||
use crate::storage::{MeasurementKind, NYM_PERFORMANCE_CONTRACT_STORAGE};
|
||||
use cosmwasm_std::testing::{MockApi, message_info, mock_env};
|
||||
use cosmwasm_std::{
|
||||
coin, coins, Addr, ContractInfo, Deps, DepsMut, Env, MessageInfo, QuerierWrapper, StdError,
|
||||
StdResult,
|
||||
Addr, ContractInfo, Deps, DepsMut, Env, MessageInfo, QuerierWrapper, StdError, StdResult, coin,
|
||||
coins,
|
||||
};
|
||||
use mixnet_contract::testable_mixnet_contract::MixnetContract;
|
||||
use nym_contracts_common::signing::{ContractMessageContent, MessageSignature};
|
||||
use nym_contracts_common::Percent;
|
||||
use nym_contracts_common::signing::{ContractMessageContent, MessageSignature};
|
||||
use nym_contracts_common_testing::{
|
||||
addr, AdminExt, ArbitraryContractStorageReader, ArbitraryContractStorageWriter, BankExt,
|
||||
ChainOpts, CommonStorageKeys, ContractFn, ContractOpts, ContractStorageWrapper, ContractTester,
|
||||
ContractTesterBuilder, DenomExt, PermissionedFn, QueryFn, RandExt, TestableNymContract,
|
||||
TEST_DENOM,
|
||||
AdminExt, ArbitraryContractStorageReader, ArbitraryContractStorageWriter, BankExt, ChainOpts,
|
||||
CommonStorageKeys, ContractFn, ContractOpts, ContractStorageWrapper, ContractTester,
|
||||
ContractTesterBuilder, DenomExt, PermissionedFn, QueryFn, RandExt, TEST_DENOM,
|
||||
TestableNymContract, addr,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_mixnet_contract_common::nym_node::{NodeDetailsResponse, NodeOwnershipResponse, Role};
|
||||
use nym_mixnet_contract_common::{
|
||||
CurrentIntervalResponse, EpochId, Interval, NodeCostParams, NymNode, NymNodeBondingPayload,
|
||||
RoleAssignment, SignableNymNodeBondingMsg, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT,
|
||||
DEFAULT_PROFIT_MARGIN_PERCENT,
|
||||
CurrentIntervalResponse, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
|
||||
EpochId, Interval, NodeCostParams, NymNode, NymNodeBondingPayload, RoleAssignment,
|
||||
SignableNymNodeBondingMsg,
|
||||
};
|
||||
use nym_performance_contract_common::constants::storage_keys;
|
||||
use nym_performance_contract_common::{
|
||||
ExecuteMsg, InstantiateMsg, MigrateMsg, NodeId, NodePerformance, NodeResults,
|
||||
NymPerformanceContractError, QueryMsg,
|
||||
EpochNodePerformance, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeId, NodePerformance,
|
||||
NodeResults, NymPerformanceContractError, QueryMsg,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct PerformanceContract;
|
||||
@@ -94,6 +94,20 @@ pub fn init_contract_tester() -> ContractTester<PerformanceContract> {
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, storage_keys::CONTRACT_ADMIN)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
// shorthand factory to avoid verbosity in tests
|
||||
pub(crate) fn epoch_node_performance_unchecked(
|
||||
epoch: EpochId,
|
||||
measurement_kind: MeasurementKind,
|
||||
performance: &str,
|
||||
) -> EpochNodePerformance {
|
||||
let performance = performance.parse().unwrap();
|
||||
EpochNodePerformance {
|
||||
epoch,
|
||||
performance: [(measurement_kind, performance)].into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
// we need to be able to test instantiation, but for that we require
|
||||
// deps in a state that already includes instantiated mixnet contract
|
||||
pub(crate) struct PreInitContract {
|
||||
@@ -358,11 +372,33 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dummy_measurement_kind(&mut self) -> MeasurementKind {
|
||||
String::from("dummy")
|
||||
}
|
||||
|
||||
fn define_dummy_measurement_kind(
|
||||
&mut self,
|
||||
) -> Result<MeasurementKind, NymPerformanceContractError> {
|
||||
let admin = self.admin_unchecked();
|
||||
let measurement_kind = self.dummy_measurement_kind();
|
||||
|
||||
self.execute_raw(
|
||||
admin,
|
||||
ExecuteMsg::DefineMeasurementKind {
|
||||
measurement_kind: measurement_kind.clone(),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(measurement_kind)
|
||||
}
|
||||
|
||||
fn dummy_node_performance(&mut self) -> NodePerformance {
|
||||
let node_id = self.bond_dummy_nymnode().unwrap();
|
||||
let measurement_kind = self.dummy_measurement_kind();
|
||||
NodePerformance {
|
||||
node_id,
|
||||
performance: Percent::from_percentage_value(69).unwrap(),
|
||||
measurement_kind,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +418,7 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
addr: &Addr,
|
||||
epoch_id: EpochId,
|
||||
node_id: NodeId,
|
||||
measurement_kind: MeasurementKind,
|
||||
performance: Percent,
|
||||
) -> Result<(), NymPerformanceContractError> {
|
||||
let env = self.env();
|
||||
@@ -393,6 +430,7 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
NodePerformance {
|
||||
node_id,
|
||||
performance,
|
||||
measurement_kind,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -401,11 +439,12 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
&mut self,
|
||||
addr: &Addr,
|
||||
node_id: NodeId,
|
||||
measurement_kind: MeasurementKind,
|
||||
performance: Percent,
|
||||
) -> Result<(), NymPerformanceContractError> {
|
||||
let epoch_id = self.current_mixnet_epoch()?;
|
||||
|
||||
self.insert_epoch_performance(addr, epoch_id, node_id, performance)
|
||||
self.insert_epoch_performance(addr, epoch_id, node_id, measurement_kind, performance)
|
||||
}
|
||||
|
||||
// makes testing easier
|
||||
@@ -413,11 +452,13 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
&mut self,
|
||||
addr: &Addr,
|
||||
node_id: NodeId,
|
||||
measurement_kind: MeasurementKind,
|
||||
raw: &str,
|
||||
) -> Result<(), NymPerformanceContractError> {
|
||||
self.insert_performance(
|
||||
addr,
|
||||
node_id,
|
||||
measurement_kind,
|
||||
Percent::from_str(raw).map_err(|err| {
|
||||
NymPerformanceContractError::StdErr(StdError::parse_err("Percent", err.to_string()))
|
||||
})?,
|
||||
@@ -428,11 +469,12 @@ pub(crate) trait PerformanceContractTesterExt:
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
node_id: NodeId,
|
||||
measurement_kind: MeasurementKind,
|
||||
) -> Result<NodeResults, NymPerformanceContractError> {
|
||||
let scores = NYM_PERFORMANCE_CONTRACT_STORAGE
|
||||
.performance_results
|
||||
.results
|
||||
.load(self.deps().storage, (epoch_id, node_id))?;
|
||||
.load(self.deps().storage, (epoch_id, node_id, measurement_kind))?;
|
||||
Ok(scores)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::NYM_PERFORMANCE_CONTRACT_STORAGE;
|
||||
use cosmwasm_std::{to_json_binary, DepsMut, Env, Event, MessageInfo, Response};
|
||||
use crate::storage::{MeasurementKind, NYM_PERFORMANCE_CONTRACT_STORAGE};
|
||||
use cosmwasm_std::{Addr, DepsMut, Env, Event, MessageInfo, Response, to_json_binary};
|
||||
use nym_performance_contract_common::{
|
||||
EpochId, NodeId, NodePerformance, NymPerformanceContractError,
|
||||
};
|
||||
@@ -21,6 +21,62 @@ pub fn try_update_contract_admin(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn try_define_measurement_kind(
|
||||
deps: DepsMut<'_>,
|
||||
sender: &Addr,
|
||||
measurement_kind: MeasurementKind,
|
||||
) -> Result<Response, NymPerformanceContractError> {
|
||||
NYM_PERFORMANCE_CONTRACT_STORAGE
|
||||
.contract_admin
|
||||
.assert_admin(deps.as_ref(), sender)?;
|
||||
|
||||
validate_measurement_kind(&measurement_kind)?;
|
||||
|
||||
NYM_PERFORMANCE_CONTRACT_STORAGE
|
||||
.performance_results
|
||||
.define_new_measurement_kind(deps.storage, measurement_kind)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
/// error out if validation fails, Ok otherwise
|
||||
fn validate_measurement_kind(measurement_kind: &str) -> Result<(), NymPerformanceContractError> {
|
||||
const MIN_LENGTH: usize = 2;
|
||||
const MAX_LENGTH: usize = 32;
|
||||
let error = if measurement_kind.len() < MIN_LENGTH || measurement_kind.len() > MAX_LENGTH {
|
||||
format!(
|
||||
"Length should be between {} and {} chars (exclusive)",
|
||||
MIN_LENGTH, MAX_LENGTH
|
||||
)
|
||||
} else if !measurement_kind.is_ascii() {
|
||||
"Only ASCII symbols allowed".to_string()
|
||||
} else if measurement_kind.contains(char::is_whitespace) {
|
||||
"No whitespaces allowed in measurement name".to_string()
|
||||
} else if !measurement_kind.starts_with(char::is_alphanumeric) {
|
||||
"Name needs to start with alphanumeric character(s)".to_string()
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
Err(NymPerformanceContractError::InvalidInput(error))
|
||||
}
|
||||
|
||||
pub fn try_retire_measurement_kind(
|
||||
deps: DepsMut<'_>,
|
||||
sender_addr: &Addr,
|
||||
measurement_kind: MeasurementKind,
|
||||
) -> Result<Response, NymPerformanceContractError> {
|
||||
NYM_PERFORMANCE_CONTRACT_STORAGE
|
||||
.contract_admin
|
||||
.assert_admin(deps.as_ref(), sender_addr)?;
|
||||
|
||||
NYM_PERFORMANCE_CONTRACT_STORAGE
|
||||
.performance_results
|
||||
.retire_measurement_kind(deps.storage, measurement_kind)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
pub fn try_submit_performance_results(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -61,6 +117,10 @@ pub fn try_batch_submit_performance_results(
|
||||
.add_attribute(
|
||||
"non_existent_nodes",
|
||||
format!("{:?}", res.non_existent_nodes),
|
||||
)
|
||||
.add_attribute(
|
||||
"non_existent_measurement_kinds",
|
||||
format!("{:?}", res.non_existent_measurement_kind),
|
||||
),
|
||||
);
|
||||
Ok(response)
|
||||
@@ -130,7 +190,7 @@ pub fn try_remove_epoch_measurements(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::storage::retrieval_limits;
|
||||
use crate::testing::{init_contract_tester, PerformanceContractTesterExt};
|
||||
use crate::testing::{PerformanceContractTesterExt, init_contract_tester};
|
||||
use cosmwasm_std::from_json;
|
||||
use nym_contracts_common_testing::{AdminExt, ContractOpts};
|
||||
use nym_performance_contract_common::RemoveEpochMeasurementsResponse;
|
||||
@@ -222,20 +282,24 @@ mod tests {
|
||||
let env = test.env();
|
||||
let admin = test.admin_msg();
|
||||
|
||||
assert!(try_authorise_network_monitor(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
admin.clone(),
|
||||
bad_address
|
||||
)
|
||||
.is_err());
|
||||
assert!(try_authorise_network_monitor(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
admin,
|
||||
good_address.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(
|
||||
try_authorise_network_monitor(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
admin.clone(),
|
||||
bad_address
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
try_authorise_network_monitor(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
admin,
|
||||
good_address.to_string()
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -244,7 +308,7 @@ mod tests {
|
||||
#[cfg(test)]
|
||||
mod retiring_network_monitor {
|
||||
use super::*;
|
||||
use crate::testing::{init_contract_tester, PerformanceContractTesterExt};
|
||||
use crate::testing::{PerformanceContractTesterExt, init_contract_tester};
|
||||
use nym_contracts_common_testing::{AdminExt, ContractOpts, RandExt};
|
||||
|
||||
#[test]
|
||||
@@ -258,20 +322,19 @@ mod tests {
|
||||
let env = test.env();
|
||||
let admin = test.admin_msg();
|
||||
|
||||
assert!(try_retire_network_monitor(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
admin.clone(),
|
||||
bad_address
|
||||
)
|
||||
.is_err());
|
||||
assert!(try_retire_network_monitor(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
admin,
|
||||
good_address.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(
|
||||
try_retire_network_monitor(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
admin.clone(),
|
||||
bad_address
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
try_retire_network_monitor(test.deps_mut(), env, admin, good_address.to_string())
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -285,11 +348,14 @@ mod tests {
|
||||
|
||||
let nm = tester.addr_make("network-monitor");
|
||||
tester.authorise_network_monitor(&nm)?;
|
||||
tester.define_dummy_measurement_kind()?;
|
||||
|
||||
tester.advance_mixnet_epoch()?;
|
||||
|
||||
let measurement_kind = tester.dummy_measurement_kind();
|
||||
for _ in 0..2 * retrieval_limits::EPOCH_PERFORMANCE_PURGE_LIMIT {
|
||||
let node_id = tester.bond_dummy_nymnode()?;
|
||||
tester.insert_raw_performance(&nm, node_id, "0.42")?;
|
||||
tester.insert_raw_performance(&nm, node_id, measurement_kind.clone(), "0.42")?;
|
||||
}
|
||||
|
||||
let admin = tester.admin_msg();
|
||||
@@ -311,4 +377,242 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod measurement_kind_authorization {
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use nym_contracts_common_testing::{AdminExt, ContractOpts};
|
||||
use nym_performance_contract_common::NymPerformanceContractError;
|
||||
|
||||
use crate::{
|
||||
storage::MeasurementKind,
|
||||
testing::{PerformanceContractTesterExt, init_contract_tester},
|
||||
transactions::{
|
||||
try_define_measurement_kind, try_retire_measurement_kind,
|
||||
try_submit_performance_results,
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn add_requires_admin() {
|
||||
let mut tester = init_contract_tester();
|
||||
let admin = tester.admin_msg();
|
||||
let new_measurement = MeasurementKind::from("new-measurement");
|
||||
|
||||
assert!(
|
||||
try_define_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
&admin.sender,
|
||||
new_measurement.clone()
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn retire_requires_admin() {
|
||||
let mut tester = init_contract_tester();
|
||||
let admin = tester.admin_msg();
|
||||
let new_measurement = MeasurementKind::from("new-measurement");
|
||||
|
||||
try_define_measurement_kind(tester.deps_mut(), &admin.sender, new_measurement.clone())
|
||||
.unwrap();
|
||||
|
||||
let unauthorized_addr = tester.addr_make("unauthorized-addr");
|
||||
let unauthorized = try_retire_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
&unauthorized_addr,
|
||||
new_measurement.clone(),
|
||||
);
|
||||
assert!(matches!(
|
||||
unauthorized,
|
||||
Err(NymPerformanceContractError::Admin { .. })
|
||||
));
|
||||
|
||||
let authorized = try_retire_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
&admin.sender,
|
||||
new_measurement.clone(),
|
||||
);
|
||||
assert!(authorized.is_ok());
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn cannot_add_existing() {
|
||||
let mut tester = init_contract_tester();
|
||||
let admin = tester.admin_msg();
|
||||
let new_measurement = MeasurementKind::from("new-measurement");
|
||||
|
||||
let first_attempt = try_define_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
&admin.sender,
|
||||
new_measurement.clone(),
|
||||
);
|
||||
assert!(first_attempt.is_ok());
|
||||
|
||||
let second_attempt =
|
||||
try_define_measurement_kind(tester.deps_mut(), &admin.sender, new_measurement);
|
||||
assert!(matches!(
|
||||
second_attempt,
|
||||
Err(NymPerformanceContractError::MeasurementAlreadyDefined { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn cannot_retire_nonexistent() {
|
||||
let mut tester = init_contract_tester();
|
||||
let admin = tester.admin_msg();
|
||||
let nonexistent = MeasurementKind::from("nonexistent");
|
||||
|
||||
let err = try_retire_measurement_kind(tester.deps_mut(), &admin.sender, nonexistent);
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
Err(NymPerformanceContractError::UnsupportedMeasurementKind { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn cannot_submit_undefined() {
|
||||
let mut tester = init_contract_tester();
|
||||
let env = tester.env();
|
||||
let admin = tester.admin_msg();
|
||||
let dummy_perf = tester.dummy_node_performance();
|
||||
let nm = tester.addr_make("network-monitor");
|
||||
tester.authorise_network_monitor(&nm).unwrap();
|
||||
|
||||
let dummy_measurement = dummy_perf.measurement_kind.clone();
|
||||
|
||||
let first_attempt = try_submit_performance_results(
|
||||
tester.deps_mut(),
|
||||
env.clone(),
|
||||
// network monitor submits
|
||||
message_info(&nm, &[]),
|
||||
0,
|
||||
dummy_perf.clone(),
|
||||
);
|
||||
assert!(matches!(
|
||||
first_attempt,
|
||||
Err(NymPerformanceContractError::UnsupportedMeasurementKind { .. })
|
||||
));
|
||||
|
||||
try_define_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
// admin defines
|
||||
&admin.sender,
|
||||
dummy_measurement.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let second_attempt = try_submit_performance_results(
|
||||
tester.deps_mut(),
|
||||
env,
|
||||
// network monitor submits
|
||||
message_info(&nm, &[]),
|
||||
0,
|
||||
dummy_perf,
|
||||
);
|
||||
assert!(second_attempt.is_ok());
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[test]
|
||||
fn cannot_submit_retired() {
|
||||
let mut tester = init_contract_tester();
|
||||
let env = tester.env();
|
||||
let admin = tester.admin_msg();
|
||||
let dummy_perf = tester.dummy_node_performance();
|
||||
let nm = tester.addr_make("network-monitor");
|
||||
tester.authorise_network_monitor(&nm).unwrap();
|
||||
|
||||
let dummy_measurement = dummy_perf.measurement_kind.clone();
|
||||
|
||||
try_define_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
// admin defines
|
||||
&admin.sender,
|
||||
dummy_measurement.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let defined_ok = try_submit_performance_results(
|
||||
tester.deps_mut(),
|
||||
env.clone(),
|
||||
// network monitor submits
|
||||
message_info(&nm, &[]),
|
||||
0,
|
||||
dummy_perf.clone(),
|
||||
);
|
||||
assert!(defined_ok.is_ok());
|
||||
|
||||
// can't submit for the same node in the same epoch again
|
||||
tester.advance_mixnet_epoch().unwrap();
|
||||
|
||||
try_retire_measurement_kind(
|
||||
tester.deps_mut(),
|
||||
// admin defines
|
||||
&admin.sender,
|
||||
dummy_measurement.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let retired_err = try_submit_performance_results(
|
||||
tester.deps_mut(),
|
||||
env,
|
||||
// network monitor submits
|
||||
message_info(&nm, &[]),
|
||||
1,
|
||||
dummy_perf,
|
||||
);
|
||||
println!("{:#?}", retired_err);
|
||||
assert!(matches!(
|
||||
retired_err,
|
||||
Err(NymPerformanceContractError::UnsupportedMeasurementKind { .. })
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
mod measurement_kind_validation {
|
||||
use nym_performance_contract_common::NymPerformanceContractError;
|
||||
|
||||
use crate::transactions::validate_measurement_kind;
|
||||
|
||||
#[test]
|
||||
fn invalid_names() {
|
||||
let invalid_names = [
|
||||
"a",
|
||||
"NameLongerThanTheUpperLimitForContract",
|
||||
"contains spaces",
|
||||
"nonaščii",
|
||||
"-+*/",
|
||||
// starts with a symbol
|
||||
"+-*/invalid",
|
||||
];
|
||||
|
||||
for kind in invalid_names {
|
||||
let err = validate_measurement_kind(kind);
|
||||
assert!(matches!(
|
||||
err,
|
||||
Err(NymPerformanceContractError::InvalidInput(..))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_names() {
|
||||
let valid_names = [
|
||||
"ascii-symbols",
|
||||
"UpperLowerCase",
|
||||
// starts with an alphanumeric char
|
||||
"valid-+*/",
|
||||
];
|
||||
|
||||
for kind in valid_names {
|
||||
let err = validate_measurement_kind(kind);
|
||||
assert!(matches!(err, Ok(())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user