tawasuyu

tawasuyu/minga

Sovereign P2P content sharing with a content-addressed FUSE mount, in Rust.

⎇ 85795c42cbd49259b2fef73a15989c73ac319141 03_ukupacha/minga/minga-store/src/node_store.rs Rust · 171 líneas · 5.9 KB Historia Quién tocó qué crudo

feat: minga standalone — compartición P2P soberana con montaje FUSE content-addressed (front-door, git-dep al monorepo) 85795c42c · Sergio ·

1//! Almacén persistente de `StoredNode`s indexados por `ContentHash`.
2//!
3//! Cada nodo se serializa con postcard y se inserta en una `sled::Tree`
4//! cuya clave son los 32 bytes del hash. La operación `put` es
5//! recursiva sobre los hijos (igual que `MemStore::put`): cada
6//! subárbol se hashea y persiste exactamente una vez.
7
8use minga_core::{cas, hash_stored, ContentHash, SemanticNode, StoredNode};
9use sled::{Db, Tree};
10
11use crate::error::StoreError;
12
13pub struct SledNodeStore {
14    tree: Tree,
15}
16
17impl SledNodeStore {
18    pub fn open_tree(db: &Db, name: &str) -> Result<Self, StoreError> {
19        Ok(Self {
20            tree: db.open_tree(name)?,
21        })
22    }
23
24    /// Inserta un árbol completo. Recursivamente desempaqueta hijos.
25    /// Devuelve el hash de la raíz. Idempotente: insertar el mismo
26    /// árbol dos veces no añade entradas nuevas.
27    pub fn put(&self, node: &SemanticNode) -> Result<ContentHash, StoreError> {
28        let mut child_hashes = Vec::with_capacity(node.children.len());
29        for c in &node.children {
30            child_hashes.push(self.put(c)?);
31        }
32        let h = cas::hash_components(
33            &node.kind,
34            node.field_name.as_deref(),
35            node.leaf_text.as_deref(),
36            &child_hashes,
37        );
38        if !self.tree.contains_key(h.0)? {
39            let stored = StoredNode {
40                kind: node.kind.clone(),
41                field_name: node.field_name.clone(),
42                leaf_text: node.leaf_text.clone(),
43                children: child_hashes,
44            };
45            let bytes = postcard::to_allocvec(&stored)?;
46            self.tree.insert(h.0, bytes)?;
47        }
48        Ok(h)
49    }
50
51    /// Inserta un nodo ya troceado por hash. Verifica que el hash
52    /// coincida con `hash_stored(stored)` antes de insertar — sin
53    /// esa verificación no podemos confiar en la integridad de lo
54    /// que viene del wire.
55    pub fn put_chunked(
56        &self,
57        hash: ContentHash,
58        stored: &StoredNode,
59    ) -> Result<(), StoreError> {
60        if hash_stored(stored) != hash {
61            return Err(StoreError::HashMismatch);
62        }
63        if !self.tree.contains_key(hash.0)? {
64            let bytes = postcard::to_allocvec(stored)?;
65            self.tree.insert(hash.0, bytes)?;
66        }
67        Ok(())
68    }
69
70    pub fn get(&self, h: &ContentHash) -> Result<Option<StoredNode>, StoreError> {
71        match self.tree.get(h.0)? {
72            Some(bytes) => Ok(Some(postcard::from_bytes(&bytes)?)),
73            None => Ok(None),
74        }
75    }
76
77    pub fn contains(&self, h: &ContentHash) -> Result<bool, StoreError> {
78        Ok(self.tree.contains_key(h.0)?)
79    }
80
81    /// Reconstruye un `SemanticNode` resolviendo recursivamente todos
82    /// los hijos. `Ok(None)` si algún hash no está en el store
83    /// (almacén incompleto).
84    pub fn reconstruct(&self, h: &ContentHash) -> Result<Option<SemanticNode>, StoreError> {
85        let stored = match self.get(h)? {
86            Some(s) => s,
87            None => return Ok(None),
88        };
89        let mut children = Vec::with_capacity(stored.children.len());
90        for ch in &stored.children {
91            match self.reconstruct(ch)? {
92                Some(n) => children.push(n),
93                None => return Ok(None),
94            }
95        }
96        Ok(Some(SemanticNode {
97            kind: stored.kind,
98            field_name: stored.field_name,
99            leaf_text: stored.leaf_text,
100            children,
101        }))
102    }
103
104    pub fn len(&self) -> usize {
105        self.tree.len()
106    }
107
108    pub fn is_empty(&self) -> bool {
109        self.tree.is_empty()
110    }
111
112    pub fn flush(&self) -> Result<(), StoreError> {
113        self.tree.flush()?;
114        Ok(())
115    }
116
117    /// Elimina un nodo del store por su hash. **Cuidado**: los hijos no
118    /// se borran en cascada (otros nodos pueden referenciarlos). El
119    /// caller es responsable de la consistencia (típicamente: usar
120    /// mark-sweep sobre raíces vivas).
121    pub fn remove(&self, h: &ContentHash) -> Result<bool, StoreError> {
122        Ok(self.tree.remove(h.0)?.is_some())
123    }
124
125    /// Itera sólo los hashes (sin deserializar el valor). Más liviano
126    /// que `iter` cuando sólo se necesitan las claves — útil para
127    /// mark-sweep del GC.
128    pub fn iter_hashes(&self) -> impl Iterator<Item = Result<ContentHash, StoreError>> + '_ {
129        self.tree.iter().map(|kv| {
130            let (k, _) = kv?;
131            if k.len() != 32 {
132                return Err(StoreError::HashMismatch);
133            }
134            let mut bytes = [0u8; 32];
135            bytes.copy_from_slice(&k);
136            Ok(ContentHash(bytes))
137        })
138    }
139
140    /// Lee sólo los hashes de los hijos de un nodo (sin reconstruir
141    /// `StoredNode` completo más allá del shape del header postcard).
142    /// Optimización del walk del mark-sweep: para visitar el subárbol
143    /// no necesitamos `kind`/`field_name`/`leaf_text`.
144    pub fn children_of(&self, h: &ContentHash) -> Result<Option<Vec<ContentHash>>, StoreError> {
145        match self.tree.get(h.0)? {
146            Some(bytes) => {
147                let stored: StoredNode = postcard::from_bytes(&bytes)?;
148                Ok(Some(stored.children))
149            }
150            None => Ok(None),
151        }
152    }
153
154    /// Itera todos los pares `(hash, stored_node)` persistidos. Sin
155    /// orden garantizado más allá del lexicográfico de sled. Usado al
156    /// arrancar para volcar el contenido a un `MemStore` en memoria.
157    pub fn iter(
158        &self,
159    ) -> impl Iterator<Item = Result<(ContentHash, StoredNode), StoreError>> + '_ {
160        self.tree.iter().map(|kv| {
161            let (k, v) = kv?;
162            if k.len() != 32 {
163                return Err(StoreError::HashMismatch);
164            }
165            let mut bytes = [0u8; 32];
166            bytes.copy_from_slice(&k);
167            let stored: StoredNode = postcard::from_bytes(&v)?;
168            Ok((ContentHash(bytes), stored))
169        })
170    }
171}