From 8e93b621977f0d813650cb7353f0312ad3e60d9d Mon Sep 17 00:00:00 2001 From: qima Date: Wed, 4 Sep 2024 17:27:54 +0800 Subject: [PATCH] chore: refactor store_cost calculation --- sn_networking/src/record_store.rs | 76 +++++++++++++++---------------- sn_transfers/src/genesis.rs | 10 +++- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/sn_networking/src/record_store.rs b/sn_networking/src/record_store.rs index a39a72f7a9..649f934dea 100644 --- a/sn_networking/src/record_store.rs +++ b/sn_networking/src/record_store.rs @@ -9,9 +9,9 @@ use crate::cmd::LocalSwarmCmd; use crate::driver::MAX_PACKET_SIZE; +use crate::send_local_swarm_cmd; use crate::target_arch::{spawn, Instant}; use crate::{event::NetworkEvent, log_markers::Marker}; -use crate::{send_local_swarm_cmd, CLOSE_GROUP_SIZE}; use aes_gcm_siv::{ aead::{Aead, KeyInit, OsRng}, Aes256GcmSiv, Nonce, @@ -34,7 +34,7 @@ use sn_protocol::{ storage::{RecordHeader, RecordKind, RecordType}, NetworkAddress, PrettyPrintRecordKey, }; -use sn_transfers::{NanoTokens, QuotingMetrics, TOTAL_SUPPLY}; +use sn_transfers::{NanoTokens, QuotingMetrics}; use std::collections::VecDeque; use std::{ borrow::Cow, @@ -62,6 +62,12 @@ const MAX_RECORDS_CACHE_SIZE: usize = 100; /// File name of the recorded historical quoting metrics. const HISTORICAL_QUOTING_METRICS_FILENAME: &str = "historic_quoting_metrics"; +/// Max store cost for a chunk. +const MAX_STORE_COST: u64 = 1_000_000; + +// Min store cost for a chunk. +const MIN_STORE_COST: u64 = 1; + /// A `RecordStore` that stores records on disk. pub struct NodeRecordStore { /// The identity of the peer owning the store. @@ -937,35 +943,27 @@ pub fn calculate_cost_for_records(quoting_metrics: &QuotingMetrics) -> u64 { let max_records = quoting_metrics.max_records; let live_time = quoting_metrics.live_time; - let ori_cost = (10 * records_stored) as u64; - let divider = max(1, records_stored / max(1, received_payment_count)) as u64; + let ori_cost = positive_input_0_1_sigmoid(records_stored as f64 / max_records as f64) + * MAX_STORE_COST as f64; + + let divider = max(1, records_stored / max(1, received_payment_count)); // Gaining one step for every day that staying in the network let reward_steps: u64 = live_time / (24 * 3600); - let base_multiplier = 1.1_f32; - let rewarder = max(1, base_multiplier.powf(reward_steps as f32) as u64); - - // Fine tuning here helps to get a desired curve: - // 1, Close to the max supply (4.3E+18) when stored records reaching full. - // 2, Charging around `token`s near the situation of 80% storage reached. - // - // 1.02.powf(1638) = 1.25E+14 => charge_at_full = 10 * MAX_RECORDS * 1.25E+14 = 5.4E+18 - // 1.02.powf(820) = 1.1E+7 => charge_at_80_percent = 10 * 0.8 * MAX_RECORDS * 1.1E+7 = 3.6E+11 (360 tokens) - let base_multiplier = 1.0225_f32; - - // Given currently the max_records is set at MAX_RECORDS, - // hence setting the multiplier trigger at 60% of the max_records - let exponential_pricing_trigger = 6 * max_records / 10; - - let multiplier = max( - 1, - base_multiplier.powf(records_stored.saturating_sub(exponential_pricing_trigger) as f32) - as u64, - ); + let base_multiplier = 1.01_f64; + let rewarder = max(1, base_multiplier.powf(reward_steps as f64) as u64); - let charge = max(10, ori_cost.saturating_mul(multiplier) / divider / rewarder); + // Deploy a lower cap safe_guard to the store_cost + let charge = max( + MIN_STORE_COST, + (ori_cost / divider as f64 / rewarder as f64) as u64, + ); // Deploy an upper cap safe_guard to the store_cost - min(TOTAL_SUPPLY / CLOSE_GROUP_SIZE as u64, charge) + min(MAX_STORE_COST, charge) +} + +fn positive_input_0_1_sigmoid(x: f64) -> f64 { + 1.0 / (1.0 + (-30.0 * (x - 0.5)).exp()) } #[allow(trivial_casts)] @@ -1026,7 +1024,7 @@ mod tests { received_payment_count: MAX_RECORDS_COUNT + 1, live_time: 1, }); - assert_eq!(sut, TOTAL_SUPPLY / CLOSE_GROUP_SIZE as u64); + assert_eq!(sut, MAX_STORE_COST - 1); } #[test] @@ -1039,7 +1037,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 20480); + assert_eq!(sut, 500000); } #[test] fn test_calculate_60_percent_cost_for_records() { @@ -1051,7 +1049,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 24570); + assert_eq!(sut, 952375); } #[test] @@ -1064,7 +1062,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 2528900); + assert_eq!(sut, 988981); } #[test] @@ -1077,7 +1075,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 262645870); + assert_eq!(sut, 997523); } #[test] @@ -1090,7 +1088,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 2689140767040); + assert_eq!(sut, 999875); } #[test] @@ -1103,7 +1101,7 @@ mod tests { live_time: 1, }); // at this point we should be at max cost - assert_eq!(sut, 27719885856440320); + assert_eq!(sut, 999993); } #[test] @@ -1114,7 +1112,7 @@ mod tests { received_payment_count: 0, live_time: 1, }); - assert_eq!(sut, 10); + assert_eq!(sut, MIN_STORE_COST); } #[test] @@ -1366,7 +1364,9 @@ mod tests { for _ in 0..max_records - 1 { let record_key = NetworkAddress::from_peer(PeerId::random()).to_record_key(); let value = match try_serialize_record( - &(0..50).map(|_| rand::random::()).collect::(), + &(0..max_records) + .map(|_| rand::random::()) + .collect::(), RecordKind::Chunk, ) { Ok(value) => value.to_vec(), @@ -1575,14 +1575,14 @@ mod tests { // Execute for 50 iterations, which allows the test can be executed in normal CI runs. if iteration == 50 { - assert_eq!(0, empty_earned_nodes, "every node has earnt _something_"); + assert!(empty_earned_nodes < 5, "0.25% of nodes still not earning"); assert!( (max_store_cost / min_store_cost) < 100, - "store cost is balanced" + "store cost is not balanced" ); assert!( (max_earned / min_earned) < 1000, - "earning distribution is well balanced" + "earning distribution is not balanced" ); break; } diff --git a/sn_transfers/src/genesis.rs b/sn_transfers/src/genesis.rs index 959fc8dc2e..56d96f5990 100644 --- a/sn_transfers/src/genesis.rs +++ b/sn_transfers/src/genesis.rs @@ -40,12 +40,18 @@ const DEFAULT_LIVE_GENESIS_SK: &str = /// Default genesis PK for testing purposes. Be sure to pass the correct `GENESIS_PK` value via env for release. const DEFAULT_LIVE_GENESIS_PK: &str = "9934c21469a68415e6b06a435709e16bff6e92bf302aeb0ea9199d2d06a55f1b1a21e155853d3f94ae31f8f313f886ee"; // DevSkim: ignore DS173237 +/// MIN_STORE_COST is 1, hence to have a MIN_ROYALTY_FEE to avoid zero royalty_fee. +const MIN_ROYALTY_FEE: u64 = 1; + /// Based on the given store cost, it calculates what's the expected amount to be paid as network royalties. /// Network royalties fee is expected to be 15% of the payment amount, i.e. 85% of store cost + 15% royalties fees. pub fn calculate_royalties_fee(store_cost: NanoTokens) -> NanoTokens { - let fees_amount = (store_cost.as_nano() as f64 * 0.15) / 0.85; + let fees_amount = std::cmp::max( + MIN_ROYALTY_FEE, + ((store_cost.as_nano() as f64 * 0.15) / 0.85) as u64, + ); // we round down the calculated amount - NanoTokens::from(fees_amount as u64) + NanoTokens::from(fees_amount) } /// A specialised `Result` type for genesis crate.