1use crate::coherence::{CoherenceConfig, CoherenceTracker};
27use crate::proof_policy::{ProofPolicy, ProofVerifier};
28use crate::witness::WitnessLog;
29use crate::Result;
30
31use ruvix_region::backing::MemoryBacking;
32use ruvix_region::slab::{SlabAllocator, SlotHandle};
33use ruvix_types::{
34 CapRights, Capability, GraphHandle, GraphMutation, GraphMutationKind, KernelError,
35 ProofAttestation, ProofToken, RegionHandle,
36};
37
38const MAX_EDGES_PER_NODE: usize = 64;
40
41#[derive(Debug, Clone, Copy)]
43#[repr(C)]
44pub struct GraphNode {
45 pub id: u64,
47
48 pub edge_count: u32,
50
51 pub partition_id: u32,
53
54 pub edge_list_slot: SlotHandle,
56
57 pub coherence_score: u16,
59
60 pub mutation_epoch: u64,
62
63 _padding: [u8; 6],
65}
66
67impl GraphNode {
68 pub const SIZE: usize = core::mem::size_of::<Self>();
70
71 #[inline]
73 #[must_use]
74 pub const fn new(id: u64, partition_id: u32) -> Self {
75 Self {
76 id,
77 edge_count: 0,
78 partition_id,
79 edge_list_slot: SlotHandle::invalid(),
80 coherence_score: 10000,
81 mutation_epoch: 0,
82 _padding: [0; 6],
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy)]
89#[repr(C)]
90pub struct EdgeEntry {
91 pub target_id: u64,
93
94 pub weight_fp: i32,
96
97 _padding: [u8; 4],
99}
100
101impl EdgeEntry {
102 pub const SIZE: usize = core::mem::size_of::<Self>();
104
105 #[inline]
107 #[must_use]
108 pub const fn new(target_id: u64, weight_fp: i32) -> Self {
109 Self {
110 target_id,
111 weight_fp,
112 _padding: [0; 4],
113 }
114 }
115
116 #[inline]
118 #[must_use]
119 pub fn weight(&self) -> f32 {
120 self.weight_fp as f32 / 10000.0
121 }
122}
123
124#[derive(Debug, Clone, Copy)]
126#[repr(C)]
127pub struct PartitionMeta {
128 pub partition_count: u32,
130
131 pub cut_size: u32,
133
134 pub average_coherence: u16,
136
137 pub last_update_epoch: u64,
139}
140
141impl Default for PartitionMeta {
142 fn default() -> Self {
143 Self {
144 partition_count: 1,
145 cut_size: 0,
146 average_coherence: 10000,
147 last_update_epoch: 0,
148 }
149 }
150}
151
152impl PartitionMeta {
153 #[inline]
155 #[must_use]
156 pub const fn new(partition_count: u32) -> Self {
157 Self {
158 partition_count,
159 cut_size: 0,
160 average_coherence: 10000,
161 last_update_epoch: 0,
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq)]
168pub struct GraphMutationResult {
169 pub attestation: ProofAttestation,
171
172 pub partition_changed: bool,
174
175 pub affected_partitions: [u32; 2],
177}
178
179struct NodeMap {
181 entries: [(u64, SlotHandle); 256],
182 count: usize,
183}
184
185impl NodeMap {
186 fn new() -> Self {
187 Self {
188 entries: [(0, SlotHandle::invalid()); 256],
189 count: 0,
190 }
191 }
192
193 fn get(&self, node_id: u64) -> Option<SlotHandle> {
194 for i in 0..self.count {
195 if self.entries[i].0 == node_id {
196 return Some(self.entries[i].1);
197 }
198 }
199 None
200 }
201
202 fn insert(&mut self, node_id: u64, handle: SlotHandle) -> Result<()> {
203 for i in 0..self.count {
205 if self.entries[i].0 == node_id {
206 self.entries[i].1 = handle;
207 return Ok(());
208 }
209 }
210
211 if self.count >= 256 {
212 return Err(KernelError::LimitExceeded);
213 }
214
215 self.entries[self.count] = (node_id, handle);
216 self.count += 1;
217 Ok(())
218 }
219
220 fn remove(&mut self, node_id: u64) -> Option<SlotHandle> {
221 for i in 0..self.count {
222 if self.entries[i].0 == node_id {
223 let handle = self.entries[i].1;
224 self.entries[i] = self.entries[self.count - 1];
225 self.entries[self.count - 1] = (0, SlotHandle::invalid());
226 self.count -= 1;
227 return Some(handle);
228 }
229 }
230 None
231 }
232
233 fn len(&self) -> usize {
234 self.count
235 }
236}
237
238pub struct GraphStoreBuilder {
240 capacity: u32,
241 coherence_config: CoherenceConfig,
242 proof_policy: ProofPolicy,
243 partition_count: u32,
244}
245
246impl GraphStoreBuilder {
247 #[inline]
249 #[must_use]
250 pub fn new(capacity: u32) -> Self {
251 Self {
252 capacity,
253 coherence_config: CoherenceConfig::default(),
254 proof_policy: ProofPolicy::standard(),
255 partition_count: 1,
256 }
257 }
258
259 #[inline]
261 #[must_use]
262 pub fn with_coherence_config(mut self, config: CoherenceConfig) -> Self {
263 self.coherence_config = config;
264 self
265 }
266
267 #[inline]
269 #[must_use]
270 pub fn with_proof_policy(mut self, policy: ProofPolicy) -> Self {
271 self.proof_policy = policy;
272 self
273 }
274
275 #[inline]
277 #[must_use]
278 pub fn with_partitions(mut self, count: u32) -> Self {
279 self.partition_count = count;
280 self
281 }
282
283 pub fn build<B: MemoryBacking>(
285 self,
286 node_backing: B,
287 edge_backing: B,
288 witness_backing: B,
289 node_handle: RegionHandle,
290 edge_handle: RegionHandle,
291 witness_handle: RegionHandle,
292 store_id: u32,
293 ) -> Result<KernelGraphStore<B>> {
294 KernelGraphStore::new(
295 node_backing,
296 edge_backing,
297 witness_backing,
298 node_handle,
299 edge_handle,
300 witness_handle,
301 self.capacity,
302 self.coherence_config,
303 self.proof_policy,
304 self.partition_count,
305 store_id,
306 )
307 }
308}
309
310pub struct KernelGraphStore<B: MemoryBacking> {
314 node_slab: SlabAllocator<B>,
316
317 edge_slab: SlabAllocator<B>,
319
320 witness_log: WitnessLog<B>,
322
323 node_handle: RegionHandle,
325 edge_handle: RegionHandle,
326
327 node_map: NodeMap,
329
330 coherence_tracker: CoherenceTracker,
332
333 proof_verifier: ProofVerifier,
335
336 partition_meta: PartitionMeta,
338
339 capacity: u32,
341
342 store_id: u32,
344
345 handle: GraphHandle,
347
348 edge_count: u32,
350}
351
352impl<B: MemoryBacking> KernelGraphStore<B> {
353 #[allow(clippy::too_many_arguments)]
355 pub fn new(
356 node_backing: B,
357 edge_backing: B,
358 witness_backing: B,
359 node_handle: RegionHandle,
360 edge_handle: RegionHandle,
361 witness_handle: RegionHandle,
362 capacity: u32,
363 coherence_config: CoherenceConfig,
364 proof_policy: ProofPolicy,
365 partition_count: u32,
366 store_id: u32,
367 ) -> Result<Self> {
368 let node_slab = SlabAllocator::new(node_backing, GraphNode::SIZE, capacity as usize)?;
369
370 let edge_slot_size = 4 + MAX_EDGES_PER_NODE * EdgeEntry::SIZE;
372 let edge_slab = SlabAllocator::new(edge_backing, edge_slot_size, capacity as usize)?;
373
374 let witness_log = WitnessLog::new(
375 witness_backing,
376 capacity as usize * 4, witness_handle,
378 store_id,
379 )?;
380
381 Ok(Self {
382 node_slab,
383 edge_slab,
384 witness_log,
385 node_handle,
386 edge_handle,
387 node_map: NodeMap::new(),
388 coherence_tracker: CoherenceTracker::new(coherence_config),
389 proof_verifier: ProofVerifier::new(proof_policy),
390 partition_meta: PartitionMeta::new(partition_count),
391 capacity,
392 store_id,
393 handle: GraphHandle::new(store_id, 0),
394 edge_count: 0,
395 })
396 }
397
398 #[inline]
400 #[must_use]
401 pub const fn handle(&self) -> GraphHandle {
402 self.handle
403 }
404
405 #[inline]
407 #[must_use]
408 pub fn node_count(&self) -> usize {
409 self.node_map.len()
410 }
411
412 #[inline]
414 #[must_use]
415 pub const fn edge_count(&self) -> u32 {
416 self.edge_count
417 }
418
419 #[inline]
421 #[must_use]
422 pub const fn capacity(&self) -> u32 {
423 self.capacity
424 }
425
426 #[inline]
428 #[must_use]
429 pub const fn partition_meta(&self) -> &PartitionMeta {
430 &self.partition_meta
431 }
432
433 pub fn graph_apply_proved(
449 &mut self,
450 mutation: &GraphMutation,
451 proof: &ProofToken,
452 capability: &Capability,
453 current_time_ns: u64,
454 ) -> Result<GraphMutationResult> {
455 let mutation_hash = compute_graph_mutation_hash(mutation);
457
458 let attestation =
460 self.proof_verifier
461 .verify(proof, &mutation_hash, current_time_ns, capability)?;
462
463 let (partition_changed, affected_partitions) = match mutation.kind {
465 GraphMutationKind::AddNode => self.apply_add_node(mutation.node_a)?,
466 GraphMutationKind::RemoveNode => self.apply_remove_node(mutation.node_a)?,
467 GraphMutationKind::AddEdge => {
468 self.apply_add_edge(mutation.node_a, mutation.node_b, mutation.weight_fp)?
469 }
470 GraphMutationKind::RemoveEdge => {
471 self.apply_remove_edge(mutation.node_a, mutation.node_b)?
472 }
473 GraphMutationKind::UpdateEdgeWeight => {
474 self.apply_update_edge_weight(mutation.node_a, mutation.node_b, mutation.weight_fp)?
475 }
476 GraphMutationKind::UpdateNodeMeta => {
477 self.apply_update_node_meta(mutation.node_a, mutation.partition_hint)?
479 }
480 };
481
482 self.witness_log
484 .record_graph_mutation(mutation, attestation, current_time_ns)?;
485
486 Ok(GraphMutationResult {
487 attestation,
488 partition_changed,
489 affected_partitions,
490 })
491 }
492
493 fn apply_add_node(&mut self, node_id: u64) -> Result<(bool, [u32; 2])> {
495 if self.node_map.get(node_id).is_some() {
497 return Err(KernelError::AlreadyExists);
498 }
499
500 let node_slot = self.node_slab.alloc()?;
502
503 let edge_slot = self.edge_slab.alloc()?;
505
506 let mut node = GraphNode::new(node_id, 0);
508 node.edge_list_slot = edge_slot;
509 node.mutation_epoch = self.coherence_tracker.advance_epoch();
510
511 self.write_node(node_slot, &node)?;
513
514 self.write_edge_count(edge_slot, 0)?;
516
517 self.node_map.insert(node_id, node_slot)?;
519
520 self.coherence_tracker.on_entry_added(node.coherence_score);
522
523 Ok((false, [0, 0]))
524 }
525
526 fn apply_remove_node(&mut self, node_id: u64) -> Result<(bool, [u32; 2])> {
528 let node_slot = self.node_map.get(node_id).ok_or(KernelError::NotFound)?;
530
531 let node = self.read_node(node_slot)?;
533
534 if !node.edge_list_slot.is_invalid() {
536 let edge_count = self.read_edge_count(node.edge_list_slot)?;
538 self.edge_count = self.edge_count.saturating_sub(edge_count);
539
540 self.edge_slab.free(node.edge_list_slot)?;
541 }
542
543 self.node_slab.free(node_slot)?;
545
546 self.node_map.remove(node_id);
548
549 self.coherence_tracker
551 .on_entry_removed(node.coherence_score);
552
553 Ok((true, [node.partition_id, 0]))
554 }
555
556 fn apply_add_edge(
558 &mut self,
559 from_id: u64,
560 to_id: u64,
561 weight_fp: i32,
562 ) -> Result<(bool, [u32; 2])> {
563 let from_slot = self.node_map.get(from_id).ok_or(KernelError::NotFound)?;
565
566 if self.node_map.get(to_id).is_none() {
568 return Err(KernelError::NotFound);
569 }
570
571 let mut node = self.read_node(from_slot)?;
573
574 let edge_count = self.read_edge_count(node.edge_list_slot)?;
576
577 if edge_count as usize >= MAX_EDGES_PER_NODE {
578 return Err(KernelError::LimitExceeded);
579 }
580
581 for i in 0..edge_count {
583 let edge = self.read_edge(node.edge_list_slot, i)?;
584 if edge.target_id == to_id {
585 return Err(KernelError::AlreadyExists);
586 }
587 }
588
589 let edge = EdgeEntry::new(to_id, weight_fp);
591 self.write_edge(node.edge_list_slot, edge_count, &edge)?;
592 self.write_edge_count(node.edge_list_slot, edge_count + 1)?;
593
594 node.edge_count = edge_count + 1;
596 node.mutation_epoch = self.coherence_tracker.advance_epoch();
597 self.write_node(from_slot, &node)?;
598
599 self.edge_count += 1;
600
601 let to_slot = self.node_map.get(to_id).unwrap();
603 let to_node = self.read_node(to_slot)?;
604
605 let partition_changed = node.partition_id != to_node.partition_id;
606 if partition_changed {
607 self.partition_meta.cut_size += 1;
608 }
609
610 Ok((partition_changed, [node.partition_id, to_node.partition_id]))
611 }
612
613 fn apply_remove_edge(&mut self, from_id: u64, to_id: u64) -> Result<(bool, [u32; 2])> {
615 let from_slot = self.node_map.get(from_id).ok_or(KernelError::NotFound)?;
617
618 let mut node = self.read_node(from_slot)?;
620
621 let edge_count = self.read_edge_count(node.edge_list_slot)?;
623 let mut found_idx = None;
624
625 for i in 0..edge_count {
626 let edge = self.read_edge(node.edge_list_slot, i)?;
627 if edge.target_id == to_id {
628 found_idx = Some(i);
629 break;
630 }
631 }
632
633 let idx = found_idx.ok_or(KernelError::NotFound)?;
634
635 if idx < edge_count - 1 {
637 let last_edge = self.read_edge(node.edge_list_slot, edge_count - 1)?;
638 self.write_edge(node.edge_list_slot, idx, &last_edge)?;
639 }
640 self.write_edge_count(node.edge_list_slot, edge_count - 1)?;
641
642 node.edge_count = edge_count - 1;
644 node.mutation_epoch = self.coherence_tracker.advance_epoch();
645 self.write_node(from_slot, &node)?;
646
647 self.edge_count = self.edge_count.saturating_sub(1);
648
649 Ok((false, [node.partition_id, 0]))
650 }
651
652 fn apply_update_edge_weight(
654 &mut self,
655 from_id: u64,
656 to_id: u64,
657 weight_fp: i32,
658 ) -> Result<(bool, [u32; 2])> {
659 let from_slot = self.node_map.get(from_id).ok_or(KernelError::NotFound)?;
661
662 let mut node = self.read_node(from_slot)?;
664
665 let edge_count = self.read_edge_count(node.edge_list_slot)?;
667
668 for i in 0..edge_count {
669 let mut edge = self.read_edge(node.edge_list_slot, i)?;
670 if edge.target_id == to_id {
671 edge.weight_fp = weight_fp;
672 self.write_edge(node.edge_list_slot, i, &edge)?;
673
674 node.mutation_epoch = self.coherence_tracker.advance_epoch();
676 self.write_node(from_slot, &node)?;
677
678 return Ok((false, [node.partition_id, 0]));
679 }
680 }
681
682 Err(KernelError::NotFound)
683 }
684
685 fn apply_update_node_meta(
687 &mut self,
688 node_id: u64,
689 new_partition: u32,
690 ) -> Result<(bool, [u32; 2])> {
691 let node_slot = self.node_map.get(node_id).ok_or(KernelError::NotFound)?;
693
694 let mut node = self.read_node(node_slot)?;
696 let old_partition = node.partition_id;
697
698 node.partition_id = new_partition;
700 node.mutation_epoch = self.coherence_tracker.advance_epoch();
701
702 self.write_node(node_slot, &node)?;
703
704 let partition_changed = old_partition != new_partition;
705 Ok((partition_changed, [old_partition, new_partition]))
706 }
707
708 fn read_node(&self, slot: SlotHandle) -> Result<GraphNode> {
710 let mut bytes = [0u8; GraphNode::SIZE];
711 self.node_slab.read(slot, &mut bytes)?;
712 Ok(unsafe { core::ptr::read(bytes.as_ptr() as *const GraphNode) })
713 }
714
715 fn write_node(&mut self, slot: SlotHandle, node: &GraphNode) -> Result<()> {
717 let bytes =
718 unsafe { core::slice::from_raw_parts(node as *const _ as *const u8, GraphNode::SIZE) };
719 self.node_slab.write(slot, bytes)?;
720 Ok(())
721 }
722
723 fn read_edge_count(&self, slot: SlotHandle) -> Result<u32> {
725 let mut bytes = [0u8; 4];
726 let ptr = self.edge_slab.slot_ptr(slot)?;
727 unsafe {
728 core::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 4);
729 }
730 Ok(u32::from_le_bytes(bytes))
731 }
732
733 fn write_edge_count(&mut self, slot: SlotHandle, count: u32) -> Result<()> {
735 let bytes = count.to_le_bytes();
736 let ptr = self.edge_slab.slot_ptr(slot)?;
737 unsafe {
738 core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, 4);
739 }
740 Ok(())
741 }
742
743 fn read_edge(&self, slot: SlotHandle, index: u32) -> Result<EdgeEntry> {
745 let offset = 4 + (index as usize) * EdgeEntry::SIZE;
746 let ptr = self.edge_slab.slot_ptr(slot)?;
747 let mut bytes = [0u8; EdgeEntry::SIZE];
748 unsafe {
749 core::ptr::copy_nonoverlapping(ptr.add(offset), bytes.as_mut_ptr(), EdgeEntry::SIZE);
750 }
751 Ok(unsafe { core::ptr::read(bytes.as_ptr() as *const EdgeEntry) })
752 }
753
754 fn write_edge(&mut self, slot: SlotHandle, index: u32, edge: &EdgeEntry) -> Result<()> {
756 let offset = 4 + (index as usize) * EdgeEntry::SIZE;
757 let ptr = self.edge_slab.slot_ptr(slot)?;
758 let bytes = unsafe {
759 core::slice::from_raw_parts(edge as *const _ as *const u8, EdgeEntry::SIZE)
760 };
761 unsafe {
762 core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(offset), EdgeEntry::SIZE);
763 }
764 Ok(())
765 }
766
767 #[inline]
769 #[must_use]
770 pub const fn proof_policy(&self) -> &ProofPolicy {
771 self.proof_verifier.policy()
772 }
773
774 #[inline]
776 #[must_use]
777 pub fn witness_entry_count(&self) -> usize {
778 self.witness_log.entry_count()
779 }
780}
781
782fn compute_graph_mutation_hash(mutation: &GraphMutation) -> [u8; 32] {
784 let mut hash = [0u8; 32];
785
786 hash[0] = mutation.kind as u8;
787 hash[1..9].copy_from_slice(&mutation.node_a.to_le_bytes());
788 hash[9..17].copy_from_slice(&mutation.node_b.to_le_bytes());
789 hash[17..21].copy_from_slice(&mutation.weight_fp.to_le_bytes());
790 hash[21..25].copy_from_slice(&mutation.partition_hint.to_le_bytes());
791
792 hash
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798 use ruvix_region::backing::StaticBacking;
799 use ruvix_types::{ObjectType, ProofPayload, ProofTier};
800
801 fn create_test_capability() -> Capability {
802 Capability::new(
803 1,
804 ObjectType::GraphStore,
805 CapRights::READ | CapRights::WRITE | CapRights::PROVE,
806 0,
807 1,
808 )
809 }
810
811 fn create_test_proof(mutation: &GraphMutation, valid_until_ns: u64, nonce: u64) -> ProofToken {
812 let mutation_hash = compute_graph_mutation_hash(mutation);
813 ProofToken::new(
814 mutation_hash,
815 ProofTier::Standard,
816 ProofPayload::Hash { hash: mutation_hash },
817 valid_until_ns,
818 nonce,
819 )
820 }
821
822 fn create_test_store() -> KernelGraphStore<StaticBacking<16384>> {
823 let node_backing = StaticBacking::<16384>::new();
824 let edge_backing = StaticBacking::<16384>::new();
825 let witness_backing = StaticBacking::<16384>::new();
826
827 GraphStoreBuilder::new(10) .with_proof_policy(ProofPolicy::reflex())
829 .build(
830 node_backing,
831 edge_backing,
832 witness_backing,
833 RegionHandle::new(1, 0),
834 RegionHandle::new(2, 0),
835 RegionHandle::new(3, 0),
836 1,
837 )
838 .unwrap()
839 }
840
841 #[test]
842 fn test_graph_store_creation() {
843 let store = create_test_store();
844 assert_eq!(store.node_count(), 0);
845 assert_eq!(store.edge_count(), 0);
846 assert_eq!(store.capacity(), 10);
847 }
848
849 #[test]
850 fn test_add_node() {
851 let mut store = create_test_store();
852 let cap = create_test_capability();
853
854 let mutation = GraphMutation::add_node(1);
855 let proof = create_test_proof(&mutation, 1_000_000_000, 1);
856
857 let result = store
858 .graph_apply_proved(&mutation, &proof, &cap, 500_000_000)
859 .unwrap();
860
861 assert!(!result.partition_changed);
862 assert_eq!(store.node_count(), 1);
863 }
864
865 #[test]
866 fn test_add_duplicate_node() {
867 let mut store = create_test_store();
868 let cap = create_test_capability();
869
870 let mutation1 = GraphMutation::add_node(1);
872 let proof1 = create_test_proof(&mutation1, 1_000_000_000, 1);
873 store
874 .graph_apply_proved(&mutation1, &proof1, &cap, 500_000_000)
875 .unwrap();
876
877 let mutation2 = GraphMutation::add_node(1);
879 let proof2 = create_test_proof(&mutation2, 1_000_000_000, 2);
880 let result = store.graph_apply_proved(&mutation2, &proof2, &cap, 500_000_001);
881
882 assert_eq!(result, Err(KernelError::AlreadyExists));
883 }
884
885 #[test]
886 fn test_add_edge() {
887 let mut store = create_test_store();
888 let cap = create_test_capability();
889
890 let add1 = GraphMutation::add_node(1);
892 let proof1 = create_test_proof(&add1, 1_000_000_000, 1);
893 store
894 .graph_apply_proved(&add1, &proof1, &cap, 500_000_000)
895 .unwrap();
896
897 let add2 = GraphMutation::add_node(2);
898 let proof2 = create_test_proof(&add2, 1_000_000_000, 2);
899 store
900 .graph_apply_proved(&add2, &proof2, &cap, 500_000_001)
901 .unwrap();
902
903 let edge = GraphMutation::add_edge(1, 2, 0.75);
905 let proof3 = create_test_proof(&edge, 1_000_000_000, 3);
906 store
907 .graph_apply_proved(&edge, &proof3, &cap, 500_000_002)
908 .unwrap();
909
910 assert_eq!(store.edge_count(), 1);
911 }
912
913 #[test]
914 fn test_remove_edge() {
915 let mut store = create_test_store();
916 let cap = create_test_capability();
917
918 let add1 = GraphMutation::add_node(1);
920 let add2 = GraphMutation::add_node(2);
921 let edge = GraphMutation::add_edge(1, 2, 0.75);
922
923 store
924 .graph_apply_proved(
925 &add1,
926 &create_test_proof(&add1, 1_000_000_000, 1),
927 &cap,
928 500_000_000,
929 )
930 .unwrap();
931 store
932 .graph_apply_proved(
933 &add2,
934 &create_test_proof(&add2, 1_000_000_000, 2),
935 &cap,
936 500_000_001,
937 )
938 .unwrap();
939 store
940 .graph_apply_proved(
941 &edge,
942 &create_test_proof(&edge, 1_000_000_000, 3),
943 &cap,
944 500_000_002,
945 )
946 .unwrap();
947
948 assert_eq!(store.edge_count(), 1);
949
950 let remove = GraphMutation::remove_edge(1, 2);
952 let proof = create_test_proof(&remove, 1_000_000_000, 4);
953 store
954 .graph_apply_proved(&remove, &proof, &cap, 500_000_003)
955 .unwrap();
956
957 assert_eq!(store.edge_count(), 0);
958 }
959
960 #[test]
961 fn test_update_edge_weight() {
962 let mut store = create_test_store();
963 let cap = create_test_capability();
964
965 let add1 = GraphMutation::add_node(1);
967 let add2 = GraphMutation::add_node(2);
968 let edge = GraphMutation::add_edge(1, 2, 0.5);
969
970 store
971 .graph_apply_proved(
972 &add1,
973 &create_test_proof(&add1, 1_000_000_000, 1),
974 &cap,
975 500_000_000,
976 )
977 .unwrap();
978 store
979 .graph_apply_proved(
980 &add2,
981 &create_test_proof(&add2, 1_000_000_000, 2),
982 &cap,
983 500_000_001,
984 )
985 .unwrap();
986 store
987 .graph_apply_proved(
988 &edge,
989 &create_test_proof(&edge, 1_000_000_000, 3),
990 &cap,
991 500_000_002,
992 )
993 .unwrap();
994
995 let update = GraphMutation::update_edge_weight(1, 2, 0.9);
997 let proof = create_test_proof(&update, 1_000_000_000, 4);
998 store
999 .graph_apply_proved(&update, &proof, &cap, 500_000_003)
1000 .unwrap();
1001
1002 assert_eq!(store.edge_count(), 1);
1004 }
1005
1006 #[test]
1007 fn test_remove_node() {
1008 let mut store = create_test_store();
1009 let cap = create_test_capability();
1010
1011 let add = GraphMutation::add_node(1);
1013 let proof1 = create_test_proof(&add, 1_000_000_000, 1);
1014 store
1015 .graph_apply_proved(&add, &proof1, &cap, 500_000_000)
1016 .unwrap();
1017
1018 assert_eq!(store.node_count(), 1);
1019
1020 let remove = GraphMutation::remove_node(1);
1022 let proof2 = create_test_proof(&remove, 1_000_000_000, 2);
1023 let result = store
1024 .graph_apply_proved(&remove, &proof2, &cap, 500_000_001)
1025 .unwrap();
1026
1027 assert!(result.partition_changed);
1028 assert_eq!(store.node_count(), 0);
1029 }
1030
1031 #[test]
1032 fn test_proof_rejected() {
1033 let mut store = create_test_store();
1034 let cap = create_test_capability();
1035
1036 let mutation = GraphMutation::add_node(1);
1037
1038 let wrong_proof = ProofToken::new(
1040 [0u8; 32],
1041 ProofTier::Standard,
1042 ProofPayload::Hash { hash: [0u8; 32] },
1043 1_000_000_000,
1044 1,
1045 );
1046
1047 let result = store.graph_apply_proved(&mutation, &wrong_proof, &cap, 500_000_000);
1048
1049 assert_eq!(result, Err(KernelError::ProofRejected));
1050 assert_eq!(store.node_count(), 0);
1051 }
1052
1053 #[test]
1054 fn test_witness_log_recording() {
1055 let mut store = create_test_store();
1056 let cap = create_test_capability();
1057
1058 for i in 0..5 {
1060 let mutation = GraphMutation::add_node(i);
1061 let proof = create_test_proof(&mutation, 1_000_000_000, i);
1062 store
1063 .graph_apply_proved(&mutation, &proof, &cap, 500_000_000 + i)
1064 .unwrap();
1065 }
1066
1067 assert_eq!(store.witness_entry_count(), 5);
1068 }
1069}