Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,319 @@
//! Causal ordering with vector clocks and hybrid logical clocks
use std::cmp::Ordering;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::ReplicaId;
/// Vector clock for causal ordering
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VectorClock {
/// Map of replica ID to logical timestamp
clock: HashMap<ReplicaId, u64>,
}
impl VectorClock {
/// Create a new vector clock
pub fn new() -> Self {
Self {
clock: HashMap::new(),
}
}
/// Increment the clock for a replica
pub fn increment(&mut self, replica_id: &str) {
let counter = self.clock.entry(replica_id.to_string()).or_insert(0);
*counter += 1;
}
/// Get the timestamp for a replica
pub fn get(&self, replica_id: &str) -> u64 {
self.clock.get(replica_id).copied().unwrap_or(0)
}
/// Merge with another vector clock (take max of each component)
pub fn merge(&mut self, other: &VectorClock) {
for (replica_id, &timestamp) in &other.clock {
let current = self.clock.entry(replica_id.clone()).or_insert(0);
*current = (*current).max(timestamp);
}
}
/// Check if this clock happens-before another
pub fn happens_before(&self, other: &VectorClock) -> bool {
let mut at_least_one_less = false;
// Check all replicas in self
for (replica_id, &self_ts) in &self.clock {
let other_ts = other.get(replica_id);
if self_ts > other_ts {
return false;
}
if self_ts < other_ts {
at_least_one_less = true;
}
}
// Check replicas only in other
for (replica_id, &other_ts) in &other.clock {
if !self.clock.contains_key(replica_id) && other_ts > 0 {
at_least_one_less = true;
}
}
at_least_one_less
}
/// Check if two clocks are concurrent
pub fn is_concurrent(&self, other: &VectorClock) -> bool {
!self.happens_before(other) && !other.happens_before(self) && self != other
}
/// Compare two vector clocks
pub fn compare(&self, other: &VectorClock) -> CausalOrder {
if self == other {
CausalOrder::Equal
} else if self.happens_before(other) {
CausalOrder::Before
} else if other.happens_before(self) {
CausalOrder::After
} else {
CausalOrder::Concurrent
}
}
/// Get all replica IDs
pub fn replicas(&self) -> Vec<&ReplicaId> {
self.clock.keys().collect()
}
/// Get total count (sum of all timestamps)
pub fn total(&self) -> u64 {
self.clock.values().sum()
}
}
impl Default for VectorClock {
fn default() -> Self {
Self::new()
}
}
/// Causal ordering relationship
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CausalOrder {
/// Equal clocks
Equal,
/// First happens before second
Before,
/// First happens after second
After,
/// Concurrent (conflicting)
Concurrent,
}
/// Hybrid Logical Clock for combining physical and logical time
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HybridLogicalClock {
/// Physical time component (milliseconds)
pub physical: u64,
/// Logical counter
pub logical: u32,
/// Replica ID
pub replica: ReplicaId,
}
impl HybridLogicalClock {
/// Create a new HLC
pub fn new(replica: ReplicaId) -> Self {
Self {
physical: current_time_millis(),
logical: 0,
replica,
}
}
/// Get current timestamp
pub fn now(&mut self) -> HlcTimestamp {
let pt = current_time_millis();
if pt > self.physical {
self.physical = pt;
self.logical = 0;
} else {
self.logical += 1;
}
HlcTimestamp {
physical: self.physical,
logical: self.logical,
replica: self.replica.clone(),
}
}
/// Update clock on message receive
pub fn receive(&mut self, remote: &HlcTimestamp) -> HlcTimestamp {
let pt = current_time_millis();
if pt > self.physical && pt > remote.physical {
self.physical = pt;
self.logical = 0;
} else if self.physical > remote.physical {
self.logical += 1;
} else if remote.physical > self.physical {
self.physical = remote.physical;
self.logical = remote.logical + 1;
} else {
self.logical = self.logical.max(remote.logical) + 1;
}
HlcTimestamp {
physical: self.physical,
logical: self.logical,
replica: self.replica.clone(),
}
}
}
/// A timestamp from an HLC
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HlcTimestamp {
/// Physical time
pub physical: u64,
/// Logical counter
pub logical: u32,
/// Origin replica
pub replica: ReplicaId,
}
impl HlcTimestamp {
/// Compare timestamps
pub fn compare(&self, other: &HlcTimestamp) -> Ordering {
match self.physical.cmp(&other.physical) {
Ordering::Equal => match self.logical.cmp(&other.logical) {
Ordering::Equal => self.replica.cmp(&other.replica),
other => other,
},
other => other,
}
}
}
impl PartialOrd for HlcTimestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.compare(other))
}
}
impl Ord for HlcTimestamp {
fn cmp(&self, other: &Self) -> Ordering {
self.compare(other)
}
}
/// Get current time in milliseconds
fn current_time_millis() -> u64 {
chrono::Utc::now().timestamp_millis() as u64
}
/// Lamport timestamp (simpler alternative to vector clocks)
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct LamportClock {
/// Logical timestamp
pub timestamp: u64,
/// Replica ID for tie-breaking
pub replica: ReplicaId,
}
impl LamportClock {
/// Create a new Lamport clock
pub fn new(replica: ReplicaId) -> Self {
Self {
timestamp: 0,
replica,
}
}
/// Increment on local event
pub fn tick(&mut self) -> u64 {
self.timestamp += 1;
self.timestamp
}
/// Update on message receive
pub fn receive(&mut self, remote_timestamp: u64) -> u64 {
self.timestamp = self.timestamp.max(remote_timestamp) + 1;
self.timestamp
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_clock_happens_before() {
let mut clock1 = VectorClock::new();
clock1.increment("r1");
let mut clock2 = clock1.clone();
clock2.increment("r1");
assert!(clock1.happens_before(&clock2));
assert!(!clock2.happens_before(&clock1));
}
#[test]
fn test_vector_clock_concurrent() {
let mut clock1 = VectorClock::new();
clock1.increment("r1");
let mut clock2 = VectorClock::new();
clock2.increment("r2");
assert!(clock1.is_concurrent(&clock2));
assert!(clock2.is_concurrent(&clock1));
}
#[test]
fn test_vector_clock_merge() {
let mut clock1 = VectorClock::new();
clock1.increment("r1");
clock1.increment("r1");
let mut clock2 = VectorClock::new();
clock2.increment("r2");
clock2.increment("r2");
clock2.increment("r2");
clock1.merge(&clock2);
assert_eq!(clock1.get("r1"), 2);
assert_eq!(clock1.get("r2"), 3);
}
#[test]
fn test_hlc_ordering() {
let mut hlc1 = HybridLogicalClock::new("r1".to_string());
let mut hlc2 = HybridLogicalClock::new("r2".to_string());
let ts1 = hlc1.now();
let ts2 = hlc2.now();
// Timestamps should be comparable
let cmp = ts1.compare(&ts2);
assert!(cmp != Ordering::Equal || ts1.replica != ts2.replica);
}
#[test]
fn test_lamport_clock() {
let mut clock = LamportClock::new("r1".to_string());
assert_eq!(clock.tick(), 1);
assert_eq!(clock.tick(), 2);
assert_eq!(clock.receive(10), 11);
assert_eq!(clock.tick(), 12);
}
}

View File

@@ -0,0 +1,285 @@
//! Conflict resolution strategies for concurrent deltas
use ruvector_delta_core::{Delta, VectorDelta};
use crate::{ConsensusError, Result};
/// Conflict resolution strategy
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictStrategy {
/// Last write wins (by timestamp)
LastWriteWins,
/// First write wins
FirstWriteWins,
/// Merge all deltas
Merge,
/// Custom resolution function
Custom,
}
/// Result of conflict resolution
#[derive(Debug, Clone)]
pub struct MergeResult {
/// The resolved delta
pub delta: VectorDelta,
/// Number of deltas merged
pub merged_count: usize,
/// Whether conflicts were detected
pub had_conflicts: bool,
}
/// Trait for conflict resolution
pub trait ConflictResolver<T> {
/// Resolve conflicts between multiple deltas
fn resolve(&self, deltas: &[&T]) -> Result<T>;
}
/// Last-write-wins resolver
pub struct LastWriteWinsResolver;
impl ConflictResolver<VectorDelta> for LastWriteWinsResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the last delta (assumed to be sorted by timestamp)
Ok(deltas.last().unwrap().clone().clone())
}
}
/// First-write-wins resolver
pub struct FirstWriteWinsResolver;
impl ConflictResolver<VectorDelta> for FirstWriteWinsResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the first delta
Ok(deltas.first().unwrap().clone().clone())
}
}
/// Merge resolver - composes all deltas
#[derive(Default)]
pub struct MergeResolver {
/// Weight for averaging (if applicable)
pub averaging: bool,
}
impl MergeResolver {
/// Create new merge resolver
pub fn new() -> Self {
Self { averaging: false }
}
/// Create with averaging enabled
pub fn with_averaging() -> Self {
Self { averaging: true }
}
}
impl ConflictResolver<VectorDelta> for MergeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
if deltas.len() == 1 {
return Ok(deltas[0].clone().clone());
}
// Compose all deltas
let mut result = deltas[0].clone().clone();
for delta in deltas.iter().skip(1) {
result = result.compose((*delta).clone());
}
// Optionally average the result
if self.averaging {
let scale = 1.0 / deltas.len() as f32;
result = result.scale(scale);
}
Ok(result)
}
}
/// Weighted merge resolver
pub struct WeightedMergeResolver {
/// Default weight for deltas without explicit weight
pub default_weight: f32,
}
impl WeightedMergeResolver {
/// Create new weighted resolver
pub fn new(default_weight: f32) -> Self {
Self { default_weight }
}
}
/// Maximum magnitude resolver - takes delta with largest L2 norm
pub struct MaxMagnitudeResolver;
impl ConflictResolver<VectorDelta> for MaxMagnitudeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
let max_delta = deltas
.iter()
.max_by(|a, b| {
a.l2_norm()
.partial_cmp(&b.l2_norm())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
Ok((*max_delta).clone())
}
}
/// Minimum magnitude resolver - takes delta with smallest L2 norm
pub struct MinMagnitudeResolver;
impl ConflictResolver<VectorDelta> for MinMagnitudeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
let min_delta = deltas
.iter()
.min_by(|a, b| {
a.l2_norm()
.partial_cmp(&b.l2_norm())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
Ok((*min_delta).clone())
}
}
/// Clipped merge resolver - merges and clips to range
pub struct ClippedMergeResolver {
/// Minimum value
pub min: f32,
/// Maximum value
pub max: f32,
}
impl ClippedMergeResolver {
/// Create new clipped resolver
pub fn new(min: f32, max: f32) -> Self {
Self { min, max }
}
}
impl ConflictResolver<VectorDelta> for ClippedMergeResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
let merge = MergeResolver::new();
let merged = merge.resolve(deltas)?;
Ok(merged.clip(self.min, self.max))
}
}
/// Resolve by sparsity - prefer sparser deltas
pub struct SparsityResolver;
impl ConflictResolver<VectorDelta> for SparsityResolver {
fn resolve(&self, deltas: &[&VectorDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
// Take the sparsest delta
let sparsest = deltas.iter().min_by_key(|d| d.value.nnz()).unwrap();
Ok((*sparsest).clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_last_write_wins() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let d3 = VectorDelta::from_dense(vec![0.0, 0.0, 1.0]);
let resolver = LastWriteWinsResolver;
let result = resolver.resolve(&[&d1, &d2, &d3]).unwrap();
// Should return d3 (last)
assert_eq!(result.dimensions, 3);
}
#[test]
fn test_first_write_wins() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let resolver = FirstWriteWinsResolver;
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Should return d1 (first)
assert_eq!(result.dimensions, 3);
}
#[test]
fn test_merge_resolver() {
let d1 = VectorDelta::from_dense(vec![1.0, 0.0, 0.0]);
let d2 = VectorDelta::from_dense(vec![0.0, 1.0, 0.0]);
let resolver = MergeResolver::new();
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Should compose both deltas
assert!(!result.is_identity());
}
#[test]
fn test_max_magnitude() {
let small = VectorDelta::from_dense(vec![0.1, 0.1, 0.1]);
let large = VectorDelta::from_dense(vec![1.0, 1.0, 1.0]);
let resolver = MaxMagnitudeResolver;
let result = resolver.resolve(&[&small, &large]).unwrap();
// Should return the larger delta
assert!(result.l2_norm() > 1.0);
}
#[test]
fn test_clipped_merge() {
let d1 = VectorDelta::from_dense(vec![10.0, -10.0]);
let d2 = VectorDelta::from_dense(vec![5.0, -5.0]);
let resolver = ClippedMergeResolver::new(-1.0, 1.0);
let result = resolver.resolve(&[&d1, &d2]).unwrap();
// Values should be clipped to [-1, 1]
if let ruvector_delta_core::DeltaValue::Dense(values) = &result.value {
assert!(values[0] <= 1.0);
assert!(values[1] >= -1.0);
}
}
}

View File

@@ -0,0 +1,485 @@
//! CRDT implementations for delta-based replication
//!
//! Conflict-free Replicated Data Types that can be used with delta propagation.
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use serde::{Deserialize, Serialize};
use crate::ReplicaId;
/// Trait for delta-based CRDTs
pub trait DeltaCrdt: Clone + Send + Sync {
/// The delta type for this CRDT
type Delta: Clone + Send + Sync;
/// Get the current delta (changes since last sync)
fn delta(&self) -> Self::Delta;
/// Apply a delta from another replica
fn apply_delta(&mut self, delta: &Self::Delta);
/// Merge with another CRDT state
fn merge(&mut self, other: &Self);
/// Clear the delta accumulator
fn clear_delta(&mut self);
}
/// G-Counter (Grow-only counter)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GCounter {
/// Per-replica counts
counts: HashMap<ReplicaId, u64>,
/// Delta since last sync
delta: HashMap<ReplicaId, u64>,
}
impl GCounter {
/// Create a new G-Counter
pub fn new() -> Self {
Self {
counts: HashMap::new(),
delta: HashMap::new(),
}
}
/// Increment the counter for a replica
pub fn increment(&mut self, replica: &ReplicaId) {
let count = self.counts.entry(replica.clone()).or_insert(0);
*count += 1;
// Track delta
let delta_count = self.delta.entry(replica.clone()).or_insert(0);
*delta_count += 1;
}
/// Get the current value
pub fn value(&self) -> u64 {
self.counts.values().sum()
}
}
impl Default for GCounter {
fn default() -> Self {
Self::new()
}
}
impl DeltaCrdt for GCounter {
type Delta = HashMap<ReplicaId, u64>;
fn delta(&self) -> Self::Delta {
self.delta.clone()
}
fn apply_delta(&mut self, delta: &Self::Delta) {
for (replica, &count) in delta {
let current = self.counts.entry(replica.clone()).or_insert(0);
*current = (*current).max(count);
}
}
fn merge(&mut self, other: &Self) {
for (replica, &count) in &other.counts {
let current = self.counts.entry(replica.clone()).or_insert(0);
*current = (*current).max(count);
}
}
fn clear_delta(&mut self) {
self.delta.clear();
}
}
/// PN-Counter (Positive-Negative counter)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PNCounter {
/// Positive counts
positive: GCounter,
/// Negative counts
negative: GCounter,
}
impl PNCounter {
/// Create a new PN-Counter
pub fn new() -> Self {
Self {
positive: GCounter::new(),
negative: GCounter::new(),
}
}
/// Increment the counter
pub fn increment(&mut self, replica: &ReplicaId) {
self.positive.increment(replica);
}
/// Decrement the counter
pub fn decrement(&mut self, replica: &ReplicaId) {
self.negative.increment(replica);
}
/// Get the current value
pub fn value(&self) -> i64 {
self.positive.value() as i64 - self.negative.value() as i64
}
}
impl Default for PNCounter {
fn default() -> Self {
Self::new()
}
}
/// Delta for PN-Counter
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PNCounterDelta {
positive: HashMap<ReplicaId, u64>,
negative: HashMap<ReplicaId, u64>,
}
impl DeltaCrdt for PNCounter {
type Delta = PNCounterDelta;
fn delta(&self) -> Self::Delta {
PNCounterDelta {
positive: self.positive.delta(),
negative: self.negative.delta(),
}
}
fn apply_delta(&mut self, delta: &Self::Delta) {
self.positive.apply_delta(&delta.positive);
self.negative.apply_delta(&delta.negative);
}
fn merge(&mut self, other: &Self) {
self.positive.merge(&other.positive);
self.negative.merge(&other.negative);
}
fn clear_delta(&mut self) {
self.positive.clear_delta();
self.negative.clear_delta();
}
}
/// LWW-Register (Last-Writer-Wins Register)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LWWRegister<T: Clone> {
/// Current value
value: Option<T>,
/// Timestamp of last write
timestamp: u64,
/// Replica that made last write
replica: ReplicaId,
/// Delta (new write)
delta: Option<(T, u64, ReplicaId)>,
}
impl<T: Clone> LWWRegister<T> {
/// Create a new register
pub fn new() -> Self {
Self {
value: None,
timestamp: 0,
replica: String::new(),
delta: None,
}
}
/// Set the value
pub fn set(&mut self, value: T, timestamp: u64, replica: ReplicaId) {
if timestamp > self.timestamp || (timestamp == self.timestamp && replica > self.replica) {
self.delta = Some((value.clone(), timestamp, replica.clone()));
self.value = Some(value);
self.timestamp = timestamp;
self.replica = replica;
}
}
/// Get the current value
pub fn get(&self) -> Option<&T> {
self.value.as_ref()
}
/// Get the timestamp
pub fn timestamp(&self) -> u64 {
self.timestamp
}
}
impl<T: Clone> Default for LWWRegister<T> {
fn default() -> Self {
Self::new()
}
}
/// Delta for LWW-Register
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LWWRegisterDelta<T: Clone> {
pub value: Option<T>,
pub timestamp: u64,
pub replica: ReplicaId,
}
impl<T: Clone + Send + Sync> DeltaCrdt for LWWRegister<T> {
type Delta = LWWRegisterDelta<T>;
fn delta(&self) -> Self::Delta {
if let Some((value, timestamp, replica)) = &self.delta {
LWWRegisterDelta {
value: Some(value.clone()),
timestamp: *timestamp,
replica: replica.clone(),
}
} else {
LWWRegisterDelta {
value: None,
timestamp: 0,
replica: String::new(),
}
}
}
fn apply_delta(&mut self, delta: &Self::Delta) {
if let Some(value) = &delta.value {
self.set(value.clone(), delta.timestamp, delta.replica.clone());
}
}
fn merge(&mut self, other: &Self) {
if let Some(value) = &other.value {
self.set(value.clone(), other.timestamp, other.replica.clone());
}
}
fn clear_delta(&mut self) {
self.delta = None;
}
}
/// OR-Set (Observed-Remove Set)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ORSet<T: Clone + Eq + Hash> {
/// Elements with their unique tags
elements: HashMap<T, HashSet<String>>,
/// Tombstones (removed tags)
tombstones: HashSet<String>,
/// Delta additions
delta_adds: Vec<(T, String)>,
/// Delta removes
delta_removes: Vec<String>,
}
impl<T: Clone + Eq + Hash> ORSet<T> {
/// Create a new OR-Set
pub fn new() -> Self {
Self {
elements: HashMap::new(),
tombstones: HashSet::new(),
delta_adds: Vec::new(),
delta_removes: Vec::new(),
}
}
/// Add an element
pub fn add(&mut self, element: T, replica: &ReplicaId) {
let tag = format!("{}:{}", replica, uuid::Uuid::new_v4());
self.elements
.entry(element.clone())
.or_insert_with(HashSet::new)
.insert(tag.clone());
self.delta_adds.push((element, tag));
}
/// Remove an element (all its tags)
pub fn remove(&mut self, element: &T) {
if let Some(tags) = self.elements.remove(element) {
for tag in tags {
self.tombstones.insert(tag.clone());
self.delta_removes.push(tag);
}
}
}
/// Check if element is in set
pub fn contains(&self, element: &T) -> bool {
if let Some(tags) = self.elements.get(element) {
!tags.is_empty()
} else {
false
}
}
/// Get all elements
pub fn elements(&self) -> Vec<&T> {
self.elements
.iter()
.filter(|(_, tags)| !tags.is_empty())
.map(|(e, _)| e)
.collect()
}
/// Get size
pub fn len(&self) -> usize {
self.elements
.iter()
.filter(|(_, tags)| !tags.is_empty())
.count()
}
/// Check if empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T: Clone + Eq + Hash> Default for ORSet<T> {
fn default() -> Self {
Self::new()
}
}
/// Delta for OR-Set
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ORSetDelta<T: Clone> {
pub adds: Vec<(T, String)>,
pub removes: Vec<String>,
}
impl<T: Clone + Eq + Hash + Send + Sync> DeltaCrdt for ORSet<T> {
type Delta = ORSetDelta<T>;
fn delta(&self) -> Self::Delta {
ORSetDelta {
adds: self.delta_adds.clone(),
removes: self.delta_removes.clone(),
}
}
fn apply_delta(&mut self, delta: &Self::Delta) {
// Apply removes first
for tag in &delta.removes {
self.tombstones.insert(tag.clone());
for tags in self.elements.values_mut() {
tags.remove(tag);
}
}
// Apply adds (if not tombstoned)
for (element, tag) in &delta.adds {
if !self.tombstones.contains(tag) {
self.elements
.entry(element.clone())
.or_insert_with(HashSet::new)
.insert(tag.clone());
}
}
}
fn merge(&mut self, other: &Self) {
// Merge tombstones
for tag in &other.tombstones {
self.tombstones.insert(tag.clone());
for tags in self.elements.values_mut() {
tags.remove(tag);
}
}
// Merge elements
for (element, other_tags) in &other.elements {
let tags = self
.elements
.entry(element.clone())
.or_insert_with(HashSet::new);
for tag in other_tags {
if !self.tombstones.contains(tag) {
tags.insert(tag.clone());
}
}
}
}
fn clear_delta(&mut self) {
self.delta_adds.clear();
self.delta_removes.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcounter() {
let mut c1 = GCounter::new();
let mut c2 = GCounter::new();
c1.increment(&"r1".to_string());
c1.increment(&"r1".to_string());
c2.increment(&"r2".to_string());
c1.merge(&c2);
assert_eq!(c1.value(), 3);
}
#[test]
fn test_pncounter() {
let mut c = PNCounter::new();
c.increment(&"r1".to_string());
c.increment(&"r1".to_string());
c.decrement(&"r1".to_string());
assert_eq!(c.value(), 1);
}
#[test]
fn test_lww_register() {
let mut r1 = LWWRegister::new();
let mut r2 = LWWRegister::new();
r1.set("value1".to_string(), 1, "r1".to_string());
r2.set("value2".to_string(), 2, "r2".to_string());
r1.merge(&r2);
assert_eq!(r1.get(), Some(&"value2".to_string()));
}
#[test]
fn test_orset() {
let mut s1 = ORSet::new();
let mut s2 = ORSet::new();
s1.add("a", &"r1".to_string());
s1.add("b", &"r1".to_string());
s2.add("c", &"r2".to_string());
s1.merge(&s2);
assert!(s1.contains(&"a"));
assert!(s1.contains(&"b"));
assert!(s1.contains(&"c"));
assert_eq!(s1.len(), 3);
}
#[test]
fn test_orset_remove() {
let mut s1 = ORSet::new();
let mut s2 = ORSet::new();
s1.add("a", &"r1".to_string());
s2.merge(&s1);
s1.remove(&"a");
s2.merge(&s1);
assert!(!s2.contains(&"a"));
}
}

View File

@@ -0,0 +1,43 @@
//! Error types for consensus operations
use std::fmt;
/// Result type for consensus operations
pub type Result<T> = std::result::Result<T, ConsensusError>;
/// Errors that can occur during consensus
#[derive(Debug, Clone)]
pub enum ConsensusError {
/// Delta error
DeltaError(String),
/// Causal ordering violation
CausalViolation(String),
/// Conflict resolution failed
ConflictResolutionFailed(String),
/// Invalid operation
InvalidOperation(String),
/// Network error
NetworkError(String),
/// Timeout
Timeout(String),
/// Replica not found
ReplicaNotFound(String),
}
impl fmt::Display for ConsensusError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DeltaError(msg) => write!(f, "Delta error: {}", msg),
Self::CausalViolation(msg) => write!(f, "Causal violation: {}", msg),
Self::ConflictResolutionFailed(msg) => {
write!(f, "Conflict resolution failed: {}", msg)
}
Self::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
Self::NetworkError(msg) => write!(f, "Network error: {}", msg),
Self::Timeout(msg) => write!(f, "Timeout: {}", msg),
Self::ReplicaNotFound(id) => write!(f, "Replica not found: {}", id),
}
}
}
impl std::error::Error for ConsensusError {}

View File

@@ -0,0 +1,488 @@
//! # RuVector Delta Consensus
//!
//! Distributed delta consensus using CRDTs and causal ordering.
//! Enables consistent delta application across distributed nodes.
//!
//! ## Key Features
//!
//! - CRDT-based delta merging
//! - Causal ordering with vector clocks
//! - Conflict resolution strategies
//! - Delta compression for network transfer
#![warn(missing_docs)]
#![warn(clippy::all)]
pub mod causal;
pub mod conflict;
pub mod crdt;
pub mod error;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use ruvector_delta_core::{Delta, VectorDelta};
pub use causal::{CausalOrder, HybridLogicalClock, VectorClock};
pub use conflict::{ConflictResolver, ConflictStrategy, MergeResult};
pub use crdt::{DeltaCrdt, GCounter, LWWRegister, ORSet, PNCounter};
pub use error::{ConsensusError, Result};
/// A replica identifier
pub type ReplicaId = String;
/// A delta with causal metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CausalDelta {
/// Unique delta ID
pub id: Uuid,
/// The delta data
pub delta: VectorDelta,
/// Vector clock for causal ordering
pub vector_clock: VectorClock,
/// Origin replica
pub origin: ReplicaId,
/// Timestamp (for HLC)
pub timestamp: u64,
/// Dependencies (delta IDs this depends on)
pub dependencies: Vec<Uuid>,
}
impl CausalDelta {
/// Create a new causal delta
pub fn new(delta: VectorDelta, origin: ReplicaId, clock: VectorClock) -> Self {
Self {
id: Uuid::new_v4(),
delta,
vector_clock: clock,
origin,
timestamp: chrono::Utc::now().timestamp_millis() as u64,
dependencies: Vec::new(),
}
}
/// Add a dependency
pub fn with_dependency(mut self, dep: Uuid) -> Self {
self.dependencies.push(dep);
self
}
/// Check if this delta is causally before another
pub fn is_before(&self, other: &CausalDelta) -> bool {
self.vector_clock.happens_before(&other.vector_clock)
}
/// Check if deltas are concurrent
pub fn is_concurrent(&self, other: &CausalDelta) -> bool {
self.vector_clock.is_concurrent(&other.vector_clock)
}
}
/// Configuration for consensus
#[derive(Debug, Clone)]
pub struct ConsensusConfig {
/// This replica's ID
pub replica_id: ReplicaId,
/// Conflict resolution strategy
pub conflict_strategy: ConflictStrategy,
/// Maximum pending deltas before compaction
pub max_pending: usize,
/// Whether to enable causal delivery
pub causal_delivery: bool,
}
impl Default for ConsensusConfig {
fn default() -> Self {
Self {
replica_id: Uuid::new_v4().to_string(),
conflict_strategy: ConflictStrategy::LastWriteWins,
max_pending: 1000,
causal_delivery: true,
}
}
}
/// Delta consensus coordinator
pub struct DeltaConsensus {
config: ConsensusConfig,
/// Current vector clock
clock: RwLock<VectorClock>,
/// Pending deltas awaiting delivery
pending: RwLock<HashMap<Uuid, CausalDelta>>,
/// Applied delta IDs
applied: RwLock<HashSet<Uuid>>,
/// Conflict resolver
resolver: Box<dyn ConflictResolver<VectorDelta> + Send + Sync>,
}
impl DeltaConsensus {
/// Create a new consensus coordinator
pub fn new(config: ConsensusConfig) -> Self {
let resolver: Box<dyn ConflictResolver<VectorDelta> + Send + Sync> =
match config.conflict_strategy {
ConflictStrategy::LastWriteWins => Box::new(conflict::LastWriteWinsResolver),
ConflictStrategy::FirstWriteWins => Box::new(conflict::FirstWriteWinsResolver),
ConflictStrategy::Merge => Box::new(conflict::MergeResolver::default()),
ConflictStrategy::Custom => Box::new(conflict::MergeResolver::default()),
};
Self {
config,
clock: RwLock::new(VectorClock::new()),
pending: RwLock::new(HashMap::new()),
applied: RwLock::new(HashSet::new()),
resolver,
}
}
/// Get current replica ID
pub fn replica_id(&self) -> &ReplicaId {
&self.config.replica_id
}
/// Create a new local delta with causal metadata
pub fn create_delta(&self, delta: VectorDelta) -> CausalDelta {
let mut clock = self.clock.write();
clock.increment(&self.config.replica_id);
CausalDelta::new(delta, self.config.replica_id.clone(), clock.clone())
}
/// Receive a delta from another replica
pub fn receive(&self, delta: CausalDelta) -> Result<DeliveryStatus> {
// Check if already applied
if self.applied.read().contains(&delta.id) {
return Ok(DeliveryStatus::AlreadyApplied);
}
// Check causal dependencies
if self.config.causal_delivery {
if !self.dependencies_satisfied(&delta) {
// Queue for later delivery
self.pending.write().insert(delta.id, delta);
return Ok(DeliveryStatus::Pending);
}
}
// Update vector clock
{
let mut clock = self.clock.write();
clock.merge(&delta.vector_clock);
clock.increment(&self.config.replica_id);
}
// Mark as applied
self.applied.write().insert(delta.id);
// Try to deliver pending deltas
self.try_deliver_pending()?;
Ok(DeliveryStatus::Delivered)
}
/// Apply delta to a base vector, handling conflicts
pub fn apply_with_consensus(
&self,
delta: &CausalDelta,
base: &mut Vec<f32>,
concurrent_deltas: &[CausalDelta],
) -> Result<()> {
if concurrent_deltas.is_empty() {
// No conflicts, apply directly
delta
.delta
.apply(base)
.map_err(|e| ConsensusError::DeltaError(format!("{:?}", e)))?;
} else {
// Resolve conflicts
let mut all_deltas: Vec<&CausalDelta> = vec![delta];
all_deltas.extend(concurrent_deltas);
let resolved = self.resolve_conflicts(&all_deltas)?;
resolved
.apply(base)
.map_err(|e| ConsensusError::DeltaError(format!("{:?}", e)))?;
}
Ok(())
}
/// Get all pending deltas
pub fn pending_deltas(&self) -> Vec<CausalDelta> {
self.pending.read().values().cloned().collect()
}
/// Get number of pending deltas
pub fn pending_count(&self) -> usize {
self.pending.read().len()
}
/// Get current vector clock
pub fn current_clock(&self) -> VectorClock {
self.clock.read().clone()
}
/// Clear applied history (for memory management)
pub fn clear_history(&self) {
self.applied.write().clear();
}
// Private methods
fn dependencies_satisfied(&self, delta: &CausalDelta) -> bool {
let applied = self.applied.read();
for dep in &delta.dependencies {
if !applied.contains(dep) {
return false;
}
}
true
}
fn try_deliver_pending(&self) -> Result<usize> {
let mut delivered = 0;
loop {
let pending = self.pending.read();
let ready: Vec<Uuid> = pending
.iter()
.filter(|(_, d)| self.dependencies_satisfied(d))
.map(|(id, _)| *id)
.collect();
drop(pending);
if ready.is_empty() {
break;
}
for id in ready {
if let Some(delta) = self.pending.write().remove(&id) {
// Update clock
{
let mut clock = self.clock.write();
clock.merge(&delta.vector_clock);
clock.increment(&self.config.replica_id);
}
self.applied.write().insert(id);
delivered += 1;
}
}
}
Ok(delivered)
}
fn resolve_conflicts(&self, deltas: &[&CausalDelta]) -> Result<VectorDelta> {
if deltas.is_empty() {
return Err(ConsensusError::InvalidOperation(
"No deltas to resolve".into(),
));
}
if deltas.len() == 1 {
return Ok(deltas[0].delta.clone());
}
// Sort by timestamp for deterministic resolution
let mut sorted: Vec<_> = deltas.iter().collect();
sorted.sort_by_key(|d| d.timestamp);
// Use resolver
let delta_refs: Vec<_> = sorted.iter().map(|d| &d.delta).collect();
self.resolver.resolve(&delta_refs)
}
}
/// Status of delta delivery
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeliveryStatus {
/// Delta was delivered successfully
Delivered,
/// Delta is pending (waiting for dependencies)
Pending,
/// Delta was already applied
AlreadyApplied,
/// Delta was rejected
Rejected,
}
/// Gossip protocol for delta dissemination
pub struct DeltaGossip {
consensus: Arc<DeltaConsensus>,
/// Known peers
peers: RwLock<HashSet<ReplicaId>>,
/// Deltas to send
outbox: RwLock<Vec<CausalDelta>>,
}
impl DeltaGossip {
/// Create new gossip protocol
pub fn new(consensus: Arc<DeltaConsensus>) -> Self {
Self {
consensus,
peers: RwLock::new(HashSet::new()),
outbox: RwLock::new(Vec::new()),
}
}
/// Add a peer
pub fn add_peer(&self, peer: ReplicaId) {
self.peers.write().insert(peer);
}
/// Remove a peer
pub fn remove_peer(&self, peer: &ReplicaId) {
self.peers.write().remove(peer);
}
/// Queue delta for gossip
pub fn broadcast(&self, delta: CausalDelta) {
self.outbox.write().push(delta);
}
/// Get deltas to send
pub fn get_outbox(&self) -> Vec<CausalDelta> {
let mut outbox = self.outbox.write();
std::mem::take(&mut *outbox)
}
/// Receive gossip from peer
pub fn receive_gossip(&self, deltas: Vec<CausalDelta>) -> Result<GossipResult> {
let mut delivered = 0;
let mut pending = 0;
let mut already_applied = 0;
for delta in deltas {
match self.consensus.receive(delta)? {
DeliveryStatus::Delivered => delivered += 1,
DeliveryStatus::Pending => pending += 1,
DeliveryStatus::AlreadyApplied => already_applied += 1,
DeliveryStatus::Rejected => {}
}
}
Ok(GossipResult {
delivered,
pending,
already_applied,
})
}
/// Get anti-entropy summary (for sync)
pub fn get_summary(&self) -> GossipSummary {
GossipSummary {
replica_id: self.consensus.replica_id().clone(),
clock: self.consensus.current_clock(),
pending_count: self.consensus.pending_count(),
}
}
}
/// Result of gossip receive
#[derive(Debug, Clone)]
pub struct GossipResult {
/// Deltas delivered
pub delivered: usize,
/// Deltas pending
pub pending: usize,
/// Deltas already applied
pub already_applied: usize,
}
/// Summary for anti-entropy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GossipSummary {
/// Replica ID
pub replica_id: ReplicaId,
/// Current vector clock
pub clock: VectorClock,
/// Number of pending deltas
pub pending_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_delta() {
let config = ConsensusConfig {
replica_id: "replica1".to_string(),
..Default::default()
};
let consensus = DeltaConsensus::new(config);
let delta = VectorDelta::from_dense(vec![1.0, 2.0, 3.0]);
let causal = consensus.create_delta(delta);
assert_eq!(causal.origin, "replica1");
assert!(!causal.id.is_nil());
}
#[test]
fn test_receive_delta() {
let config = ConsensusConfig {
replica_id: "replica1".to_string(),
causal_delivery: false,
..Default::default()
};
let consensus = DeltaConsensus::new(config);
let delta = VectorDelta::from_dense(vec![1.0, 2.0, 3.0]);
let causal = CausalDelta::new(delta, "replica2".to_string(), VectorClock::new());
let status = consensus.receive(causal).unwrap();
assert_eq!(status, DeliveryStatus::Delivered);
}
#[test]
fn test_causal_ordering() {
let clock1 = {
let mut c = VectorClock::new();
c.increment("r1");
c
};
let clock2 = {
let mut c = clock1.clone();
c.increment("r1");
c
};
let d1 = CausalDelta::new(VectorDelta::from_dense(vec![1.0]), "r1".to_string(), clock1);
let d2 = CausalDelta::new(VectorDelta::from_dense(vec![2.0]), "r1".to_string(), clock2);
assert!(d1.is_before(&d2));
assert!(!d2.is_before(&d1));
}
#[test]
fn test_concurrent_deltas() {
let clock1 = {
let mut c = VectorClock::new();
c.increment("r1");
c
};
let clock2 = {
let mut c = VectorClock::new();
c.increment("r2");
c
};
let d1 = CausalDelta::new(VectorDelta::from_dense(vec![1.0]), "r1".to_string(), clock1);
let d2 = CausalDelta::new(VectorDelta::from_dense(vec![2.0]), "r2".to_string(), clock2);
assert!(d1.is_concurrent(&d2));
}
}