mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 22:08:11 +08:00
Add graph-storage crate (#4198)
* Add graph-storage crate * Split graph-storage lib.rs into smaller modules * Make graph_craft/core_types dependency optional * Fix CRDT correctness issues flagged in graph-storage PR review * Treat trailing empty export slots as value-equal in Network::value_equal * Address graph-storage review: parameterize CrdtError, reject hot-log corruption, drop LamportClock Default * Address graph-storage review: fix root-delta history walk, validate cross-network refs, make compute_deltas deterministic, harden Priority, index nodes by network * Address graph-storage review: sort sources on deserialize, treat inputs_attributes length as structural, dedupe demo-artwork loader * Add opt-in Delta rev validation, dedup resource sources on deserialize, fix doc grammar * Persist scope injections through storage; harden gesture-end and embedded-source checks * Review * Review * Review * Use new types for NodeId and NetworkId * Adress minor review comments --------- Co-authored-by: Timon <me@timon.zip>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
use crate::RegistryDelta;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Stable, document-scoped identity for a node. Minted as a truncated `blake3(peer, counter)` so it
|
||||
/// reproduces across `to_runtime` -> `from_runtime` round trips. Used purely as an opaque key.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct NodeId(pub u64);
|
||||
|
||||
/// Stable identity for a node network. `ROOT_NETWORK` for the renderable graph; nested networks get a
|
||||
/// path-derived ID via `owned_network_id`. Used purely as an opaque key.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct NetworkId(pub u64);
|
||||
|
||||
impl std::fmt::Display for NodeId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NetworkId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Content-addressed identity for a `Delta`.
|
||||
/// 128-bit blake3 truncation: comfortable collision headroom for any plausible document lifetime
|
||||
/// without being adversarial-grade. Same delta content always produces the same `Rev`.
|
||||
pub type Rev = u128;
|
||||
|
||||
/// Root network ID. The renderable graph lives in `networks[&ROOT_NETWORK]`.
|
||||
pub const ROOT_NETWORK: NetworkId = NetworkId(0);
|
||||
|
||||
/// Upper bound on a network's export slot count, guarding `SetExport` against a malicious or corrupted
|
||||
/// slot index forcing an unbounded `exports` allocation.
|
||||
pub(crate) const MAX_EXPORT_SLOTS: usize = 1 << 16;
|
||||
|
||||
/// Per-device identity. Stable per `(device, document)`. Used for CRDT tiebreaking and `NodeId`
|
||||
/// scoping. Globally unique across all peers ever in a document.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PeerId(pub u64);
|
||||
|
||||
/// Per-human identity. Stable across devices (one user, many devices). Used for identity display
|
||||
/// and undo-chain walking. Derived from `PeerId` via `Registry.peer_users`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct UserId(pub u64);
|
||||
|
||||
/// Lamport timestamp with a peer-ID tiebreak. Higher counter wins; ties broken by peer.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
|
||||
pub struct TimeStamp {
|
||||
pub counter: u64,
|
||||
pub peer: PeerId,
|
||||
}
|
||||
|
||||
impl TimeStamp {
|
||||
/// Pre-edit origin. Used by initial `from_runtime` conversion before any edits have happened.
|
||||
pub const ORIGIN: Self = TimeStamp { counter: 0, peer: PeerId(0) };
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LamportClock {
|
||||
pub(crate) counter: u64,
|
||||
peer: PeerId,
|
||||
}
|
||||
|
||||
impl LamportClock {
|
||||
pub fn new(peer: PeerId) -> Self {
|
||||
Self { counter: 0, peer }
|
||||
}
|
||||
|
||||
/// Mints a fresh local timestamp.
|
||||
pub fn tick(&mut self) -> TimeStamp {
|
||||
self.counter += 1;
|
||||
TimeStamp {
|
||||
counter: self.counter,
|
||||
peer: self.peer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances past an incoming op so future local ticks are causally later.
|
||||
pub fn observe(&mut self, incoming: TimeStamp) {
|
||||
self.counter = self.counter.max(incoming.counter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash the identity-bearing fields of a `Delta` with blake3 and truncate to 128 bits.
|
||||
pub(crate) fn compute_rev(parents: &[Rev], author: PeerId, timestamp: TimeStamp, delta_type: &RegistryDelta) -> Rev {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
let bytes = rmp_serde::to_vec(&(parents, author, timestamp, delta_type)).expect("Delta identity fields must serialize");
|
||||
hasher.update(&bytes);
|
||||
let digest = hasher.finalize();
|
||||
let mut truncated = [0u8; 16];
|
||||
truncated.copy_from_slice(&digest.as_bytes()[..16]);
|
||||
Rev::from_le_bytes(truncated)
|
||||
}
|
||||
Reference in New Issue
Block a user