hashiverse_lib/protocol/posting/
amplification.rs1use crate::tools::buckets::BUCKET_DURATIONS;
24use crate::tools::config;
25use crate::tools::time::{DurationMillis, MILLIS_IN_MONTH};
26use crate::tools::types::Pow;
27
28pub fn get_minimum_post_pow(post_length: usize, linked_base_ids_len: usize, duration: DurationMillis) -> anyhow::Result<Pow> {
29 if !BUCKET_DURATIONS.contains(&duration) {
30 anyhow::bail!("Unsupported bucket duration {}", duration);
31 }
32
33 let pow_amplification_post_length = if post_length <= 1024 { 1.0f64 } else { 1.0f64 + 1.75 * (post_length as f64 / 1024f64).log2() };
34 let pow_amplification_linked_base_ids = if linked_base_ids_len < 2 {1.0f64} else { 1.0f64 + 0.2f64 * (linked_base_ids_len as f64).powi(2) };
35 let pow_amplification_bucket = (1.0f64 + (MILLIS_IN_MONTH.0 as f64 / duration.0 as f64).log2()).max(1.0f64);
36
37 let pow_amplification = pow_amplification_post_length * pow_amplification_linked_base_ids * pow_amplification_bucket;
38 let additional_pow_bits = pow_amplification.log2().ceil();
39 Ok(Pow(config::POW_MINIMUM_PER_POST.0 + additional_pow_bits as u8))
40}
41
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::tools::config;
47 use crate::tools::time::{MILLIS_IN_DAY, MILLIS_IN_HOUR, MILLIS_IN_MINUTE, MILLIS_IN_MONTH, MILLIS_IN_SECOND, MILLIS_IN_YEAR};
48 use crate::tools::types::Pow;
49
50 #[test]
52 fn baseline() {
53 let pow = get_minimum_post_pow(512, 1, MILLIS_IN_MONTH).unwrap();
54 assert_eq!(pow, config::POW_MINIMUM_PER_POST);
55 }
56
57 #[test]
59 fn day_bucket() {
60 let pow = get_minimum_post_pow(512, 1, MILLIS_IN_DAY).unwrap();
61 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 3));
62 }
63
64 #[test]
66 fn hour_bucket() {
67 let pow = get_minimum_post_pow(512, 1, MILLIS_IN_HOUR).unwrap();
68 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 4));
69 }
70
71 #[test]
73 fn linked_ids() {
74 let pow = get_minimum_post_pow(512, 3, MILLIS_IN_MONTH).unwrap();
75 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 2));
76 }
77
78 #[test]
80 fn combined() {
81 let pow = get_minimum_post_pow(4096, 3, MILLIS_IN_DAY).unwrap();
82 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 7));
83 }
84
85 #[test]
87 fn medium_post_4kb() {
88 let pow = get_minimum_post_pow(4 * 1024, 1, MILLIS_IN_MONTH).unwrap();
89 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 3));
90 }
91
92 #[test]
94 fn large_post_64kb() {
95 let pow = get_minimum_post_pow(64 * 1024, 1, MILLIS_IN_MONTH).unwrap();
96 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 4));
97 }
98
99 #[test]
101 fn large_post_512kb() {
102 let pow = get_minimum_post_pow(512 * 1024, 1, MILLIS_IN_MONTH).unwrap();
103 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 5));
104 }
105
106 #[test]
108 fn minute_granularity_5min() {
109 let pow = get_minimum_post_pow(512, 1, MILLIS_IN_MINUTE.const_mul(5)).unwrap();
110 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 4));
111 }
112
113 #[test]
115 fn many_linked_ids_10() {
116 let pow = get_minimum_post_pow(512, 10, MILLIS_IN_MONTH).unwrap();
117 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 5));
118 }
119
120 #[test]
122 fn monotone_duration() {
123 let pow_month = get_minimum_post_pow(512, 1, MILLIS_IN_MONTH).unwrap();
124 let pow_day = get_minimum_post_pow(512, 1, MILLIS_IN_DAY).unwrap();
125 let pow_hour = get_minimum_post_pow(512, 1, MILLIS_IN_HOUR).unwrap();
126 assert!(pow_month <= pow_day);
127 assert!(pow_day <= pow_hour);
128 assert!(pow_month < pow_hour);
129 }
130
131 #[test]
133 fn monotone_linked_ids() {
134 let pow_1 = get_minimum_post_pow(512, 1, MILLIS_IN_DAY).unwrap();
135 let pow_2 = get_minimum_post_pow(512, 2, MILLIS_IN_DAY).unwrap();
136 let pow_3 = get_minimum_post_pow(512, 3, MILLIS_IN_DAY).unwrap();
137 let pow_4 = get_minimum_post_pow(512, 4, MILLIS_IN_DAY).unwrap();
138 assert!(pow_1 <= pow_2);
139 assert!(pow_2 <= pow_3);
140 assert!(pow_3 <= pow_4);
141 assert!(pow_1 < pow_4);
142 }
143
144 #[test]
146 fn monotone_post_length() {
147 let pow_small = get_minimum_post_pow(1024, 1, MILLIS_IN_DAY).unwrap();
148 let pow_medium = get_minimum_post_pow(8192, 1, MILLIS_IN_DAY).unwrap();
149 let pow_large = get_minimum_post_pow(1024 * 1024, 1, MILLIS_IN_DAY).unwrap();
150 assert!(pow_small <= pow_medium);
151 assert!(pow_medium <= pow_large);
152 assert!(pow_small < pow_large);
153 }
154
155 #[test]
157 fn never_below_minimum() {
158 let lengths = [1, 512, 1024, 4096, 64 * 1024];
159 let links = [1, 2, 5, 10];
160 for post_length in lengths {
161 for linked_base_ids_len in links {
162 for duration in BUCKET_DURATIONS {
163 let pow = get_minimum_post_pow(post_length, linked_base_ids_len, duration).unwrap();
164 assert!(
165 pow >= config::POW_MINIMUM_PER_POST,
166 "pow={:?} below minimum for post_length={} links={} duration={:?}",
167 pow, post_length, linked_base_ids_len, duration
168 );
169 }
170 }
171 }
172 }
173
174 #[test]
177 fn one_minute_bucket() {
178 let pow = get_minimum_post_pow(512, 1, MILLIS_IN_MINUTE).unwrap();
179 assert_eq!(pow, Pow(config::POW_MINIMUM_PER_POST.0 + 5));
180 }
181
182 #[test]
183 fn every_bucket_duration_is_accepted() {
184 for bucket_duration in BUCKET_DURATIONS {
185 let result = get_minimum_post_pow(512, 1, bucket_duration);
186 assert!(result.is_ok(), "bucket_duration={} rejected: {:?}", bucket_duration, result.err());
187 }
188 }
189
190 #[test]
192 fn rejects_unsupported_duration() {
193 let unsupported_durations = [DurationMillis(0), MILLIS_IN_SECOND.const_mul(30), MILLIS_IN_MINUTE.const_mul(2), DurationMillis(-1), -MILLIS_IN_MONTH];
194 for unsupported_duration in unsupported_durations {
195 assert!(get_minimum_post_pow(512, 1, unsupported_duration).is_err(), "duration={} should be rejected", unsupported_duration);
196 }
197 }
198
199 #[test]
202 fn year_bucket_matches_month_bucket() {
203 let cases = [(512, 1), (64 * 1024, 1), (512, 10), (4096, 3)];
204 for (post_length, linked_base_ids_len) in cases {
205 let pow_year = get_minimum_post_pow(post_length, linked_base_ids_len, MILLIS_IN_YEAR).unwrap();
206 let pow_month = get_minimum_post_pow(post_length, linked_base_ids_len, MILLIS_IN_MONTH).unwrap();
207 assert_eq!(pow_year, pow_month, "post_length={} links={}", post_length, linked_base_ids_len);
208 }
209 assert!(get_minimum_post_pow(64 * 1024, 10, MILLIS_IN_YEAR).unwrap() > config::POW_MINIMUM_PER_POST);
210 }
211}