Add simulated rewarding system foundation
Implement foundation for simulated reward calculations to compare old (24h cache-based) vs new (1h route-based) methodologies without blockchain transactions. Database Changes: - Add migration with 4 new tables for simulation system - simulated_reward_epochs: tracks each simulation run - simulated_node_performance: stores performance calculations - simulated_rewards: stores reward calculation results - simulated_route_analysis: metadata for route analysis - Add comprehensive indexes for efficient querying Configuration Changes: - Add simulation_mode flag to rewarding configuration - Add CLI flag --simulate-rewarding with proper dependencies/conflicts - Add validation for simulation-specific settings - Add time window configuration for new method (default: 1 hour) Storage Layer: - Add model structs with SQLx FromRow derives for all simulation tables - Add comprehensive CRUD methods for simulation data management - Add proper type annotations to fix SQLx compile issues - Maintain separation between simulation and real rewarding logic The simulation mode is mutually exclusive with real rewarding and does not require mnemonic since no blockchain transactions are performed.
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", identity_key, reliability_score as \"reliability_score!\", \n positive_samples as \"positive_samples!: u32\", negative_samples as \"negative_samples!: u32\", \n final_fail_sequence as \"final_fail_sequence!: u32\", work_factor, \n calculation_method as \"calculation_method!\", calculated_at as \"calculated_at!\"\n FROM simulated_node_performance\n WHERE simulated_epoch_id = ? AND calculation_method = ?\n ORDER BY node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "final_fail_sequence!: u32",
|
||||
"ordinal": 8,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 9,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2b9270a8c0c8c9571a6a0f5a0a8137cb12c8eed76e44fa4a3dd99b7f0778156d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_reward_epochs \n (epoch_id, calculation_method, start_timestamp, end_timestamp, description)\n VALUES (?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2bb062a25129bfc6b060f68592e74aa91d8c82b8b912009fce14f9c19fd4464c"
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", calculated_reward_amount as \"calculated_reward_amount!\", \n reward_currency as \"reward_currency!\", performance_component as \"performance_component!\", \n work_component as \"work_component!\", calculation_method as \"calculation_method!\", \n calculated_at as \"calculated_at!\"\n FROM simulated_rewards\n WHERE simulated_epoch_id = ? AND calculation_method = ?\n ORDER BY node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_reward_amount!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "reward_currency!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_component!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "work_component!",
|
||||
"ordinal": 7,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "56749b2601818f11c53320695bd43fb58124cb2763943c722a74d0f6f84e5ed1"
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", calculated_reward_amount as \"calculated_reward_amount!\", \n reward_currency as \"reward_currency!\", performance_component as \"performance_component!\", \n work_component as \"work_component!\", calculation_method as \"calculation_method!\", \n calculated_at as \"calculated_at!\"\n FROM simulated_rewards\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method, node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_reward_amount!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "reward_currency!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "performance_component!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "work_component!",
|
||||
"ordinal": 7,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9adb9f15d87374f79b8d046be615ff0e3d86f9ccca5b6b4de60fd9fdea33d9ff"
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", epoch_id as \"epoch_id!: u32\", calculation_method as \"calculation_method!\", \n start_timestamp as \"start_timestamp!\", end_timestamp as \"end_timestamp!\", \n description, created_at as \"created_at!\"\n FROM simulated_reward_epochs\n ORDER BY created_at DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "epoch_id!: u32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "start_timestamp!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "end_timestamp!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at!",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ba98102a1fdd68f3086b7dc7469fd747b213c7e399ca022437ac3ed7eb200f17"
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id as \"id!\", simulated_epoch_id as \"simulated_epoch_id!\", node_id as \"node_id!: NodeId\", \n node_type as \"node_type!\", identity_key, reliability_score as \"reliability_score!\", \n positive_samples as \"positive_samples!: u32\", negative_samples as \"negative_samples!: u32\", \n final_fail_sequence as \"final_fail_sequence!: u32\", work_factor, \n calculation_method as \"calculation_method!\", calculated_at as \"calculated_at!\"\n FROM simulated_node_performance\n WHERE simulated_epoch_id = ?\n ORDER BY calculation_method, node_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "simulated_epoch_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_id!: NodeId",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "node_type!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "identity_key",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "reliability_score!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "positive_samples!: u32",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "negative_samples!: u32",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "final_fail_sequence!: u32",
|
||||
"ordinal": 8,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "work_factor",
|
||||
"ordinal": 9,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calculation_method!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "calculated_at!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d039ccbd1b4b60138b3df6e0774e1f9d3ee234024b1bc4fbf6163ee67e31bc35"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_rewards\n (simulated_epoch_id, node_id, node_type, calculated_reward_amount, reward_currency,\n performance_component, work_component, calculation_method)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dca18e13c50e360f88ba5ea46fd60b8ff1895161fef17c291f9bf5217c457f74"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_route_analysis\n (simulated_epoch_id, calculation_method, total_routes_analyzed, successful_routes,\n failed_routes, average_route_reliability, time_window_hours, analysis_parameters)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "df9013912c0e4177625267776656cb139cf3ac8dd4cd4c5146005b055aefd228"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO simulated_node_performance\n (simulated_epoch_id, node_id, node_type, identity_key, reliability_score,\n positive_samples, negative_samples, final_fail_sequence, work_factor, calculation_method)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 10
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ec4d3491a6f46fb187443493dc483eac678a50351433f4e6d7f2aee172c7df44"
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
-- Migration: Add simulated rewarding system tables
|
||||
-- This migration adds support for simulated reward calculations to compare
|
||||
-- old (24h cache-based) vs new (1h route-based) rewarding methodologies
|
||||
|
||||
-- Simulated reward epochs track each simulation run
|
||||
CREATE TABLE IF NOT EXISTS simulated_reward_epochs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new', 'comparison')),
|
||||
start_timestamp INTEGER NOT NULL,
|
||||
end_timestamp INTEGER NOT NULL,
|
||||
description TEXT,
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
|
||||
-- Node performance data calculated from different methodologies
|
||||
CREATE TABLE IF NOT EXISTS simulated_node_performance (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
node_type TEXT NOT NULL CHECK (node_type IN ('mixnode', 'gateway')),
|
||||
identity_key TEXT,
|
||||
reliability_score REAL NOT NULL CHECK (reliability_score >= 0.0 AND reliability_score <= 100.0),
|
||||
positive_samples INTEGER NOT NULL DEFAULT 0,
|
||||
negative_samples INTEGER NOT NULL DEFAULT 0,
|
||||
final_fail_sequence INTEGER NOT NULL DEFAULT 0,
|
||||
work_factor REAL CHECK (work_factor >= 0.0 AND work_factor <= 1.0),
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new')),
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Simulated reward calculations with full breakdown
|
||||
CREATE TABLE IF NOT EXISTS simulated_rewards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
node_id INTEGER NOT NULL,
|
||||
node_type TEXT NOT NULL CHECK (node_type IN ('mixnode', 'gateway')),
|
||||
calculated_reward_amount REAL NOT NULL CHECK (calculated_reward_amount >= 0.0),
|
||||
reward_currency TEXT NOT NULL DEFAULT 'nym',
|
||||
performance_component REAL NOT NULL CHECK (performance_component >= 0.0 AND performance_component <= 100.0),
|
||||
work_component REAL NOT NULL CHECK (work_component >= 0.0 AND work_component <= 1.0),
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new')),
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Route analysis metadata for each simulation run
|
||||
CREATE TABLE IF NOT EXISTS simulated_route_analysis (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
simulated_epoch_id INTEGER NOT NULL,
|
||||
calculation_method TEXT NOT NULL CHECK (calculation_method IN ('old', 'new')),
|
||||
total_routes_analyzed INTEGER NOT NULL DEFAULT 0,
|
||||
successful_routes INTEGER NOT NULL DEFAULT 0,
|
||||
failed_routes INTEGER NOT NULL DEFAULT 0,
|
||||
average_route_reliability REAL CHECK (average_route_reliability >= 0.0 AND average_route_reliability <= 100.0),
|
||||
time_window_hours INTEGER NOT NULL DEFAULT 1, -- New method uses 1 hour, old uses 24
|
||||
analysis_parameters TEXT, -- JSON with additional analysis configuration
|
||||
calculated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
FOREIGN KEY (simulated_epoch_id) REFERENCES simulated_reward_epochs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Indexes for efficient querying
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_epoch_id ON simulated_reward_epochs(epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_calculation_method ON simulated_reward_epochs(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_reward_epochs_created_at ON simulated_reward_epochs(created_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_simulated_epoch_id ON simulated_node_performance(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_node_id ON simulated_node_performance(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_calculation_method ON simulated_node_performance(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_node_performance_node_type ON simulated_node_performance(node_type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_rewards_simulated_epoch_id ON simulated_rewards(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_rewards_node_id ON simulated_rewards(node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_rewards_calculation_method ON simulated_rewards(calculation_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_rewards_node_type ON simulated_rewards(node_type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_route_analysis_simulated_epoch_id ON simulated_route_analysis(simulated_epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_simulated_route_analysis_calculation_method ON simulated_route_analysis(calculation_method);
|
||||
@@ -61,10 +61,22 @@ pub(crate) struct Args {
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
requires = "mnemonic",
|
||||
conflicts_with = "simulate_rewarding",
|
||||
env = "NYMAPI_ENABLE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Enable simulated rewarding mode to compare old vs new reward calculations
|
||||
/// Requires monitoring but does NOT require mnemonic (no blockchain transactions)
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(
|
||||
long,
|
||||
requires = "enable_monitor",
|
||||
conflicts_with = "enable_rewarding",
|
||||
env = "NYMAPI_SIMULATE_REWARDING_ARG"
|
||||
)]
|
||||
pub(crate) simulate_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long, env = "NYMAPI_NYXD_VALIDATOR_ARG")]
|
||||
|
||||
@@ -136,14 +136,30 @@ impl Config {
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
let can_sign = self.base.mnemonic.is_some();
|
||||
|
||||
if !can_sign && self.rewarding.enabled {
|
||||
// Real rewarding requires mnemonic, but simulation mode does not
|
||||
if !can_sign && self.rewarding.enabled && !self.rewarding.simulation_mode {
|
||||
bail!("can't enable rewarding without providing a mnemonic")
|
||||
}
|
||||
|
||||
// Simulation mode and real rewarding are mutually exclusive
|
||||
if self.rewarding.enabled && self.rewarding.simulation_mode {
|
||||
bail!("cannot enable both real rewarding and simulation mode simultaneously")
|
||||
}
|
||||
|
||||
if !can_sign && self.ecash_signer.enabled {
|
||||
bail!("can't enable coconut signer without providing a mnemonic")
|
||||
}
|
||||
|
||||
// Validate simulation-specific settings
|
||||
if self.rewarding.simulation_mode {
|
||||
if self.rewarding.debug.simulation_new_method_time_window_hours == 0 {
|
||||
bail!("simulation_new_method_time_window_hours must be greater than 0")
|
||||
}
|
||||
if self.rewarding.debug.simulation_new_method_time_window_hours > 24 {
|
||||
bail!("simulation_new_method_time_window_hours should not exceed 24 hours")
|
||||
}
|
||||
}
|
||||
|
||||
self.ecash_signer.validate()?;
|
||||
|
||||
Ok(())
|
||||
@@ -158,6 +174,9 @@ impl Config {
|
||||
if let Some(enable_rewarding) = args.enable_rewarding {
|
||||
self.rewarding.enabled = enable_rewarding;
|
||||
}
|
||||
if let Some(simulate_rewarding) = args.simulate_rewarding {
|
||||
self.rewarding.simulation_mode = simulate_rewarding;
|
||||
}
|
||||
if let Some(nyxd_upstream) = args.nyxd_validator {
|
||||
self.base.local_validator = nyxd_upstream;
|
||||
}
|
||||
@@ -499,6 +518,9 @@ pub struct Rewarding {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Specifies whether to run in simulation mode (compare old vs new calculations without blockchain transactions)
|
||||
pub simulation_mode: bool,
|
||||
|
||||
// this should really be a thing too...
|
||||
// pub paths: RewardingPathfinder,
|
||||
#[serde(default)]
|
||||
@@ -510,6 +532,7 @@ impl Default for Rewarding {
|
||||
fn default() -> Self {
|
||||
Rewarding {
|
||||
enabled: false,
|
||||
simulation_mode: false,
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -522,12 +545,21 @@ pub struct RewardingDebug {
|
||||
/// distribute rewards for given interval.
|
||||
/// Note, only values in range 0-100 are valid
|
||||
pub minimum_interval_monitor_threshold: u8,
|
||||
|
||||
/// Time window in hours for new route-based performance calculation (default: 1 hour)
|
||||
/// Old method always uses 24 hours for comparison
|
||||
pub simulation_new_method_time_window_hours: u32,
|
||||
|
||||
/// Whether to run both old and new calculations in simulation mode
|
||||
pub simulation_run_both_methods: bool,
|
||||
}
|
||||
|
||||
impl Default for RewardingDebug {
|
||||
fn default() -> Self {
|
||||
RewardingDebug {
|
||||
minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD,
|
||||
simulation_new_method_time_window_hours: 1, // New method uses 1 hour
|
||||
simulation_run_both_methods: true, // Default to running both for comparison
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ pub(crate) struct OverrideConfig {
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Specifies whether simulated rewarding is enabled on this API
|
||||
pub(crate) simulate_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
@@ -40,6 +43,7 @@ impl From<init::Args> for OverrideConfig {
|
||||
OverrideConfig {
|
||||
enable_monitor: Some(args.enable_monitor),
|
||||
enable_rewarding: Some(args.enable_rewarding),
|
||||
simulate_rewarding: None, // Not available during initialization
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: Some(args.enable_zk_nym),
|
||||
@@ -56,6 +60,7 @@ impl From<run::Args> for OverrideConfig {
|
||||
OverrideConfig {
|
||||
enable_monitor: args.enable_monitor,
|
||||
enable_rewarding: args.enable_rewarding,
|
||||
simulate_rewarding: args.simulate_rewarding,
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: args.enable_zk_nym,
|
||||
|
||||
@@ -1529,6 +1529,7 @@ impl StorageManager {
|
||||
}
|
||||
|
||||
pub(crate) mod v3_migration {
|
||||
use crate::storage::models::{SimulatedNodePerformance, SimulatedReward, SimulatedRewardEpoch, SimulatedRouteAnalysis};
|
||||
use crate::support::storage::manager::StorageManager;
|
||||
use crate::support::storage::models::GatewayDetailsBeforeMigration;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
@@ -1608,5 +1609,235 @@ pub(crate) mod v3_migration {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Simulated Rewarding System CRUD Methods
|
||||
|
||||
/// Creates a new simulated reward epoch and returns its ID
|
||||
pub(crate) async fn create_simulated_reward_epoch(
|
||||
&self,
|
||||
epoch_id: u32,
|
||||
calculation_method: &str,
|
||||
start_timestamp: i64,
|
||||
end_timestamp: i64,
|
||||
description: Option<&str>,
|
||||
) -> Result<i64, sqlx::Error> {
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO simulated_reward_epochs
|
||||
(epoch_id, calculation_method, start_timestamp, end_timestamp, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
epoch_id,
|
||||
calculation_method,
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
description,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(result.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// Inserts simulated node performance data
|
||||
pub(crate) async fn insert_simulated_node_performance(
|
||||
&self,
|
||||
performance_data: &[SimulatedNodePerformance],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let mut tx = self.connection_pool.begin().await?;
|
||||
|
||||
for performance in performance_data {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO simulated_node_performance
|
||||
(simulated_epoch_id, node_id, node_type, identity_key, reliability_score,
|
||||
positive_samples, negative_samples, final_fail_sequence, work_factor, calculation_method)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
performance.simulated_epoch_id,
|
||||
performance.node_id,
|
||||
performance.node_type,
|
||||
performance.identity_key,
|
||||
performance.reliability_score,
|
||||
performance.positive_samples,
|
||||
performance.negative_samples,
|
||||
performance.final_fail_sequence,
|
||||
performance.work_factor,
|
||||
performance.calculation_method,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
/// Inserts simulated reward calculation results
|
||||
pub(crate) async fn insert_simulated_rewards(
|
||||
&self,
|
||||
reward_data: &[SimulatedReward],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let mut tx = self.connection_pool.begin().await?;
|
||||
|
||||
for reward in reward_data {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO simulated_rewards
|
||||
(simulated_epoch_id, node_id, node_type, calculated_reward_amount, reward_currency,
|
||||
performance_component, work_component, calculation_method)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
reward.simulated_epoch_id,
|
||||
reward.node_id,
|
||||
reward.node_type,
|
||||
reward.calculated_reward_amount,
|
||||
reward.reward_currency,
|
||||
reward.performance_component,
|
||||
reward.work_component,
|
||||
reward.calculation_method,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
/// Inserts route analysis metadata for a simulation
|
||||
pub(crate) async fn insert_simulated_route_analysis(
|
||||
&self,
|
||||
analysis: &SimulatedRouteAnalysis,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO simulated_route_analysis
|
||||
(simulated_epoch_id, calculation_method, total_routes_analyzed, successful_routes,
|
||||
failed_routes, average_route_reliability, time_window_hours, analysis_parameters)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
analysis.simulated_epoch_id,
|
||||
analysis.calculation_method,
|
||||
analysis.total_routes_analyzed,
|
||||
analysis.successful_routes,
|
||||
analysis.failed_routes,
|
||||
analysis.average_route_reliability,
|
||||
analysis.time_window_hours,
|
||||
analysis.analysis_parameters,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieves all simulated reward epochs
|
||||
pub(crate) async fn get_simulated_reward_epochs(&self) -> Result<Vec<SimulatedRewardEpoch>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
SimulatedRewardEpoch,
|
||||
r#"
|
||||
SELECT id as "id!", epoch_id as "epoch_id!: u32", calculation_method as "calculation_method!",
|
||||
start_timestamp as "start_timestamp!", end_timestamp as "end_timestamp!",
|
||||
description, created_at as "created_at!"
|
||||
FROM simulated_reward_epochs
|
||||
ORDER BY created_at DESC
|
||||
"#
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieves node performance data for a specific simulation
|
||||
pub(crate) async fn get_simulated_node_performance(
|
||||
&self,
|
||||
simulated_epoch_id: i64,
|
||||
calculation_method: Option<&str>,
|
||||
) -> Result<Vec<SimulatedNodePerformance>, sqlx::Error> {
|
||||
match calculation_method {
|
||||
Some(method) => {
|
||||
sqlx::query_as!(
|
||||
SimulatedNodePerformance,
|
||||
r#"
|
||||
SELECT id as "id!", simulated_epoch_id as "simulated_epoch_id!", node_id as "node_id!: NodeId",
|
||||
node_type as "node_type!", identity_key, reliability_score as "reliability_score!",
|
||||
positive_samples as "positive_samples!: u32", negative_samples as "negative_samples!: u32",
|
||||
final_fail_sequence as "final_fail_sequence!: u32", work_factor,
|
||||
calculation_method as "calculation_method!", calculated_at as "calculated_at!"
|
||||
FROM simulated_node_performance
|
||||
WHERE simulated_epoch_id = ? AND calculation_method = ?
|
||||
ORDER BY node_id
|
||||
"#,
|
||||
simulated_epoch_id,
|
||||
method
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
},
|
||||
None => {
|
||||
sqlx::query_as!(
|
||||
SimulatedNodePerformance,
|
||||
r#"
|
||||
SELECT id as "id!", simulated_epoch_id as "simulated_epoch_id!", node_id as "node_id!: NodeId",
|
||||
node_type as "node_type!", identity_key, reliability_score as "reliability_score!",
|
||||
positive_samples as "positive_samples!: u32", negative_samples as "negative_samples!: u32",
|
||||
final_fail_sequence as "final_fail_sequence!: u32", work_factor,
|
||||
calculation_method as "calculation_method!", calculated_at as "calculated_at!"
|
||||
FROM simulated_node_performance
|
||||
WHERE simulated_epoch_id = ?
|
||||
ORDER BY calculation_method, node_id
|
||||
"#,
|
||||
simulated_epoch_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves simulated rewards for a specific simulation
|
||||
pub(crate) async fn get_simulated_rewards(
|
||||
&self,
|
||||
simulated_epoch_id: i64,
|
||||
calculation_method: Option<&str>,
|
||||
) -> Result<Vec<SimulatedReward>, sqlx::Error> {
|
||||
match calculation_method {
|
||||
Some(method) => {
|
||||
sqlx::query_as!(
|
||||
SimulatedReward,
|
||||
r#"
|
||||
SELECT id as "id!", simulated_epoch_id as "simulated_epoch_id!", node_id as "node_id!: NodeId",
|
||||
node_type as "node_type!", calculated_reward_amount as "calculated_reward_amount!",
|
||||
reward_currency as "reward_currency!", performance_component as "performance_component!",
|
||||
work_component as "work_component!", calculation_method as "calculation_method!",
|
||||
calculated_at as "calculated_at!"
|
||||
FROM simulated_rewards
|
||||
WHERE simulated_epoch_id = ? AND calculation_method = ?
|
||||
ORDER BY node_id
|
||||
"#,
|
||||
simulated_epoch_id,
|
||||
method
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
},
|
||||
None => {
|
||||
sqlx::query_as!(
|
||||
SimulatedReward,
|
||||
r#"
|
||||
SELECT id as "id!", simulated_epoch_id as "simulated_epoch_id!", node_id as "node_id!: NodeId",
|
||||
node_type as "node_type!", calculated_reward_amount as "calculated_reward_amount!",
|
||||
reward_currency as "reward_currency!", performance_component as "performance_component!",
|
||||
work_component as "work_component!", calculation_method as "calculation_method!",
|
||||
calculated_at as "calculated_at!"
|
||||
FROM simulated_rewards
|
||||
WHERE simulated_epoch_id = ?
|
||||
ORDER BY calculation_method, node_id
|
||||
"#,
|
||||
simulated_epoch_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,3 +146,65 @@ pub struct HistoricalUptime {
|
||||
pub date: Date,
|
||||
pub uptime: i64,
|
||||
}
|
||||
|
||||
// Simulated Rewarding System Models
|
||||
// These models support comparison between old (24h cache-based) and new (1h route-based) rewarding
|
||||
|
||||
/// Represents a simulated reward epoch run
|
||||
#[derive(FromRow, Debug, Clone)]
|
||||
pub struct SimulatedRewardEpoch {
|
||||
pub id: i64,
|
||||
pub epoch_id: u32,
|
||||
pub calculation_method: String, // 'old', 'new', or 'comparison'
|
||||
pub start_timestamp: i64,
|
||||
pub end_timestamp: i64,
|
||||
pub description: Option<String>,
|
||||
pub created_at: i64,
|
||||
}
|
||||
|
||||
/// Node performance calculated using different methodologies
|
||||
#[derive(FromRow, Debug, Clone)]
|
||||
pub struct SimulatedNodePerformance {
|
||||
pub id: i64,
|
||||
pub simulated_epoch_id: i64,
|
||||
pub node_id: NodeId,
|
||||
pub node_type: String, // 'mixnode' or 'gateway'
|
||||
pub identity_key: Option<String>,
|
||||
pub reliability_score: f64, // 0.0 to 100.0
|
||||
pub positive_samples: u32,
|
||||
pub negative_samples: u32,
|
||||
pub final_fail_sequence: u32,
|
||||
pub work_factor: Option<f64>, // 0.0 to 1.0
|
||||
pub calculation_method: String, // 'old' or 'new'
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
/// Simulated reward calculation results
|
||||
#[derive(FromRow, Debug, Clone)]
|
||||
pub struct SimulatedReward {
|
||||
pub id: i64,
|
||||
pub simulated_epoch_id: i64,
|
||||
pub node_id: NodeId,
|
||||
pub node_type: String, // 'mixnode' or 'gateway'
|
||||
pub calculated_reward_amount: f64,
|
||||
pub reward_currency: String, // typically 'nym'
|
||||
pub performance_component: f64, // 0.0 to 100.0
|
||||
pub work_component: f64, // 0.0 to 1.0
|
||||
pub calculation_method: String, // 'old' or 'new'
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
/// Route analysis metadata for simulation runs
|
||||
#[derive(FromRow, Debug, Clone)]
|
||||
pub struct SimulatedRouteAnalysis {
|
||||
pub id: i64,
|
||||
pub simulated_epoch_id: i64,
|
||||
pub calculation_method: String, // 'old' or 'new'
|
||||
pub total_routes_analyzed: u32,
|
||||
pub successful_routes: u32,
|
||||
pub failed_routes: u32,
|
||||
pub average_route_reliability: Option<f64>, // 0.0 to 100.0
|
||||
pub time_window_hours: u32, // 1 for new method, 24 for old method
|
||||
pub analysis_parameters: Option<String>, // JSON with analysis config
|
||||
pub calculated_at: i64,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user