@peerbit/native-backbone 0.1.2 → 0.1.4

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.
@@ -9,6 +9,7 @@ use peerbit_shared_log_rust::{EntryCoordinateCommit, GidLeaderPlan};
9
9
  use std::collections::{HashMap, HashSet};
10
10
  use wasm_bindgen::prelude::*;
11
11
 
12
+ use crate::error::BackboneError;
12
13
  use crate::js_interop::{
13
14
  bytes_vec_from_array, ensure_same_len, hash_number_u64, numbers_to_rows, strings_from_array,
14
15
  strings_slice_to_array, strings_to_array,
@@ -101,7 +102,7 @@ fn prepared_raw_receive_selection_from_leader_plans(
101
102
  leader_plans: &[GidLeaderPlan],
102
103
  self_hash: &str,
103
104
  used_leader_sample_plans: bool,
104
- ) -> Result<JsValue, JsValue> {
105
+ ) -> Result<JsValue, BackboneError> {
105
106
  if leader_plans.len() != groups.len() {
106
107
  return Ok(JsValue::UNDEFINED);
107
108
  }
@@ -130,7 +131,7 @@ fn prepared_raw_receive_selection_from_leader_plans(
130
131
  let mut dropped_indexes = Vec::new();
131
132
  for (original_index, retained) in retained_original_indexes.iter().enumerate() {
132
133
  let original_index = u32::try_from(original_index)
133
- .map_err(|_| JsValue::from_str("Raw receive original index overflow"))?;
134
+ .map_err(|_| BackboneError::RawReceiveOriginalIndexOverflow)?;
134
135
  if *retained {
135
136
  retained_indexes.push(original_index);
136
137
  } else {
@@ -148,7 +149,7 @@ fn prepared_raw_receive_selection_from_leader_plans(
148
149
  selected_index_by_original[original_index] = Some(selected_index);
149
150
  selected_index = selected_index
150
151
  .checked_add(1)
151
- .ok_or_else(|| JsValue::from_str("Raw receive selected index overflow"))?;
152
+ .ok_or(BackboneError::RawReceiveSelectedIndexOverflow)?;
152
153
  }
153
154
  }
154
155
  for (index, (group, leader_plan)) in groups.iter().zip(leader_plans).enumerate() {
@@ -236,10 +237,10 @@ fn prepared_raw_entry_v0_to_row(entry: &PreparedRawEntryV0, hash_number: Option<
236
237
  impl NativePeerbitBackbone {
237
238
  pub fn prepare_raw_receive_batch(&mut self, blocks: Array) -> Result<Array, JsValue> {
238
239
  let profile_enabled = self.append_profile_enabled;
239
- let input_started = profile_enabled.then(js_sys::Date::now);
240
+ let input_started = profile_enabled.then(crate::time::now_ms);
240
241
  let blocks = bytes_vec_from_array(blocks)?;
241
242
  if let Some(started) = input_started {
242
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
243
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
243
244
  }
244
245
  let prepared = self.prepare_raw_receive_entries(blocks, None, true)?;
245
246
  let out = Array::new();
@@ -266,13 +267,13 @@ impl NativePeerbitBackbone {
266
267
 
267
268
  pub fn prepare_raw_receive_columns_batch(&mut self, blocks: Array) -> Result<Array, JsValue> {
268
269
  let profile_enabled = self.append_profile_enabled;
269
- let input_started = profile_enabled.then(js_sys::Date::now);
270
+ let input_started = profile_enabled.then(crate::time::now_ms);
270
271
  let blocks = bytes_vec_from_array(blocks)?;
271
272
  if let Some(started) = input_started {
272
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
273
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
273
274
  }
274
275
  let prepared = self.prepare_raw_receive_entries(blocks, None, true)?;
275
- self.prepare_raw_receive_columns_from_entries(prepared, true, true)
276
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, true, true)?)
276
277
  }
277
278
 
278
279
  pub fn prepare_raw_receive_unverified_columns_batch(
@@ -280,13 +281,13 @@ impl NativePeerbitBackbone {
280
281
  blocks: Array,
281
282
  ) -> Result<Array, JsValue> {
282
283
  let profile_enabled = self.append_profile_enabled;
283
- let input_started = profile_enabled.then(js_sys::Date::now);
284
+ let input_started = profile_enabled.then(crate::time::now_ms);
284
285
  let blocks = bytes_vec_from_array(blocks)?;
285
286
  if let Some(started) = input_started {
286
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
287
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
287
288
  }
288
289
  let prepared = self.prepare_raw_receive_entries(blocks, None, false)?;
289
- self.prepare_raw_receive_columns_from_entries(prepared, true, true)
290
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, true, true)?)
290
291
  }
291
292
 
292
293
  pub fn prepare_raw_receive_expected_columns_batch(
@@ -295,14 +296,14 @@ impl NativePeerbitBackbone {
295
296
  hashes: Array,
296
297
  ) -> Result<Array, JsValue> {
297
298
  let profile_enabled = self.append_profile_enabled;
298
- let input_started = profile_enabled.then(js_sys::Date::now);
299
+ let input_started = profile_enabled.then(crate::time::now_ms);
299
300
  let blocks = bytes_vec_from_array(blocks)?;
300
301
  let hashes = strings_from_array(hashes)?;
301
302
  if let Some(started) = input_started {
302
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
303
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
303
304
  }
304
305
  let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), true)?;
305
- self.prepare_raw_receive_columns_from_entries(prepared, true, true)
306
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, true, true)?)
306
307
  }
307
308
 
308
309
  pub fn prepare_raw_receive_unverified_expected_columns_batch(
@@ -311,14 +312,14 @@ impl NativePeerbitBackbone {
311
312
  hashes: Array,
312
313
  ) -> Result<Array, JsValue> {
313
314
  let profile_enabled = self.append_profile_enabled;
314
- let input_started = profile_enabled.then(js_sys::Date::now);
315
+ let input_started = profile_enabled.then(crate::time::now_ms);
315
316
  let blocks = bytes_vec_from_array(blocks)?;
316
317
  let hashes = strings_from_array(hashes)?;
317
318
  if let Some(started) = input_started {
318
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
319
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
319
320
  }
320
321
  let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), false)?;
321
- self.prepare_raw_receive_columns_from_entries(prepared, true, true)
322
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, true, true)?)
322
323
  }
323
324
 
324
325
  pub fn prepare_raw_receive_expected_compact_columns_batch(
@@ -327,14 +328,14 @@ impl NativePeerbitBackbone {
327
328
  hashes: Array,
328
329
  ) -> Result<Array, JsValue> {
329
330
  let profile_enabled = self.append_profile_enabled;
330
- let input_started = profile_enabled.then(js_sys::Date::now);
331
+ let input_started = profile_enabled.then(crate::time::now_ms);
331
332
  let blocks = bytes_vec_from_array(blocks)?;
332
333
  let hashes = strings_from_array(hashes)?;
333
334
  if let Some(started) = input_started {
334
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
335
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
335
336
  }
336
337
  let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), true)?;
337
- self.prepare_raw_receive_columns_from_entries(prepared, false, false)
338
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, false, false)?)
338
339
  }
339
340
 
340
341
  pub fn prepare_raw_receive_unverified_expected_compact_columns_batch(
@@ -343,14 +344,14 @@ impl NativePeerbitBackbone {
343
344
  hashes: Array,
344
345
  ) -> Result<Array, JsValue> {
345
346
  let profile_enabled = self.append_profile_enabled;
346
- let input_started = profile_enabled.then(js_sys::Date::now);
347
+ let input_started = profile_enabled.then(crate::time::now_ms);
347
348
  let blocks = bytes_vec_from_array(blocks)?;
348
349
  let hashes = strings_from_array(hashes)?;
349
350
  if let Some(started) = input_started {
350
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
351
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
351
352
  }
352
353
  let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), false)?;
353
- self.prepare_raw_receive_columns_from_entries(prepared, false, false)
354
+ Ok(self.prepare_raw_receive_columns_from_entries(prepared, false, false)?)
354
355
  }
355
356
 
356
357
  #[allow(clippy::too_many_arguments)]
@@ -371,11 +372,11 @@ impl NativePeerbitBackbone {
371
372
  _from_hash: String,
372
373
  ) -> Result<JsValue, JsValue> {
373
374
  let profile_enabled = self.append_profile_enabled;
374
- let input_started = profile_enabled.then(js_sys::Date::now);
375
+ let input_started = profile_enabled.then(crate::time::now_ms);
375
376
  let blocks = bytes_vec_from_array(blocks)?;
376
377
  let hashes = strings_from_array(hashes)?;
377
378
  if let Some(started) = input_started {
378
- self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
379
+ self.append_profile.raw_receive_input_copy_ms += crate::time::now_ms() - started;
379
380
  }
380
381
  let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes.clone()), false)?;
381
382
  let columns = self.prepare_raw_receive_columns_from_entries(prepared, false, false)?;
@@ -611,7 +612,7 @@ impl NativePeerbitBackbone {
611
612
  include_strict_full_replica: bool,
612
613
  _from_hash: String,
613
614
  ) -> Result<JsValue, JsValue> {
614
- self.plan_prepared_raw_receive_selection_core(
615
+ Ok(self.plan_prepared_raw_receive_selection_core(
615
616
  strings_from_array(hashes)?,
616
617
  min_replicas,
617
618
  max_replicas,
@@ -623,7 +624,7 @@ impl NativePeerbitBackbone {
623
624
  include_self,
624
625
  full_replica_fallback,
625
626
  include_strict_full_replica,
626
- )
627
+ )?)
627
628
  }
628
629
 
629
630
  #[allow(clippy::too_many_arguments)]
@@ -757,7 +758,7 @@ impl NativePeerbitBackbone {
757
758
  return Ok(JsValue::UNDEFINED);
758
759
  }
759
760
 
760
- prepared_raw_receive_selection_from_leader_plans(
761
+ Ok(prepared_raw_receive_selection_from_leader_plans(
761
762
  &self.resolution,
762
763
  &groups,
763
764
  expected_hash_count,
@@ -765,7 +766,7 @@ impl NativePeerbitBackbone {
765
766
  &leader_plans,
766
767
  &self_hash,
767
768
  false,
768
- )
769
+ )?)
769
770
  }
770
771
 
771
772
  pub fn verify_prepared_raw_receive_entries(
@@ -802,7 +803,7 @@ impl NativePeerbitBackbone {
802
803
  coordinate_assigned_to_range_boundaries,
803
804
  coordinate_requested_replicas,
804
805
  )?;
805
- self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)
806
+ Ok(self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)?)
806
807
  }
807
808
 
808
809
  #[allow(clippy::too_many_arguments)]
@@ -830,7 +831,7 @@ impl NativePeerbitBackbone {
830
831
  coordinate_assigned_to_range_boundaries,
831
832
  coordinate_requested_replicas,
832
833
  )?;
833
- self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)
834
+ Ok(self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)?)
834
835
  }
835
836
 
836
837
  #[allow(clippy::too_many_arguments)]
@@ -856,7 +857,7 @@ impl NativePeerbitBackbone {
856
857
  coordinate_assigned_to_range_boundaries,
857
858
  coordinate_requested_replicas,
858
859
  )?;
859
- self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)
860
+ Ok(self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)?)
860
861
  }
861
862
 
862
863
  #[allow(clippy::too_many_arguments)]
@@ -884,7 +885,7 @@ impl NativePeerbitBackbone {
884
885
  coordinate_assigned_to_range_boundaries,
885
886
  coordinate_requested_replicas,
886
887
  )?;
887
- self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)
888
+ Ok(self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)?)
888
889
  }
889
890
 
890
891
  #[allow(clippy::too_many_arguments)]
@@ -912,12 +913,12 @@ impl NativePeerbitBackbone {
912
913
  coordinate_assigned_to_range_boundaries,
913
914
  coordinate_requested_replicas,
914
915
  )?;
915
- self.commit_verified_prepared_raw_receive_join_batch_core(
916
+ Ok(self.commit_verified_prepared_raw_receive_join_batch_core(
916
917
  hashes,
917
918
  &heads,
918
919
  Some(verify_hashes),
919
920
  coordinate_commits,
920
- )
921
+ )?)
921
922
  }
922
923
 
923
924
  #[allow(clippy::too_many_arguments)]
@@ -947,12 +948,12 @@ impl NativePeerbitBackbone {
947
948
  coordinate_assigned_to_range_boundaries,
948
949
  coordinate_requested_replicas,
949
950
  )?;
950
- self.commit_verified_prepared_raw_receive_join_batch_core(
951
+ Ok(self.commit_verified_prepared_raw_receive_join_batch_core(
951
952
  hashes,
952
953
  &heads,
953
954
  Some(verify_hashes),
954
955
  coordinate_commits,
955
- )
956
+ )?)
956
957
  }
957
958
 
958
959
  #[allow(clippy::too_many_arguments)]
@@ -978,12 +979,12 @@ impl NativePeerbitBackbone {
978
979
  coordinate_assigned_to_range_boundaries,
979
980
  coordinate_requested_replicas,
980
981
  )?;
981
- self.commit_verified_prepared_raw_receive_join_batch_core(
982
+ Ok(self.commit_verified_prepared_raw_receive_join_batch_core(
982
983
  hashes,
983
984
  &heads,
984
985
  None,
985
986
  coordinate_commits,
986
- )
987
+ )?)
987
988
  }
988
989
 
989
990
  #[allow(clippy::too_many_arguments)]
@@ -1011,12 +1012,12 @@ impl NativePeerbitBackbone {
1011
1012
  coordinate_assigned_to_range_boundaries,
1012
1013
  coordinate_requested_replicas,
1013
1014
  )?;
1014
- self.commit_verified_prepared_raw_receive_join_batch_core(
1015
+ Ok(self.commit_verified_prepared_raw_receive_join_batch_core(
1015
1016
  hashes,
1016
1017
  &heads,
1017
1018
  None,
1018
1019
  coordinate_commits,
1019
- )
1020
+ )?)
1020
1021
  }
1021
1022
 
1022
1023
  pub fn clear_prepared_raw_receive_entries(&mut self, hashes: Array) -> Result<usize, JsValue> {
@@ -1036,9 +1037,9 @@ impl NativePeerbitBackbone {
1036
1037
  blocks: Vec<Vec<u8>>,
1037
1038
  expected_cids: Option<Vec<String>>,
1038
1039
  verify_signatures: bool,
1039
- ) -> Result<Vec<PreparedRawEntryV0>, JsValue> {
1040
+ ) -> Result<Vec<PreparedRawEntryV0>, BackboneError> {
1040
1041
  let profile_enabled = self.append_profile_enabled;
1041
- let prepare_started = profile_enabled.then(js_sys::Date::now);
1042
+ let prepare_started = profile_enabled.then(crate::time::now_ms);
1042
1043
  let mut raw_profile = profile_enabled.then(RawEntryV0PrepareProfile::default);
1043
1044
  let prepared = prepare_raw_entry_v0_blocks_with_expected_cids_and_verify_profiled(
1044
1045
  blocks,
@@ -1047,7 +1048,7 @@ impl NativePeerbitBackbone {
1047
1048
  raw_profile.as_mut(),
1048
1049
  )?;
1049
1050
  if let Some(started) = prepare_started {
1050
- self.append_profile.raw_receive_prepare_ms += js_sys::Date::now() - started;
1051
+ self.append_profile.raw_receive_prepare_ms += crate::time::now_ms() - started;
1051
1052
  }
1052
1053
  if let Some(raw_profile) = raw_profile {
1053
1054
  self.append_profile.add_raw_prepare_profile(&raw_profile);
@@ -1060,9 +1061,9 @@ impl NativePeerbitBackbone {
1060
1061
  prepared: Vec<PreparedRawEntryV0>,
1061
1062
  include_hash_digest_bytes: bool,
1062
1063
  include_cids: bool,
1063
- ) -> Result<Array, JsValue> {
1064
+ ) -> Result<Array, BackboneError> {
1064
1065
  let profile_enabled = self.append_profile_enabled;
1065
- let columns_started = profile_enabled.then(js_sys::Date::now);
1066
+ let columns_started = profile_enabled.then(crate::time::now_ms);
1066
1067
  let len = prepared.len();
1067
1068
  let cids = Array::new();
1068
1069
  let hash_digest_bytes = Array::new();
@@ -1137,7 +1138,7 @@ impl NativePeerbitBackbone {
1137
1138
  out.push(&Uint32Array::from(requested_replicas.as_slice()));
1138
1139
  out.push(&BigUint64Array::from(hash_numbers.as_slice()));
1139
1140
  if let Some(started) = columns_started {
1140
- self.append_profile.raw_receive_prepare_columns_ms += js_sys::Date::now() - started;
1141
+ self.append_profile.raw_receive_prepare_columns_ms += crate::time::now_ms() - started;
1141
1142
  }
1142
1143
  Ok(out)
1143
1144
  }
@@ -1156,7 +1157,7 @@ impl NativePeerbitBackbone {
1156
1157
  include_self: bool,
1157
1158
  full_replica_fallback: bool,
1158
1159
  include_strict_full_replica: bool,
1159
- ) -> Result<JsValue, JsValue> {
1160
+ ) -> Result<JsValue, BackboneError> {
1160
1161
  let expected_hash_count = hashes.len();
1161
1162
  if expected_hash_count == 0 {
1162
1163
  return Ok(JsValue::UNDEFINED);
@@ -1193,7 +1194,7 @@ impl NativePeerbitBackbone {
1193
1194
  include_self: bool,
1194
1195
  full_replica_fallback: bool,
1195
1196
  include_strict_full_replica: bool,
1196
- ) -> Result<JsValue, JsValue> {
1197
+ ) -> Result<JsValue, BackboneError> {
1197
1198
  let expected_hash_count = hashes.len();
1198
1199
  let mut planned_hash_count = 0usize;
1199
1200
  let mut gids = Vec::with_capacity(groups.len());
@@ -1250,7 +1251,7 @@ impl NativePeerbitBackbone {
1250
1251
  hashes: Vec<String>,
1251
1252
  min_replicas: u32,
1252
1253
  max_replicas: JsValue,
1253
- ) -> Result<Option<Vec<ResolvedPendingRawReceiveGroupPlan>>, JsValue> {
1254
+ ) -> Result<Option<Vec<ResolvedPendingRawReceiveGroupPlan>>, BackboneError> {
1254
1255
  if hashes.is_empty() {
1255
1256
  return Ok(Some(Vec::new()));
1256
1257
  }
@@ -1261,7 +1262,7 @@ impl NativePeerbitBackbone {
1261
1262
  } else {
1262
1263
  max_replicas
1263
1264
  .as_f64()
1264
- .ok_or_else(|| JsValue::from_str("maxReplicas must be a number"))?
1265
+ .ok_or(BackboneError::MaxReplicasMustBeNumber)?
1265
1266
  .max(0.0)
1266
1267
  .min(u32::MAX as f64) as u32
1267
1268
  };
@@ -1277,7 +1278,7 @@ impl NativePeerbitBackbone {
1277
1278
  return Ok(None);
1278
1279
  };
1279
1280
  let input_index = u32::try_from(input_index)
1280
- .map_err(|_| JsValue::from_str("Raw receive group index overflow"))?;
1281
+ .map_err(|_| BackboneError::RawReceiveGroupIndexOverflow)?;
1281
1282
 
1282
1283
  let group_index = if let Some(index) = group_indexes.get(&pending.entry.gid) {
1283
1284
  *index
@@ -1345,7 +1346,7 @@ impl NativePeerbitBackbone {
1345
1346
  fn verify_pending_raw_receive_entries(
1346
1347
  &mut self,
1347
1348
  hashes: &[String],
1348
- ) -> Result<Option<Vec<u8>>, JsValue> {
1349
+ ) -> Result<Option<Vec<u8>>, BackboneError> {
1349
1350
  if hashes.is_empty() {
1350
1351
  return Ok(Some(Vec::new()));
1351
1352
  }
@@ -1366,7 +1367,18 @@ impl NativePeerbitBackbone {
1366
1367
  if entries.is_empty() {
1367
1368
  Some(Vec::new())
1368
1369
  } else {
1369
- verify_prepared_entry_v0_ed25519_storage_slices(&entries).ok()
1370
+ // Deliberate, documented swallow. This returns Err only when
1371
+ // the signature slices are not parseable as Ed25519 — i.e. a
1372
+ // non-Ed25519 signature scheme or malformed bytes. That is the
1373
+ // load-bearing "native cannot natively verify these slices ->
1374
+ // defer to the caller's TS fallback" path: mapping the parse
1375
+ // error to None (and thence Ok(None) below) is correct for
1376
+ // mixed-scheme verification, so the bound LogError is
1377
+ // intentionally discarded rather than propagated.
1378
+ match verify_prepared_entry_v0_ed25519_storage_slices(&entries) {
1379
+ Ok(flags) => Some(flags),
1380
+ Err(_parse_error) => None,
1381
+ }
1370
1382
  }
1371
1383
  };
1372
1384
 
@@ -1374,9 +1386,7 @@ impl NativePeerbitBackbone {
1374
1386
  return Ok(None);
1375
1387
  };
1376
1388
  if verified.len() != verify_positions.len() {
1377
- return Err(JsValue::from_str(
1378
- "Expected equal prepared raw receive verify lengths",
1379
- ));
1389
+ return Err(BackboneError::MismatchedPreparedRawReceiveVerifyLengths);
1380
1390
  }
1381
1391
  for (index, flag) in verify_positions.iter().zip(verified.iter()) {
1382
1392
  out[*index] = *flag;
@@ -1393,7 +1403,7 @@ impl NativePeerbitBackbone {
1393
1403
  fn verify_pending_raw_receive_entries_all(
1394
1404
  &mut self,
1395
1405
  hashes: &[String],
1396
- ) -> Result<Option<bool>, JsValue> {
1406
+ ) -> Result<Option<bool>, BackboneError> {
1397
1407
  if hashes.is_empty() {
1398
1408
  return Ok(Some(true));
1399
1409
  }
@@ -1413,7 +1423,17 @@ impl NativePeerbitBackbone {
1413
1423
  if entries.is_empty() {
1414
1424
  Some(true)
1415
1425
  } else {
1416
- verify_prepared_entry_v0_ed25519_storage_slices_all(&entries).ok()
1426
+ // Deliberate, documented swallow (see the per-entry twin
1427
+ // verify_pending_raw_receive_entries above). Err here means the
1428
+ // signature slices are not parseable as Ed25519 — a non-Ed25519
1429
+ // scheme or malformed bytes — so native cannot natively verify
1430
+ // them and we defer to the caller's TS fallback by resolving to
1431
+ // None -> Ok(None). The bound LogError is intentionally
1432
+ // discarded to preserve that mixed-scheme fallback control flow.
1433
+ match verify_prepared_entry_v0_ed25519_storage_slices_all(&entries) {
1434
+ Ok(all_verified) => Some(all_verified),
1435
+ Err(_parse_error) => None,
1436
+ }
1417
1437
  }
1418
1438
  };
1419
1439
 
@@ -1436,53 +1456,53 @@ impl NativePeerbitBackbone {
1436
1456
  hashes: Vec<String>,
1437
1457
  heads: &Uint8Array,
1438
1458
  coordinate_commits: Vec<EntryCoordinateCommit>,
1439
- ) -> Result<bool, JsValue> {
1459
+ ) -> Result<bool, BackboneError> {
1440
1460
  ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1441
1461
  let profile_enabled = self.append_profile_enabled;
1442
- let pending_check_started = profile_enabled.then(js_sys::Date::now);
1462
+ let pending_check_started = profile_enabled.then(crate::time::now_ms);
1443
1463
  let missing_pending = hashes
1444
1464
  .iter()
1445
1465
  .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1446
1466
  if let Some(started) = pending_check_started {
1447
- self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1467
+ self.append_profile.raw_receive_pending_check_ms += crate::time::now_ms() - started;
1448
1468
  }
1449
1469
  if missing_pending {
1450
1470
  return Ok(false);
1451
1471
  }
1452
1472
 
1453
- let remove_started = profile_enabled.then(js_sys::Date::now);
1473
+ let remove_started = profile_enabled.then(crate::time::now_ms);
1454
1474
  let mut block_entries = Vec::with_capacity(hashes.len());
1455
1475
  let mut graph_entries = Vec::with_capacity(hashes.len());
1456
1476
  for (index, hash) in hashes.into_iter().enumerate() {
1457
1477
  let pending = self
1458
1478
  .pending_raw_receive_entries
1459
1479
  .remove(&hash)
1460
- .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1480
+ .ok_or(BackboneError::MissingPreparedRawReceiveEntry)?;
1461
1481
  block_entries.push((hash, pending.storage_bytes));
1462
1482
  let mut graph_entry = pending.entry;
1463
1483
  graph_entry.head = heads.get_index(index as u32) != 0;
1464
1484
  graph_entries.push(graph_entry);
1465
1485
  }
1466
1486
  if let Some(started) = remove_started {
1467
- self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1487
+ self.append_profile.raw_receive_remove_ms += crate::time::now_ms() - started;
1468
1488
  }
1469
1489
 
1470
- let block_put_started = profile_enabled.then(js_sys::Date::now);
1490
+ let block_put_started = profile_enabled.then(crate::time::now_ms);
1471
1491
  self.blocks.put_entries_core(block_entries);
1472
1492
  if let Some(started) = block_put_started {
1473
- self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1493
+ self.append_profile.raw_receive_block_put_ms += crate::time::now_ms() - started;
1474
1494
  }
1475
- let graph_put_started = profile_enabled.then(js_sys::Date::now);
1495
+ let graph_put_started = profile_enabled.then(crate::time::now_ms);
1476
1496
  self.log.put_entries_core(graph_entries);
1477
1497
  if let Some(started) = graph_put_started {
1478
- self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1498
+ self.append_profile.raw_receive_graph_put_ms += crate::time::now_ms() - started;
1479
1499
  }
1480
1500
  if !coordinate_commits.is_empty() {
1481
- let coordinate_started = profile_enabled.then(js_sys::Date::now);
1501
+ let coordinate_started = profile_enabled.then(crate::time::now_ms);
1482
1502
  self.commit_entry_coordinate_commits(coordinate_commits);
1483
1503
  if let Some(started) = coordinate_started {
1484
1504
  self.append_profile.raw_receive_coordinate_commit_ms +=
1485
- js_sys::Date::now() - started;
1505
+ crate::time::now_ms() - started;
1486
1506
  }
1487
1507
  }
1488
1508
  Ok(true)
@@ -1493,35 +1513,35 @@ impl NativePeerbitBackbone {
1493
1513
  hashes: Vec<String>,
1494
1514
  heads: &Uint8Array,
1495
1515
  coordinate_commits: Vec<EntryCoordinateCommit>,
1496
- ) -> Result<bool, JsValue> {
1516
+ ) -> Result<bool, BackboneError> {
1497
1517
  ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1498
1518
  let profile_enabled = self.append_profile_enabled;
1499
- let pending_check_started = profile_enabled.then(js_sys::Date::now);
1519
+ let pending_check_started = profile_enabled.then(crate::time::now_ms);
1500
1520
  let missing_pending = hashes
1501
1521
  .iter()
1502
1522
  .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1503
1523
  if let Some(started) = pending_check_started {
1504
- self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1524
+ self.append_profile.raw_receive_pending_check_ms += crate::time::now_ms() - started;
1505
1525
  }
1506
1526
  if missing_pending {
1507
1527
  return Ok(false);
1508
1528
  }
1509
1529
 
1510
1530
  {
1511
- let join_plan_started = profile_enabled.then(js_sys::Date::now);
1531
+ let join_plan_started = profile_enabled.then(crate::time::now_ms);
1512
1532
  let mut graph_entries = Vec::with_capacity(hashes.len());
1513
1533
  for hash in &hashes {
1514
1534
  let pending = self
1515
1535
  .pending_raw_receive_entries
1516
1536
  .get(hash)
1517
- .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1537
+ .ok_or(BackboneError::MissingPreparedRawReceiveEntry)?;
1518
1538
  graph_entries.push(&pending.entry);
1519
1539
  }
1520
1540
  let join_plans = self
1521
1541
  .log
1522
1542
  .plan_join_entry_refs_core(&graph_entries, false, true);
1523
1543
  if let Some(started) = join_plan_started {
1524
- self.append_profile.raw_receive_join_plan_ms += js_sys::Date::now() - started;
1544
+ self.append_profile.raw_receive_join_plan_ms += crate::time::now_ms() - started;
1525
1545
  }
1526
1546
  let mut batch_hashes: Option<HashSet<&str>> = None;
1527
1547
  for plan in join_plans {
@@ -1542,39 +1562,39 @@ impl NativePeerbitBackbone {
1542
1562
  }
1543
1563
  }
1544
1564
 
1545
- let remove_started = profile_enabled.then(js_sys::Date::now);
1565
+ let remove_started = profile_enabled.then(crate::time::now_ms);
1546
1566
  let mut block_entries = Vec::with_capacity(hashes.len());
1547
1567
  let mut graph_entries = Vec::with_capacity(hashes.len());
1548
1568
  for (index, hash) in hashes.into_iter().enumerate() {
1549
1569
  let pending = self
1550
1570
  .pending_raw_receive_entries
1551
1571
  .remove(&hash)
1552
- .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1572
+ .ok_or(BackboneError::MissingPreparedRawReceiveEntry)?;
1553
1573
  block_entries.push((hash, pending.storage_bytes));
1554
1574
  let mut graph_entry = pending.entry;
1555
1575
  graph_entry.head = heads.get_index(index as u32) != 0;
1556
1576
  graph_entries.push(graph_entry);
1557
1577
  }
1558
1578
  if let Some(started) = remove_started {
1559
- self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1579
+ self.append_profile.raw_receive_remove_ms += crate::time::now_ms() - started;
1560
1580
  }
1561
1581
 
1562
- let block_put_started = profile_enabled.then(js_sys::Date::now);
1582
+ let block_put_started = profile_enabled.then(crate::time::now_ms);
1563
1583
  self.blocks.put_entries_core(block_entries);
1564
1584
  if let Some(started) = block_put_started {
1565
- self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1585
+ self.append_profile.raw_receive_block_put_ms += crate::time::now_ms() - started;
1566
1586
  }
1567
- let graph_put_started = profile_enabled.then(js_sys::Date::now);
1587
+ let graph_put_started = profile_enabled.then(crate::time::now_ms);
1568
1588
  self.log.put_join_batch_entries_core(graph_entries);
1569
1589
  if let Some(started) = graph_put_started {
1570
- self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1590
+ self.append_profile.raw_receive_graph_put_ms += crate::time::now_ms() - started;
1571
1591
  }
1572
1592
  if !coordinate_commits.is_empty() {
1573
- let coordinate_started = profile_enabled.then(js_sys::Date::now);
1593
+ let coordinate_started = profile_enabled.then(crate::time::now_ms);
1574
1594
  self.commit_entry_coordinate_commits(coordinate_commits);
1575
1595
  if let Some(started) = coordinate_started {
1576
1596
  self.append_profile.raw_receive_coordinate_commit_ms +=
1577
- js_sys::Date::now() - started;
1597
+ crate::time::now_ms() - started;
1578
1598
  }
1579
1599
  }
1580
1600
  Ok(true)
@@ -1586,7 +1606,7 @@ impl NativePeerbitBackbone {
1586
1606
  heads: &Uint8Array,
1587
1607
  verify_hashes: Option<Vec<String>>,
1588
1608
  coordinate_commits: Vec<EntryCoordinateCommit>,
1589
- ) -> Result<bool, JsValue> {
1609
+ ) -> Result<bool, BackboneError> {
1590
1610
  ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1591
1611
  let profile_enabled = self.append_profile_enabled;
1592
1612
  let verify_hashes_cover_commit = match verify_hashes.as_ref() {
@@ -1600,41 +1620,47 @@ impl NativePeerbitBackbone {
1600
1620
  }
1601
1621
  };
1602
1622
  if !verify_hashes_cover_commit {
1603
- let pending_check_started = profile_enabled.then(js_sys::Date::now);
1623
+ let pending_check_started = profile_enabled.then(crate::time::now_ms);
1604
1624
  let missing_pending = hashes
1605
1625
  .iter()
1606
1626
  .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1607
1627
  if let Some(started) = pending_check_started {
1608
- self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1628
+ self.append_profile.raw_receive_pending_check_ms += crate::time::now_ms() - started;
1609
1629
  }
1610
1630
  if missing_pending {
1611
1631
  return Ok(false);
1612
1632
  }
1613
1633
  }
1614
- let verify_started = profile_enabled.then(js_sys::Date::now);
1634
+ let verify_started = profile_enabled.then(crate::time::now_ms);
1615
1635
  if verify_hashes_cover_commit {
1616
1636
  let verify_hashes = verify_hashes.as_ref().unwrap_or(&hashes);
1617
1637
  let Some(verified) = self.verify_pending_raw_receive_entries_all(verify_hashes)? else {
1618
1638
  return Ok(false);
1619
1639
  };
1620
1640
  if let Some(started) = verify_started {
1621
- self.append_profile.raw_receive_verify_ms += js_sys::Date::now() - started;
1641
+ self.append_profile.raw_receive_verify_ms += crate::time::now_ms() - started;
1622
1642
  }
1623
1643
  if !verified {
1624
1644
  return Ok(false);
1625
1645
  }
1626
1646
  } else {
1627
- let verify_hashes = verify_hashes.expect("partial verify hashes");
1647
+ // Unreachable by construction: verify_hashes_cover_commit is `true`
1648
+ // whenever verify_hashes is None (the match arm above), so reaching
1649
+ // this !verify_hashes_cover_commit branch guarantees Some. The
1650
+ // former `.expect("partial verify hashes")` trapped the whole wasm
1651
+ // instance on a breach; return a typed error instead so a breach
1652
+ // surfaces as a normal thrown value rather than a process abort.
1653
+ let verify_hashes = verify_hashes.ok_or(BackboneError::MissingPartialVerifyHashes)?;
1628
1654
  let Some(verified) = self.verify_pending_raw_receive_entries(&verify_hashes)? else {
1629
1655
  return Ok(false);
1630
1656
  };
1631
1657
  if let Some(started) = verify_started {
1632
- self.append_profile.raw_receive_verify_ms += js_sys::Date::now() - started;
1658
+ self.append_profile.raw_receive_verify_ms += crate::time::now_ms() - started;
1633
1659
  }
1634
1660
  if verified.iter().any(|flag| *flag == 0) {
1635
1661
  return Ok(false);
1636
1662
  }
1637
- let verify_status_started = profile_enabled.then(js_sys::Date::now);
1663
+ let verify_status_started = profile_enabled.then(crate::time::now_ms);
1638
1664
  let missing_verified = hashes.iter().any(|hash| {
1639
1665
  self.pending_raw_receive_entries
1640
1666
  .get(hash)
@@ -1642,7 +1668,7 @@ impl NativePeerbitBackbone {
1642
1668
  .unwrap_or(true)
1643
1669
  });
1644
1670
  if let Some(started) = verify_status_started {
1645
- self.append_profile.raw_receive_verify_status_ms += js_sys::Date::now() - started;
1671
+ self.append_profile.raw_receive_verify_status_ms += crate::time::now_ms() - started;
1646
1672
  }
1647
1673
  if missing_verified {
1648
1674
  return Ok(false);
@@ -1650,20 +1676,20 @@ impl NativePeerbitBackbone {
1650
1676
  }
1651
1677
 
1652
1678
  {
1653
- let join_plan_started = profile_enabled.then(js_sys::Date::now);
1679
+ let join_plan_started = profile_enabled.then(crate::time::now_ms);
1654
1680
  let mut graph_entries = Vec::with_capacity(hashes.len());
1655
1681
  for hash in &hashes {
1656
1682
  let pending = self
1657
1683
  .pending_raw_receive_entries
1658
1684
  .get(hash)
1659
- .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1685
+ .ok_or(BackboneError::MissingPreparedRawReceiveEntry)?;
1660
1686
  graph_entries.push(&pending.entry);
1661
1687
  }
1662
1688
  let join_plans = self
1663
1689
  .log
1664
1690
  .plan_join_entry_refs_core(&graph_entries, false, true);
1665
1691
  if let Some(started) = join_plan_started {
1666
- self.append_profile.raw_receive_join_plan_ms += js_sys::Date::now() - started;
1692
+ self.append_profile.raw_receive_join_plan_ms += crate::time::now_ms() - started;
1667
1693
  }
1668
1694
  let mut batch_hashes: Option<HashSet<&str>> = None;
1669
1695
  for plan in join_plans {
@@ -1684,41 +1710,90 @@ impl NativePeerbitBackbone {
1684
1710
  }
1685
1711
  }
1686
1712
 
1687
- let remove_started = profile_enabled.then(js_sys::Date::now);
1713
+ let remove_started = profile_enabled.then(crate::time::now_ms);
1688
1714
  let mut block_entries = Vec::with_capacity(hashes.len());
1689
1715
  let mut graph_entries = Vec::with_capacity(hashes.len());
1690
1716
  for (index, hash) in hashes.into_iter().enumerate() {
1691
1717
  let pending = self
1692
1718
  .pending_raw_receive_entries
1693
1719
  .remove(&hash)
1694
- .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1720
+ .ok_or(BackboneError::MissingPreparedRawReceiveEntry)?;
1695
1721
  block_entries.push((hash, pending.storage_bytes));
1696
1722
  let mut graph_entry = pending.entry;
1697
1723
  graph_entry.head = heads.get_index(index as u32) != 0;
1698
1724
  graph_entries.push(graph_entry);
1699
1725
  }
1700
1726
  if let Some(started) = remove_started {
1701
- self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1727
+ self.append_profile.raw_receive_remove_ms += crate::time::now_ms() - started;
1702
1728
  }
1703
1729
 
1704
- let block_put_started = profile_enabled.then(js_sys::Date::now);
1730
+ let block_put_started = profile_enabled.then(crate::time::now_ms);
1705
1731
  self.blocks.put_entries_core(block_entries);
1706
1732
  if let Some(started) = block_put_started {
1707
- self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1733
+ self.append_profile.raw_receive_block_put_ms += crate::time::now_ms() - started;
1708
1734
  }
1709
- let graph_put_started = profile_enabled.then(js_sys::Date::now);
1735
+ let graph_put_started = profile_enabled.then(crate::time::now_ms);
1710
1736
  self.log.put_join_batch_entries_core(graph_entries);
1711
1737
  if let Some(started) = graph_put_started {
1712
- self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1738
+ self.append_profile.raw_receive_graph_put_ms += crate::time::now_ms() - started;
1713
1739
  }
1714
1740
  if !coordinate_commits.is_empty() {
1715
- let coordinate_started = profile_enabled.then(js_sys::Date::now);
1741
+ let coordinate_started = profile_enabled.then(crate::time::now_ms);
1716
1742
  self.commit_entry_coordinate_commits(coordinate_commits);
1717
1743
  if let Some(started) = coordinate_started {
1718
1744
  self.append_profile.raw_receive_coordinate_commit_ms +=
1719
- js_sys::Date::now() - started;
1745
+ crate::time::now_ms() - started;
1720
1746
  }
1721
1747
  }
1722
1748
  Ok(true)
1723
1749
  }
1724
1750
  }
1751
+
1752
+ #[cfg(test)]
1753
+ mod tests {
1754
+ use crate::error::BackboneError;
1755
+
1756
+ #[test]
1757
+ fn raw_receive_selection_variants_render_exact_messages() {
1758
+ for (error, message) in [
1759
+ (
1760
+ BackboneError::MaxReplicasMustBeNumber,
1761
+ "maxReplicas must be a number",
1762
+ ),
1763
+ (
1764
+ BackboneError::RawReceiveOriginalIndexOverflow,
1765
+ "Raw receive original index overflow",
1766
+ ),
1767
+ (
1768
+ BackboneError::RawReceiveSelectedIndexOverflow,
1769
+ "Raw receive selected index overflow",
1770
+ ),
1771
+ (
1772
+ BackboneError::RawReceiveGroupIndexOverflow,
1773
+ "Raw receive group index overflow",
1774
+ ),
1775
+ ] {
1776
+ assert_eq!(error.to_string(), message);
1777
+ }
1778
+ }
1779
+
1780
+ #[test]
1781
+ fn raw_receive_verify_and_commit_variants_render_exact_messages() {
1782
+ for (error, message) in [
1783
+ (
1784
+ BackboneError::MissingPreparedRawReceiveEntry,
1785
+ "Missing prepared raw receive entry",
1786
+ ),
1787
+ (
1788
+ BackboneError::MismatchedPreparedRawReceiveVerifyLengths,
1789
+ "Expected equal prepared raw receive verify lengths",
1790
+ ),
1791
+ (
1792
+ BackboneError::MissingPartialVerifyHashes,
1793
+ "Missing partial verify hashes",
1794
+ ),
1795
+ ] {
1796
+ assert_eq!(error.to_string(), message);
1797
+ }
1798
+ }
1799
+ }