epoch paged

try_load_performance returns per kind

load_measurement_kind & unit test

Naming

stale submission unit test & note
This commit is contained in:
dynco-nym
2025-12-11 00:30:27 +01:00
parent df93356a23
commit dcc0ef0e47
12 changed files with 850 additions and 323 deletions
@@ -14,8 +14,8 @@ pub use nym_performance_contract_common::{
EpochMeasurementsPagedResponse, EpochNodePerformance, EpochPerformancePagedResponse,
FullHistoricalPerformancePagedResponse, HistoricalPerformance, LastSubmission,
NetworkMonitorInformation, NetworkMonitorsPagedResponse, NodeId, NodeMeasurement,
NodeMeasurementsResponse, NodePerformancePagedResponse, NodePerformanceResponse,
NodePerformanceSpecific, RetiredNetworkMonitor, RetiredNetworkMonitorsPagedResponse,
NodeMeasurementsPerKindResponse, NodePerformance, NodePerformancePagedResponse,
NodePerformanceResponse, RetiredNetworkMonitor, RetiredNetworkMonitorsPagedResponse,
};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -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
}
@@ -167,7 +167,7 @@ pub trait PagedPerformanceQueryClient: PerformanceQueryClient {
async fn get_all_epoch_performance(
&self,
epoch_id: EpochId,
) -> Result<Vec<NodePerformanceSpecific>, NyxdError> {
) -> Result<Vec<NodePerformance>, NyxdError> {
collect_paged!(self, get_epoch_performance_paged, performance, epoch_id)
}
@@ -10,7 +10,7 @@ use crate::nyxd::{Fee, SigningCosmWasmClient};
use crate::signing::signer::OfflineSigner;
use async_trait::async_trait;
use nym_performance_contract_common::{
EpochId, ExecuteMsg as PerformanceExecuteMsg, NodeId, NodePerformanceSpecific,
EpochId, ExecuteMsg as PerformanceExecuteMsg, NodeId, NodePerformance,
RemoveEpochMeasurementsResponse,
};
@@ -42,7 +42,7 @@ pub trait PerformanceSigningClient {
async fn submit_performance(
&self,
epoch: EpochId,
data: NodePerformanceSpecific,
data: NodePerformance,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_performance_contract(
@@ -57,7 +57,7 @@ pub trait PerformanceSigningClient {
async fn batch_submit_performance(
&self,
epoch: EpochId,
data: Vec<NodePerformanceSpecific>,
data: Vec<NodePerformance>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_performance_contract(
@@ -9,7 +9,6 @@ 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";
@@ -1,14 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochId, NodeId, NodePerformanceSpecific};
use crate::{EpochId, NodeId, NodePerformance};
use cosmwasm_schema::cw_serde;
#[cfg(feature = "schema")]
use crate::types::{
EpochMeasurementsPagedResponse, EpochPerformancePagedResponse,
FullHistoricalPerformancePagedResponse, LastSubmission, NetworkMonitorResponse,
NetworkMonitorsPagedResponse, NodeMeasurementsResponse, NodePerformancePagedResponse,
NetworkMonitorsPagedResponse, NodeMeasurementsPerKindResponse, NodePerformancePagedResponse,
NodePerformanceResponse, RetiredNetworkMonitorsPagedResponse,
};
@@ -23,17 +23,16 @@ pub enum ExecuteMsg {
/// Change the admin
UpdateAdmin { admin: String },
// TODO dz rename to remove speicifc suffix
/// Attempt to submit performance data of a particular node for given epoch
SubmitSpecific {
Submit {
epoch: EpochId,
data: NodePerformanceSpecific,
data: NodePerformance,
},
/// Attempt to submit performance data of a batch of nodes for given epoch
BatchSubmit {
epoch: EpochId,
data: Vec<NodePerformanceSpecific>,
data: Vec<NodePerformance>,
},
/// Measurement kind needs to be defined by the admin before measurements of
@@ -78,11 +77,9 @@ 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 },
NodeMeasurementsSpecific {
NodeMeasurements {
epoch_id: EpochId,
node_id: NodeId,
kind: String,
@@ -23,7 +23,7 @@ pub struct LastSubmission {
pub struct LastSubmittedData {
pub sender: Addr,
pub epoch_id: EpochId,
pub data: NodePerformanceSpecific,
pub data: NodePerformance,
}
#[cw_serde]
@@ -50,14 +50,13 @@ pub struct RetiredNetworkMonitor {
pub retired_at_height: u64,
}
// TODO dz remove specific from name
#[cw_serde]
pub struct NodePerformanceSpecific {
pub struct NodePerformance {
#[serde(rename = "n")]
pub node_id: NodeId,
#[serde(rename = "m")]
pub measurement_kind: String,
pub measurement_kind: MeasurementKind,
// note: value is rounded to 2 decimal places.
#[serde(rename = "p")]
@@ -106,13 +105,17 @@ impl NodeResults {
}
}
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>,
}
@@ -120,14 +123,13 @@ pub struct NodeMeasurementsResponse {
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<String, Option<NodeResults>>,
pub measurements: HashMap<MeasurementKind, Option<NodeResults>>,
}
#[cw_serde]
#[derive(Copy)]
pub struct EpochNodePerformance {
pub epoch: EpochId,
pub performance: Option<Percent>,
pub performance: HashMap<MeasurementKind, Percent>,
}
#[cw_serde]
@@ -140,20 +142,20 @@ pub struct NodePerformancePagedResponse {
#[cw_serde]
pub struct EpochPerformancePagedResponse {
pub epoch_id: EpochId,
pub performance: Vec<NodePerformanceSpecific>,
pub performance: Vec<NodePerformance>,
pub start_next_after: Option<NodeId>,
}
#[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>,
}
+7 -14
View File
@@ -4,9 +4,8 @@
use crate::queries::{
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,
query_node_measurements_specific, query_node_performance, query_node_performance_paged,
query_retired_network_monitors_paged,
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::{
@@ -58,7 +57,7 @@ pub fn execute(
) -> Result<Response, NymPerformanceContractError> {
match msg {
ExecuteMsg::UpdateAdmin { admin } => try_update_contract_admin(deps, info, admin),
ExecuteMsg::SubmitSpecific { epoch, data } => {
ExecuteMsg::Submit { epoch, data } => {
try_submit_performance_results(deps, env, info, epoch, data)
}
ExecuteMsg::BatchSubmit { epoch, data } => {
@@ -126,18 +125,12 @@ 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::NodeMeasurementsSpecific {
QueryMsg::NodeMeasurements {
epoch_id,
node_id,
kind: measurement_kind,
} => Ok(to_json_binary(&query_node_measurements_specific(
deps,
epoch_id,
node_id,
measurement_kind,
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)?,
+609 -110
View File
@@ -3,7 +3,7 @@
use std::collections::HashMap;
use crate::storage::{NYM_PERFORMANCE_CONTRACT_STORAGE, retrieval_limits};
use crate::storage::{MeasurementKind, NYM_PERFORMANCE_CONTRACT_STORAGE, retrieval_limits};
use cosmwasm_std::{Addr, Deps, Order, StdResult};
use cw_controllers::AdminResponse;
use cw_storage_plus::Bound;
@@ -11,8 +11,8 @@ use nym_performance_contract_common::{
AllNodeMeasurementsResponse, EpochId, EpochMeasurementsPagedResponse, EpochNodePerformance,
EpochPerformancePagedResponse, FullHistoricalPerformancePagedResponse, HistoricalPerformance,
LastSubmission, NetworkMonitorInformation, NetworkMonitorResponse,
NetworkMonitorsPagedResponse, NodeId, NodeMeasurement, NodeMeasurementsResponse,
NodePerformancePagedResponse, NodePerformanceResponse, NodePerformanceSpecific,
NetworkMonitorsPagedResponse, NodeId, NodeMeasurements, NodeMeasurementsPerKindResponse,
NodePerformance, NodePerformancePagedResponse, NodePerformanceResponse, NodeResults,
NymPerformanceContractError, RetiredNetworkMonitorsPagedResponse,
};
@@ -23,7 +23,6 @@ pub fn query_admin(deps: Deps) -> Result<AdminResponse, NymPerformanceContractEr
.map_err(Into::into)
}
// TODO dz return measurement_kind in response
pub fn query_node_performance(
deps: Deps,
epoch_id: EpochId,
@@ -34,44 +33,28 @@ pub fn query_node_performance(
Ok(NodePerformanceResponse { performance })
}
pub fn query_node_measurements(
deps: Deps,
epoch_id: EpochId,
node_id: NodeId,
) -> Result<NodeMeasurementsResponse, NymPerformanceContractError> {
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.results
.may_load(deps.storage, (epoch_id, node_id))?;
Ok(NodeMeasurementsResponse { measurements })
}
// TODO dz unit test
pub fn query_node_measurements_specific(
pub fn query_node_measurements_for_kind(
deps: Deps,
epoch_id: EpochId,
node_id: NodeId,
measurement_kind: String,
) -> Result<NodeMeasurementsResponse, NymPerformanceContractError> {
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.results_specific;
) -> Result<NodeMeasurementsPerKindResponse, NymPerformanceContractError> {
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE.try_load_measurement_kind(
deps.storage,
epoch_id,
node_id,
measurement_kind,
)?;
let key = (epoch_id, node_id, measurement_kind);
let measurements = measurements.may_load(deps.storage, key)?;
Ok(NodeMeasurementsResponse { measurements })
Ok(NodeMeasurementsPerKindResponse { measurements })
}
// TODO dz unit test
pub fn query_all_node_measurements(
deps: Deps,
epoch_id: EpochId,
node_id: NodeId,
) -> Result<AllNodeMeasurementsResponse, NymPerformanceContractError> {
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.results_specific;
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE.performance_results.results;
// retrieve a list of currently defined measurements, only return results for those
// (storage may contain measurements that have since been deleted by admin -
@@ -146,7 +129,6 @@ pub fn query_node_performance_paged(
})
}
// TODO dz unit test changes
pub fn query_epoch_performance_paged(
deps: Deps,
epoch_id: EpochId,
@@ -161,18 +143,16 @@ pub fn query_epoch_performance_paged(
let performance = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| {
record.map(
|((node_id, measurement_kind), results)| NodePerformanceSpecific {
node_id,
performance: results.median(),
measurement_kind,
},
)
record.map(|((node_id, measurement_kind), results)| NodePerformance {
node_id,
performance: results.median(),
measurement_kind,
})
})
.collect::<StdResult<Vec<_>>>()?;
@@ -195,27 +175,51 @@ pub fn query_epoch_measurements_paged(
.unwrap_or(retrieval_limits::NODE_EPOCH_MEASUREMENTS_DEFAULT_LIMIT)
.min(retrieval_limits::NODE_EPOCH_MEASUREMENTS_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let start = start_after.map(|node_id| Bound::exclusive((node_id + 1, String::new())));
// because API aggregates per NodeId, and the storage doesn't, we have to
// first collect all different measurements for a node and use an
// intermediary struct to map from storage to the object returned on the API
let mut measurements_per_node: HashMap<NodeId, Vec<(MeasurementKind, NodeResults)>> =
HashMap::new();
let measurements = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.results
.prefix(epoch_id)
.sub_prefix(epoch_id)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| {
record.map(|(node_id, measurements)| NodeMeasurement {
node_id,
measurements,
record.inspect(|((node_id, kind), measurements)| {
measurements_per_node
.entry(*node_id)
.and_modify(|vec| vec.push((kind.to_string(), measurements.to_owned())))
.or_insert_with(|| vec![(kind.to_string(), measurements.to_owned())]);
})
})
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = measurements.last().map(|last| last.node_id);
// transforming collected data into a returning type
let mut returning = Vec::new();
for (node_id, measurements_per_kind) in measurements_per_node.into_iter() {
let mut measurements = HashMap::new();
for (measurement_kind, results) in measurements_per_kind {
measurements.insert(measurement_kind, results);
}
returning.push(NodeMeasurements {
node_id,
measurements_per_kind: measurements,
});
}
// storage keeps nodes in ascending order for pagination
// intermediary hashmap doesn't have deterministic order so we need to order
// explicitly here before returning
returning.sort_by_key(|elem| elem.node_id);
let start_next_after = measurements.last().map(|((last, _), _)| *last);
Ok(EpochMeasurementsPagedResponse {
epoch_id,
measurements,
measurements: returning,
start_next_after,
})
}
@@ -229,7 +233,7 @@ pub fn query_full_historical_performance_paged(
.unwrap_or(retrieval_limits::NODE_HISTORICAL_PERFORMANCE_DEFAULT_LIMIT)
.min(retrieval_limits::NODE_HISTORICAL_PERFORMANCE_MAX_LIMIT) as usize;
let start = start_after.map(Bound::exclusive);
let start = start_after.map(|(n, e)| Bound::exclusive((n, e, String::new())));
let performance = NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
@@ -237,7 +241,8 @@ pub fn query_full_historical_performance_paged(
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| {
record.map(|((epoch_id, node_id), results)| HistoricalPerformance {
record.map(|((epoch_id, node_id, _), results)| HistoricalPerformance {
// TODO dz map kind as well
epoch_id,
node_id,
performance: results.median(),
@@ -368,9 +373,11 @@ pub fn query_last_submission(deps: Deps) -> Result<LastSubmission, NymPerformanc
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{PerformanceContractTesterExt, init_contract_tester};
use nym_contracts_common_testing::{ChainOpts, ContractOpts, RandExt};
use nym_performance_contract_common::{LastSubmittedData, NodePerformanceSpecific};
use crate::testing::{
PerformanceContractTesterExt, epoch_node_performance_unchecked, init_contract_tester,
};
use nym_contracts_common_testing::{AdminExt, ChainOpts, ContractOpts, RandExt};
use nym_performance_contract_common::{ExecuteMsg, LastSubmittedData, NodePerformance};
#[cfg(test)]
mod admin_query {
@@ -409,6 +416,160 @@ mod tests {
}
}
#[test]
fn querying_node_measurements_kind() -> anyhow::Result<()> {
let mut test = init_contract_tester();
// test setup
let nm1 = test.generate_account();
let nm2 = test.generate_account();
test.authorise_network_monitor(&nm1)?;
test.authorise_network_monitor(&nm2)?;
let admin = test.admin_unchecked();
let kind_mixnet = String::from("mixnet");
let kind_wireguard = String::from("wireguard");
test.execute_raw(
admin.clone(),
ExecuteMsg::DefineMeasurementKind {
measurement_kind: kind_mixnet.clone(),
},
)?;
test.execute_raw(
admin,
ExecuteMsg::DefineMeasurementKind {
measurement_kind: kind_wireguard.clone(),
},
)?;
let node1 = test.bond_dummy_nymnode()?;
let node2 = test.bond_dummy_nymnode()?;
let epoch_id = 10;
test.set_mixnet_epoch(epoch_id)?;
let deps = test.deps();
// ===== Test: undefined measurement kind =====
let undefined_kind = String::from("undefined");
let res = query_node_measurements_for_kind(deps, epoch_id, node1, undefined_kind);
assert!(res.is_err());
assert!(matches!(
res.unwrap_err(),
NymPerformanceContractError::UnsupportedMeasurementKind { .. }
));
// ===== Test: query returns None for defined kind with no data =====
let res = query_node_measurements_for_kind(deps, epoch_id, node1, kind_mixnet.clone())?;
assert!(res.measurements.is_none());
// ===== Test happy path: single measurement from one monitor =====
test.insert_raw_performance(&nm1, node1, kind_mixnet.clone(), "0.5")?;
let res =
query_node_measurements_for_kind(test.deps(), epoch_id, node1, kind_mixnet.clone())?;
let measurements = res.measurements.unwrap();
assert_eq!(measurements.inner().len(), 1);
assert_eq!(measurements.inner()[0], "0.5".parse()?);
// Verify against raw storage
let expected = test.read_raw_scores(epoch_id, node1, kind_mixnet.clone())?;
assert_eq!(measurements.inner(), expected.inner());
// ===== Test: multiple measurements from different monitors =====
// each monitor can only submit once per (epoch, node) pair
test.insert_raw_performance(&nm2, node1, kind_mixnet.clone(), "0.3")?;
let res =
query_node_measurements_for_kind(test.deps(), epoch_id, node1, kind_mixnet.clone())?;
let measurements = res.measurements.unwrap();
assert_eq!(measurements.inner().len(), 2);
// ===== Test: multiple measurement kinds are independent =====
// we need a new epoch since monitors already submitted in this epoch
let epoch_11 = 11;
test.set_mixnet_epoch(epoch_11)?;
// now submit
test.insert_raw_performance(&nm1, node1, kind_wireguard.clone(), "0.8")?;
test.insert_raw_performance(&nm2, node1, kind_wireguard.clone(), "0.9")?;
// verify data for submitted kind is there
let res_wireguard_e11 =
query_node_measurements_for_kind(test.deps(), epoch_11, node1, kind_wireguard.clone())?;
let wg_measurements = res_wireguard_e11.measurements.unwrap();
assert_eq!(wg_measurements.inner().len(), 2);
assert_eq!(wg_measurements.inner()[0], "0.8".parse()?);
assert_eq!(wg_measurements.inner()[1], "0.9".parse()?);
// not submitted for this kind in this epoch: should have no data
let res_mixnet_e11 =
query_node_measurements_for_kind(test.deps(), epoch_11, node1, kind_mixnet.clone())?;
assert!(res_mixnet_e11.measurements.is_none());
// however, mixnet kind should still have old data in previous epoch
let res_mixnet_e10 =
query_node_measurements_for_kind(test.deps(), epoch_id, node1, kind_mixnet.clone())?;
let mixnet_measurements = res_mixnet_e10.measurements.unwrap();
assert_eq!(mixnet_measurements.inner().len(), 2);
assert_eq!(mixnet_measurements.inner()[0], "0.3".parse()?);
assert_eq!(mixnet_measurements.inner()[1], "0.5".parse()?);
// ===== Test: different epochs are independent =====
// advance epoch again & submit something
let epoch_12 = 12;
test.set_mixnet_epoch(epoch_12)?;
test.insert_raw_performance(&nm1, node1, kind_mixnet.clone(), "0.25")?;
// epoch 12 should have new data
let res_epoch12 =
query_node_measurements_for_kind(test.deps(), epoch_12, node1, kind_mixnet.clone())?;
assert!(res_epoch12.measurements.is_some());
let epoch12_measurements = res_epoch12.measurements.unwrap();
assert_eq!(epoch12_measurements.inner().len(), 1);
assert_eq!(epoch12_measurements.inner()[0], "0.25".parse()?);
// epoch 10 should still have old data
let res_epoch10 =
query_node_measurements_for_kind(test.deps(), epoch_id, node1, kind_mixnet.clone())?;
assert_eq!(res_epoch10.measurements.unwrap().inner().len(), 2);
// epoch 11 with mixnet (no data) should return None
let res_epoch11_mixnet =
query_node_measurements_for_kind(test.deps(), epoch_11, node1, kind_mixnet.clone())?;
assert!(res_epoch11_mixnet.measurements.is_none());
// ===== Test: different nodes are independent =====
// nm1 can now submit for node2 in epoch 12 since node2 > node1
test.insert_raw_performance(&nm1, node2, kind_mixnet.clone(), "0.42")?;
// Query node1 in epoch 12 - should have nm1's data
let res_node1_e12 =
query_node_measurements_for_kind(test.deps(), epoch_12, node1, kind_mixnet.clone())?;
assert_eq!(res_node1_e12.measurements.unwrap().inner().len(), 1);
// Query node2 in epoch 12 - should have different data
let res_node2_e12 =
query_node_measurements_for_kind(test.deps(), epoch_12, node2, kind_mixnet.clone())?;
let node2_measurements = res_node2_e12.measurements.unwrap();
assert_eq!(node2_measurements.inner().len(), 1);
assert_eq!(node2_measurements.inner()[0], "0.42".parse()?);
// Query node2 in epoch 10 (no data) - should return None
let res_node2_e10 =
query_node_measurements_for_kind(test.deps(), epoch_id, node2, kind_mixnet.clone())?;
assert!(res_node2_e10.measurements.is_none());
// verify against raw data
let raw_scores = test.read_raw_scores(epoch_id, node1, kind_mixnet.clone())?;
let query_result =
query_node_measurements_for_kind(test.deps(), epoch_id, node1, kind_mixnet.clone())?;
assert_eq!(
query_result.measurements.unwrap().inner(),
raw_scores.inner()
);
Ok(())
}
#[test]
fn querying_node_performance_paged() -> anyhow::Result<()> {
let mut test = init_contract_tester();
@@ -454,10 +615,11 @@ mod tests {
assert!(res.start_next_after.is_none());
assert_eq!(
res.performance,
vec![EpochNodePerformance {
epoch: 5,
performance: Some("0.5".parse()?),
}]
vec![epoch_node_performance_unchecked(
5,
measurement_kind.clone(),
"0.5"
)]
);
let res = query_node_performance_paged(deps, node_id, Some(2), None)?;
@@ -465,18 +627,9 @@ mod tests {
assert_eq!(
res.performance,
vec![
EpochNodePerformance {
epoch: 3,
performance: Some("0.3".parse()?),
},
EpochNodePerformance {
epoch: 4,
performance: Some("0.4".parse()?),
},
EpochNodePerformance {
epoch: 5,
performance: Some("0.5".parse()?),
}
epoch_node_performance_unchecked(3, measurement_kind.clone(), "0.3"),
epoch_node_performance_unchecked(4, measurement_kind.clone(), "0.4"),
epoch_node_performance_unchecked(5, measurement_kind.clone(), "0.5"),
]
);
@@ -485,30 +638,12 @@ mod tests {
assert_eq!(
res.performance,
vec![
EpochNodePerformance {
epoch: 0,
performance: Some("0".parse()?),
},
EpochNodePerformance {
epoch: 1,
performance: Some("0.1".parse()?),
},
EpochNodePerformance {
epoch: 2,
performance: Some("0.2".parse()?),
},
EpochNodePerformance {
epoch: 3,
performance: Some("0.3".parse()?),
},
EpochNodePerformance {
epoch: 4,
performance: Some("0.4".parse()?),
},
EpochNodePerformance {
epoch: 5,
performance: Some("0.5".parse()?),
}
epoch_node_performance_unchecked(0, measurement_kind.clone(), "0"),
epoch_node_performance_unchecked(1, measurement_kind.clone(), "0.1"),
epoch_node_performance_unchecked(2, measurement_kind.clone(), "0.2"),
epoch_node_performance_unchecked(3, measurement_kind.clone(), "0.3"),
epoch_node_performance_unchecked(4, measurement_kind.clone(), "0.4"),
epoch_node_performance_unchecked(5, measurement_kind.clone(), "0.5"),
]
);
@@ -516,10 +651,11 @@ mod tests {
assert_eq!(res.start_next_after, Some(3));
assert_eq!(
res.performance,
vec![EpochNodePerformance {
epoch: 3,
performance: Some("0.3".parse()?),
}]
vec![epoch_node_performance_unchecked(
3,
measurement_kind.clone(),
"0.3"
)]
);
Ok(())
@@ -562,12 +698,12 @@ mod tests {
assert_eq!(
res.performance,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[5],
performance: "0.5".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: "0.6".parse()?,
measurement_kind: measurement_kind.clone()
@@ -579,12 +715,12 @@ mod tests {
assert_eq!(
res.performance,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[5],
performance: "0.5".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: "0.6".parse()?,
measurement_kind: measurement_kind.clone()
@@ -597,17 +733,17 @@ mod tests {
assert_eq!(
res.performance,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[3],
performance: "0.3".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[5],
performance: "0.5".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: "0.6".parse()?,
measurement_kind: measurement_kind.clone()
@@ -620,27 +756,27 @@ mod tests {
assert_eq!(
res.performance,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[1],
performance: "0.1".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[2],
performance: "0.2".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[3],
performance: "0.3".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[5],
performance: "0.5".parse()?,
measurement_kind: measurement_kind.clone()
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: "0.6".parse()?,
measurement_kind: measurement_kind.clone()
@@ -652,7 +788,7 @@ mod tests {
assert_eq!(res.start_next_after, Some(nodes[3]));
assert_eq!(
res.performance,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: nodes[3],
performance: "0.3".parse()?,
measurement_kind: measurement_kind.clone()
@@ -662,6 +798,270 @@ mod tests {
Ok(())
}
#[test]
fn querying_epoch_measurements_paged() -> anyhow::Result<()> {
let mut test = init_contract_tester();
let nm = test.generate_account();
test.authorise_network_monitor(&nm)?;
let measurement_kind = test.define_dummy_measurement_kind().unwrap();
let mut nodes = Vec::new();
for _ in 0..10 {
nodes.push(test.bond_dummy_nymnode()?);
}
let epoch_id = 5;
test.set_mixnet_epoch(epoch_id)?;
test.insert_raw_performance(&nm, nodes[1], measurement_kind.clone(), "0.1")?;
test.insert_raw_performance(&nm, nodes[2], measurement_kind.clone(), "0.2")?;
test.insert_raw_performance(&nm, nodes[3], measurement_kind.clone(), "0.3")?;
// 4 is missing
test.insert_raw_performance(&nm, nodes[5], measurement_kind.clone(), "0.5")?;
test.insert_raw_performance(&nm, nodes[6], measurement_kind.clone(), "0.6")?;
let deps = test.deps();
// query starting after nodes[6]
let res = query_epoch_measurements_paged(deps, epoch_id, Some(nodes[6]), None)?;
assert!(res.start_next_after.is_none());
assert!(res.measurements.is_empty());
// query after non-existent high node ID
let res = query_epoch_measurements_paged(deps, epoch_id, Some(42), None)?;
assert!(res.start_next_after.is_none());
assert!(res.measurements.is_empty());
// query starting after nodes[4] (should return nodes 5 and 6)
let res = query_epoch_measurements_paged(deps, epoch_id, Some(nodes[4]), None)?;
assert_eq!(res.start_next_after, Some(nodes[6]));
assert_eq!(res.measurements.len(), 2);
assert_eq!(res.measurements[0].node_id, nodes[5]);
assert_eq!(res.measurements[1].node_id, nodes[6]);
// verify returned data against raw results
let node5_results = res.measurements[0]
.measurements_per_kind
.get(&measurement_kind)
.unwrap();
let expected_results =
test.read_raw_scores(epoch_id, nodes[5], measurement_kind.clone())?;
assert_eq!(node5_results.inner(), expected_results.inner());
let node6_results = res.measurements[1]
.measurements_per_kind
.get(&measurement_kind)
.unwrap();
let expected_results =
test.read_raw_scores(epoch_id, nodes[6], measurement_kind.clone())?;
assert_eq!(node6_results.inner(), expected_results.inner());
// query starting after nodes[3]
// should skip nodes[3] entirely and start from nodes[5] (nodes[4] doesn't exist)
let res = query_epoch_measurements_paged(deps, epoch_id, Some(nodes[3]), None)?;
assert_eq!(res.start_next_after, Some(nodes[6]));
// Verify only nodes[5] and nodes[6] are present (nodes[3] is skipped)
assert_eq!(res.measurements.len(), 2);
assert_eq!(res.measurements[0].node_id, nodes[5]);
assert_eq!(res.measurements[1].node_id, nodes[6]);
// query with start_after = nodes[2]
let res = query_epoch_measurements_paged(deps, epoch_id, Some(nodes[2]), None)?;
assert_eq!(res.start_next_after, Some(nodes[6]));
assert_eq!(res.measurements.len(), 3);
// only nodes[3], nodes[5], nodes[6] are present (nodes[2] is skipped)
assert_eq!(res.measurements[0].node_id, nodes[3]);
assert_eq!(res.measurements[1].node_id, nodes[5]);
assert_eq!(res.measurements[2].node_id, nodes[6]);
// measurements HashMap structure for all nodes
for measurement in &res.measurements {
assert!(
measurement
.measurements_per_kind
.contains_key(&measurement_kind)
);
}
// query from beginning (no start_after) - should return all nodes
let res = query_epoch_measurements_paged(deps, epoch_id, None, None)?;
assert_eq!(res.start_next_after, Some(nodes[6]));
assert_eq!(res.measurements.len(), 5);
// verify all expected nodes are present IN SORTED ORDER
assert_eq!(res.measurements[0].node_id, nodes[1]);
assert_eq!(res.measurements[1].node_id, nodes[2]);
assert_eq!(res.measurements[2].node_id, nodes[3]);
assert_eq!(res.measurements[3].node_id, nodes[5]);
assert_eq!(res.measurements[4].node_id, nodes[6]);
// query with custom limit
// With limit=1, we fetch 1 storage item starting from nodes[3] (nodes[2] + 1)
let res = query_epoch_measurements_paged(deps, epoch_id, Some(nodes[2]), Some(1))?;
assert_eq!(res.start_next_after, Some(nodes[3]));
assert_eq!(res.measurements.len(), 1);
assert_eq!(res.measurements[0].node_id, nodes[3]);
Ok(())
}
#[test]
fn querying_epoch_measurements_paged_multiple_kinds() -> anyhow::Result<()> {
let mut test = init_contract_tester();
// Use two different network monitors for different measurement kinds
let nm1 = test.generate_account();
let nm2 = test.generate_account();
test.authorise_network_monitor(&nm1)?;
test.authorise_network_monitor(&nm2)?;
// define two different measurement kinds
let admin = test.admin_unchecked();
let kind1 = String::from("mixnet");
let kind2 = String::from("wireguard");
test.execute_raw(
admin.clone(),
ExecuteMsg::DefineMeasurementKind {
measurement_kind: kind1.clone(),
},
)?;
test.execute_raw(
admin,
ExecuteMsg::DefineMeasurementKind {
measurement_kind: kind2.clone(),
},
)?;
// bond some nodes
let node1 = test.bond_dummy_nymnode()?;
let node2 = test.bond_dummy_nymnode()?;
let node3 = test.bond_dummy_nymnode()?;
let epoch_id = 10;
test.set_mixnet_epoch(epoch_id)?;
// both measurement kinds (different network monitors)
test.insert_raw_performance(&nm1, node1, kind1.clone(), "0.11")?;
test.insert_raw_performance(&nm2, node1, kind2.clone(), "0.12")?;
// only first kind
test.insert_raw_performance(&nm1, node2, kind1.clone(), "0.21")?;
// both kinds (different network monitors)
test.insert_raw_performance(&nm1, node3, kind1.clone(), "0.31")?;
test.insert_raw_performance(&nm2, node3, kind2.clone(), "0.32")?;
let deps = test.deps();
// query all measurements for this epoch
let res = query_epoch_measurements_paged(deps, epoch_id, None, None)?;
assert_eq!(res.epoch_id, epoch_id);
assert_eq!(res.measurements.len(), 3);
assert_eq!(res.measurements[0].node_id, node1);
assert_eq!(res.measurements[1].node_id, node2);
assert_eq!(res.measurements[2].node_id, node3);
let node1_measurements = &res.measurements[0];
let node2_measurements = &res.measurements[1];
let node3_measurements = &res.measurements[2];
// verify node 1 has 2 measurement kinds
assert_eq!(node1_measurements.measurements_per_kind.len(), 2);
assert!(
node1_measurements
.measurements_per_kind
.contains_key(&kind1)
);
assert!(
node1_measurements
.measurements_per_kind
.contains_key(&kind2)
);
// raw data for node 1
let node1_kind1_results = node1_measurements
.measurements_per_kind
.get(&kind1)
.unwrap();
let expected = test.read_raw_scores(epoch_id, node1, kind1.clone())?;
assert_eq!(node1_kind1_results.inner(), expected.inner());
let node1_kind2_results = node1_measurements
.measurements_per_kind
.get(&kind2)
.unwrap();
let expected = test.read_raw_scores(epoch_id, node1, kind2.clone())?;
assert_eq!(node1_kind2_results.inner(), expected.inner());
// node 2 has only 1 measurement kind
assert_eq!(node2_measurements.measurements_per_kind.len(), 1);
assert!(
node2_measurements
.measurements_per_kind
.contains_key(&kind1)
);
assert!(
!node2_measurements
.measurements_per_kind
.contains_key(&kind2)
);
// raw data for node 2
let node2_kind1_results = node2_measurements
.measurements_per_kind
.get(&kind1)
.unwrap();
let expected = test.read_raw_scores(epoch_id, node2, kind1.clone())?;
assert_eq!(node2_kind1_results.inner(), expected.inner());
// node 3 has 2 measurement kinds
assert_eq!(node3_measurements.measurements_per_kind.len(), 2);
assert!(
node3_measurements
.measurements_per_kind
.contains_key(&kind1)
);
assert!(
node3_measurements
.measurements_per_kind
.contains_key(&kind2)
);
// raw data for node 3
let node3_kind1_results = node3_measurements
.measurements_per_kind
.get(&kind1)
.unwrap();
let expected = test.read_raw_scores(epoch_id, node3, kind1.clone())?;
assert_eq!(node3_kind1_results.inner(), expected.inner());
let node3_kind2_results = node3_measurements
.measurements_per_kind
.get(&kind2)
.unwrap();
let expected = test.read_raw_scores(epoch_id, node3, kind2.clone())?;
assert_eq!(node3_kind2_results.inner(), expected.inner());
// pagination with multiple kinds - query after node1
let res = query_epoch_measurements_paged(deps, epoch_id, Some(node1), None)?;
assert_eq!(res.measurements.len(), 2); // node2 and node3 only (node1 is skipped)
assert_eq!(res.measurements[0].node_id, node2);
assert_eq!(res.measurements[1].node_id, node3);
assert_eq!(res.measurements[0].measurements_per_kind.len(), 1); // only latency
assert_eq!(res.measurements[1].measurements_per_kind.len(), 2); // both kinds
// pagination after node2 - should only return node3
let res = query_epoch_measurements_paged(deps, epoch_id, Some(node2), None)?;
assert_eq!(res.measurements.len(), 1); // only node3
assert_eq!(res.measurements[0].node_id, node3);
assert_eq!(res.measurements[0].measurements_per_kind.len(), 2); // both kinds
Ok(())
}
#[test]
fn last_submission_query() -> anyhow::Result<()> {
let mut test = init_contract_tester();
@@ -700,7 +1100,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm1.clone(),
epoch_id: 10,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: id1,
performance: "0.2".parse()?,
measurement_kind: measurement_kind.clone(),
@@ -724,7 +1124,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm2.clone(),
epoch_id: 5,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: id2,
performance: "0.3".parse()?,
measurement_kind: measurement_kind.clone(),
@@ -735,4 +1135,103 @@ mod tests {
Ok(())
}
#[test]
#[ignore]
// TODO uncomment test:
// currently logic for stale submission doesn't work well with different measurement kinds
fn last_submission_query_multiple_kinds() -> anyhow::Result<()> {
let mut test = init_contract_tester();
let env = test.env();
// Bond one node and authorize one monitor
let id1 = test.bond_dummy_nymnode()?;
let nm1 = test.generate_account();
test.authorise_network_monitor(&nm1)?;
test.set_mixnet_epoch(10)?;
// Define TWO measurement kinds
let measurement_mixnet = MeasurementKind::from("mixnet");
let measurement_dvpn = MeasurementKind::from("dvpn");
let admin = test.admin_unchecked();
test.execute_raw(
admin.clone(),
ExecuteMsg::DefineMeasurementKind {
measurement_kind: measurement_dvpn.clone(),
},
)?;
test.execute_raw(
admin,
ExecuteMsg::DefineMeasurementKind {
measurement_kind: measurement_mixnet.clone(),
},
)?;
// no submissions yet
let data = query_last_submission(test.deps())?;
assert_eq!(
data,
LastSubmission {
block_height: env.block.height,
block_time: env.block.time,
data: None,
}
);
// Submit first measurement kind in epoch 10
test.insert_raw_performance(&nm1, id1, measurement_dvpn.clone(), "0.75")?;
let data = query_last_submission(test.deps())?;
assert_eq!(
data,
LastSubmission {
block_height: env.block.height,
block_time: env.block.time,
data: Some(LastSubmittedData {
sender: nm1.clone(),
epoch_id: 10,
data: NodePerformance {
node_id: id1,
performance: "0.75".parse()?,
measurement_kind: measurement_dvpn.clone(),
},
}),
}
);
let env = test.env();
// submit second measurement kind: same monitor, same node, same epoch
test.insert_raw_performance(&nm1, id1, measurement_mixnet.clone(), "0.85")?;
// verify that last submission is updated with the new measurement kind
let data = query_last_submission(test.deps())?;
assert_eq!(
data,
LastSubmission {
block_height: env.block.height,
block_time: env.block.time,
data: Some(LastSubmittedData {
sender: nm1.clone(),
epoch_id: 10,
data: NodePerformance {
node_id: id1,
performance: "0.85".parse()?,
measurement_kind: measurement_mixnet.clone(),
},
}),
}
);
// verify both measurements are stored independently in the same epoch
let bandwidth_results = test.read_raw_scores(10, id1, measurement_dvpn.clone())?;
assert_eq!(bandwidth_results.inner().len(), 1);
assert_eq!(bandwidth_results.inner()[0], "0.75".parse()?);
let latency_results = test.read_raw_scores(10, id1, measurement_mixnet.clone())?;
assert_eq!(latency_results.inner().len(), 1);
assert_eq!(latency_results.inner()[0], "0.85".parse()?);
Ok(())
}
}
+165 -155
View File
@@ -1,6 +1,8 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use crate::helpers::MixnetContractQuerier;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, StdError, Storage};
use cw_controllers::Admin;
@@ -9,7 +11,7 @@ use nym_contracts_common::Percent;
use nym_performance_contract_common::constants::storage_keys;
use nym_performance_contract_common::{
BatchSubmissionResult, EpochId, LastSubmission, LastSubmittedData, NetworkMonitorDetails,
NetworkMonitorSubmissionMetadata, NodeId, NodePerformanceSpecific, NodeResults,
NetworkMonitorSubmissionMetadata, NodeId, NodePerformance, NodeResults,
NymPerformanceContractError, RemoveEpochMeasurementsResponse, RetiredNetworkMonitor,
};
@@ -115,7 +117,7 @@ impl NymPerformanceContractStorage {
env: Env,
sender: &Addr,
epoch_id: EpochId,
data: NodePerformanceSpecific,
data: NodePerformance,
) -> Result<(), NymPerformanceContractError> {
// 1. check if the sender is authorised to submit performance data
self.network_monitors
@@ -138,7 +140,7 @@ impl NymPerformanceContractStorage {
// 4 insert performance data into the storage
self.performance_results
.insert_performance_data_specific(deps.storage, epoch_id, &data)?;
.insert_performance_data(deps.storage, epoch_id, &data)?;
// 5. update submission metadata based on the last result we submitted
self.performance_results.update_submission_metadata(
@@ -171,7 +173,7 @@ impl NymPerformanceContractStorage {
env: Env,
sender: &Addr,
epoch_id: EpochId,
data: Vec<NodePerformanceSpecific>,
data: Vec<NodePerformance>,
) -> Result<BatchSubmissionResult, NymPerformanceContractError> {
// 1. check if the sender is authorised to submit performance data
self.network_monitors
@@ -196,11 +198,10 @@ impl NymPerformanceContractStorage {
// 3. submit it
if self.node_bonded(deps.as_ref(), first.node_id)? {
match self.performance_results.insert_performance_data_specific(
deps.storage,
epoch_id,
first,
) {
match self
.performance_results
.insert_performance_data(deps.storage, epoch_id, first)
{
Ok(_) => accepted_scores += 1,
Err(NymPerformanceContractError::UnsupportedMeasurementKind { kind }) => {
non_existent_measurement_kind.push(kind);
@@ -226,11 +227,8 @@ impl NymPerformanceContractStorage {
// 5. insert performance data into the storage
if self.node_bonded(deps.as_ref(), perf.node_id)? {
self.performance_results.insert_performance_data_specific(
deps.storage,
epoch_id,
perf,
)?;
self.performance_results
.insert_performance_data(deps.storage, epoch_id, perf)?;
accepted_scores += 1;
} else {
non_existent_nodes.push(perf.node_id);
@@ -332,45 +330,51 @@ impl NymPerformanceContractStorage {
.retire(deps, &env, sender, &network_monitor)
}
// TODO dz needs to support measurement_kind
pub fn try_load_measurement_kind(
&self,
storage: &dyn Storage,
epoch_id: EpochId,
node_id: NodeId,
measurement_kind: MeasurementKind,
) -> Result<Option<NodeResults>, NymPerformanceContractError> {
self.performance_results
.assert_measurement_defined(storage, measurement_kind.clone())?;
let key = (epoch_id, node_id, measurement_kind);
self.performance_results
.results
.may_load(storage, key)
.map_err(From::from)
}
pub fn try_load_performance(
&self,
storage: &dyn Storage,
epoch_id: EpochId,
node_id: NodeId,
) -> Result<Option<Percent>, NymPerformanceContractError> {
) -> Result<HashMap<String, Percent>, NymPerformanceContractError> {
// Get all defined measurement kinds
let measurement_kinds = self.performance_results.defined_measurements(storage)?;
// short-circuit if no measurement kinds are defined
if measurement_kinds.is_empty() {
return Ok(None);
return Ok(HashMap::new());
}
// Collect median values from all measurement kinds
let mut medians = Vec::new();
let mut performance_per_kind: HashMap<String, Percent> = HashMap::new();
// collect median values per measurement kind
for kind in measurement_kinds {
if let Some(results) = self
.performance_results
.results_specific
.may_load(storage, (epoch_id, node_id, kind))?
.results
.may_load(storage, (epoch_id, node_id, kind.clone()))?
{
medians.push(results.median());
performance_per_kind.insert(kind, results.median());
}
}
// If no data found for any measurement kind, return None
if medians.is_empty() {
return Ok(None);
}
// Build NodeResults from collected medians and return the median of medians
let mut aggregated = NodeResults::new(medians[0]);
for &median in &medians[1..] {
aggregated.insert_new(median);
}
Ok(Some(aggregated.median()))
Ok(performance_per_kind)
}
pub fn remove_node_measurements(
@@ -388,7 +392,7 @@ impl NymPerformanceContractStorage {
.defined_measurements(deps.storage)?;
for kind in measurement_kinds {
self.performance_results
.results_specific
.results
.remove(deps.storage, (epoch_id, node_id, kind));
}
@@ -404,18 +408,15 @@ impl NymPerformanceContractStorage {
self.ensure_is_admin(deps.as_ref(), sender)?;
// 1. purge the entries according to the limit
self.performance_results
.results_specific
.sub_prefix(epoch_id)
.clear(
deps.storage,
Some(retrieval_limits::EPOCH_PERFORMANCE_PURGE_LIMIT),
);
self.performance_results.results.sub_prefix(epoch_id).clear(
deps.storage,
Some(retrieval_limits::EPOCH_PERFORMANCE_PURGE_LIMIT),
);
// 2. see if there's anything left
let additional_entries_to_remove_remaining = !self
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.is_empty(deps.storage);
@@ -509,9 +510,7 @@ impl NetworkMonitorsStorage {
}
pub(crate) struct PerformanceResultsStorage {
pub(crate) results: Map<(EpochId, NodeId), NodeResults>,
pub(crate) results_specific: Map<(EpochId, NodeId, MeasurementKind), NodeResults>,
pub(crate) results: Map<(EpochId, NodeId, MeasurementKind), NodeResults>,
/// only measurements defined here can be submitted, as defined by contract admin
defined_measurements: Map<MeasurementKind, ()>,
@@ -529,8 +528,7 @@ impl PerformanceResultsStorage {
#[allow(clippy::new_without_default)]
const fn new() -> Self {
PerformanceResultsStorage {
results: Map::new(storage_keys::PERFORMANCE_RESULTS),
results_specific: Map::new(storage_keys::PERFORMANCE_RESULTS_PER_KIND),
results: Map::new(storage_keys::PERFORMANCE_RESULTS_PER_KIND),
defined_measurements: Map::new(storage_keys::PERFORMANCE_DEFINED_KINDS),
submission_metadata: Map::new(storage_keys::SUBMISSION_METADATA),
}
@@ -542,12 +540,14 @@ impl PerformanceResultsStorage {
&self,
storage: &mut dyn Storage,
epoch_id: EpochId,
data: &NodePerformanceSpecific,
data: &NodePerformance,
) -> Result<(), NymPerformanceContractError> {
self.assert_measurement_defined(storage, data.measurement_kind.clone())?;
let performance = data.performance;
let key = (epoch_id, data.node_id);
let updated = match self.results.may_load(storage, key)? {
let key = (epoch_id, data.node_id, data.measurement_kind.clone());
let updated = match self.results.may_load(storage, key.clone())? {
None => NodeResults::new(performance),
Some(mut existing) => {
existing.insert_new(performance);
@@ -559,29 +559,6 @@ impl PerformanceResultsStorage {
Ok(())
}
fn insert_performance_data_specific(
&self,
storage: &mut dyn Storage,
epoch_id: EpochId,
data: &NodePerformanceSpecific,
) -> Result<(), NymPerformanceContractError> {
self.assert_measurement_defined(storage, data.measurement_kind.clone())?;
let performance = data.performance;
let key = (epoch_id, data.node_id, data.measurement_kind.clone());
let updated = match self.results_specific.may_load(storage, key.clone())? {
None => NodeResults::new(performance),
Some(mut existing) => {
existing.insert_new(performance);
existing
}
};
self.results_specific.save(storage, key, &updated)?;
Ok(())
}
pub(crate) fn defined_measurements(
&self,
storage: &dyn Storage,
@@ -594,7 +571,7 @@ impl PerformanceResultsStorage {
fn is_measurement_defined(
&self,
storage: &mut dyn Storage,
storage: &dyn Storage,
measurement_kind: MeasurementKind,
) -> Result<bool, cosmwasm_std::StdError> {
self.defined_measurements
@@ -604,7 +581,7 @@ impl PerformanceResultsStorage {
fn assert_measurement_defined(
&self,
storage: &mut dyn Storage,
storage: &dyn Storage,
measurement_kind: MeasurementKind,
) -> Result<(), NymPerformanceContractError> {
if !self.is_measurement_defined(storage, measurement_kind.clone())? {
@@ -670,6 +647,11 @@ impl PerformanceResultsStorage {
Ok(())
}
// TODO: this logic was written with ONE measurement kind in mind.
// Whether different measurement kinds are submitted sequentially or not,
// batched by node_id or not etc. is an open question. Until that is decided,
// this implementation is flawed and needs to be FIXED.
// There is a unit test (currently ignored) showing desired behaviour
fn ensure_non_stale_submission(
&self,
storage: &dyn Storage,
@@ -1015,12 +997,12 @@ mod tests {
let id1 = tester.bond_dummy_nymnode()?;
let id2 = tester.bond_dummy_nymnode()?;
let data = NodePerformanceSpecific {
let data = NodePerformance {
node_id: id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let another_data = NodePerformanceSpecific {
let another_data = NodePerformance {
node_id: id2,
performance: Percent::hundred(),
measurement_kind,
@@ -1110,12 +1092,12 @@ mod tests {
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let data = NodePerformanceSpecific {
let data = NodePerformance {
node_id: id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let another_data = NodePerformanceSpecific {
let another_data = NodePerformance {
node_id: id2,
performance: Percent::hundred(),
measurement_kind,
@@ -1270,7 +1252,7 @@ mod tests {
env.clone(),
&nm,
0,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1288,7 +1270,7 @@ mod tests {
env.clone(),
&nm,
0,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[3],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1306,7 +1288,7 @@ mod tests {
env.clone(),
&nm,
1,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1324,7 +1306,7 @@ mod tests {
env.clone(),
&nm,
12345,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[8],
performance: Default::default(),
measurement_kind,
@@ -1359,7 +1341,7 @@ mod tests {
env.clone(),
&nm,
0,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1374,7 +1356,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 0,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -1388,7 +1370,7 @@ mod tests {
env.clone(),
&nm,
0,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1403,7 +1385,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 0,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[6],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -1417,7 +1399,7 @@ mod tests {
env.clone(),
&nm,
1,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[2],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1432,7 +1414,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 1,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[2],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -1446,7 +1428,7 @@ mod tests {
env.clone(),
&nm,
12345,
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[9],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1461,7 +1443,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 12345,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[9],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -1483,7 +1465,7 @@ mod tests {
tester.authorise_network_monitor(&nm)?;
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let dummy_perf = NodePerformanceSpecific {
let dummy_perf = NodePerformance {
node_id: 12345,
performance: Percent::from_percentage_value(69)?,
measurement_kind: measurement_kind.clone(),
@@ -1508,7 +1490,7 @@ mod tests {
// bonded nym-node
let node_id = tester.bond_dummy_nymnode()?;
let perf = NodePerformanceSpecific {
let perf = NodePerformance {
node_id,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -1631,17 +1613,17 @@ mod tests {
let id2 = tester.bond_dummy_nymnode()?;
let id3 = tester.bond_dummy_nymnode()?;
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let data = NodePerformanceSpecific {
let data = NodePerformance {
node_id: id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let another_data = NodePerformanceSpecific {
let another_data = NodePerformance {
node_id: id2,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let more_data = NodePerformanceSpecific {
let more_data = NodePerformance {
node_id: id3,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
@@ -1724,12 +1706,12 @@ mod tests {
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let data = NodePerformanceSpecific {
let data = NodePerformance {
node_id: id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let another_data = NodePerformanceSpecific {
let another_data = NodePerformance {
node_id: id2,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
@@ -1831,12 +1813,12 @@ mod tests {
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let data = NodePerformanceSpecific {
let data = NodePerformance {
node_id: id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let another_data = NodePerformanceSpecific {
let another_data = NodePerformance {
node_id: id2,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
@@ -2016,7 +1998,7 @@ mod tests {
env.clone(),
&nm,
0,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2035,7 +2017,7 @@ mod tests {
env.clone(),
&nm,
1,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2055,17 +2037,17 @@ mod tests {
&nm,
1,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[2],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[3],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[4],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2086,17 +2068,17 @@ mod tests {
&nm,
2,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[6],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[8],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2134,7 +2116,7 @@ mod tests {
env.clone(),
&nm,
0,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2149,7 +2131,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 0,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[0],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -2164,7 +2146,7 @@ mod tests {
env.clone(),
&nm,
1,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2179,7 +2161,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 1,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -2195,17 +2177,17 @@ mod tests {
&nm,
1,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[2],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[3],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[4],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2221,7 +2203,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 1,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[4],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -2237,17 +2219,17 @@ mod tests {
&nm,
2,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[1],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[7],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nodes[8],
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2263,7 +2245,7 @@ mod tests {
data: Some(LastSubmittedData {
sender: nm.clone(),
epoch_id: 2,
data: NodePerformanceSpecific {
data: NodePerformance {
node_id: nodes[8],
performance: Default::default(),
measurement_kind: measurement_kind.clone()
@@ -2304,7 +2286,7 @@ mod tests {
env.clone(),
&nm,
0,
vec![NodePerformanceSpecific {
vec![NodePerformance {
node_id: 999999,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2320,12 +2302,12 @@ mod tests {
&nm,
1,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: nym_node1,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: 999999,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2342,22 +2324,22 @@ mod tests {
&nm,
2,
vec![
NodePerformanceSpecific {
NodePerformance {
node_id: 2,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nym_node1,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nym_node_between,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
},
NodePerformanceSpecific {
NodePerformance {
node_id: nym_node2,
performance: Default::default(),
measurement_kind: measurement_kind.clone(),
@@ -2651,7 +2633,10 @@ mod tests {
// no results
let node_id = tester.bond_dummy_nymnode()?;
assert_eq!(storage.try_load_performance(&tester, 0, node_id)?, None);
assert_eq!(
storage.try_load_performance(&tester, 0, node_id)?,
HashMap::new()
);
//
// always returns median value with 2decimal places precision
@@ -2664,6 +2649,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2677,6 +2663,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2690,6 +2677,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2704,6 +2692,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2720,6 +2709,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2737,6 +2727,7 @@ mod tests {
assert_eq!(
storage
.try_load_performance(&tester, 0, node_id)?
.get(&measurement_kind)
.unwrap()
.value()
.to_string(),
@@ -2848,7 +2839,7 @@ mod tests {
let before = storage
.performance_results
.results_specific
.results
.prefix((epoch_id, id1))
.all_values(tester.storage())
.unwrap();
@@ -2858,7 +2849,7 @@ mod tests {
let after = storage
.performance_results
.results_specific
.results
.prefix((epoch_id, id1))
.all_values(tester.storage())
.unwrap();
@@ -2871,7 +2862,7 @@ mod tests {
let before = storage
.performance_results
.results_specific
.results
.prefix((epoch_id, id2))
.all_values(tester.storage())
.unwrap();
@@ -2881,7 +2872,7 @@ mod tests {
let after = storage
.performance_results
.results_specific
.results
.prefix((epoch_id, id2))
.all_values(tester.storage())
.unwrap();
@@ -2998,7 +2989,7 @@ mod tests {
let before = storage
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.all_values(&tester)?;
assert_eq!(before.len(), 10);
@@ -3008,7 +2999,7 @@ mod tests {
let after = storage
.performance_results
.results
.prefix(epoch_id)
.sub_prefix(epoch_id)
.all_values(&tester)?;
assert!(after.is_empty());
@@ -3031,7 +3022,7 @@ mod tests {
let after = storage
.performance_results
.results
.prefix(epoch_id)
.sub_prefix(epoch_id)
.all_values(&tester)?;
assert!(after.is_empty());
@@ -3065,7 +3056,7 @@ mod tests {
let before = storage
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.all_values(&tester)?;
assert_eq!(
@@ -3077,7 +3068,7 @@ mod tests {
assert!(res.additional_entries_to_remove_remaining);
let after = storage
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.all_values(&tester)?;
@@ -3090,7 +3081,7 @@ mod tests {
assert!(res.additional_entries_to_remove_remaining);
let after = storage
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.all_values(&tester)?;
@@ -3100,7 +3091,7 @@ mod tests {
assert!(!res.additional_entries_to_remove_remaining);
let after = storage
.performance_results
.results_specific
.results
.sub_prefix(epoch_id)
.all_values(&tester)?;
@@ -3266,69 +3257,88 @@ mod tests {
let node_id2 = 456;
let measurement_kind = tester.define_dummy_measurement_kind().unwrap();
let data1 = NodePerformanceSpecific {
let data1 = NodePerformance {
node_id: node_id1,
performance: Percent::from_str("0.23")?,
measurement_kind: measurement_kind.clone(),
};
let data2 = NodePerformanceSpecific {
let data2 = NodePerformance {
node_id: node_id1,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
let data3 = NodePerformanceSpecific {
let data3 = NodePerformance {
node_id: node_id2,
performance: Percent::from_str("0.23643634")?,
measurement_kind: measurement_kind.clone(),
};
let data4 = NodePerformanceSpecific {
let data4 = NodePerformance {
node_id: node_id2,
performance: Percent::hundred(),
measurement_kind: measurement_kind.clone(),
};
assert!(storage.results.may_load(&tester, (1, node_id1))?.is_none());
assert!(storage.results.may_load(&tester, (1, node_id2))?.is_none());
assert!(
storage
.results
.may_load(&tester, (1, node_id1, measurement_kind.clone()))?
.is_none()
);
assert!(
storage
.results
.may_load(&tester, (1, node_id2, measurement_kind.clone()))?
.is_none()
);
storage.insert_performance_data(&mut tester, 1, &data1)?;
assert_eq!(
tester.read_raw_scores(1, node_id1)?.inner(),
tester
.read_raw_scores(1, node_id1, measurement_kind.clone())?
.inner(),
&[data1.performance]
);
storage.insert_performance_data(&mut tester, 1, &data2)?;
assert_eq!(
tester.read_raw_scores(1, node_id1)?.inner(),
tester
.read_raw_scores(1, node_id1, measurement_kind.clone())?
.inner(),
&[data1.performance, data2.performance]
);
storage.insert_performance_data(&mut tester, 1, &data3)?;
assert_eq!(
tester.read_raw_scores(1, node_id2)?.inner(),
tester
.read_raw_scores(1, node_id2, measurement_kind.clone())?
.inner(),
&[data3.performance.round_to_two_decimal_places()]
);
storage.insert_performance_data(&mut tester, 1, &data4)?;
assert_eq!(
tester.read_raw_scores(1, node_id2)?.inner(),
tester
.read_raw_scores(1, node_id2, measurement_kind.clone())?
.inner(),
&[
data3.performance.round_to_two_decimal_places(),
data4.performance
]
);
storage.insert_performance_data(&mut tester, 2, &data2)?;
storage.insert_performance_data(&mut tester, 2, &data2)?;
assert_eq!(
tester.read_raw_scores(2, node_id1)?.inner(),
tester
.read_raw_scores(2, node_id1, measurement_kind.clone())?
.inner(),
&[data2.performance, data2.performance]
);
storage.insert_performance_data(&mut tester, 2, &data4)?;
storage.insert_performance_data(&mut tester, 2, &data4)?;
assert_eq!(
tester.read_raw_scores(2, node_id2)?.inner(),
tester
.read_raw_scores(2, node_id2, measurement_kind.clone())?
.inner(),
&[data4.performance, data4.performance]
);
+21 -6
View File
@@ -27,8 +27,8 @@ use nym_mixnet_contract_common::{
};
use nym_performance_contract_common::constants::storage_keys;
use nym_performance_contract_common::{
ExecuteMsg, InstantiateMsg, MigrateMsg, NodeId, NodePerformanceSpecific, NodeResults,
NymPerformanceContractError, QueryMsg,
EpochNodePerformance, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeId, NodePerformance,
NodeResults, NymPerformanceContractError, QueryMsg,
};
use serde::Serialize;
use serde::de::DeserializeOwned;
@@ -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 {
@@ -378,10 +392,10 @@ pub(crate) trait PerformanceContractTesterExt:
Ok(measurement_kind)
}
fn dummy_node_performance(&mut self) -> NodePerformanceSpecific {
fn dummy_node_performance(&mut self) -> NodePerformance {
let node_id = self.bond_dummy_nymnode().unwrap();
let measurement_kind = self.dummy_measurement_kind();
NodePerformanceSpecific {
NodePerformance {
node_id,
performance: Percent::from_percentage_value(69).unwrap(),
measurement_kind,
@@ -413,7 +427,7 @@ pub(crate) trait PerformanceContractTesterExt:
env,
addr,
epoch_id,
NodePerformanceSpecific {
NodePerformance {
node_id,
performance,
measurement_kind,
@@ -455,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)
}
+15 -3
View File
@@ -4,7 +4,7 @@
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, NodePerformanceSpecific, NymPerformanceContractError,
EpochId, NodeId, NodePerformance, NymPerformanceContractError,
};
pub fn try_update_contract_admin(
@@ -30,6 +30,18 @@ pub fn try_define_measurement_kind(
.contract_admin
.assert_admin(deps.as_ref(), sender)?;
// validation
if measurement_kind.len() < 2
|| measurement_kind.len() > 20
|| !measurement_kind.is_ascii()
|| measurement_kind.contains(char::is_whitespace)
{
return Err(NymPerformanceContractError::InvalidInput(format!(
"Cannot define {} as measurement kind",
measurement_kind
)));
}
NYM_PERFORMANCE_CONTRACT_STORAGE
.performance_results
.define_new_measurement_kind(deps.storage, measurement_kind)?;
@@ -58,7 +70,7 @@ pub fn try_submit_performance_results(
env: Env,
info: MessageInfo,
epoch_id: EpochId,
data: NodePerformanceSpecific,
data: NodePerformance,
) -> Result<Response, NymPerformanceContractError> {
NYM_PERFORMANCE_CONTRACT_STORAGE.submit_performance_data(
deps,
@@ -77,7 +89,7 @@ pub fn try_batch_submit_performance_results(
env: Env,
info: MessageInfo,
epoch_id: EpochId,
data: Vec<NodePerformanceSpecific>,
data: Vec<NodePerformance>,
) -> Result<Response, NymPerformanceContractError> {
let res = NYM_PERFORMANCE_CONTRACT_STORAGE.batch_submit_performance_results(
deps,
@@ -6,7 +6,7 @@ use nym_api_requests::models::RoutingScore;
use nym_contracts_common::NaiveFloat;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{EpochId, NodeId};
use nym_validator_client::nyxd::contract_traits::performance_query_client::NodePerformanceSpecific;
use nym_validator_client::nyxd::contract_traits::performance_query_client::NodePerformance;
use std::collections::{BTreeMap, HashMap};
pub(crate) struct PerformanceContractEpochCacheData {
@@ -16,7 +16,7 @@ pub(crate) struct PerformanceContractEpochCacheData {
impl PerformanceContractEpochCacheData {
pub(crate) fn from_node_performance(
performance: Vec<NodePerformanceSpecific>,
performance: Vec<NodePerformance>,
epoch_id: EpochId,
) -> Self {
let median_performance = performance
+2 -2
View File
@@ -36,7 +36,7 @@ use nym_mixnet_contract_common::{
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt;
use nym_validator_client::nyxd::contract_traits::performance_query_client::{
LastSubmission, NodePerformanceSpecific,
LastSubmission, NodePerformance,
};
use nym_validator_client::nyxd::contract_traits::{
PagedDkgQueryClient, PagedPerformanceQueryClient, PerformanceQueryClient,
@@ -414,7 +414,7 @@ impl Client {
pub(crate) async fn get_full_epoch_performance(
&self,
epoch_id: nym_mixnet_contract_common::EpochId,
) -> Result<Vec<NodePerformanceSpecific>, NyxdError> {
) -> Result<Vec<NodePerformance>, NyxdError> {
nyxd_query!(self, get_all_epoch_performance(epoch_id).await)
}
}