@peerbit/native-backbone 0.1.0

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.
@@ -0,0 +1,1724 @@
1
+ use js_sys::{Array, BigUint64Array, Uint32Array, Uint8Array};
2
+ use peerbit_log_rust::{
3
+ prepare_raw_entry_v0_blocks_with_expected_cids_and_verify_profiled,
4
+ verify_prepared_entry_v0_ed25519_storage_slices,
5
+ verify_prepared_entry_v0_ed25519_storage_slices_all, LogIndexEntry,
6
+ PreparedEntryV0SignatureInput, PreparedRawEntryV0, RawEntryV0PrepareProfile,
7
+ };
8
+ use peerbit_shared_log_rust::{EntryCoordinateCommit, GidLeaderPlan};
9
+ use std::collections::{HashMap, HashSet};
10
+ use wasm_bindgen::prelude::*;
11
+
12
+ use crate::js_interop::{
13
+ bytes_vec_from_array, ensure_same_len, hash_number_u64, numbers_to_rows, strings_from_array,
14
+ strings_slice_to_array, strings_to_array,
15
+ };
16
+ use crate::shared_log_plan::{
17
+ clamp_replicas_u32, coordinate_commits_from_string_columns,
18
+ coordinate_commits_from_u64_columns, leader_samples_to_rows,
19
+ };
20
+ use crate::NativePeerbitBackbone;
21
+
22
+ pub(crate) struct PendingRawReceiveEntry {
23
+ storage_bytes: Vec<u8>,
24
+ entry: LogIndexEntry,
25
+ requested_replicas: Option<u32>,
26
+ signature_verified: bool,
27
+ signable_prefix_len: usize,
28
+ signature_with_key_start: usize,
29
+ signature_with_key_len: usize,
30
+ }
31
+
32
+ impl PendingRawReceiveEntry {
33
+ pub(crate) fn storage_bytes(&self) -> &[u8] {
34
+ &self.storage_bytes
35
+ }
36
+
37
+ fn signature_input(&self) -> PreparedEntryV0SignatureInput<'_> {
38
+ PreparedEntryV0SignatureInput {
39
+ storage_bytes: &self.storage_bytes,
40
+ signable_prefix_len: self.signable_prefix_len,
41
+ signature_with_key_start: self.signature_with_key_start,
42
+ signature_with_key_len: self.signature_with_key_len,
43
+ }
44
+ }
45
+ }
46
+
47
+ struct PendingRawReceiveGroupPlan {
48
+ gid: String,
49
+ hashes: Vec<String>,
50
+ indexes: Vec<u32>,
51
+ requested_replicas: Vec<u32>,
52
+ latest_hash: String,
53
+ latest_index: u32,
54
+ latest_wall_time: u64,
55
+ latest_logical: u32,
56
+ max_requested_replicas: u32,
57
+ }
58
+
59
+ struct ResolvedPendingRawReceiveGroupPlan {
60
+ gid: String,
61
+ hashes: Vec<String>,
62
+ indexes: Vec<u32>,
63
+ requested_replicas: Vec<u32>,
64
+ latest_hash: String,
65
+ latest_index: u32,
66
+ max_replicas_from_head: u32,
67
+ max_replicas_from_new_entries: u32,
68
+ max_max_replicas: u32,
69
+ }
70
+
71
+ fn leader_plan_contains_hash(plan: &GidLeaderPlan, hash: &str) -> bool {
72
+ plan.leaders.iter().any(|leader| leader.hash == hash)
73
+ }
74
+
75
+ fn prepared_raw_receive_selection_all_drop(
76
+ hashes: Vec<String>,
77
+ group_count: usize,
78
+ planned_hash_count: usize,
79
+ used_leader_sample_plans: bool,
80
+ ) -> JsValue {
81
+ let retained_indexes: Vec<u32> = Vec::new();
82
+ let dropped_indexes: Vec<u32> = (0..hashes.len() as u32).collect();
83
+ let out = Array::new();
84
+ out.push(&strings_to_array(Vec::new()));
85
+ out.push(&strings_to_array(hashes));
86
+ out.push(&JsValue::from_f64(group_count as f64));
87
+ out.push(&JsValue::from_f64(planned_hash_count as f64));
88
+ out.push(&JsValue::TRUE);
89
+ out.push(&JsValue::from_bool(used_leader_sample_plans));
90
+ out.push(&JsValue::UNDEFINED);
91
+ out.push(&Uint32Array::from(retained_indexes.as_slice()));
92
+ out.push(&Uint32Array::from(dropped_indexes.as_slice()));
93
+ out.into()
94
+ }
95
+
96
+ fn prepared_raw_receive_selection_from_leader_plans(
97
+ resolution: &str,
98
+ groups: &[ResolvedPendingRawReceiveGroupPlan],
99
+ expected_hash_count: usize,
100
+ planned_hash_count: usize,
101
+ leader_plans: &[GidLeaderPlan],
102
+ self_hash: &str,
103
+ used_leader_sample_plans: bool,
104
+ ) -> Result<JsValue, JsValue> {
105
+ if leader_plans.len() != groups.len() {
106
+ return Ok(JsValue::UNDEFINED);
107
+ }
108
+
109
+ let mut retained_hashes = Vec::new();
110
+ let mut dropped_hashes = Vec::new();
111
+ let mut retained_groups = vec![false; groups.len()];
112
+ let mut retained_original_indexes = vec![false; expected_hash_count];
113
+ for (index, (group, leader_plan)) in groups.iter().zip(leader_plans).enumerate() {
114
+ if leader_plan_contains_hash(leader_plan, self_hash) {
115
+ retained_groups[index] = true;
116
+ retained_hashes.extend(group.hashes.iter().cloned());
117
+ for original_index in &group.indexes {
118
+ let original_index = *original_index as usize;
119
+ if original_index >= retained_original_indexes.len() {
120
+ return Ok(JsValue::UNDEFINED);
121
+ }
122
+ retained_original_indexes[original_index] = true;
123
+ }
124
+ } else {
125
+ dropped_hashes.extend(group.hashes.iter().cloned());
126
+ }
127
+ }
128
+
129
+ let mut retained_indexes = Vec::new();
130
+ let mut dropped_indexes = Vec::new();
131
+ for (original_index, retained) in retained_original_indexes.iter().enumerate() {
132
+ let original_index = u32::try_from(original_index)
133
+ .map_err(|_| JsValue::from_str("Raw receive original index overflow"))?;
134
+ if *retained {
135
+ retained_indexes.push(original_index);
136
+ } else {
137
+ dropped_indexes.push(original_index);
138
+ }
139
+ }
140
+
141
+ let used_native_fast_drop_plan = retained_hashes.is_empty();
142
+ let retained_group_leader_plans = Array::new();
143
+ if !used_native_fast_drop_plan {
144
+ let mut selected_index_by_original = vec![None; expected_hash_count];
145
+ let mut selected_index = 0u32;
146
+ for (original_index, retained) in retained_original_indexes.iter().enumerate() {
147
+ if *retained {
148
+ selected_index_by_original[original_index] = Some(selected_index);
149
+ selected_index = selected_index
150
+ .checked_add(1)
151
+ .ok_or_else(|| JsValue::from_str("Raw receive selected index overflow"))?;
152
+ }
153
+ }
154
+ for (index, (group, leader_plan)) in groups.iter().zip(leader_plans).enumerate() {
155
+ if !retained_groups[index] {
156
+ continue;
157
+ }
158
+ let mut selected_indexes = Vec::with_capacity(group.indexes.len());
159
+ for original_index in &group.indexes {
160
+ let Some(selected_index) = selected_index_by_original
161
+ .get(*original_index as usize)
162
+ .and_then(|value| *value)
163
+ else {
164
+ return Ok(JsValue::UNDEFINED);
165
+ };
166
+ selected_indexes.push(selected_index);
167
+ }
168
+ let Some(selected_latest_index) = selected_index_by_original
169
+ .get(group.latest_index as usize)
170
+ .and_then(|value| *value)
171
+ else {
172
+ return Ok(JsValue::UNDEFINED);
173
+ };
174
+ let row = Array::new();
175
+ row.push(&JsValue::from_str(&group.gid));
176
+ row.push(&Uint32Array::from(selected_indexes.as_slice()));
177
+ row.push(&Uint32Array::from(group.requested_replicas.as_slice()));
178
+ row.push(&JsValue::from_f64(selected_latest_index as f64));
179
+ row.push(&JsValue::from_f64(group.max_replicas_from_head as f64));
180
+ row.push(&JsValue::from_f64(
181
+ group.max_replicas_from_new_entries as f64,
182
+ ));
183
+ row.push(&JsValue::from_f64(group.max_max_replicas as f64));
184
+ row.push(&numbers_to_rows(resolution, &leader_plan.coordinates));
185
+ row.push(&leader_samples_to_rows(&leader_plan.leaders));
186
+ retained_group_leader_plans.push(&row);
187
+ }
188
+ }
189
+ let out = Array::new();
190
+ out.push(&strings_to_array(retained_hashes));
191
+ out.push(&strings_to_array(dropped_hashes));
192
+ out.push(&JsValue::from_f64(groups.len() as f64));
193
+ out.push(&JsValue::from_f64(planned_hash_count as f64));
194
+ out.push(&JsValue::from_bool(used_native_fast_drop_plan));
195
+ out.push(&JsValue::from_bool(used_leader_sample_plans));
196
+ if retained_group_leader_plans.length() > 0 {
197
+ out.push(&retained_group_leader_plans);
198
+ } else {
199
+ out.push(&JsValue::UNDEFINED);
200
+ }
201
+ out.push(&Uint32Array::from(retained_indexes.as_slice()));
202
+ out.push(&Uint32Array::from(dropped_indexes.as_slice()));
203
+ Ok(out.into())
204
+ }
205
+
206
+ fn prepared_raw_entry_v0_to_row(entry: &PreparedRawEntryV0, hash_number: Option<u64>) -> Array {
207
+ let row = Array::new();
208
+ row.push(&JsValue::from_str(&entry.cid));
209
+ row.push(&Uint8Array::from(entry.hash_digest_bytes.as_slice()));
210
+ row.push(&JsValue::from_f64(entry.byte_length as f64));
211
+ row.push(&Uint8Array::from(entry.clock_id.as_slice()));
212
+ row.push(&JsValue::from_str(&entry.wall_time.to_string()));
213
+ row.push(&JsValue::from_f64(entry.logical as f64));
214
+ row.push(&JsValue::from_str(&entry.gid));
215
+ row.push(&strings_to_array(entry.next.clone()));
216
+ row.push(&JsValue::from_f64(entry.entry_type as f64));
217
+ row.push(&Uint8Array::from(entry.meta_bytes.as_slice()));
218
+ match &entry.meta_data {
219
+ Some(data) => row.push(&Uint8Array::from(data.as_slice())),
220
+ None => row.push(&JsValue::UNDEFINED),
221
+ };
222
+ row.push(&JsValue::from_f64(entry.payload_byte_length as f64));
223
+ row.push(&JsValue::from_bool(entry.signature_verified));
224
+ match entry.requested_replicas {
225
+ Some(value) => row.push(&JsValue::from_f64(value as f64)),
226
+ None => row.push(&JsValue::UNDEFINED),
227
+ };
228
+ match hash_number {
229
+ Some(value) => row.push(&JsValue::from_str(&value.to_string())),
230
+ None => row.push(&JsValue::UNDEFINED),
231
+ };
232
+ row
233
+ }
234
+
235
+ #[wasm_bindgen]
236
+ impl NativePeerbitBackbone {
237
+ pub fn prepare_raw_receive_batch(&mut self, blocks: Array) -> Result<Array, JsValue> {
238
+ let profile_enabled = self.append_profile_enabled;
239
+ let input_started = profile_enabled.then(js_sys::Date::now);
240
+ let blocks = bytes_vec_from_array(blocks)?;
241
+ if let Some(started) = input_started {
242
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
243
+ }
244
+ let prepared = self.prepare_raw_receive_entries(blocks, None, true)?;
245
+ let out = Array::new();
246
+ for entry in prepared {
247
+ let hash_number = hash_number_u64(&self.resolution, &entry.hash_digest_bytes)?;
248
+ let row = prepared_raw_entry_v0_to_row(&entry, Some(hash_number));
249
+ let log_entry = entry.log_index_entry(true)?;
250
+ self.pending_raw_receive_entries.insert(
251
+ entry.cid.clone(),
252
+ PendingRawReceiveEntry {
253
+ storage_bytes: entry.storage_bytes,
254
+ entry: log_entry,
255
+ requested_replicas: entry.requested_replicas,
256
+ signature_verified: entry.signature_verified,
257
+ signable_prefix_len: entry.signable_prefix_len,
258
+ signature_with_key_start: entry.signature_with_key_start,
259
+ signature_with_key_len: entry.signature_with_key_len,
260
+ },
261
+ );
262
+ out.push(&row);
263
+ }
264
+ Ok(out)
265
+ }
266
+
267
+ pub fn prepare_raw_receive_columns_batch(&mut self, blocks: Array) -> Result<Array, JsValue> {
268
+ let profile_enabled = self.append_profile_enabled;
269
+ let input_started = profile_enabled.then(js_sys::Date::now);
270
+ let blocks = bytes_vec_from_array(blocks)?;
271
+ if let Some(started) = input_started {
272
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
273
+ }
274
+ let prepared = self.prepare_raw_receive_entries(blocks, None, true)?;
275
+ self.prepare_raw_receive_columns_from_entries(prepared, true, true)
276
+ }
277
+
278
+ pub fn prepare_raw_receive_unverified_columns_batch(
279
+ &mut self,
280
+ blocks: Array,
281
+ ) -> Result<Array, JsValue> {
282
+ let profile_enabled = self.append_profile_enabled;
283
+ let input_started = profile_enabled.then(js_sys::Date::now);
284
+ let blocks = bytes_vec_from_array(blocks)?;
285
+ if let Some(started) = input_started {
286
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
287
+ }
288
+ let prepared = self.prepare_raw_receive_entries(blocks, None, false)?;
289
+ self.prepare_raw_receive_columns_from_entries(prepared, true, true)
290
+ }
291
+
292
+ pub fn prepare_raw_receive_expected_columns_batch(
293
+ &mut self,
294
+ blocks: Array,
295
+ hashes: Array,
296
+ ) -> Result<Array, JsValue> {
297
+ let profile_enabled = self.append_profile_enabled;
298
+ let input_started = profile_enabled.then(js_sys::Date::now);
299
+ let blocks = bytes_vec_from_array(blocks)?;
300
+ let hashes = strings_from_array(hashes)?;
301
+ if let Some(started) = input_started {
302
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
303
+ }
304
+ let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), true)?;
305
+ self.prepare_raw_receive_columns_from_entries(prepared, true, true)
306
+ }
307
+
308
+ pub fn prepare_raw_receive_unverified_expected_columns_batch(
309
+ &mut self,
310
+ blocks: Array,
311
+ hashes: Array,
312
+ ) -> Result<Array, JsValue> {
313
+ let profile_enabled = self.append_profile_enabled;
314
+ let input_started = profile_enabled.then(js_sys::Date::now);
315
+ let blocks = bytes_vec_from_array(blocks)?;
316
+ let hashes = strings_from_array(hashes)?;
317
+ if let Some(started) = input_started {
318
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
319
+ }
320
+ let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), false)?;
321
+ self.prepare_raw_receive_columns_from_entries(prepared, true, true)
322
+ }
323
+
324
+ pub fn prepare_raw_receive_expected_compact_columns_batch(
325
+ &mut self,
326
+ blocks: Array,
327
+ hashes: Array,
328
+ ) -> Result<Array, JsValue> {
329
+ let profile_enabled = self.append_profile_enabled;
330
+ let input_started = profile_enabled.then(js_sys::Date::now);
331
+ let blocks = bytes_vec_from_array(blocks)?;
332
+ let hashes = strings_from_array(hashes)?;
333
+ if let Some(started) = input_started {
334
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
335
+ }
336
+ let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), true)?;
337
+ self.prepare_raw_receive_columns_from_entries(prepared, false, false)
338
+ }
339
+
340
+ pub fn prepare_raw_receive_unverified_expected_compact_columns_batch(
341
+ &mut self,
342
+ blocks: Array,
343
+ hashes: Array,
344
+ ) -> Result<Array, JsValue> {
345
+ let profile_enabled = self.append_profile_enabled;
346
+ let input_started = profile_enabled.then(js_sys::Date::now);
347
+ let blocks = bytes_vec_from_array(blocks)?;
348
+ let hashes = strings_from_array(hashes)?;
349
+ if let Some(started) = input_started {
350
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
351
+ }
352
+ let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes), false)?;
353
+ self.prepare_raw_receive_columns_from_entries(prepared, false, false)
354
+ }
355
+
356
+ #[allow(clippy::too_many_arguments)]
357
+ pub fn prepare_raw_receive_unverified_expected_compact_columns_and_selection_batch(
358
+ &mut self,
359
+ blocks: Array,
360
+ hashes: Array,
361
+ min_replicas: u32,
362
+ max_replicas: JsValue,
363
+ role_age_ms: f64,
364
+ now: String,
365
+ peer_filter: JsValue,
366
+ expand_peer_filter: bool,
367
+ self_hash: String,
368
+ include_self: bool,
369
+ full_replica_fallback: bool,
370
+ include_strict_full_replica: bool,
371
+ _from_hash: String,
372
+ ) -> Result<JsValue, JsValue> {
373
+ let profile_enabled = self.append_profile_enabled;
374
+ let input_started = profile_enabled.then(js_sys::Date::now);
375
+ let blocks = bytes_vec_from_array(blocks)?;
376
+ let hashes = strings_from_array(hashes)?;
377
+ if let Some(started) = input_started {
378
+ self.append_profile.raw_receive_input_copy_ms += js_sys::Date::now() - started;
379
+ }
380
+ let prepared = self.prepare_raw_receive_entries(blocks, Some(hashes.clone()), false)?;
381
+ let columns = self.prepare_raw_receive_columns_from_entries(prepared, false, false)?;
382
+ let selection = self.plan_prepared_raw_receive_selection_core(
383
+ hashes,
384
+ min_replicas,
385
+ max_replicas,
386
+ role_age_ms,
387
+ now,
388
+ peer_filter,
389
+ expand_peer_filter,
390
+ self_hash,
391
+ include_self,
392
+ full_replica_fallback,
393
+ include_strict_full_replica,
394
+ )?;
395
+ let out = Array::new();
396
+ out.push(&columns);
397
+ out.push(&selection);
398
+ Ok(out.into())
399
+ }
400
+
401
+ pub fn plan_prepared_raw_receive_groups(
402
+ &self,
403
+ hashes: Array,
404
+ min_replicas: u32,
405
+ max_replicas: JsValue,
406
+ ) -> Result<JsValue, JsValue> {
407
+ let hashes = strings_from_array(hashes)?;
408
+ let Some(groups) =
409
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
410
+ else {
411
+ return Ok(JsValue::UNDEFINED);
412
+ };
413
+
414
+ let out = Array::new();
415
+ for group in groups {
416
+ let row = Array::new();
417
+ row.push(&JsValue::from_str(&group.gid));
418
+ row.push(&strings_to_array(group.hashes));
419
+ row.push(&Uint32Array::from(group.requested_replicas.as_slice()));
420
+ row.push(&JsValue::from_str(&group.latest_hash));
421
+ row.push(&JsValue::from_f64(group.max_replicas_from_head as f64));
422
+ row.push(&JsValue::from_f64(
423
+ group.max_replicas_from_new_entries as f64,
424
+ ));
425
+ row.push(&JsValue::from_f64(group.max_max_replicas as f64));
426
+ out.push(&row);
427
+ }
428
+
429
+ Ok(out.into())
430
+ }
431
+
432
+ pub fn plan_prepared_raw_receive_group_indexes(
433
+ &self,
434
+ hashes: Array,
435
+ min_replicas: u32,
436
+ max_replicas: JsValue,
437
+ ) -> Result<JsValue, JsValue> {
438
+ let hashes = strings_from_array(hashes)?;
439
+ let Some(groups) =
440
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
441
+ else {
442
+ return Ok(JsValue::UNDEFINED);
443
+ };
444
+
445
+ let out = Array::new();
446
+ for group in groups {
447
+ let row = Array::new();
448
+ row.push(&JsValue::from_str(&group.gid));
449
+ row.push(&Uint32Array::from(group.indexes.as_slice()));
450
+ row.push(&Uint32Array::from(group.requested_replicas.as_slice()));
451
+ row.push(&JsValue::from_f64(group.latest_index as f64));
452
+ row.push(&JsValue::from_f64(group.max_replicas_from_head as f64));
453
+ row.push(&JsValue::from_f64(
454
+ group.max_replicas_from_new_entries as f64,
455
+ ));
456
+ row.push(&JsValue::from_f64(group.max_max_replicas as f64));
457
+ out.push(&row);
458
+ }
459
+
460
+ Ok(out.into())
461
+ }
462
+
463
+ #[allow(clippy::too_many_arguments)]
464
+ pub fn plan_prepared_raw_receive_group_leaders(
465
+ &self,
466
+ hashes: Array,
467
+ min_replicas: u32,
468
+ max_replicas: JsValue,
469
+ role_age_ms: f64,
470
+ now: String,
471
+ peer_filter: JsValue,
472
+ expand_peer_filter: bool,
473
+ self_hash: String,
474
+ include_self: bool,
475
+ full_replica_fallback: bool,
476
+ include_strict_full_replica: bool,
477
+ ) -> Result<JsValue, JsValue> {
478
+ let hashes = strings_from_array(hashes)?;
479
+ let Some(groups) =
480
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
481
+ else {
482
+ return Ok(JsValue::UNDEFINED);
483
+ };
484
+ let gids = Array::new();
485
+ let replica_counts = Array::new();
486
+ for group in &groups {
487
+ gids.push(&JsValue::from_str(&group.gid));
488
+ replica_counts.push(&JsValue::from_f64(group.max_max_replicas as f64));
489
+ }
490
+ let leader_plans = self.shared_log.plan_leaders_for_gids_batch(
491
+ gids,
492
+ replica_counts,
493
+ role_age_ms,
494
+ now,
495
+ peer_filter,
496
+ expand_peer_filter,
497
+ self_hash,
498
+ include_self,
499
+ full_replica_fallback,
500
+ include_strict_full_replica,
501
+ )?;
502
+ if leader_plans.length() as usize != groups.len() {
503
+ return Ok(JsValue::UNDEFINED);
504
+ }
505
+
506
+ let out = Array::new();
507
+ for (index, group) in groups.into_iter().enumerate() {
508
+ let leader_plan = Array::from(&leader_plans.get(index as u32));
509
+ if leader_plan.length() < 2 {
510
+ return Ok(JsValue::UNDEFINED);
511
+ }
512
+ let row = Array::new();
513
+ row.push(&JsValue::from_str(&group.gid));
514
+ row.push(&Uint32Array::from(group.indexes.as_slice()));
515
+ row.push(&Uint32Array::from(group.requested_replicas.as_slice()));
516
+ row.push(&JsValue::from_f64(group.latest_index as f64));
517
+ row.push(&JsValue::from_f64(group.max_replicas_from_head as f64));
518
+ row.push(&JsValue::from_f64(
519
+ group.max_replicas_from_new_entries as f64,
520
+ ));
521
+ row.push(&JsValue::from_f64(group.max_max_replicas as f64));
522
+ row.push(&leader_plan.get(0));
523
+ row.push(&leader_plan.get(1));
524
+ out.push(&row);
525
+ }
526
+
527
+ Ok(out.into())
528
+ }
529
+
530
+ #[allow(clippy::too_many_arguments)]
531
+ pub fn plan_prepared_raw_receive_group_assignments(
532
+ &self,
533
+ hashes: Array,
534
+ min_replicas: u32,
535
+ max_replicas: JsValue,
536
+ role_age_ms: f64,
537
+ now: String,
538
+ peer_filter: JsValue,
539
+ expand_peer_filter: bool,
540
+ self_hash: String,
541
+ include_self: bool,
542
+ full_replica_fallback: bool,
543
+ include_strict_full_replica: bool,
544
+ from_hash: String,
545
+ ) -> Result<JsValue, JsValue> {
546
+ let hashes = strings_from_array(hashes)?;
547
+ let Some(groups) =
548
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
549
+ else {
550
+ return Ok(JsValue::UNDEFINED);
551
+ };
552
+ let gids: Vec<String> = groups.iter().map(|group| group.gid.clone()).collect();
553
+ let replica_counts: Vec<usize> = groups
554
+ .iter()
555
+ .map(|group| group.max_max_replicas as usize)
556
+ .collect();
557
+ let assignments = self
558
+ .shared_log
559
+ .plan_leader_assignments_for_gids_batch_core(
560
+ &gids,
561
+ &replica_counts,
562
+ role_age_ms,
563
+ now,
564
+ peer_filter,
565
+ expand_peer_filter,
566
+ &self_hash,
567
+ include_self,
568
+ full_replica_fallback,
569
+ include_strict_full_replica,
570
+ &from_hash,
571
+ )?;
572
+ if assignments.len() != groups.len() {
573
+ return Ok(JsValue::UNDEFINED);
574
+ }
575
+
576
+ let out = Array::new();
577
+ for (group, assignment) in groups.into_iter().zip(assignments.into_iter()) {
578
+ let row = Array::new();
579
+ row.push(&JsValue::from_str(&group.gid));
580
+ row.push(&Uint32Array::from(group.indexes.as_slice()));
581
+ row.push(&Uint32Array::from(group.requested_replicas.as_slice()));
582
+ row.push(&JsValue::from_f64(group.latest_index as f64));
583
+ row.push(&JsValue::from_f64(group.max_replicas_from_head as f64));
584
+ row.push(&JsValue::from_f64(
585
+ group.max_replicas_from_new_entries as f64,
586
+ ));
587
+ row.push(&JsValue::from_f64(group.max_max_replicas as f64));
588
+ row.push(&numbers_to_rows(&self.resolution, &assignment.coordinates));
589
+ row.push(&JsValue::from_bool(assignment.is_self_leader));
590
+ row.push(&JsValue::from_bool(assignment.from_is_leader));
591
+ row.push(&JsValue::from_bool(assignment.assigned_to_range_boundary));
592
+ out.push(&row);
593
+ }
594
+
595
+ Ok(out.into())
596
+ }
597
+
598
+ #[allow(clippy::too_many_arguments)]
599
+ pub fn plan_prepared_raw_receive_selection(
600
+ &self,
601
+ hashes: Array,
602
+ min_replicas: u32,
603
+ max_replicas: JsValue,
604
+ role_age_ms: f64,
605
+ now: String,
606
+ peer_filter: JsValue,
607
+ expand_peer_filter: bool,
608
+ self_hash: String,
609
+ include_self: bool,
610
+ full_replica_fallback: bool,
611
+ include_strict_full_replica: bool,
612
+ _from_hash: String,
613
+ ) -> Result<JsValue, JsValue> {
614
+ self.plan_prepared_raw_receive_selection_core(
615
+ strings_from_array(hashes)?,
616
+ min_replicas,
617
+ max_replicas,
618
+ role_age_ms,
619
+ now,
620
+ peer_filter,
621
+ expand_peer_filter,
622
+ self_hash,
623
+ include_self,
624
+ full_replica_fallback,
625
+ include_strict_full_replica,
626
+ )
627
+ }
628
+
629
+ #[allow(clippy::too_many_arguments)]
630
+ pub fn plan_prepared_raw_receive_fast_drop(
631
+ &self,
632
+ hashes: Array,
633
+ min_replicas: u32,
634
+ max_replicas: JsValue,
635
+ role_age_ms: f64,
636
+ now: String,
637
+ peer_filter: JsValue,
638
+ expand_peer_filter: bool,
639
+ self_hash: String,
640
+ include_self: bool,
641
+ full_replica_fallback: bool,
642
+ include_strict_full_replica: bool,
643
+ _from_hash: String,
644
+ ) -> Result<JsValue, JsValue> {
645
+ let hashes = strings_from_array(hashes)?;
646
+ let expected_hash_count = hashes.len();
647
+ if expected_hash_count == 0 {
648
+ return Ok(JsValue::UNDEFINED);
649
+ }
650
+ let Some(groups) =
651
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
652
+ else {
653
+ return Ok(JsValue::UNDEFINED);
654
+ };
655
+ let mut planned_hash_count = 0usize;
656
+ let gids = Array::new();
657
+ let replica_counts = Array::new();
658
+ for group in &groups {
659
+ planned_hash_count += group.hashes.len();
660
+ gids.push(&JsValue::from_str(&group.gid));
661
+ replica_counts.push(&JsValue::from_f64(group.max_max_replicas as f64));
662
+ }
663
+ if planned_hash_count != expected_hash_count {
664
+ return Ok(JsValue::UNDEFINED);
665
+ }
666
+
667
+ let leader_rows = self.shared_log.plan_leader_samples_for_gids_batch(
668
+ gids,
669
+ replica_counts,
670
+ role_age_ms,
671
+ now,
672
+ peer_filter,
673
+ expand_peer_filter,
674
+ self_hash.clone(),
675
+ include_self,
676
+ full_replica_fallback,
677
+ include_strict_full_replica,
678
+ )?;
679
+ if leader_rows.length() as usize != groups.len() {
680
+ return Ok(JsValue::UNDEFINED);
681
+ }
682
+ for group_leaders in leader_rows.iter() {
683
+ let rows = Array::from(&group_leaders);
684
+ for row_value in rows.iter() {
685
+ let row = Array::from(&row_value);
686
+ let Some(leader_hash) = row.get(0).as_string() else {
687
+ return Ok(JsValue::UNDEFINED);
688
+ };
689
+ if leader_hash == self_hash {
690
+ let out = Array::new();
691
+ out.push(&JsValue::FALSE);
692
+ out.push(&JsValue::from_f64(groups.len() as f64));
693
+ out.push(&JsValue::from_f64(planned_hash_count as f64));
694
+ return Ok(out.into());
695
+ }
696
+ }
697
+ }
698
+
699
+ let out = Array::new();
700
+ out.push(&JsValue::TRUE);
701
+ out.push(&JsValue::from_f64(groups.len() as f64));
702
+ out.push(&JsValue::from_f64(planned_hash_count as f64));
703
+ Ok(out.into())
704
+ }
705
+
706
+ #[allow(clippy::too_many_arguments)]
707
+ pub fn select_prepared_raw_receive_hashes(
708
+ &self,
709
+ hashes: Array,
710
+ min_replicas: u32,
711
+ max_replicas: JsValue,
712
+ role_age_ms: f64,
713
+ now: String,
714
+ peer_filter: JsValue,
715
+ expand_peer_filter: bool,
716
+ self_hash: String,
717
+ include_self: bool,
718
+ full_replica_fallback: bool,
719
+ include_strict_full_replica: bool,
720
+ _from_hash: String,
721
+ ) -> Result<JsValue, JsValue> {
722
+ let hashes = strings_from_array(hashes)?;
723
+ let expected_hash_count = hashes.len();
724
+ if expected_hash_count == 0 {
725
+ return Ok(JsValue::UNDEFINED);
726
+ }
727
+ let Some(groups) =
728
+ self.prepared_raw_receive_group_plans(hashes, min_replicas, max_replicas)?
729
+ else {
730
+ return Ok(JsValue::UNDEFINED);
731
+ };
732
+ let mut planned_hash_count = 0usize;
733
+ let mut gids = Vec::with_capacity(groups.len());
734
+ let mut replica_counts = Vec::with_capacity(groups.len());
735
+ for group in &groups {
736
+ planned_hash_count += group.hashes.len();
737
+ gids.push(group.gid.clone());
738
+ replica_counts.push(group.max_max_replicas as usize);
739
+ }
740
+ if planned_hash_count != expected_hash_count {
741
+ return Ok(JsValue::UNDEFINED);
742
+ }
743
+
744
+ let leader_plans = self.shared_log.plan_leaders_for_gids_batch_core(
745
+ &gids,
746
+ &replica_counts,
747
+ role_age_ms,
748
+ &now,
749
+ peer_filter,
750
+ expand_peer_filter,
751
+ &self_hash,
752
+ include_self,
753
+ full_replica_fallback,
754
+ include_strict_full_replica,
755
+ )?;
756
+ if leader_plans.len() != groups.len() {
757
+ return Ok(JsValue::UNDEFINED);
758
+ }
759
+
760
+ prepared_raw_receive_selection_from_leader_plans(
761
+ &self.resolution,
762
+ &groups,
763
+ expected_hash_count,
764
+ planned_hash_count,
765
+ &leader_plans,
766
+ &self_hash,
767
+ false,
768
+ )
769
+ }
770
+
771
+ pub fn verify_prepared_raw_receive_entries(
772
+ &mut self,
773
+ hashes: Array,
774
+ ) -> Result<JsValue, JsValue> {
775
+ let hashes = strings_from_array(hashes)?;
776
+ let Some(out) = self.verify_pending_raw_receive_entries(&hashes)? else {
777
+ return Ok(JsValue::UNDEFINED);
778
+ };
779
+ Ok(Uint8Array::from(out.as_slice()).into())
780
+ }
781
+
782
+ #[allow(clippy::too_many_arguments)]
783
+ pub fn commit_prepared_raw_receive_batch(
784
+ &mut self,
785
+ hashes: Array,
786
+ heads: Uint8Array,
787
+ coordinate_hashes: Array,
788
+ coordinate_gids: Array,
789
+ coordinate_hash_numbers: Array,
790
+ coordinate_batches: Array,
791
+ coordinate_next_hash_batches: Array,
792
+ coordinate_assigned_to_range_boundaries: Uint8Array,
793
+ coordinate_requested_replicas: Array,
794
+ ) -> Result<bool, JsValue> {
795
+ let hashes = strings_from_array(hashes)?;
796
+ let coordinate_commits = coordinate_commits_from_string_columns(
797
+ coordinate_hashes,
798
+ coordinate_gids,
799
+ coordinate_hash_numbers,
800
+ coordinate_batches,
801
+ coordinate_next_hash_batches,
802
+ coordinate_assigned_to_range_boundaries,
803
+ coordinate_requested_replicas,
804
+ )?;
805
+ self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)
806
+ }
807
+
808
+ #[allow(clippy::too_many_arguments)]
809
+ pub fn commit_prepared_raw_receive_batch_u64(
810
+ &mut self,
811
+ hashes: Array,
812
+ heads: Uint8Array,
813
+ coordinate_hashes: Array,
814
+ coordinate_gids: Array,
815
+ coordinate_hash_numbers: BigUint64Array,
816
+ coordinate_counts: Uint32Array,
817
+ coordinates: BigUint64Array,
818
+ coordinate_next_hash_batches: Array,
819
+ coordinate_assigned_to_range_boundaries: Uint8Array,
820
+ coordinate_requested_replicas: Uint32Array,
821
+ ) -> Result<bool, JsValue> {
822
+ let hashes = strings_from_array(hashes)?;
823
+ let coordinate_commits = coordinate_commits_from_u64_columns(
824
+ coordinate_hashes,
825
+ coordinate_gids,
826
+ coordinate_hash_numbers,
827
+ coordinate_counts,
828
+ coordinates,
829
+ coordinate_next_hash_batches,
830
+ coordinate_assigned_to_range_boundaries,
831
+ coordinate_requested_replicas,
832
+ )?;
833
+ self.commit_prepared_raw_receive_batch_core(hashes, &heads, coordinate_commits)
834
+ }
835
+
836
+ #[allow(clippy::too_many_arguments)]
837
+ pub fn commit_prepared_raw_receive_join_batch(
838
+ &mut self,
839
+ hashes: Array,
840
+ heads: Uint8Array,
841
+ coordinate_hashes: Array,
842
+ coordinate_gids: Array,
843
+ coordinate_hash_numbers: Array,
844
+ coordinate_batches: Array,
845
+ coordinate_next_hash_batches: Array,
846
+ coordinate_assigned_to_range_boundaries: Uint8Array,
847
+ coordinate_requested_replicas: Array,
848
+ ) -> Result<bool, JsValue> {
849
+ let hashes = strings_from_array(hashes)?;
850
+ let coordinate_commits = coordinate_commits_from_string_columns(
851
+ coordinate_hashes,
852
+ coordinate_gids,
853
+ coordinate_hash_numbers,
854
+ coordinate_batches,
855
+ coordinate_next_hash_batches,
856
+ coordinate_assigned_to_range_boundaries,
857
+ coordinate_requested_replicas,
858
+ )?;
859
+ self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)
860
+ }
861
+
862
+ #[allow(clippy::too_many_arguments)]
863
+ pub fn commit_prepared_raw_receive_join_batch_u64(
864
+ &mut self,
865
+ hashes: Array,
866
+ heads: Uint8Array,
867
+ coordinate_hashes: Array,
868
+ coordinate_gids: Array,
869
+ coordinate_hash_numbers: BigUint64Array,
870
+ coordinate_counts: Uint32Array,
871
+ coordinates: BigUint64Array,
872
+ coordinate_next_hash_batches: Array,
873
+ coordinate_assigned_to_range_boundaries: Uint8Array,
874
+ coordinate_requested_replicas: Uint32Array,
875
+ ) -> Result<bool, JsValue> {
876
+ let hashes = strings_from_array(hashes)?;
877
+ let coordinate_commits = coordinate_commits_from_u64_columns(
878
+ coordinate_hashes,
879
+ coordinate_gids,
880
+ coordinate_hash_numbers,
881
+ coordinate_counts,
882
+ coordinates,
883
+ coordinate_next_hash_batches,
884
+ coordinate_assigned_to_range_boundaries,
885
+ coordinate_requested_replicas,
886
+ )?;
887
+ self.commit_prepared_raw_receive_join_batch_core(hashes, &heads, coordinate_commits)
888
+ }
889
+
890
+ #[allow(clippy::too_many_arguments)]
891
+ pub fn commit_verified_prepared_raw_receive_join_batch(
892
+ &mut self,
893
+ hashes: Array,
894
+ heads: Uint8Array,
895
+ verify_hashes: Array,
896
+ coordinate_hashes: Array,
897
+ coordinate_gids: Array,
898
+ coordinate_hash_numbers: Array,
899
+ coordinate_batches: Array,
900
+ coordinate_next_hash_batches: Array,
901
+ coordinate_assigned_to_range_boundaries: Uint8Array,
902
+ coordinate_requested_replicas: Array,
903
+ ) -> Result<bool, JsValue> {
904
+ let hashes = strings_from_array(hashes)?;
905
+ let verify_hashes = strings_from_array(verify_hashes)?;
906
+ let coordinate_commits = coordinate_commits_from_string_columns(
907
+ coordinate_hashes,
908
+ coordinate_gids,
909
+ coordinate_hash_numbers,
910
+ coordinate_batches,
911
+ coordinate_next_hash_batches,
912
+ coordinate_assigned_to_range_boundaries,
913
+ coordinate_requested_replicas,
914
+ )?;
915
+ self.commit_verified_prepared_raw_receive_join_batch_core(
916
+ hashes,
917
+ &heads,
918
+ Some(verify_hashes),
919
+ coordinate_commits,
920
+ )
921
+ }
922
+
923
+ #[allow(clippy::too_many_arguments)]
924
+ pub fn commit_verified_prepared_raw_receive_join_batch_u64(
925
+ &mut self,
926
+ hashes: Array,
927
+ heads: Uint8Array,
928
+ verify_hashes: Array,
929
+ coordinate_hashes: Array,
930
+ coordinate_gids: Array,
931
+ coordinate_hash_numbers: BigUint64Array,
932
+ coordinate_counts: Uint32Array,
933
+ coordinates: BigUint64Array,
934
+ coordinate_next_hash_batches: Array,
935
+ coordinate_assigned_to_range_boundaries: Uint8Array,
936
+ coordinate_requested_replicas: Uint32Array,
937
+ ) -> Result<bool, JsValue> {
938
+ let hashes = strings_from_array(hashes)?;
939
+ let verify_hashes = strings_from_array(verify_hashes)?;
940
+ let coordinate_commits = coordinate_commits_from_u64_columns(
941
+ coordinate_hashes,
942
+ coordinate_gids,
943
+ coordinate_hash_numbers,
944
+ coordinate_counts,
945
+ coordinates,
946
+ coordinate_next_hash_batches,
947
+ coordinate_assigned_to_range_boundaries,
948
+ coordinate_requested_replicas,
949
+ )?;
950
+ self.commit_verified_prepared_raw_receive_join_batch_core(
951
+ hashes,
952
+ &heads,
953
+ Some(verify_hashes),
954
+ coordinate_commits,
955
+ )
956
+ }
957
+
958
+ #[allow(clippy::too_many_arguments)]
959
+ pub fn commit_verified_all_prepared_raw_receive_join_batch(
960
+ &mut self,
961
+ hashes: Array,
962
+ heads: Uint8Array,
963
+ coordinate_hashes: Array,
964
+ coordinate_gids: Array,
965
+ coordinate_hash_numbers: Array,
966
+ coordinate_batches: Array,
967
+ coordinate_next_hash_batches: Array,
968
+ coordinate_assigned_to_range_boundaries: Uint8Array,
969
+ coordinate_requested_replicas: Array,
970
+ ) -> Result<bool, JsValue> {
971
+ let hashes = strings_from_array(hashes)?;
972
+ let coordinate_commits = coordinate_commits_from_string_columns(
973
+ coordinate_hashes,
974
+ coordinate_gids,
975
+ coordinate_hash_numbers,
976
+ coordinate_batches,
977
+ coordinate_next_hash_batches,
978
+ coordinate_assigned_to_range_boundaries,
979
+ coordinate_requested_replicas,
980
+ )?;
981
+ self.commit_verified_prepared_raw_receive_join_batch_core(
982
+ hashes,
983
+ &heads,
984
+ None,
985
+ coordinate_commits,
986
+ )
987
+ }
988
+
989
+ #[allow(clippy::too_many_arguments)]
990
+ pub fn commit_verified_all_prepared_raw_receive_join_batch_u64(
991
+ &mut self,
992
+ hashes: Array,
993
+ heads: Uint8Array,
994
+ coordinate_hashes: Array,
995
+ coordinate_gids: Array,
996
+ coordinate_hash_numbers: BigUint64Array,
997
+ coordinate_counts: Uint32Array,
998
+ coordinates: BigUint64Array,
999
+ coordinate_next_hash_batches: Array,
1000
+ coordinate_assigned_to_range_boundaries: Uint8Array,
1001
+ coordinate_requested_replicas: Uint32Array,
1002
+ ) -> Result<bool, JsValue> {
1003
+ let hashes = strings_from_array(hashes)?;
1004
+ let coordinate_commits = coordinate_commits_from_u64_columns(
1005
+ coordinate_hashes,
1006
+ coordinate_gids,
1007
+ coordinate_hash_numbers,
1008
+ coordinate_counts,
1009
+ coordinates,
1010
+ coordinate_next_hash_batches,
1011
+ coordinate_assigned_to_range_boundaries,
1012
+ coordinate_requested_replicas,
1013
+ )?;
1014
+ self.commit_verified_prepared_raw_receive_join_batch_core(
1015
+ hashes,
1016
+ &heads,
1017
+ None,
1018
+ coordinate_commits,
1019
+ )
1020
+ }
1021
+
1022
+ pub fn clear_prepared_raw_receive_entries(&mut self, hashes: Array) -> Result<usize, JsValue> {
1023
+ let mut removed = 0;
1024
+ for hash in strings_from_array(hashes)? {
1025
+ if self.pending_raw_receive_entries.remove(&hash).is_some() {
1026
+ removed += 1;
1027
+ }
1028
+ }
1029
+ Ok(removed)
1030
+ }
1031
+ }
1032
+
1033
+ impl NativePeerbitBackbone {
1034
+ pub(crate) fn prepare_raw_receive_entries(
1035
+ &mut self,
1036
+ blocks: Vec<Vec<u8>>,
1037
+ expected_cids: Option<Vec<String>>,
1038
+ verify_signatures: bool,
1039
+ ) -> Result<Vec<PreparedRawEntryV0>, JsValue> {
1040
+ let profile_enabled = self.append_profile_enabled;
1041
+ let prepare_started = profile_enabled.then(js_sys::Date::now);
1042
+ let mut raw_profile = profile_enabled.then(RawEntryV0PrepareProfile::default);
1043
+ let prepared = prepare_raw_entry_v0_blocks_with_expected_cids_and_verify_profiled(
1044
+ blocks,
1045
+ expected_cids,
1046
+ verify_signatures,
1047
+ raw_profile.as_mut(),
1048
+ )?;
1049
+ if let Some(started) = prepare_started {
1050
+ self.append_profile.raw_receive_prepare_ms += js_sys::Date::now() - started;
1051
+ }
1052
+ if let Some(raw_profile) = raw_profile {
1053
+ self.append_profile.add_raw_prepare_profile(&raw_profile);
1054
+ }
1055
+ Ok(prepared)
1056
+ }
1057
+
1058
+ pub(crate) fn prepare_raw_receive_columns_from_entries(
1059
+ &mut self,
1060
+ prepared: Vec<PreparedRawEntryV0>,
1061
+ include_hash_digest_bytes: bool,
1062
+ include_cids: bool,
1063
+ ) -> Result<Array, JsValue> {
1064
+ let profile_enabled = self.append_profile_enabled;
1065
+ let columns_started = profile_enabled.then(js_sys::Date::now);
1066
+ let len = prepared.len();
1067
+ let cids = Array::new();
1068
+ let hash_digest_bytes = Array::new();
1069
+ let mut byte_lengths = Vec::with_capacity(len);
1070
+ let clock_ids = Array::new();
1071
+ let mut wall_times = Vec::with_capacity(len);
1072
+ let mut logicals = Vec::with_capacity(len);
1073
+ let gids = Array::new();
1074
+ let nexts = Array::new();
1075
+ let mut entry_types = Vec::with_capacity(len);
1076
+ let meta_bytes = Array::new();
1077
+ let meta_datas = Array::new();
1078
+ let mut payload_byte_lengths = Vec::with_capacity(len);
1079
+ let mut signature_verified = Vec::with_capacity(len);
1080
+ let mut requested_replicas = Vec::with_capacity(len);
1081
+ let mut hash_numbers = Vec::with_capacity(len);
1082
+
1083
+ for entry in prepared {
1084
+ let hash_number = hash_number_u64(&self.resolution, &entry.hash_digest_bytes)?;
1085
+ if include_cids {
1086
+ cids.push(&JsValue::from_str(&entry.cid));
1087
+ }
1088
+ if include_hash_digest_bytes {
1089
+ hash_digest_bytes.push(&Uint8Array::from(entry.hash_digest_bytes.as_slice()));
1090
+ }
1091
+ byte_lengths.push(entry.byte_length as u32);
1092
+ clock_ids.push(&Uint8Array::from(entry.clock_id.as_slice()));
1093
+ wall_times.push(entry.wall_time);
1094
+ logicals.push(entry.logical);
1095
+ gids.push(&JsValue::from_str(&entry.gid));
1096
+ nexts.push(&strings_slice_to_array(&entry.next));
1097
+ entry_types.push(entry.entry_type);
1098
+ meta_bytes.push(&Uint8Array::from(entry.meta_bytes.as_slice()));
1099
+ match &entry.meta_data {
1100
+ Some(data) => meta_datas.push(&Uint8Array::from(data.as_slice())),
1101
+ None => meta_datas.push(&JsValue::UNDEFINED),
1102
+ };
1103
+ payload_byte_lengths.push(entry.payload_byte_length as u32);
1104
+ signature_verified.push(u8::from(entry.signature_verified));
1105
+ requested_replicas.push(entry.requested_replicas.unwrap_or(0));
1106
+ hash_numbers.push(hash_number);
1107
+
1108
+ let log_entry = entry.log_index_entry(true)?;
1109
+ self.pending_raw_receive_entries.insert(
1110
+ entry.cid.clone(),
1111
+ PendingRawReceiveEntry {
1112
+ storage_bytes: entry.storage_bytes,
1113
+ entry: log_entry,
1114
+ requested_replicas: entry.requested_replicas,
1115
+ signature_verified: entry.signature_verified,
1116
+ signable_prefix_len: entry.signable_prefix_len,
1117
+ signature_with_key_start: entry.signature_with_key_start,
1118
+ signature_with_key_len: entry.signature_with_key_len,
1119
+ },
1120
+ );
1121
+ }
1122
+
1123
+ let out = Array::new();
1124
+ out.push(&cids);
1125
+ out.push(&hash_digest_bytes);
1126
+ out.push(&Uint32Array::from(byte_lengths.as_slice()));
1127
+ out.push(&clock_ids);
1128
+ out.push(&BigUint64Array::from(wall_times.as_slice()));
1129
+ out.push(&Uint32Array::from(logicals.as_slice()));
1130
+ out.push(&gids);
1131
+ out.push(&nexts);
1132
+ out.push(&Uint8Array::from(entry_types.as_slice()));
1133
+ out.push(&meta_bytes);
1134
+ out.push(&meta_datas);
1135
+ out.push(&Uint32Array::from(payload_byte_lengths.as_slice()));
1136
+ out.push(&Uint8Array::from(signature_verified.as_slice()));
1137
+ out.push(&Uint32Array::from(requested_replicas.as_slice()));
1138
+ out.push(&BigUint64Array::from(hash_numbers.as_slice()));
1139
+ if let Some(started) = columns_started {
1140
+ self.append_profile.raw_receive_prepare_columns_ms += js_sys::Date::now() - started;
1141
+ }
1142
+ Ok(out)
1143
+ }
1144
+
1145
+ #[allow(clippy::too_many_arguments)]
1146
+ pub(crate) fn plan_prepared_raw_receive_selection_core(
1147
+ &self,
1148
+ hashes: Vec<String>,
1149
+ min_replicas: u32,
1150
+ max_replicas: JsValue,
1151
+ role_age_ms: f64,
1152
+ now: String,
1153
+ peer_filter: JsValue,
1154
+ expand_peer_filter: bool,
1155
+ self_hash: String,
1156
+ include_self: bool,
1157
+ full_replica_fallback: bool,
1158
+ include_strict_full_replica: bool,
1159
+ ) -> Result<JsValue, JsValue> {
1160
+ let expected_hash_count = hashes.len();
1161
+ if expected_hash_count == 0 {
1162
+ return Ok(JsValue::UNDEFINED);
1163
+ }
1164
+ let Some(groups) =
1165
+ self.prepared_raw_receive_group_plans(hashes.clone(), min_replicas, max_replicas)?
1166
+ else {
1167
+ return Ok(JsValue::UNDEFINED);
1168
+ };
1169
+ self.plan_prepared_raw_receive_selection_for_groups(
1170
+ hashes,
1171
+ groups,
1172
+ role_age_ms,
1173
+ now,
1174
+ peer_filter,
1175
+ expand_peer_filter,
1176
+ self_hash,
1177
+ include_self,
1178
+ full_replica_fallback,
1179
+ include_strict_full_replica,
1180
+ )
1181
+ }
1182
+
1183
+ #[allow(clippy::too_many_arguments)]
1184
+ fn plan_prepared_raw_receive_selection_for_groups(
1185
+ &self,
1186
+ hashes: Vec<String>,
1187
+ groups: Vec<ResolvedPendingRawReceiveGroupPlan>,
1188
+ role_age_ms: f64,
1189
+ now: String,
1190
+ peer_filter: JsValue,
1191
+ expand_peer_filter: bool,
1192
+ self_hash: String,
1193
+ include_self: bool,
1194
+ full_replica_fallback: bool,
1195
+ include_strict_full_replica: bool,
1196
+ ) -> Result<JsValue, JsValue> {
1197
+ let expected_hash_count = hashes.len();
1198
+ let mut planned_hash_count = 0usize;
1199
+ let mut gids = Vec::with_capacity(groups.len());
1200
+ let mut replica_counts = Vec::with_capacity(groups.len());
1201
+ for group in &groups {
1202
+ planned_hash_count += group.hashes.len();
1203
+ gids.push(group.gid.clone());
1204
+ replica_counts.push(group.max_max_replicas as usize);
1205
+ }
1206
+ if planned_hash_count != expected_hash_count {
1207
+ return Ok(JsValue::UNDEFINED);
1208
+ }
1209
+
1210
+ let leader_plans = self.shared_log.plan_leaders_for_gids_batch_core(
1211
+ &gids,
1212
+ &replica_counts,
1213
+ role_age_ms,
1214
+ &now,
1215
+ peer_filter,
1216
+ expand_peer_filter,
1217
+ &self_hash,
1218
+ include_self,
1219
+ full_replica_fallback,
1220
+ include_strict_full_replica,
1221
+ )?;
1222
+ if leader_plans.len() != groups.len() {
1223
+ return Ok(JsValue::UNDEFINED);
1224
+ }
1225
+ if !leader_plans
1226
+ .iter()
1227
+ .any(|plan| leader_plan_contains_hash(plan, &self_hash))
1228
+ {
1229
+ return Ok(prepared_raw_receive_selection_all_drop(
1230
+ hashes,
1231
+ groups.len(),
1232
+ planned_hash_count,
1233
+ true,
1234
+ ));
1235
+ }
1236
+
1237
+ prepared_raw_receive_selection_from_leader_plans(
1238
+ &self.resolution,
1239
+ &groups,
1240
+ expected_hash_count,
1241
+ planned_hash_count,
1242
+ &leader_plans,
1243
+ &self_hash,
1244
+ false,
1245
+ )
1246
+ }
1247
+
1248
+ fn prepared_raw_receive_group_plans(
1249
+ &self,
1250
+ hashes: Vec<String>,
1251
+ min_replicas: u32,
1252
+ max_replicas: JsValue,
1253
+ ) -> Result<Option<Vec<ResolvedPendingRawReceiveGroupPlan>>, JsValue> {
1254
+ if hashes.is_empty() {
1255
+ return Ok(Some(Vec::new()));
1256
+ }
1257
+
1258
+ let lower = min_replicas.max(1);
1259
+ let higher = if max_replicas.is_undefined() || max_replicas.is_null() {
1260
+ u32::MAX
1261
+ } else {
1262
+ max_replicas
1263
+ .as_f64()
1264
+ .ok_or_else(|| JsValue::from_str("maxReplicas must be a number"))?
1265
+ .max(0.0)
1266
+ .min(u32::MAX as f64) as u32
1267
+ };
1268
+
1269
+ let mut group_indexes: HashMap<String, usize> = HashMap::new();
1270
+ let mut groups: Vec<PendingRawReceiveGroupPlan> = Vec::new();
1271
+
1272
+ for (input_index, hash) in hashes.into_iter().enumerate() {
1273
+ let Some(pending) = self.pending_raw_receive_entries.get(&hash) else {
1274
+ return Ok(None);
1275
+ };
1276
+ let Some(requested_replicas) = pending.requested_replicas else {
1277
+ return Ok(None);
1278
+ };
1279
+ let input_index = u32::try_from(input_index)
1280
+ .map_err(|_| JsValue::from_str("Raw receive group index overflow"))?;
1281
+
1282
+ let group_index = if let Some(index) = group_indexes.get(&pending.entry.gid) {
1283
+ *index
1284
+ } else {
1285
+ let index = groups.len();
1286
+ group_indexes.insert(pending.entry.gid.clone(), index);
1287
+ groups.push(PendingRawReceiveGroupPlan {
1288
+ gid: pending.entry.gid.clone(),
1289
+ hashes: Vec::new(),
1290
+ indexes: Vec::new(),
1291
+ requested_replicas: Vec::new(),
1292
+ latest_hash: hash.clone(),
1293
+ latest_index: input_index,
1294
+ latest_wall_time: pending.entry.wall_time,
1295
+ latest_logical: pending.entry.logical,
1296
+ max_requested_replicas: requested_replicas,
1297
+ });
1298
+ index
1299
+ };
1300
+
1301
+ let group = &mut groups[group_index];
1302
+ group.hashes.push(hash.clone());
1303
+ group.indexes.push(input_index);
1304
+ group.requested_replicas.push(requested_replicas);
1305
+ group.max_requested_replicas = group.max_requested_replicas.max(requested_replicas);
1306
+ if pending.entry.wall_time > group.latest_wall_time
1307
+ || (pending.entry.wall_time == group.latest_wall_time
1308
+ && pending.entry.logical > group.latest_logical)
1309
+ {
1310
+ group.latest_hash = hash;
1311
+ group.latest_index = input_index;
1312
+ group.latest_wall_time = pending.entry.wall_time;
1313
+ group.latest_logical = pending.entry.logical;
1314
+ }
1315
+ }
1316
+
1317
+ let gids: Vec<String> = groups.iter().map(|group| group.gid.clone()).collect();
1318
+ let max_heads = self.log.max_head_data_u32_values(&gids);
1319
+
1320
+ Ok(Some(
1321
+ groups
1322
+ .into_iter()
1323
+ .zip(max_heads)
1324
+ .map(|(group, max_head)| {
1325
+ let max_head = max_head
1326
+ .map(|value| clamp_replicas_u32(value, lower, higher))
1327
+ .unwrap_or(lower);
1328
+ let max_new = clamp_replicas_u32(group.max_requested_replicas, lower, higher);
1329
+ ResolvedPendingRawReceiveGroupPlan {
1330
+ gid: group.gid,
1331
+ hashes: group.hashes,
1332
+ indexes: group.indexes,
1333
+ requested_replicas: group.requested_replicas,
1334
+ latest_hash: group.latest_hash,
1335
+ latest_index: group.latest_index,
1336
+ max_replicas_from_head: max_head,
1337
+ max_replicas_from_new_entries: max_new,
1338
+ max_max_replicas: max_head.max(max_new),
1339
+ }
1340
+ })
1341
+ .collect(),
1342
+ ))
1343
+ }
1344
+
1345
+ fn verify_pending_raw_receive_entries(
1346
+ &mut self,
1347
+ hashes: &[String],
1348
+ ) -> Result<Option<Vec<u8>>, JsValue> {
1349
+ if hashes.is_empty() {
1350
+ return Ok(Some(Vec::new()));
1351
+ }
1352
+
1353
+ let mut out = vec![1u8; hashes.len()];
1354
+ let mut verify_positions = Vec::new();
1355
+ let verified = {
1356
+ let mut entries = Vec::new();
1357
+ for (index, hash) in hashes.iter().enumerate() {
1358
+ let Some(pending) = self.pending_raw_receive_entries.get(hash) else {
1359
+ return Ok(None);
1360
+ };
1361
+ if !pending.signature_verified {
1362
+ verify_positions.push(index);
1363
+ entries.push(pending.signature_input());
1364
+ }
1365
+ }
1366
+ if entries.is_empty() {
1367
+ Some(Vec::new())
1368
+ } else {
1369
+ verify_prepared_entry_v0_ed25519_storage_slices(&entries).ok()
1370
+ }
1371
+ };
1372
+
1373
+ let Some(verified) = verified else {
1374
+ return Ok(None);
1375
+ };
1376
+ if verified.len() != verify_positions.len() {
1377
+ return Err(JsValue::from_str(
1378
+ "Expected equal prepared raw receive verify lengths",
1379
+ ));
1380
+ }
1381
+ for (index, flag) in verify_positions.iter().zip(verified.iter()) {
1382
+ out[*index] = *flag;
1383
+ if *flag != 0 {
1384
+ if let Some(pending) = self.pending_raw_receive_entries.get_mut(&hashes[*index]) {
1385
+ pending.signature_verified = true;
1386
+ }
1387
+ }
1388
+ }
1389
+
1390
+ Ok(Some(out))
1391
+ }
1392
+
1393
+ fn verify_pending_raw_receive_entries_all(
1394
+ &mut self,
1395
+ hashes: &[String],
1396
+ ) -> Result<Option<bool>, JsValue> {
1397
+ if hashes.is_empty() {
1398
+ return Ok(Some(true));
1399
+ }
1400
+
1401
+ let mut verify_positions = Vec::new();
1402
+ let verified = {
1403
+ let mut entries = Vec::new();
1404
+ for (index, hash) in hashes.iter().enumerate() {
1405
+ let Some(pending) = self.pending_raw_receive_entries.get(hash) else {
1406
+ return Ok(None);
1407
+ };
1408
+ if !pending.signature_verified {
1409
+ verify_positions.push(index);
1410
+ entries.push(pending.signature_input());
1411
+ }
1412
+ }
1413
+ if entries.is_empty() {
1414
+ Some(true)
1415
+ } else {
1416
+ verify_prepared_entry_v0_ed25519_storage_slices_all(&entries).ok()
1417
+ }
1418
+ };
1419
+
1420
+ let Some(verified) = verified else {
1421
+ return Ok(None);
1422
+ };
1423
+ if verified {
1424
+ for index in verify_positions {
1425
+ if let Some(pending) = self.pending_raw_receive_entries.get_mut(&hashes[index]) {
1426
+ pending.signature_verified = true;
1427
+ }
1428
+ }
1429
+ }
1430
+
1431
+ Ok(Some(verified))
1432
+ }
1433
+
1434
+ fn commit_prepared_raw_receive_batch_core(
1435
+ &mut self,
1436
+ hashes: Vec<String>,
1437
+ heads: &Uint8Array,
1438
+ coordinate_commits: Vec<EntryCoordinateCommit>,
1439
+ ) -> Result<bool, JsValue> {
1440
+ ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1441
+ let profile_enabled = self.append_profile_enabled;
1442
+ let pending_check_started = profile_enabled.then(js_sys::Date::now);
1443
+ let missing_pending = hashes
1444
+ .iter()
1445
+ .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1446
+ if let Some(started) = pending_check_started {
1447
+ self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1448
+ }
1449
+ if missing_pending {
1450
+ return Ok(false);
1451
+ }
1452
+
1453
+ let remove_started = profile_enabled.then(js_sys::Date::now);
1454
+ let mut block_entries = Vec::with_capacity(hashes.len());
1455
+ let mut graph_entries = Vec::with_capacity(hashes.len());
1456
+ for (index, hash) in hashes.into_iter().enumerate() {
1457
+ let pending = self
1458
+ .pending_raw_receive_entries
1459
+ .remove(&hash)
1460
+ .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1461
+ block_entries.push((hash, pending.storage_bytes));
1462
+ let mut graph_entry = pending.entry;
1463
+ graph_entry.head = heads.get_index(index as u32) != 0;
1464
+ graph_entries.push(graph_entry);
1465
+ }
1466
+ if let Some(started) = remove_started {
1467
+ self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1468
+ }
1469
+
1470
+ let block_put_started = profile_enabled.then(js_sys::Date::now);
1471
+ self.blocks.put_entries_core(block_entries);
1472
+ if let Some(started) = block_put_started {
1473
+ self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1474
+ }
1475
+ let graph_put_started = profile_enabled.then(js_sys::Date::now);
1476
+ self.log.put_entries_core(graph_entries);
1477
+ if let Some(started) = graph_put_started {
1478
+ self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1479
+ }
1480
+ if !coordinate_commits.is_empty() {
1481
+ let coordinate_started = profile_enabled.then(js_sys::Date::now);
1482
+ self.commit_entry_coordinate_commits(coordinate_commits);
1483
+ if let Some(started) = coordinate_started {
1484
+ self.append_profile.raw_receive_coordinate_commit_ms +=
1485
+ js_sys::Date::now() - started;
1486
+ }
1487
+ }
1488
+ Ok(true)
1489
+ }
1490
+
1491
+ fn commit_prepared_raw_receive_join_batch_core(
1492
+ &mut self,
1493
+ hashes: Vec<String>,
1494
+ heads: &Uint8Array,
1495
+ coordinate_commits: Vec<EntryCoordinateCommit>,
1496
+ ) -> Result<bool, JsValue> {
1497
+ ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1498
+ let profile_enabled = self.append_profile_enabled;
1499
+ let pending_check_started = profile_enabled.then(js_sys::Date::now);
1500
+ let missing_pending = hashes
1501
+ .iter()
1502
+ .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1503
+ if let Some(started) = pending_check_started {
1504
+ self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1505
+ }
1506
+ if missing_pending {
1507
+ return Ok(false);
1508
+ }
1509
+
1510
+ {
1511
+ let join_plan_started = profile_enabled.then(js_sys::Date::now);
1512
+ let mut graph_entries = Vec::with_capacity(hashes.len());
1513
+ for hash in &hashes {
1514
+ let pending = self
1515
+ .pending_raw_receive_entries
1516
+ .get(hash)
1517
+ .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1518
+ graph_entries.push(&pending.entry);
1519
+ }
1520
+ let join_plans = self
1521
+ .log
1522
+ .plan_join_entry_refs_core(&graph_entries, false, true);
1523
+ if let Some(started) = join_plan_started {
1524
+ self.append_profile.raw_receive_join_plan_ms += js_sys::Date::now() - started;
1525
+ }
1526
+ let mut batch_hashes: Option<HashSet<&str>> = None;
1527
+ for plan in join_plans {
1528
+ if plan.skip || plan.covered_by_cut || !plan.cut_checked {
1529
+ return Ok(false);
1530
+ }
1531
+ if !plan.missing_parents.is_empty() {
1532
+ let batch_hashes = batch_hashes
1533
+ .get_or_insert_with(|| hashes.iter().map(String::as_str).collect());
1534
+ if plan
1535
+ .missing_parents
1536
+ .iter()
1537
+ .any(|hash| !batch_hashes.contains(hash.as_str()))
1538
+ {
1539
+ return Ok(false);
1540
+ }
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ let remove_started = profile_enabled.then(js_sys::Date::now);
1546
+ let mut block_entries = Vec::with_capacity(hashes.len());
1547
+ let mut graph_entries = Vec::with_capacity(hashes.len());
1548
+ for (index, hash) in hashes.into_iter().enumerate() {
1549
+ let pending = self
1550
+ .pending_raw_receive_entries
1551
+ .remove(&hash)
1552
+ .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1553
+ block_entries.push((hash, pending.storage_bytes));
1554
+ let mut graph_entry = pending.entry;
1555
+ graph_entry.head = heads.get_index(index as u32) != 0;
1556
+ graph_entries.push(graph_entry);
1557
+ }
1558
+ if let Some(started) = remove_started {
1559
+ self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1560
+ }
1561
+
1562
+ let block_put_started = profile_enabled.then(js_sys::Date::now);
1563
+ self.blocks.put_entries_core(block_entries);
1564
+ if let Some(started) = block_put_started {
1565
+ self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1566
+ }
1567
+ let graph_put_started = profile_enabled.then(js_sys::Date::now);
1568
+ self.log.put_join_batch_entries_core(graph_entries);
1569
+ if let Some(started) = graph_put_started {
1570
+ self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1571
+ }
1572
+ if !coordinate_commits.is_empty() {
1573
+ let coordinate_started = profile_enabled.then(js_sys::Date::now);
1574
+ self.commit_entry_coordinate_commits(coordinate_commits);
1575
+ if let Some(started) = coordinate_started {
1576
+ self.append_profile.raw_receive_coordinate_commit_ms +=
1577
+ js_sys::Date::now() - started;
1578
+ }
1579
+ }
1580
+ Ok(true)
1581
+ }
1582
+
1583
+ fn commit_verified_prepared_raw_receive_join_batch_core(
1584
+ &mut self,
1585
+ hashes: Vec<String>,
1586
+ heads: &Uint8Array,
1587
+ verify_hashes: Option<Vec<String>>,
1588
+ coordinate_commits: Vec<EntryCoordinateCommit>,
1589
+ ) -> Result<bool, JsValue> {
1590
+ ensure_same_len(hashes.len(), heads.length() as usize, "raw receive heads")?;
1591
+ let profile_enabled = self.append_profile_enabled;
1592
+ let verify_hashes_cover_commit = match verify_hashes.as_ref() {
1593
+ None => true,
1594
+ Some(verify_hashes) => {
1595
+ verify_hashes.len() == hashes.len()
1596
+ && verify_hashes
1597
+ .iter()
1598
+ .zip(hashes.iter())
1599
+ .all(|(verified_hash, hash)| verified_hash == hash)
1600
+ }
1601
+ };
1602
+ if !verify_hashes_cover_commit {
1603
+ let pending_check_started = profile_enabled.then(js_sys::Date::now);
1604
+ let missing_pending = hashes
1605
+ .iter()
1606
+ .any(|hash| !self.pending_raw_receive_entries.contains_key(hash));
1607
+ if let Some(started) = pending_check_started {
1608
+ self.append_profile.raw_receive_pending_check_ms += js_sys::Date::now() - started;
1609
+ }
1610
+ if missing_pending {
1611
+ return Ok(false);
1612
+ }
1613
+ }
1614
+ let verify_started = profile_enabled.then(js_sys::Date::now);
1615
+ if verify_hashes_cover_commit {
1616
+ let verify_hashes = verify_hashes.as_ref().unwrap_or(&hashes);
1617
+ let Some(verified) = self.verify_pending_raw_receive_entries_all(verify_hashes)? else {
1618
+ return Ok(false);
1619
+ };
1620
+ if let Some(started) = verify_started {
1621
+ self.append_profile.raw_receive_verify_ms += js_sys::Date::now() - started;
1622
+ }
1623
+ if !verified {
1624
+ return Ok(false);
1625
+ }
1626
+ } else {
1627
+ let verify_hashes = verify_hashes.expect("partial verify hashes");
1628
+ let Some(verified) = self.verify_pending_raw_receive_entries(&verify_hashes)? else {
1629
+ return Ok(false);
1630
+ };
1631
+ if let Some(started) = verify_started {
1632
+ self.append_profile.raw_receive_verify_ms += js_sys::Date::now() - started;
1633
+ }
1634
+ if verified.iter().any(|flag| *flag == 0) {
1635
+ return Ok(false);
1636
+ }
1637
+ let verify_status_started = profile_enabled.then(js_sys::Date::now);
1638
+ let missing_verified = hashes.iter().any(|hash| {
1639
+ self.pending_raw_receive_entries
1640
+ .get(hash)
1641
+ .map(|pending| !pending.signature_verified)
1642
+ .unwrap_or(true)
1643
+ });
1644
+ if let Some(started) = verify_status_started {
1645
+ self.append_profile.raw_receive_verify_status_ms += js_sys::Date::now() - started;
1646
+ }
1647
+ if missing_verified {
1648
+ return Ok(false);
1649
+ }
1650
+ }
1651
+
1652
+ {
1653
+ let join_plan_started = profile_enabled.then(js_sys::Date::now);
1654
+ let mut graph_entries = Vec::with_capacity(hashes.len());
1655
+ for hash in &hashes {
1656
+ let pending = self
1657
+ .pending_raw_receive_entries
1658
+ .get(hash)
1659
+ .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1660
+ graph_entries.push(&pending.entry);
1661
+ }
1662
+ let join_plans = self
1663
+ .log
1664
+ .plan_join_entry_refs_core(&graph_entries, false, true);
1665
+ if let Some(started) = join_plan_started {
1666
+ self.append_profile.raw_receive_join_plan_ms += js_sys::Date::now() - started;
1667
+ }
1668
+ let mut batch_hashes: Option<HashSet<&str>> = None;
1669
+ for plan in join_plans {
1670
+ if plan.skip || plan.covered_by_cut || !plan.cut_checked {
1671
+ return Ok(false);
1672
+ }
1673
+ if !plan.missing_parents.is_empty() {
1674
+ let batch_hashes = batch_hashes
1675
+ .get_or_insert_with(|| hashes.iter().map(String::as_str).collect());
1676
+ if plan
1677
+ .missing_parents
1678
+ .iter()
1679
+ .any(|hash| !batch_hashes.contains(hash.as_str()))
1680
+ {
1681
+ return Ok(false);
1682
+ }
1683
+ }
1684
+ }
1685
+ }
1686
+
1687
+ let remove_started = profile_enabled.then(js_sys::Date::now);
1688
+ let mut block_entries = Vec::with_capacity(hashes.len());
1689
+ let mut graph_entries = Vec::with_capacity(hashes.len());
1690
+ for (index, hash) in hashes.into_iter().enumerate() {
1691
+ let pending = self
1692
+ .pending_raw_receive_entries
1693
+ .remove(&hash)
1694
+ .ok_or_else(|| JsValue::from_str("Missing prepared raw receive entry"))?;
1695
+ block_entries.push((hash, pending.storage_bytes));
1696
+ let mut graph_entry = pending.entry;
1697
+ graph_entry.head = heads.get_index(index as u32) != 0;
1698
+ graph_entries.push(graph_entry);
1699
+ }
1700
+ if let Some(started) = remove_started {
1701
+ self.append_profile.raw_receive_remove_ms += js_sys::Date::now() - started;
1702
+ }
1703
+
1704
+ let block_put_started = profile_enabled.then(js_sys::Date::now);
1705
+ self.blocks.put_entries_core(block_entries);
1706
+ if let Some(started) = block_put_started {
1707
+ self.append_profile.raw_receive_block_put_ms += js_sys::Date::now() - started;
1708
+ }
1709
+ let graph_put_started = profile_enabled.then(js_sys::Date::now);
1710
+ self.log.put_join_batch_entries_core(graph_entries);
1711
+ if let Some(started) = graph_put_started {
1712
+ self.append_profile.raw_receive_graph_put_ms += js_sys::Date::now() - started;
1713
+ }
1714
+ if !coordinate_commits.is_empty() {
1715
+ let coordinate_started = profile_enabled.then(js_sys::Date::now);
1716
+ self.commit_entry_coordinate_commits(coordinate_commits);
1717
+ if let Some(started) = coordinate_started {
1718
+ self.append_profile.raw_receive_coordinate_commit_ms +=
1719
+ js_sys::Date::now() - started;
1720
+ }
1721
+ }
1722
+ Ok(true)
1723
+ }
1724
+ }