diff --git a/rs/dre-canisters/trustworthy-node-metrics/add_np.py b/rs/dre-canisters/trustworthy-node-metrics/add_np.py index a613858dc..6ab2b1e61 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/add_np.py +++ b/rs/dre-canisters/trustworthy-node-metrics/add_np.py @@ -1,60 +1,51 @@ -import csv +import json import subprocess - +import time # Define the file paths and constants csv_file_path = "node_info_api.csv" did_file_path = "rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/trustworthy-node-metrics.did" network = "ic" canister_name = "trustworthy-node-metrics" -# Function to generate the dfx command for node_metadata -def generate_dfx_command(node_mappings): - nodes = [ - f'record {{ node_id = principal "{row["node_id"]}"; node_provider_id = principal "{row["node_provider_id"]}"; node_provider_name = "{row["node_provider_name"]}"; }}' - for row in node_mappings - ] - # Prepare the argument for the canister call - node_metadata_args = f"vec {{ {'; '.join(nodes)} ; }}" - +# Function to generate the dfx command for a single node +def generate_dfx_command(node_data): command = [ - "dfx", "canister", "call", canister_name, "node_metadata", - f'({node_metadata_args})', + "dfx", "canister", "call", canister_name, "np_rewardable_backfill", + f'(record {{ node_provider_id = principal "{node_data["node_provider_id"]}"; region = "{node_data["region"]}"; node_type = "{node_data["node_type"]}"; count = {node_data["count"]}; }})', "--candid", did_file_path, "--network", network ] - return command -# Read the CSV file and filter out missing providers -def read_csv(csv_file_path): - node_mappings = [] - with open(csv_file_path, mode='r') as file: - csv_reader = csv.DictReader(file) - for row in csv_reader: - if row['node_provider_id'] != "missing": # Ignore missing providers - node_mappings.append({ - "node_id": row["node_id"], - "node_provider_id": row["node_provider_id"], - "node_provider_name": row["node_provider_name"] - }) +# Read the .json file and extract node information +def read_json(json_file_path): + with open(json_file_path, mode='r') as file: + # Each line is a separate JSON object, so we use a list to store them + node_mappings = [json.loads(line) for line in file] return node_mappings +# Execute the dfx command for each node +def execute_dfx_command(command): + try: + result = subprocess.run(command, capture_output=True, text=True) + print("Command executed successfully:", result.stdout) + if result.stderr: + print("Error:", result.stderr) + except Exception as e: + print("Failed to execute the dfx command:", str(e)) + # Main execution if __name__ == "__main__": - # Read the CSV and get node mappings - node_mappings = read_csv(csv_file_path) + # Read the JSON file and get node mappings + node_mappings = read_json(json_file_path) if node_mappings: - # Generate the dfx command - dfx_command = generate_dfx_command(node_mappings) - - # Execute the dfx command - try: - result = subprocess.run(dfx_command, capture_output=True, text=True) - print(result.stdout) - if result.stderr: - print("Error:", result.stderr) - except Exception as e: - print("Failed to execute the dfx command:", str(e)) + # Loop through each node and execute a separate command + for node in node_mappings: + # Generate the dfx command for the current node + dfx_command = generate_dfx_command(node) + + # Execute the dfx command for the current node + execute_dfx_command(dfx_command) else: - print("No valid node mappings found.") + print("No valid node mappings found.") \ No newline at end of file diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodePerformanceChart.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodePerformanceChart.tsx index 27c8f6886..2aef49a66 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodePerformanceChart.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodePerformanceChart.tsx @@ -3,7 +3,7 @@ import { ChartData, formatDateToUTC, generateChartData, LoadingIndicator, NodeMe import { PeriodFilter } from './FilterBar'; import { Box, Grid } from '@mui/material'; import PerformanceChart from './PerformanceChart'; -import { NodeRewards } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did'; +import { NodeRewardsMultiplier } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did'; import { ExportTable } from './ExportTable'; import { GridColDef, GridRowsProp } from '@mui/x-data-grid'; import { Principal } from '@dfinity/principal'; @@ -16,7 +16,7 @@ export interface NodePerformanceChartProps { } export const NodePerformanceChart: React.FC = ({ node, periodFilter }) => { - const [performanceData, setPerformanceData] = useState(null); + const [performanceData, setPerformanceData] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -36,7 +36,7 @@ export const NodePerformanceChart: React.FC = ({ node } const performanceDailyData: ChartData[] = generateChartData(periodFilter, performanceData.daily_node_metrics); - const failureRateAvg = Math.round(performanceData.rewards_computation.failure_rate * 100); + const failureRateAvg = Math.round(performanceData.rewards_multiplier.failure_rate * 100); const rows: GridRowsProp = performanceData.daily_node_metrics.map((data, index) => { return { @@ -62,7 +62,7 @@ export const NodePerformanceChart: React.FC = ({ node return ( <> - + diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderChart.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderChart.tsx index 1a86eb277..bae36344a 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderChart.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderChart.tsx @@ -53,7 +53,7 @@ export const NodeProviderChart: React.FC = ({ provider, const providerNodeMetrics = providerRewards.nodes_rewards; const highFailureRateChart = providerNodeMetrics - .sort((a, b) => b.rewards_computation.failure_rate - a.rewards_computation.failure_rate) + .sort((a, b) => b.rewards_multiplier.failure_rate - a.rewards_multiplier.failure_rate) .slice(0, 3) .flatMap(nodeMetrics => { const chartData = generateChartData(periodFilter, nodeMetrics.daily_node_metrics); diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderPage.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderPage.tsx index efdd2373d..266a04c46 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderPage.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderPage.tsx @@ -63,7 +63,7 @@ export const NodeProviderPage: React.FC = ({ nodeMetadata - Provider Rewards + Last Rewarding Period diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderRewards.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderRewards.tsx index 03bb3e14a..7af4e183d 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderRewards.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeProviderRewards.tsx @@ -1,10 +1,12 @@ import React, { useEffect, useState } from 'react'; import { getLatestRewardRange, LoadingIndicator, setNodeProviderRewardsData } from '../utils/utils'; -import { Box, Grid } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; import { Principal } from '@dfinity/principal'; import { NodeProviderRewards } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did'; import { WidgetNumber } from './Widgets'; import { boxStyleWidget } from '../Styles'; +import { ExportTable } from './ExportTable'; +import { GridColDef, GridRowsProp } from '@mui/x-data-grid'; export interface NodeProviderRewardsChartProps { provider: string; @@ -36,6 +38,21 @@ export const NodeProviderRewardsChart: React.FC = return

No latestNodeRewards

; } const distribution_date = new Date(Number(latestProviderRewards.ts_distribution) * 1000); + const rows: GridRowsProp = latestProviderRewards.computation_log.map((data, index) => { + return { + id: index, + col0: index, + col1: data.reason, + col2: data.operation, + col3: data.result + }; + }); + const colDef: GridColDef[] = [ + { field: 'col0', headerName: 'Step', width: 100}, + { field: 'col1', headerName: 'Description', width: 1500}, + { field: 'col2', headerName: 'Operation', width: 500 }, + { field: 'col3', headerName: 'Result', width: 200 }, + ]; return ( <> @@ -49,10 +66,16 @@ export const NodeProviderRewardsChart: React.FC = - + + + + Computation Log + + + ); diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeRewardsChart.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeRewardsChart.tsx index 805294870..9d2bb7be5 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeRewardsChart.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/components/NodeRewardsChart.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { ChartData, generateChartData, getLatestRewardRange, LoadingIndicator, NodeMetricsStats, NodePerformanceStats, setNodeRewardsData } from '../utils/utils'; import { Grid, Typography } from '@mui/material'; import PerformanceChart from './PerformanceChart'; -import { NodeRewards } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did'; +import { NodeRewardsMultiplier } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did'; import RewardsInfo, { LinearReductionChart } from './RewardsInfo'; import { Principal } from '@dfinity/principal'; import { ExportTable } from './ExportTable'; @@ -14,7 +14,7 @@ export interface NodeRewardsChartProps { export const NodeRewardsChart: React.FC = ({ node }) => { const latestRewardRange = getLatestRewardRange(); - const [latestNodeRewards, setLatestNodeRewards] = useState(null); + const [latestNodeRewards, setLatestNodeRewards] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -33,10 +33,10 @@ export const NodeRewardsChart: React.FC = ({ node }) => { } const rewardsDailyData: ChartData[] = generateChartData(latestRewardRange, latestNodeRewards.daily_node_metrics); - const failureRateAvg = Math.round((latestNodeRewards.rewards_computation.failure_rate) * 100) - const rewardsMultiplier = Math.round((latestNodeRewards.rewards_computation.rewards_multiplier) * 100); + const failureRateAvg = Math.round((latestNodeRewards.rewards_multiplier.failure_rate) * 100) + const rewardsMultiplier = Math.round((latestNodeRewards.rewards_multiplier.rewards_multiplier) * 100); const rewardsReduction = 100 - rewardsMultiplier; - const rows: GridRowsProp = latestNodeRewards.rewards_computation.computation_log.map((data, index) => { + const rows: GridRowsProp = latestNodeRewards.rewards_multiplier.computation_log.map((data, index) => { return { id: index, col0: index, @@ -55,7 +55,7 @@ export const NodeRewardsChart: React.FC = ({ node }) => { return ( <> - + ; } @@ -35,7 +35,7 @@ const RewardTable: React.FC = ({ nodeRewards }) => { {nodeMetrics.node_id.toText()} - {nodeMetrics.rewards_computation.failure_rate * 100}% + {nodeMetrics.rewards_multiplier.failure_rate * 100}% ))} diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/utils/utils.tsx b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/utils/utils.tsx index 546bf55a9..0f1d2c2b3 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/utils/utils.tsx +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-frontend/src/utils/utils.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Principal } from "@dfinity/principal"; import { trustworthy_node_metrics } from "../../../declarations/trustworthy-node-metrics"; -import { DailyNodeMetrics, NodeRewardsArgs, NodeRewards, NodeProviderRewardsArgs, NodeProviderRewards } from "../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did"; +import { DailyNodeMetrics, NodeRewardsArgs, NodeRewardsMultiplier, NodeProviderRewardsArgs, NodeProviderRewards } from "../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did"; import { PeriodFilter } from "../components/FilterBar"; import { Box, CircularProgress } from "@mui/material"; import { WidgetNumber } from '../components/Widgets'; @@ -130,7 +130,7 @@ export const getLatestRewardRange = () => { export const setNodeRewardsData = async ( periodFilter: PeriodFilter, node_id: Principal, - setNodeRewards: React.Dispatch>, + setNodeRewards: React.Dispatch>, setIsLoading: React.Dispatch>) => { try { setIsLoading(true); @@ -186,7 +186,7 @@ export const LoadingIndicator: React.FC = () => ( ); -export const NodeMetricsStats: React.FC<{ stats: NodeRewards['rewards_computation'] | null }> = ({ stats }) => ( +export const NodeMetricsStats: React.FC<{ stats: NodeRewardsMultiplier['rewards_multiplier'] | null }> = ({ stats }) => ( diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/src/types.rs b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/src/types.rs index 59489716f..c08a5a327 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/src/types.rs +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/src/types.rs @@ -37,6 +37,30 @@ impl Storable for MonthlyNodeProviderRewardsStored { }; } +#[derive(Debug, Deserialize, Serialize, CandidType, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct NodeProviderRewardableKey { + pub node_provider_id: Principal, + pub region: String, + pub node_type: String, +} + +const MAX_VALUE_SIZE_REWARDABLE_NODES: u32 = 300; + +impl Storable for NodeProviderRewardableKey { + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Cow::Owned(Encode!(self).unwrap()) + } + + fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self { + Decode!(bytes.as_ref(), Self).unwrap() + } + + const BOUND: Bound = Bound::Bounded { + max_size: MAX_VALUE_SIZE_REWARDABLE_NODES, + is_fixed_size: false, + }; +} + #[derive(Debug, Deserialize, Serialize, CandidType, Clone)] pub struct NodeMetricsStored { pub subnet_assigned: Principal, @@ -68,7 +92,7 @@ pub struct NodeRewardRatesStored { pub rewards_rates: NodeRewardRates, } -const MAX_VALUE_SIZE_BYTES_REWARD_RATES: u32 = 133; +const MAX_VALUE_SIZE_BYTES_REWARD_RATES: u32 = 200; impl Storable for NodeRewardRatesStored { fn to_bytes(&self) -> std::borrow::Cow<[u8]> { @@ -116,7 +140,7 @@ pub struct NodeMetadataStoredV2 { pub node_type: String, } -const MAX_VALUE_SIZE_BYTES_NODE_METADATA: u32 = 204; +const MAX_VALUE_SIZE_BYTES_NODE_METADATA: u32 = 400; impl Storable for NodeMetadataStoredV2 { fn to_bytes(&self) -> std::borrow::Cow<[u8]> { @@ -212,7 +236,7 @@ impl DailyNodeMetrics { } #[derive(Debug, Deserialize, CandidType)] -pub struct RewardMultiplierResult { +pub struct RewardsMultiplier { pub days_assigned: u64, pub days_unassigned: u64, pub rewards_multiplier: f64, @@ -225,11 +249,17 @@ pub struct RewardMultiplierResult { } #[derive(Debug, Deserialize, CandidType)] -pub struct NodeRewards { +pub struct NodeRewardsMultiplier { pub node_id: Principal, pub daily_node_metrics: Vec, pub node_rate: NodeRewardRate, - pub rewards_computation: RewardMultiplierResult, + pub rewards_multiplier: RewardsMultiplier, +} + +pub struct NodeProviderRewardsComputation { + pub rewards_xdr: u64, + pub rewards_xdr_no_reduction: u64, + pub computation_log: Vec, } #[derive(Debug, Deserialize, CandidType)] @@ -240,7 +270,8 @@ pub struct NodeProviderRewards { pub rewards_xdr_old: Option, pub ts_distribution: u64, pub xdr_conversion_rate: Option, - pub nodes_rewards: Vec, + pub nodes_rewards: Vec, + pub computation_log: Vec, } #[derive(Debug, Deserialize, CandidType)] diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/trustworthy-node-metrics.did b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/trustworthy-node-metrics.did deleted file mode 100644 index 22c06ae39..000000000 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics-types/trustworthy-node-metrics.did +++ /dev/null @@ -1,48 +0,0 @@ - -type NodeMetrics = record { - node_id: principal; - num_blocks_proposed_total: nat64; - num_blocks_failures_total: nat64; -}; - -type SubnetNodeMetricsResponse = record { - ts: nat64; - subnet_id: principal; - node_metrics: vec NodeMetrics; -}; - -type SubnetNodeMetricsResult = variant { - Ok : vec SubnetNodeMetricsResponse; - Err : text; -}; - -type SubnetNodeMetricsArgs = record { - subnet_id: opt principal; - ts: opt nat64; -}; - -type DailyNodeMetrics = record { - ts: nat64; - subnet_assigned: principal; - num_blocks_proposed: nat64; - num_blocks_failed: nat64; - failure_rate: float64; - rewards_reduction: float64; -}; - -type NodeRewardsResponse = record { - node_id: principal; - rewards_no_penalty: float64; - rewards_with_penalty: float64; - daily_node_metrics: vec DailyNodeMetrics; -}; - -type NodeRewardsArgs = record { - from_ts: nat64; - to_ts: nat64; -}; - -service : { - "subnet_node_metrics" : (SubnetNodeMetricsArgs) -> (SubnetNodeMetricsResult) query; - "node_rewards" : (NodeRewardsArgs) -> (vec NodeRewardsResponse) query; -} diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/lib.rs b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/lib.rs index 3dc39425c..21f66ce3f 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/lib.rs +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/lib.rs @@ -4,8 +4,8 @@ use ic_cdk_macros::*; use itertools::Itertools; use std::collections::{btree_map::Entry, BTreeMap}; use trustworthy_node_metrics_types::types::{ - NodeMetadata, NodeMetrics, NodeMetricsStored, NodeMetricsStoredKey, NodeProviderRewards, NodeProviderRewardsArgs, NodeRewards, NodeRewardsArgs, - SubnetNodeMetricsArgs, SubnetNodeMetricsResponse, + NodeMetadata, NodeMetrics, NodeMetricsStored, NodeMetricsStoredKey, NodeProviderRewards, NodeProviderRewardsArgs, NodeRewardsArgs, + NodeRewardsMultiplier, SubnetNodeMetricsArgs, SubnetNodeMetricsResponse, }; mod chrono_utils; mod computation_logger; @@ -109,11 +109,11 @@ fn nodes_metadata() -> Vec { } #[query] -fn node_rewards(args: NodeRewardsArgs) -> NodeRewards { +fn node_rewards(args: NodeRewardsArgs) -> NodeRewardsMultiplier { let rewarding_period = DateTimeRange::new(args.from_ts, args.to_ts); let node_id = args.node_id; - let rewards = rewards_manager::compute_node_rewards(vec![node_id], rewarding_period); + let rewards = rewards_manager::node_rewards_multiplier(vec![node_id], rewarding_period); rewards.into_iter().next().unwrap() } diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/rewards_manager.rs b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/rewards_manager.rs index fee2cb29d..d4ad7d3bf 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/rewards_manager.rs +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/rewards_manager.rs @@ -4,16 +4,18 @@ use ic_nns_governance_api::pb::v1::MonthlyNodeProviderRewards; use ic_protobuf::registry::node_rewards::{v2::NodeRewardRate, v2::NodeRewardsTable}; use ic_registry_keys::NODE_REWARDS_TABLE_KEY; use itertools::Itertools; -use num_traits::{FromPrimitive, ToPrimitive}; +use num_traits::{ToPrimitive, Zero}; use rust_decimal::Decimal; use rust_decimal_macros::dec; -use std::collections::{self}; -use trustworthy_node_metrics_types::types::{DailyNodeMetrics, NodeProviderRewards, NodeRewards, RewardMultiplierResult}; +use std::collections::{self, HashMap}; +use trustworthy_node_metrics_types::types::{ + DailyNodeMetrics, NodeProviderRewards, NodeProviderRewardsComputation, NodeRewardsMultiplier, RewardsMultiplier, +}; use crate::{ chrono_utils::DateTimeRange, computation_logger::{ComputationLogger, Operation, OperationExecutor}, - stable_memory, + stable_memory::{self, RegionNodeTypeCategory}, }; const MIN_FAILURE_RATE: Decimal = dec!(0.1); @@ -96,7 +98,7 @@ fn rewards_reduction_percent(failure_rate: &Decimal) -> (Vec, /// 2. The `overall_failure_rate` is calculated by dividing the `overall_failed` blocks by the `overall_total` blocks. /// 3. The `rewards_reduction` function is applied to `overall_failure_rate`. /// 3. Finally, the rewards percentage to be distrubuted to the node is computed. -fn compute_reward_multiplier(daily_metrics: &[DailyNodeMetrics], total_days: u64) -> RewardMultiplierResult { +fn compute_rewards_multiplier(daily_metrics: &[DailyNodeMetrics], total_days: u64) -> RewardsMultiplier { let mut computation_logger = ComputationLogger::new(); let total_days = computation_logger.execute("Days In Period", Operation::Set(Decimal::from(total_days))); @@ -132,7 +134,7 @@ fn compute_reward_multiplier(daily_metrics: &[DailyNodeMetrics], total_days: u64 Operation::Divide(assigned_days_factor + unassigned_days_factor, total_days), ); - RewardMultiplierResult { + RewardsMultiplier { days_assigned: days_assigned.to_u64().unwrap(), days_unassigned: days_unassigned.to_u64().unwrap(), rewards_multiplier: rewards_multiplier.to_f64().unwrap(), @@ -145,20 +147,15 @@ fn compute_reward_multiplier(daily_metrics: &[DailyNodeMetrics], total_days: u64 } } -fn get_node_rate(node_id: &Principal) -> NodeRewardRate { - let node_metadata = stable_memory::get_node_metadata(node_id).expect("Node should have one node provider"); - - match stable_memory::get_rate(&node_metadata.region, &node_metadata.node_type) { +fn get_node_rate(region: &String, node_type: &String) -> NodeRewardRate { + match stable_memory::get_rate(region, node_type) { Some(rate) => rate, None => { ic_cdk::println!( "The Node Rewards Table does not have an entry for \ - node type '{}' within region '{}' or parent region, defaulting to 1 xdr per month per node, for \ - NodeProvider '{}' on Node Operator '{}'", - node_metadata.node_type, - node_metadata.region, - node_metadata.node_provider_id, - node_metadata.node_operator_id + node type '{}' within region '{}' or parent region, defaulting to 1 xdr per month per node", + node_type, + region ); NodeRewardRate { xdr_permyriad_per_node_per_month: 1, @@ -168,17 +165,77 @@ fn get_node_rate(node_id: &Principal) -> NodeRewardRate { } } -/// Update node rewards table -pub async fn update_node_rewards_table() -> anyhow::Result<()> { - let (rewards_table, _): (NodeRewardsTable, _) = ic_nns_common::registry::get_value(NODE_REWARDS_TABLE_KEY.as_bytes(), None).await?; - for (region, rewards_rates) in rewards_table.table { - stable_memory::insert_rewards_rates(region, rewards_rates) +#[allow(unused_variables)] +fn coumpute_node_provider_rewards( + nodes_multiplier: &[NodeRewardsMultiplier], + rewardable_nodes: collections::BTreeMap, +) -> NodeProviderRewardsComputation { + let rewards_xdr_total = dec!(0); + let rewards_xdr_no_reduction_total = dec!(0); + let computation_logger = ComputationLogger::new(); + + // 1 - Compute rewards and coefficients average for all nodes type3 and type3.1 in the same region + let mut type3_coefficients: HashMap> = HashMap::new(); + let mut type3_rewards: HashMap> = HashMap::new(); + + for ((region, node_type), count) in rewardable_nodes { + if node_type.starts_with("type3") && count > 0 { + let rate = get_node_rate(®ion, &node_type); + let current_coefficients = vec![Decimal::from(rate.reward_coefficient_percent.unwrap_or(80)) / dec!(100); count as usize]; + let current_rewards = vec![Decimal::from(rate.xdr_permyriad_per_node_per_month); count as usize]; + + let region_key = region.splitn(3, ',').take(2).collect::>().join(":"); + + type3_coefficients + .entry(region_key.clone()) + .and_modify(|c| c.extend(current_coefficients.clone())) + .or_insert(current_coefficients); + type3_rewards + .entry(region_key) + .and_modify(|c| c.extend(current_rewards.clone())) + .or_insert(current_rewards); + } } + let type3_coefficients_avg: HashMap = type3_coefficients + .iter() + .map(|(key, coefficients)| { + let sum: Decimal = coefficients.iter().cloned().fold(Decimal::zero(), |acc, val| acc + val); + let avg = sum / Decimal::from(coefficients.len()); + (key.clone(), avg) + }) + .collect(); - Ok(()) + let type3_rewards_avg: HashMap = type3_rewards + .iter() + .map(|(key, rewards)| { + let sum: Decimal = rewards.iter().cloned().fold(Decimal::zero(), |acc, val| acc + val); + let avg = sum / Decimal::from(rewards.len()); + (key.clone(), avg) + }) + .collect(); + + let type3_rewards_reduced = type3_rewards.into_iter().map(|(region, individual_rewards)| { + let mut coefficient = dec!(1); + let mut rewards_reduced_by_coeff = dec!(0); + let region_coefficient_avg = type3_coefficients_avg.get(®ion).unwrap(); + let region_rewards_avg = type3_coefficients_avg.get(®ion).unwrap(); + + for _ in individual_rewards.clone() { + rewards_reduced_by_coeff += region_rewards_avg * coefficient; + coefficient *= region_coefficient_avg; + } + + let rewards_reduced_by_coeff_avg = rewards_reduced_by_coeff / Decimal::from(individual_rewards.len()); + }); + + NodeProviderRewardsComputation { + rewards_xdr: rewards_xdr_total.to_u64().unwrap(), + rewards_xdr_no_reduction: rewards_xdr_no_reduction_total.to_u64().unwrap(), + computation_log: computation_logger.get_log(), + } } -pub fn compute_node_rewards(node_ids: Vec, rewarding_period: DateTimeRange) -> Vec { +pub fn node_rewards_multiplier(node_ids: Vec, rewarding_period: DateTimeRange) -> Vec { let total_days = rewarding_period.days_between(); let nodes_metrics = stable_memory::get_metrics_range( rewarding_period.start_timestamp_nanos(), @@ -205,67 +262,28 @@ pub fn compute_node_rewards(node_ids: Vec, rewarding_period: DateTime daily_metrics .into_iter() .map(|(node_id, daily_node_metrics)| { - let rewards_computation = compute_reward_multiplier(&daily_node_metrics, total_days); - let node_rate = get_node_rate(&node_id); + let rewards_multiplier = compute_rewards_multiplier(&daily_node_metrics, total_days); + let node_metadata = stable_memory::get_node_metadata(&node_id).expect("Node should have one node provider"); + let node_rate = get_node_rate(&node_metadata.region, &node_metadata.node_type); - NodeRewards { + NodeRewardsMultiplier { node_id, daily_node_metrics, node_rate, - rewards_computation, + rewards_multiplier, } }) .collect_vec() } -pub fn coumpute_node_provider_rewards(nodes_rewards: &[NodeRewards]) -> (Decimal, Decimal) { - let mut rewards_xdr = dec!(0); - let mut rewards_xdr_no_reduction = dec!(0); - let mut coefficient = dec!(1.0); - - let nodes_rewards_xdr_sum: Decimal = nodes_rewards - .iter() - .map(|node_rewards| Decimal::from(node_rewards.node_rate.xdr_permyriad_per_node_per_month)) - .sum(); - let nodes_rewards_total: Decimal = nodes_rewards.len().into(); - let nodes_rewards_xdr_avg = nodes_rewards_xdr_sum / nodes_rewards_total; - - for node_rewards in nodes_rewards.iter() { - let metadata = stable_memory::get_node_metadata(&node_rewards.node_id).unwrap(); - - let node_xdr = match &metadata.node_type { - t if t.starts_with("type3") => { - let reward_coefficient_percent: Decimal = Decimal::from(node_rewards.node_rate.reward_coefficient_percent.unwrap_or(80)) / dec!(100); - let nodes_rewards_xdr = nodes_rewards_xdr_avg * coefficient; - - coefficient *= reward_coefficient_percent; - nodes_rewards_xdr - } - _ => node_rewards.node_rate.xdr_permyriad_per_node_per_month.into(), - }; - rewards_xdr_no_reduction += node_xdr; - rewards_xdr += node_xdr * Decimal::from_f64(node_rewards.rewards_computation.rewards_multiplier).unwrap(); - } - - (rewards_xdr_no_reduction, rewards_xdr) -} - pub fn node_provider_rewards(node_provider_id: Principal, rewarding_period: DateTimeRange) -> NodeProviderRewards { let node_ids = stable_memory::get_node_principals(&node_provider_id); - let nodes_rewards = compute_node_rewards(node_ids, rewarding_period) - .into_iter() - .sorted_by(|a, b| { - Ord::cmp( - &b.node_rate.xdr_permyriad_per_node_per_month, - &a.node_rate.xdr_permyriad_per_node_per_month, - ) - }) - .collect_vec(); - let (rewards_xdr_no_reduction, rewards_xdr) = coumpute_node_provider_rewards(&nodes_rewards); - + let rewardable_nodes: collections::BTreeMap = stable_memory::get_rewardable_nodes(&node_provider_id); let latest_np_rewards = stable_memory::get_latest_node_providers_rewards(); - let ts_distribution = latest_np_rewards.timestamp; - let xdr_conversion_rate = latest_np_rewards.xdr_conversion_rate.and_then(|rate| rate.xdr_permyriad_per_icp); + + let nodes_multiplier = node_rewards_multiplier(node_ids, rewarding_period); + let rewards_computation = coumpute_node_provider_rewards(&nodes_multiplier, rewardable_nodes); + let rewards_xdr_old = latest_np_rewards .rewards .into_iter() @@ -283,15 +301,27 @@ pub fn node_provider_rewards(node_provider_id: Principal, rewarding_period: Date NodeProviderRewards { node_provider_id, - rewards_xdr: rewards_xdr.to_u64().unwrap(), - rewards_xdr_no_reduction: rewards_xdr_no_reduction.to_u64().unwrap(), + rewards_xdr: rewards_computation.rewards_xdr, + rewards_xdr_no_reduction: rewards_computation.rewards_xdr_no_reduction, + computation_log: rewards_computation.computation_log, rewards_xdr_old, - ts_distribution, - xdr_conversion_rate, - nodes_rewards, + ts_distribution: latest_np_rewards.timestamp, + xdr_conversion_rate: latest_np_rewards.xdr_conversion_rate.and_then(|rate| rate.xdr_permyriad_per_icp), + nodes_rewards: nodes_multiplier, } } +/// Update node rewards table +pub async fn update_node_rewards_table() -> anyhow::Result<()> { + let (rewards_table, _): (NodeRewardsTable, _) = ic_nns_common::registry::get_value(NODE_REWARDS_TABLE_KEY.as_bytes(), None).await?; + for (region, rewards_rates) in rewards_table.table { + stable_memory::insert_rewards_rates(region, rewards_rates) + } + + Ok(()) +} + +/// Update recent node providers rewards pub async fn update_recent_provider_rewards() -> anyhow::Result<()> { let (maybe_monthly_rewards,): (Option,) = ic_cdk::api::call::call( Principal::from(GOVERNANCE_CANISTER_ID), @@ -361,7 +391,7 @@ mod tests { fn test_rewards_percent() { // Overall failed = 130 Overall total = 500 Failure rate = 0.26 let daily_metrics: Vec = daily_mocked_metrics(vec![MockedMetrics::new(20, 6, 4), MockedMetrics::new(25, 10, 2)]); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 0.744); // Overall failed = 45 Overall total = 450 Failure rate = 0.1 @@ -370,14 +400,14 @@ mod tests { MockedMetrics::new(1, 400, 20), MockedMetrics::new(1, 5, 25), // no penalty ]); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 1.0); // Overall failed = 5 Overall total = 10 Failure rate = 0.5 let daily_metrics: Vec = daily_mocked_metrics(vec![ MockedMetrics::new(1, 5, 5), // no penalty ]); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 0.36); } @@ -386,7 +416,7 @@ mod tests { let daily_metrics: Vec = daily_mocked_metrics(vec![ MockedMetrics::new(10, 5, 95), // max failure rate ]); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 0.2); } @@ -395,7 +425,7 @@ mod tests { let daily_metrics: Vec = daily_mocked_metrics(vec![ MockedMetrics::new(10, 9, 1), // min failure rate ]); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 1.0); } @@ -413,17 +443,17 @@ mod tests { daily_mocked_metrics(vec![gap.clone(), MockedMetrics::new(1, 6, 4), MockedMetrics::new(1, 7, 3)]); assert_eq!( - compute_reward_multiplier(&daily_metrics_mid_gap, daily_metrics_mid_gap.len() as u64).rewards_multiplier, + compute_rewards_multiplier(&daily_metrics_mid_gap, daily_metrics_mid_gap.len() as u64).rewards_multiplier, 0.7866666666666666 ); assert_eq!( - compute_reward_multiplier(&daily_metrics_mid_gap, daily_metrics_mid_gap.len() as u64).rewards_multiplier, - compute_reward_multiplier(&daily_metrics_left_gap, daily_metrics_left_gap.len() as u64).rewards_multiplier + compute_rewards_multiplier(&daily_metrics_mid_gap, daily_metrics_mid_gap.len() as u64).rewards_multiplier, + compute_rewards_multiplier(&daily_metrics_left_gap, daily_metrics_left_gap.len() as u64).rewards_multiplier ); assert_eq!( - compute_reward_multiplier(&daily_metrics_right_gap, daily_metrics_right_gap.len() as u64).rewards_multiplier, - compute_reward_multiplier(&daily_metrics_left_gap, daily_metrics_left_gap.len() as u64).rewards_multiplier + compute_rewards_multiplier(&daily_metrics_right_gap, daily_metrics_right_gap.len() as u64).rewards_multiplier, + compute_rewards_multiplier(&daily_metrics_left_gap, daily_metrics_left_gap.len() as u64).rewards_multiplier ); } @@ -436,9 +466,9 @@ mod tests { ]); let mut daily_metrics = daily_metrics.clone(); - let result = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); daily_metrics.reverse(); - let result_rev = compute_reward_multiplier(&daily_metrics, daily_metrics.len() as u64); + let result_rev = compute_rewards_multiplier(&daily_metrics, daily_metrics.len() as u64); assert_eq!(result.rewards_multiplier, 1.0); assert_eq!(result_rev.rewards_multiplier, result.rewards_multiplier); diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/stable_memory.rs b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/stable_memory.rs index 30b493941..50f890c39 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/stable_memory.rs +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/src/stable_memory.rs @@ -9,10 +9,11 @@ use std::collections::BTreeMap; use trustworthy_node_metrics_types::types::{ MonthlyNodeProviderRewardsStored, NodeMetadata, NodeMetadataStored, NodeMetadataStoredV2, NodeMetricsStored, NodeMetricsStoredKey, - NodeRewardRatesStored, TimestampNanos, + NodeProviderRewardableKey, NodeRewardRatesStored, TimestampNanos, }; type Memory = VirtualMemory; +pub type RegionNodeTypeCategory = (String, String); thread_local! { static MEMORY_MANAGER: RefCell> = @@ -54,6 +55,12 @@ thread_local! { RefCell::new(StableBTreeMap::init( MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(6))) )); + + static NP_REWARDABLE_NODES: RefCell> = + RefCell::new(StableBTreeMap::init( + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(7))) + )); + } pub fn insert_node_metrics(key: NodeMetricsStoredKey, value: NodeMetricsStored) { @@ -214,3 +221,18 @@ pub fn insert_node_provider_rewards(timestamp: u64, monthly_node_provider_reward pub fn get_latest_node_providers_rewards() -> MonthlyNodeProviderRewards { MONTHLY_NP_REWARDS.with_borrow(|p| p.last_key_value().map(|(_, v)| v.monthly_node_provider_rewards).unwrap()) } + +pub fn get_rewardable_nodes(node_provider_id: &Principal) -> BTreeMap { + NP_REWARDABLE_NODES.with_borrow(|rewardable| { + rewardable + .iter() + .filter_map(|(key, value)| { + if &key.node_provider_id == node_provider_id { + Some(((key.region, key.node_type), value)) + } else { + None + } + }) + .collect() + }) +} diff --git a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/trustworthy-node-metrics.did b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/trustworthy-node-metrics.did index 2966dc3b4..a2b622728 100644 --- a/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/trustworthy-node-metrics.did +++ b/rs/dre-canisters/trustworthy-node-metrics/src/trustworthy-node-metrics/trustworthy-node-metrics.did @@ -35,7 +35,7 @@ type OperationExecutorLog = record { result: text; }; -type RewardMultiplierResult = record { +type RewardsMultiplier = record { days_assigned: nat64; days_unassigned: nat64; rewards_multiplier: float64; @@ -52,11 +52,11 @@ type NodeRewardRate = record { reward_coefficient_percent: opt int32; }; -type NodeRewards = record { +type NodeRewardsMultiplier = record { node_id: principal; daily_node_metrics: vec DailyNodeMetrics; node_rate: NodeRewardRate; - rewards_computation: RewardMultiplierResult; + rewards_multiplier: RewardsMultiplier; }; type NodeRewardsArgs = record { @@ -69,10 +69,11 @@ type NodeProviderRewards = record { node_provider_id: principal; rewards_xdr: nat64; rewards_xdr_no_reduction: nat64; + computation_log: vec OperationExecutorLog; rewards_xdr_old: opt nat64; ts_distribution: nat64; xdr_conversion_rate: opt nat64; - nodes_rewards: vec NodeRewards; + nodes_rewards: vec NodeRewardsMultiplier; }; type NodeProviderRewardsArgs = record { @@ -97,7 +98,7 @@ type NodeMetadata = record { service : { "subnet_node_metrics" : (SubnetNodeMetricsArgs) -> (SubnetNodeMetricsResult) query; - "node_rewards" : (NodeRewardsArgs) -> (NodeRewards) query; + "node_rewards" : (NodeRewardsArgs) -> (NodeRewardsMultiplier) query; "node_provider_rewards" : (NodeProviderRewardsArgs) -> (NodeProviderRewards) query; "node_ids" : () -> (vec principal) query; "nodes_metadata": () -> (vec NodeMetadata) query;