use self_encryption::MAX_CHUNK_SIZE;
use sn_dbc::Token;
use tracing::debug;
const TOKEN_TO_RAW_CONVERSION: u64 = 1_000_000_000;
const MAX_SUPPLY: u64 = (u32::max_value() as u64 + 1) * TOKEN_TO_RAW_CONVERSION;
pub fn required_tokens(
bytes: usize,
prefix_len: usize,
num_nodes_in_section: u8,
percent_filled: f64,
) -> Token {
debug!(
"required_tokens input values; bytes: {bytes}, prefix_len: {prefix_len}, num_nodes_in_section: {num_nodes_in_section}, percent_filled: {percent_filled}",
);
let available_nodes = num_nodes_in_section as f64;
let supply_demand_factor =
0.001 + (1_f64 / (20_f64 * available_nodes)).powf(8_f64) + percent_filled.powf(3_f64);
let byte_size_share = bytes as f64 / MAX_CHUNK_SIZE as f64;
let data_size_factor = byte_size_share + byte_size_share.powf(2_f64);
let steepness_reductor = prefix_len as f64 + 1_f64;
let supply_share = max_supply_share_per_section(prefix_len) as f64;
let token_source = steepness_reductor * supply_share.powf(0.5_f64);
let required_tokens = (token_source * data_size_factor * supply_demand_factor).round() as u64;
Token::from_nano(u64::max(1, required_tokens)) }
fn max_supply_share_per_section(prefix_len: usize) -> u64 {
(MAX_SUPPLY as f64 / 2_f64.powf(prefix_len as f64)).floor() as u64
}
#[cfg(test)]
mod test {
use super::*;
use crate::messaging::data::DataCmd;
use std::mem;
#[test]
fn tokens() {
let bytes = MAX_CHUNK_SIZE;
let prefix_len = 0;
let num_storage_nodes = 15;
let percent_filled = 1_f64 / (100_f64 * num_storage_nodes as f64);
let required_nanos =
required_tokens(bytes, prefix_len, num_storage_nodes, percent_filled).as_nano();
println!("required_nanos: {required_nanos}");
}
#[test]
fn calculates_required_tokens() {
let bytes = MAX_CHUNK_SIZE;
let prefix_len = 4;
let num_storage_nodes = 24;
let percent_filled = 3_f64 / num_storage_nodes as f64;
let required_nanos =
required_tokens(bytes, prefix_len, num_storage_nodes, percent_filled).as_nano();
println!("required_nanos: {required_nanos}");
assert_eq!(required_nanos, 15_300_364); }
#[test]
fn section_share_of_max_supply_decreases_as_network_grows() {
let first_section_nanos = max_supply_share_per_section(0);
assert_eq!(MAX_SUPPLY, first_section_nanos);
let first_split_nanos = max_supply_share_per_section(1);
assert_eq!(MAX_SUPPLY / 2, first_split_nanos);
let last_split_nanos = max_supply_share_per_section(61);
assert!(last_split_nanos > 0);
}
#[test]
fn smaller_chunks_require_fewer_tokens() {
let max_chunk_size = MAX_CHUNK_SIZE;
let prefix_len = 0;
let num_storage_nodes = 8;
let percent_filled = 7_f64 / num_storage_nodes as f64;
let standard_fee = required_tokens(
max_chunk_size,
prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
let max_chunk_size_less_one_byte = max_chunk_size - 1;
let small_chunk_fee = required_tokens(
max_chunk_size_less_one_byte,
prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
assert!(
small_chunk_fee <= standard_fee,
"small chunks don't require fewer tokens, expect {} <= {}",
small_chunk_fee,
standard_fee
);
}
#[test]
fn fewer_tokens_required_in_larger_net() {
let max_chunk_size = MAX_CHUNK_SIZE;
let prefix_len = 2; let num_storage_nodes = 8;
let percent_filled = 7_f64 / num_storage_nodes as f64;
let standard_fee = required_tokens(
max_chunk_size,
prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
let larger_prefix = prefix_len + 1;
let large_network_fee = required_tokens(
max_chunk_size,
larger_prefix,
num_storage_nodes,
percent_filled,
)
.as_nano();
assert!(
large_network_fee <= standard_fee,
"larger network is not cheaper, expect {} <= {}",
large_network_fee,
standard_fee
);
}
#[test]
fn emptier_section_requires_fewer_tokens() {
let max_chunk_size = MAX_CHUNK_SIZE;
let prefix_len = 0;
let num_storage_nodes = 8;
let percent_filled = 7_f64 / num_storage_nodes as f64;
let standard_fee = required_tokens(
max_chunk_size,
prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
let less_percent_filled = 6_f64 / num_storage_nodes as f64;
let lower_fee = required_tokens(
max_chunk_size,
prefix_len,
num_storage_nodes,
less_percent_filled,
)
.as_nano();
assert!(
lower_fee <= standard_fee,
"less filled section does not require fewer tokens, expect {} <= {}",
lower_fee,
standard_fee
);
}
#[test]
fn splitting_into_multiple_chunks_require_fewer_tokens_than_same_bytes_in_single_chunk() {
let max_chunk_size = MAX_CHUNK_SIZE;
let prefix_len = 2;
let num_storage_nodes = 8;
let percent_filled = 7_f64 / num_storage_nodes as f64;
let standard_fee = required_tokens(
max_chunk_size,
prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
let one_kb_bytes = 1024;
let reduced_fee =
required_tokens(one_kb_bytes, prefix_len, num_storage_nodes, percent_filled).as_nano();
let combined_fee = reduced_fee * (MAX_CHUNK_SIZE / one_kb_bytes) as u64;
assert!(
combined_fee <= standard_fee,
"many small chunks does not require fewer tokens than one big chunk, expect {} <= {}",
combined_fee,
standard_fee,
);
}
#[test]
fn tokens_are_required_up_to_max_network_size() {
let minimum_storage_bytes = mem::size_of::<DataCmd>();
let big_prefix_len = 256;
let num_storage_nodes = 20;
let percent_filled = 10_f64 / num_storage_nodes as f64;
let required_tokens = required_tokens(
minimum_storage_bytes,
big_prefix_len,
num_storage_nodes,
percent_filled,
)
.as_nano();
assert!(required_tokens > 0, "tokens are not always required",);
}
}