create-sia-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +179 -0
  3. package/package.json +29 -0
  4. package/template/CLAUDE.md +160 -0
  5. package/template/README.md +102 -0
  6. package/template/_gitignore +5 -0
  7. package/template/biome.json +40 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +30 -0
  10. package/template/rust/README.md +16 -0
  11. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
  12. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
  13. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
  14. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
  15. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
  16. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
  17. package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
  18. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
  19. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
  20. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
  21. package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
  22. package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
  23. package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
  24. package/template/rust/sia-sdk-rs/LICENSE +21 -0
  25. package/template/rust/sia-sdk-rs/README.md +30 -0
  26. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
  27. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
  28. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
  29. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
  30. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
  31. package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
  32. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
  33. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
  34. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
  35. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
  36. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
  37. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
  38. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
  39. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
  40. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
  41. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
  42. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
  43. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
  44. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
  45. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
  46. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
  47. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
  48. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
  49. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
  50. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
  51. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
  52. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
  53. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
  54. package/template/rust/sia-sdk-rs/knope.toml +54 -0
  55. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
  56. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
  57. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
  58. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
  59. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
  60. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
  61. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
  62. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
  63. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
  64. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
  65. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
  66. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
  67. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
  68. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
  69. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
  70. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
  71. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
  72. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
  73. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
  74. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
  75. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
  76. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
  77. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
  78. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
  88. package/template/scripts/setup-rust.js +29 -0
  89. package/template/src/App.tsx +13 -0
  90. package/template/src/components/DevNote.tsx +21 -0
  91. package/template/src/components/auth/ApproveScreen.tsx +84 -0
  92. package/template/src/components/auth/AuthFlow.tsx +77 -0
  93. package/template/src/components/auth/ConnectScreen.tsx +214 -0
  94. package/template/src/components/auth/LoadingScreen.tsx +8 -0
  95. package/template/src/components/auth/RecoveryScreen.tsx +182 -0
  96. package/template/src/components/upload/UploadZone.tsx +314 -0
  97. package/template/src/index.css +9 -0
  98. package/template/src/lib/constants.ts +8 -0
  99. package/template/src/lib/format.ts +35 -0
  100. package/template/src/lib/hex.ts +13 -0
  101. package/template/src/lib/sdk.ts +25 -0
  102. package/template/src/lib/wasm-env.ts +5 -0
  103. package/template/src/main.tsx +12 -0
  104. package/template/src/stores/auth.ts +86 -0
  105. package/template/tsconfig.app.json +31 -0
  106. package/template/tsconfig.json +7 -0
  107. package/template/tsconfig.node.json +26 -0
  108. package/template/vite.config.ts +18 -0
  109. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
  110. package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
  111. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  112. package/template/wasm/indexd_wasm/package.json +31 -0
@@ -0,0 +1,659 @@
1
+ use std::collections::{HashMap, VecDeque};
2
+ use std::fmt::{Debug, Display};
3
+ use std::sync::{Arc, RwLock};
4
+ use std::time::Duration;
5
+
6
+ use priority_queue::PriorityQueue;
7
+ use sia::rhp::Host;
8
+ use sia::signing::PublicKey;
9
+ use sia::types::v2::NetAddress;
10
+ use std::sync::Mutex;
11
+ use thiserror::Error;
12
+
13
+ #[derive(Debug, Default, Clone)]
14
+ struct RPCAverage(Option<f64>); // exponential moving average of latency in milliseconds
15
+
16
+ impl RPCAverage {
17
+ const ALPHA: f64 = 0.2;
18
+ fn add_sample(&mut self, sample: Duration) {
19
+ match self.0 {
20
+ Some(avg) => {
21
+ self.0 =
22
+ Some(Self::ALPHA * (sample.as_millis() as f64) + (1.0 - Self::ALPHA) * avg);
23
+ }
24
+ None => {
25
+ self.0 = Some(sample.as_millis() as f64);
26
+ }
27
+ }
28
+ }
29
+
30
+ fn avg(&self) -> Duration {
31
+ match self.0 {
32
+ Some(avg) => Duration::from_millis(avg as u64),
33
+ None => Duration::from_secs(3600), // 1h if no samples
34
+ }
35
+ }
36
+ }
37
+
38
+ impl Display for RPCAverage {
39
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40
+ self.avg().fmt(f)
41
+ }
42
+ }
43
+
44
+ impl PartialEq for RPCAverage {
45
+ fn eq(&self, other: &Self) -> bool {
46
+ self.avg() == other.avg()
47
+ }
48
+ }
49
+
50
+ impl Eq for RPCAverage {}
51
+
52
+ impl Ord for RPCAverage {
53
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
54
+ self.avg().cmp(&other.avg())
55
+ }
56
+ }
57
+
58
+ impl PartialOrd for RPCAverage {
59
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
60
+ Some(self.cmp(other))
61
+ }
62
+ }
63
+
64
+ #[derive(Debug, Default, Clone)]
65
+ struct FailureRate(Option<f64>); // exponential moving average of failure rate
66
+
67
+ impl FailureRate {
68
+ const ALPHA: f64 = 0.2;
69
+
70
+ fn add_sample(&mut self, success: bool) {
71
+ let sample = if success { 0.0 } else { 1.0 };
72
+ match self.0 {
73
+ Some(rate) => {
74
+ self.0 = Some(Self::ALPHA * sample + (1.0 - Self::ALPHA) * rate);
75
+ }
76
+ None => {
77
+ self.0 = Some(sample);
78
+ }
79
+ }
80
+ }
81
+
82
+ // Computes the failure rate as an integer percentage (0-100)
83
+ fn rate(&self) -> i64 {
84
+ match self.0 {
85
+ Some(rate) => (rate * 100.0).round() as i64,
86
+ None => 0, // presume no failures if no samples
87
+ }
88
+ }
89
+ }
90
+
91
+ impl Display for FailureRate {
92
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93
+ write!(f, "{}%", self.rate())
94
+ }
95
+ }
96
+
97
+ impl PartialEq for FailureRate {
98
+ fn eq(&self, other: &Self) -> bool {
99
+ self.rate() == other.rate()
100
+ }
101
+ }
102
+
103
+ impl Eq for FailureRate {}
104
+
105
+ impl PartialOrd for FailureRate {
106
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
107
+ Some(self.cmp(other))
108
+ }
109
+ }
110
+
111
+ impl Ord for FailureRate {
112
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
113
+ self.rate().cmp(&other.rate())
114
+ }
115
+ }
116
+
117
+ #[derive(Debug, Default, Clone, Eq, PartialEq)]
118
+ struct HostMetric {
119
+ rpc_write_avg: RPCAverage,
120
+ rpc_read_avg: RPCAverage,
121
+ failure_rate: FailureRate,
122
+ }
123
+
124
+ impl HostMetric {
125
+ fn add_write_sample(&mut self, d: Duration) {
126
+ self.rpc_write_avg.add_sample(d);
127
+ self.failure_rate.add_sample(true);
128
+ }
129
+
130
+ fn add_read_sample(&mut self, d: Duration) {
131
+ self.rpc_read_avg.add_sample(d);
132
+ self.failure_rate.add_sample(true);
133
+ }
134
+
135
+ fn add_failure(&mut self) {
136
+ self.failure_rate.add_sample(false);
137
+ }
138
+ }
139
+
140
+ impl Ord for HostMetric {
141
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
142
+ match other.failure_rate.cmp(&self.failure_rate) {
143
+ // lower failure rate is higher priority
144
+ std::cmp::Ordering::Equal => {
145
+ // use average of read and write RPC times as tiebreaker
146
+ let avg_self = (self
147
+ .rpc_write_avg
148
+ .avg()
149
+ .saturating_add(self.rpc_read_avg.avg()))
150
+ / 2;
151
+ let avg_other = (other
152
+ .rpc_write_avg
153
+ .avg()
154
+ .saturating_add(other.rpc_read_avg.avg()))
155
+ / 2;
156
+ avg_other.cmp(&avg_self) // lower average latency is higher priority
157
+ }
158
+ ord => ord,
159
+ }
160
+ }
161
+ }
162
+
163
+ impl PartialOrd for HostMetric {
164
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
165
+ Some(self.cmp(other))
166
+ }
167
+ }
168
+
169
+ #[derive(Debug)]
170
+ struct HostInfo {
171
+ addresses: Vec<NetAddress>,
172
+ good_for_upload: bool,
173
+ }
174
+
175
+ #[derive(Debug)]
176
+ struct HostsInner {
177
+ hosts: HashMap<PublicKey, HostInfo>,
178
+ preferred_hosts: PriorityQueue<PublicKey, HostMetric>,
179
+ }
180
+
181
+ /// Manages a list of known hosts and their performance metrics.
182
+ ///
183
+ /// It allows updating the list of hosts, recording performance samples,
184
+ /// and prioritizing hosts based on their metrics.
185
+ ///
186
+ /// It can be safely shared across threads and cloned.
187
+ #[derive(Debug, Clone)]
188
+ pub struct Hosts {
189
+ inner: Arc<RwLock<HostsInner>>,
190
+ }
191
+
192
+ impl Default for Hosts {
193
+ fn default() -> Self {
194
+ Self::new()
195
+ }
196
+ }
197
+
198
+ impl Hosts {
199
+ pub fn new() -> Self {
200
+ Self {
201
+ inner: Arc::new(RwLock::new(HostsInner {
202
+ hosts: HashMap::new(),
203
+ preferred_hosts: PriorityQueue::new(),
204
+ })),
205
+ }
206
+ }
207
+
208
+ pub fn addresses(&self, host_key: &PublicKey) -> Option<Vec<NetAddress>> {
209
+ let inner = self.inner.read().unwrap();
210
+ inner.hosts.get(host_key).map(|h| h.addresses.clone())
211
+ }
212
+
213
+ /// Sorts a list of hosts according to their priority in the client's
214
+ /// preferred hosts queue. The function `f` is used to extract the
215
+ /// public key from each item.
216
+ pub fn prioritize<H, F>(&self, hosts: &mut [H], f: F)
217
+ where
218
+ F: Fn(&H) -> &PublicKey,
219
+ {
220
+ let inner = self.inner.read().unwrap();
221
+ let preferred_hosts = &inner.preferred_hosts;
222
+ hosts.sort_by(|a, b| {
223
+ preferred_hosts
224
+ .get_priority(f(b))
225
+ .cmp(&preferred_hosts.get_priority(f(a)))
226
+ });
227
+ }
228
+
229
+ /// Updates the list of known hosts.
230
+ ///
231
+ /// Existing hosts not in the new list are removed, but their metrics are retained
232
+ /// in case they reappear later.
233
+ pub fn update(&self, hosts: Vec<Host>) {
234
+ let mut inner = self.inner.write().unwrap();
235
+ inner.hosts.clear();
236
+ for host in hosts {
237
+ inner.hosts.insert(
238
+ host.public_key,
239
+ HostInfo {
240
+ addresses: host.addresses,
241
+ good_for_upload: host.good_for_upload,
242
+ },
243
+ );
244
+ if !inner.preferred_hosts.contains(&host.public_key) {
245
+ inner
246
+ .preferred_hosts
247
+ .push(host.public_key, HostMetric::default());
248
+ }
249
+ }
250
+ }
251
+
252
+ /// Returns the number of known hosts that are good for upload.
253
+ pub fn available_for_upload(&self) -> usize {
254
+ let inner = self.inner.read().unwrap();
255
+ inner
256
+ .hosts
257
+ .iter()
258
+ .filter(|(_, h)| h.good_for_upload)
259
+ .count()
260
+ }
261
+
262
+ /// Returns a list of all known hosts, sorted by priority.
263
+ pub fn hosts(&self) -> Vec<PublicKey> {
264
+ let inner = self.inner.read().unwrap();
265
+ let preferred_hosts = &inner.preferred_hosts;
266
+ let mut hosts = inner.hosts.iter().map(|h| *h.0).collect::<Vec<_>>();
267
+
268
+ hosts.sort_by(|a, b| {
269
+ preferred_hosts
270
+ .get_priority(b)
271
+ .cmp(&preferred_hosts.get_priority(a))
272
+ });
273
+ hosts
274
+ }
275
+
276
+ /// Creates a queue of hosts that are good to upload to for sequential
277
+ /// access sorted by priority.
278
+ pub fn upload_queue(&self) -> HostQueue {
279
+ let inner = self.inner.read().unwrap();
280
+ let preferred_hosts = &inner.preferred_hosts;
281
+ let mut hosts = inner
282
+ .hosts
283
+ .iter()
284
+ .filter_map(|(hk, h)| h.good_for_upload.then_some(*hk))
285
+ .collect::<Vec<_>>();
286
+
287
+ hosts.sort_by(|a, b| {
288
+ preferred_hosts
289
+ .get_priority(b)
290
+ .cmp(&preferred_hosts.get_priority(a))
291
+ });
292
+ HostQueue::new(hosts)
293
+ }
294
+
295
+ /// Adds a failure for the given host, updating its metrics and priority.
296
+ pub fn add_failure(&self, host_key: &PublicKey) {
297
+ let mut inner = self.inner.write().unwrap();
298
+ inner
299
+ .preferred_hosts
300
+ .change_priority_by(host_key, |metric| {
301
+ metric.add_failure();
302
+ });
303
+ }
304
+
305
+ /// Adds a read sample for the given host, updating its metrics and priority.
306
+ pub fn add_read_sample(&self, host_key: &PublicKey, duration: Duration) {
307
+ let mut inner = self.inner.write().unwrap();
308
+ inner
309
+ .preferred_hosts
310
+ .change_priority_by(host_key, |metric| {
311
+ metric.add_read_sample(duration);
312
+ });
313
+ }
314
+
315
+ /// Adds a write sample for the given host, updating its metrics and priority.
316
+ pub fn add_write_sample(&self, host_key: &PublicKey, duration: Duration) {
317
+ let mut inner = self.inner.write().unwrap();
318
+ inner
319
+ .preferred_hosts
320
+ .change_priority_by(host_key, |metric| {
321
+ metric.add_write_sample(duration);
322
+ });
323
+ }
324
+ }
325
+
326
+ #[derive(Debug, Error)]
327
+ pub enum QueueError {
328
+ #[error("no more hosts available")]
329
+ NoMoreHosts,
330
+ #[error("not enough initial hosts")]
331
+ InsufficientHosts,
332
+ #[error("client closed")]
333
+ Closed,
334
+ #[error("internal mutex error")]
335
+ MutexError,
336
+ }
337
+
338
+ /// Maximum number of times to retry a host before giving up
339
+ const MAX_HOST_RETRIES: usize = 3;
340
+
341
+ #[derive(Debug)]
342
+ struct HostQueueInner {
343
+ hosts: VecDeque<PublicKey>,
344
+ attempts: HashMap<PublicKey, usize>,
345
+ }
346
+
347
+ /// A thread-safe queue of host public keys.
348
+ #[derive(Debug, Clone)]
349
+ pub struct HostQueue {
350
+ inner: Arc<Mutex<HostQueueInner>>,
351
+ }
352
+
353
+ impl HostQueue {
354
+ pub fn new(hosts: Vec<PublicKey>) -> Self {
355
+ Self {
356
+ inner: Arc::new(Mutex::new(HostQueueInner {
357
+ hosts: VecDeque::from(hosts),
358
+ attempts: HashMap::new(),
359
+ })),
360
+ }
361
+ }
362
+
363
+ pub fn pop_front(&self) -> Result<(PublicKey, usize), QueueError> {
364
+ let mut inner = self.inner.lock().map_err(|_| QueueError::MutexError)?;
365
+ let host_key = inner.hosts.pop_front().ok_or(QueueError::NoMoreHosts)?;
366
+
367
+ let attempts = inner.attempts.get(&host_key).cloned().unwrap_or(0);
368
+ Ok((host_key, attempts + 1))
369
+ }
370
+
371
+ pub fn pop_n(&self, n: usize) -> Result<Vec<(PublicKey, usize)>, QueueError> {
372
+ let mut inner = self.inner.lock().map_err(|_| QueueError::MutexError)?;
373
+ if inner.hosts.len() < n {
374
+ return Err(QueueError::NoMoreHosts);
375
+ }
376
+ let mut result = Vec::with_capacity(n);
377
+ for _ in 0..n {
378
+ let host_key = inner.hosts.pop_front().ok_or(QueueError::NoMoreHosts)?;
379
+ let attempts = inner.attempts.get(&host_key).cloned().unwrap_or(0);
380
+ result.push((host_key, attempts + 1));
381
+ }
382
+ Ok(result)
383
+ }
384
+
385
+ pub fn retry(&self, host: PublicKey) -> Result<(), QueueError> {
386
+ let mut inner = self.inner.lock().map_err(|_| QueueError::MutexError)?;
387
+ let attempts = inner.attempts.get(&host).cloned().unwrap_or(0);
388
+
389
+ // Only retry if we haven't exceeded the maximum retry limit
390
+ if attempts < MAX_HOST_RETRIES {
391
+ inner.hosts.push_back(host);
392
+ inner
393
+ .attempts
394
+ .entry(host)
395
+ .and_modify(|e| *e += 1)
396
+ .or_insert(1);
397
+ }
398
+ Ok(())
399
+ }
400
+ }
401
+
402
+ #[cfg(test)]
403
+ mod test {
404
+ use sia::signing::PrivateKey;
405
+
406
+ use super::*;
407
+
408
+ #[test]
409
+ fn test_failure_rate() {
410
+ let mut fr = FailureRate::default();
411
+ assert_eq!(fr.rate(), 0, "initial failure rate should be 0%");
412
+ fr.add_sample(false);
413
+ assert_eq!(fr.rate(), 100, "initial failure should be 100%");
414
+
415
+ for _ in 0..5 {
416
+ fr.add_sample(true);
417
+ }
418
+ assert!(
419
+ fr.rate() < 100,
420
+ "failure rate should decrease after successes"
421
+ );
422
+
423
+ let mut fr2 = FailureRate::default();
424
+ for _ in 0..5 {
425
+ fr2.add_sample(true);
426
+ }
427
+ assert_eq!(
428
+ fr2.rate(),
429
+ 0,
430
+ "failure rate should be 0% after only successes"
431
+ );
432
+ assert_eq!(
433
+ fr.cmp(&fr2),
434
+ std::cmp::Ordering::Greater,
435
+ "higher failure rate should be greater"
436
+ );
437
+ }
438
+
439
+ #[test]
440
+ fn test_rpc_average() {
441
+ let mut avg = RPCAverage::default();
442
+ avg.add_sample(Duration::from_millis(100));
443
+ assert_eq!(
444
+ avg.avg(),
445
+ Duration::from_millis(100),
446
+ "initial average should be first sample"
447
+ );
448
+
449
+ avg.add_sample(Duration::from_millis(200));
450
+ assert!(
451
+ avg.avg() > Duration::from_millis(100),
452
+ "average should increase after higher sample"
453
+ );
454
+
455
+ avg.add_sample(Duration::from_millis(50));
456
+ assert!(
457
+ avg.avg() < Duration::from_millis(200),
458
+ "average should decrease after lower sample"
459
+ );
460
+
461
+ let mut avg2 = RPCAverage::default();
462
+ avg2.add_sample(Duration::from_millis(150));
463
+ assert_eq!(
464
+ avg.cmp(&avg2),
465
+ std::cmp::Ordering::Less,
466
+ "lower average should be lesser"
467
+ );
468
+ }
469
+
470
+ #[test]
471
+ fn test_host_metric_ordering() {
472
+ let mut hosts = vec![
473
+ HostMetric::default(),
474
+ HostMetric::default(),
475
+ HostMetric::default(),
476
+ ];
477
+ hosts[0].failure_rate.add_sample(false);
478
+ hosts[1].failure_rate.add_sample(false);
479
+ hosts[2].failure_rate.add_sample(false);
480
+ for _ in 0..10 {
481
+ hosts[0].failure_rate.add_sample(true);
482
+ }
483
+ for _ in 0..5 {
484
+ hosts[1].failure_rate.add_sample(true);
485
+ }
486
+ hosts.sort();
487
+
488
+ let rates = hosts
489
+ .into_iter()
490
+ .rev()
491
+ .map(|h| h.failure_rate)
492
+ .collect::<Vec<FailureRate>>();
493
+ assert!(
494
+ rates.is_sorted(),
495
+ "hosts should be sorted by failure rate desc"
496
+ );
497
+
498
+ let mut hosts = vec![
499
+ HostMetric::default(),
500
+ HostMetric::default(),
501
+ HostMetric::default(),
502
+ ];
503
+ hosts[0]
504
+ .rpc_write_avg
505
+ .add_sample(Duration::from_millis(100));
506
+ hosts[1]
507
+ .rpc_write_avg
508
+ .add_sample(Duration::from_millis(1000));
509
+ hosts[2]
510
+ .rpc_write_avg
511
+ .add_sample(Duration::from_millis(500));
512
+ hosts.sort();
513
+
514
+ let rates = hosts
515
+ .into_iter()
516
+ .rev()
517
+ .map(|h| h.rpc_write_avg)
518
+ .collect::<Vec<RPCAverage>>();
519
+ assert!(
520
+ rates.is_sorted(),
521
+ "hosts should be sorted by rpc write avg desc"
522
+ );
523
+ }
524
+
525
+ #[test]
526
+ fn test_host_priority_queue() {
527
+ let mut pq = PriorityQueue::<PublicKey, HostMetric>::new();
528
+ let mut hosts = vec![];
529
+ for _ in 0..5 {
530
+ let pk = PrivateKey::from_seed(&rand::random()).public_key();
531
+ pq.push(pk, HostMetric::default());
532
+ hosts.push(pk);
533
+ }
534
+
535
+ // initially, the order is the same as insertion
536
+ assert_eq!(pq.peek().unwrap().0, &hosts[0]);
537
+
538
+ // fourth host has a sample, should have highest priority
539
+ pq.change_priority_by(&hosts[3], |metric| {
540
+ metric.add_write_sample(Duration::from_millis(100));
541
+ });
542
+ assert_eq!(pq.peek().unwrap().0, &hosts[3]);
543
+
544
+ // add a faster sample to second host, should have higher priority than fourth
545
+ pq.change_priority_by(&hosts[1], |metric| {
546
+ metric.add_read_sample(Duration::from_millis(50));
547
+ });
548
+ assert_eq!(pq.peek().unwrap().0, &hosts[1]);
549
+
550
+ // add a failure to the second host, should lower its priority below fourth
551
+ pq.change_priority_by(&hosts[1], |metric| {
552
+ metric.add_failure();
553
+ });
554
+ assert_eq!(pq.peek().unwrap().0, &hosts[3]);
555
+ }
556
+
557
+ #[test]
558
+ fn test_upload_queue() {
559
+ let hosts_manager = Hosts::new();
560
+
561
+ let hk1 = PrivateKey::from_seed(&rand::random()).public_key();
562
+ let hk2 = PrivateKey::from_seed(&rand::random()).public_key();
563
+ let hk3 = PrivateKey::from_seed(&rand::random()).public_key();
564
+
565
+ hosts_manager.update(vec![
566
+ Host {
567
+ public_key: hk1,
568
+ addresses: vec![],
569
+ country_code: String::new(),
570
+ latitude: 0.0,
571
+ longitude: 0.0,
572
+ good_for_upload: false,
573
+ },
574
+ Host {
575
+ public_key: hk2,
576
+ addresses: vec![],
577
+ country_code: String::new(),
578
+ latitude: 0.0,
579
+ longitude: 0.0,
580
+ good_for_upload: true,
581
+ },
582
+ Host {
583
+ public_key: hk3,
584
+ addresses: vec![],
585
+ country_code: String::new(),
586
+ latitude: 0.0,
587
+ longitude: 0.0,
588
+ good_for_upload: false,
589
+ },
590
+ ]);
591
+
592
+ let queue = hosts_manager.upload_queue();
593
+ let (first, _) = queue.pop_front().unwrap();
594
+ assert_eq!(first, hk2);
595
+ assert!(
596
+ queue.pop_front().is_err(),
597
+ "queue should only have one host"
598
+ );
599
+ }
600
+
601
+ #[test]
602
+ fn test_host_queue_pop_n() {
603
+ let hosts: Vec<_> = (0..5)
604
+ .map(|_| PrivateKey::from_seed(&rand::random()).public_key())
605
+ .collect();
606
+ let queue = HostQueue::new(hosts.clone());
607
+
608
+ // pop 3 hosts
609
+ let popped = queue.pop_n(3).expect("should pop 3 hosts");
610
+ assert_eq!(popped.len(), 3);
611
+ assert_eq!(popped[0].0, hosts[0]);
612
+ assert_eq!(popped[1].0, hosts[1]);
613
+ assert_eq!(popped[2].0, hosts[2]);
614
+
615
+ // all should have attempts = 1
616
+ assert!(popped.iter().all(|(_, attempts)| *attempts == 1));
617
+
618
+ // pop remaining 2
619
+ let popped = queue.pop_n(2).expect("should pop 2 hosts");
620
+ assert_eq!(popped.len(), 2);
621
+ assert_eq!(popped[0].0, hosts[3]);
622
+ assert_eq!(popped[1].0, hosts[4]);
623
+
624
+ // queue should be empty
625
+ assert!(matches!(queue.pop_front(), Err(QueueError::NoMoreHosts)));
626
+ }
627
+
628
+ #[test]
629
+ fn test_host_queue_pop_n_not_enough_hosts() {
630
+ let hosts: Vec<_> = (0..3)
631
+ .map(|_| PrivateKey::from_seed(&rand::random()).public_key())
632
+ .collect();
633
+ let queue = HostQueue::new(hosts.clone());
634
+
635
+ // try to pop more than available
636
+ let result = queue.pop_n(5);
637
+ assert!(matches!(result, Err(QueueError::NoMoreHosts)));
638
+
639
+ // queue should be unchanged - can still pop all 3
640
+ let popped = queue.pop_n(3).expect("should pop 3");
641
+ assert_eq!(popped.len(), 3);
642
+ }
643
+
644
+ #[test]
645
+ fn test_host_queue_pop_n_zero() {
646
+ let hosts: Vec<_> = (0..3)
647
+ .map(|_| PrivateKey::from_seed(&rand::random()).public_key())
648
+ .collect();
649
+ let queue = HostQueue::new(hosts);
650
+
651
+ // pop 0 hosts should succeed with empty vec
652
+ let popped = queue.pop_n(0).expect("should succeed");
653
+ assert!(popped.is_empty());
654
+
655
+ // queue should be unchanged - can still pop 3
656
+ let popped = queue.pop_n(3).expect("should pop 3");
657
+ assert_eq!(popped.len(), 3);
658
+ }
659
+ }