exocore_core/cell/
cell.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4    str::FromStr,
5    sync::{Arc, RwLock},
6};
7
8use exocore_protos::{apps::Manifest, generated::exocore_core::CellConfig, registry::Registry};
9use libp2p::PeerId;
10
11use super::{
12    cell_apps::cell_app_directory, config::CellConfigExt, ApplicationId, CellApplications,
13    CellNode, CellNodeRole, CellNodes, CellNodesRead, CellNodesWrite, Error, LocalNode, Node,
14    NodeId,
15};
16use crate::{
17    dir::DynDirectory,
18    sec::keys::{Keypair, PublicKey},
19};
20
21const CELL_CONFIG_FILE: &str = "cell.yaml";
22
23/// A Cell represents a private enclosure in which the data and applications of
24/// a user are hosted. A Cell resides on multiple nodes.
25#[derive(Clone)]
26pub struct Cell {
27    identity: Arc<Identity>,
28    nodes: Arc<RwLock<HashMap<NodeId, CellNode>>>,
29    apps: CellApplications,
30    schemas: Arc<Registry>,
31    dir: DynDirectory,
32}
33
34struct Identity {
35    config: CellConfig,
36    public_key: PublicKey,
37    cell_id: CellId,
38    local_node: LocalNode,
39    name: String,
40}
41
42impl Cell {
43    pub fn from_config(config: CellConfig, local_node: LocalNode) -> Result<EitherCell, Error> {
44        let cell = Cell::build(config.clone(), local_node)?;
45
46        let either_cell = if !config.keypair.is_empty() {
47            let keypair = Keypair::decode_base58_string(&config.keypair)
48                .map_err(|err| Error::Cell(anyhow!("Couldn't parse cell keypair: {}", err)))?;
49            let full_cell = FullCell::build(keypair, cell);
50            EitherCell::Full(Box::new(full_cell))
51        } else {
52            EitherCell::Cell(Box::new(cell))
53        };
54
55        Ok(either_cell)
56    }
57
58    pub fn from_directory(
59        dir: impl Into<DynDirectory>,
60        local_node: LocalNode,
61    ) -> Result<EitherCell, Error> {
62        let dir = dir.into();
63
64        let cell_config = {
65            let config_file = dir.open_read(Path::new(CELL_CONFIG_FILE))?;
66            CellConfig::read_yaml(config_file)?
67        };
68
69        Self::from_config(cell_config, local_node)
70    }
71
72    pub fn from_local_node(local_node: LocalNode) -> Result<Vec<EitherCell>, Error> {
73        let config = local_node.config();
74
75        let mut either_cells = Vec::new();
76        for node_cell_config in &config.cells {
77            let either_cell = if node_cell_config.location.is_none() {
78                let cell_id = CellId::from_str(&node_cell_config.id).map_err(|_err| {
79                    Error::Cell(anyhow!("couldn't parse cell id '{}'", node_cell_config.id))
80                })?;
81                let cell_dir = local_node.cell_directory(&cell_id);
82                Cell::from_directory(cell_dir, local_node.clone()).map_err(|err| {
83                    Error::Cell(anyhow!("Failed to load cell id '{}': {}", cell_id, err))
84                })?
85            } else {
86                warn!("Loading from inlined cell config...");
87                let cell_config = CellConfig::from_node_cell(node_cell_config)?;
88                Self::from_config(cell_config, local_node.clone())?
89            };
90
91            either_cells.push(either_cell);
92        }
93
94        Ok(either_cells)
95    }
96
97    pub fn from_local_node_directory(
98        dir: impl Into<DynDirectory>,
99    ) -> Result<(Vec<EitherCell>, LocalNode), Error> {
100        let local_node = LocalNode::from_directory(dir.into())?;
101        let cells = Self::from_local_node(local_node.clone())?;
102        Ok((cells, local_node))
103    }
104
105    fn build(config: CellConfig, local_node: LocalNode) -> Result<Cell, Error> {
106        let public_key = PublicKey::decode_base58_string(&config.public_key)
107            .map_err(|err| Error::Cell(anyhow!("Couldn't parse cell public key: {}", err)))?;
108        let cell_id = CellId::from_public_key(&public_key);
109
110        let mut nodes_map = HashMap::new();
111        let local_cell_node = CellNode::new(local_node.node().clone());
112        nodes_map.insert(local_node.id().clone(), local_cell_node);
113
114        let name = Some(config.name.clone())
115            .filter(|n| !String::is_empty(n))
116            .unwrap_or_else(|| public_key.generate_name());
117
118        let schemas = Arc::new(Registry::new_with_exocore_types());
119
120        let dir = local_node.cell_directory(&cell_id);
121
122        let cell = Cell {
123            identity: Arc::new(Identity {
124                config: config.clone(),
125                public_key,
126                cell_id,
127                local_node,
128                name,
129            }),
130            apps: CellApplications::new(schemas.clone()),
131            nodes: Arc::new(RwLock::new(nodes_map)),
132            schemas,
133            dir,
134        };
135
136        {
137            // load nodes from config
138            let mut nodes = cell.nodes_mut();
139            for node_config in &config.nodes {
140                let node = Node::from_config(node_config.node.clone().ok_or_else(|| {
141                    Error::Config(anyhow!("Cell node config node is not defined"))
142                })?)?;
143
144                let mut cell_node = CellNode::new(node);
145
146                for role in node_config.roles() {
147                    cell_node.add_role(CellNodeRole::from_config(role)?);
148                }
149
150                nodes.add_cell_node(cell_node);
151            }
152        }
153
154        {
155            // load apps from config
156            let apps_dir = &cell.apps_directory();
157            cell.apps
158                .load_from_configurations(cell.id(), apps_dir, config.apps.iter())?;
159        }
160
161        Ok(cell)
162    }
163
164    pub fn id(&self) -> &CellId {
165        &self.identity.cell_id
166    }
167
168    pub fn name(&self) -> &str {
169        &self.identity.name
170    }
171
172    pub fn local_node(&self) -> &LocalNode {
173        &self.identity.local_node
174    }
175
176    pub fn local_node_has_role(&self, role: CellNodeRole) -> bool {
177        let nodes = self.nodes();
178        if let Some(cn) = nodes.get(self.identity.local_node.id()) {
179            cn.has_role(role)
180        } else {
181            false
182        }
183    }
184
185    pub fn public_key(&self) -> &PublicKey {
186        &self.identity.public_key
187    }
188
189    pub fn config(&self) -> &CellConfig {
190        &self.identity.config
191    }
192
193    pub fn nodes(&self) -> CellNodesRead {
194        let nodes = self
195            .nodes
196            .read()
197            .expect("Couldn't acquire read lock on nodes");
198        CellNodesRead { cell: self, nodes }
199    }
200
201    pub fn nodes_mut(&self) -> CellNodesWrite {
202        let nodes = self
203            .nodes
204            .write()
205            .expect("Couldn't acquire write lock on nodes");
206        CellNodesWrite { cell: self, nodes }
207    }
208
209    pub fn schemas(&self) -> &Arc<Registry> {
210        &self.schemas
211    }
212
213    pub fn applications(&self) -> &CellApplications {
214        &self.apps
215    }
216
217    pub fn directory(&self) -> &DynDirectory {
218        &self.dir
219    }
220
221    pub fn chain_directory(&self) -> DynDirectory {
222        self.directory().scope(PathBuf::from("chain"))
223    }
224
225    pub fn store_directory(&self) -> DynDirectory {
226        self.directory().scope(PathBuf::from("store"))
227    }
228
229    pub fn apps_directory(&self) -> DynDirectory {
230        self.directory().scope(PathBuf::from("apps"))
231    }
232
233    pub fn app_directory(&self, app_manifest: &Manifest) -> Result<DynDirectory, Error> {
234        let app_id = ApplicationId::from_base58_public_key(&app_manifest.public_key)?;
235        let apps_dir = self.apps_directory();
236        Ok(cell_app_directory(
237            &apps_dir,
238            &app_id,
239            &app_manifest.version,
240        ))
241    }
242
243    pub fn temp_directory(&self) -> DynDirectory {
244        self.directory().scope(PathBuf::from("tmp"))
245    }
246
247    pub fn save_config(&self, config: &CellConfig) -> Result<(), Error> {
248        Self::write_cell_config(self.directory(), config)
249    }
250
251    pub fn write_cell_config(dir: &DynDirectory, config: &CellConfig) -> Result<(), Error> {
252        let file = dir.open_create(Path::new(CELL_CONFIG_FILE))?;
253        config.write_yaml(file)?;
254        Ok(())
255    }
256}
257
258impl std::fmt::Display for Cell {
259    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
260        f.write_str("Cell{")?;
261        f.write_str(&self.identity.name)?;
262        f.write_str("}")
263    }
264}
265
266/// Unique identifier of a cell, which is built by hashing the public key
267///
268/// For now, this ID is generated the same way as node IDs.
269#[derive(PartialEq, Eq, Clone, Debug, Hash)]
270pub struct CellId(String);
271
272impl CellId {
273    /// Create a Cell ID from a public key by using libp2p method to be
274    /// compatible with it
275    pub fn from_public_key(public_key: &PublicKey) -> CellId {
276        let peer_id = PeerId::from_public_key(public_key.to_libp2p());
277        CellId(peer_id.to_string())
278    }
279
280    pub fn from_string(id: String) -> CellId {
281        CellId(id)
282    }
283
284    pub fn from_bytes(id: &[u8]) -> CellId {
285        CellId(String::from_utf8_lossy(id).to_string())
286    }
287
288    pub fn as_bytes(&self) -> &[u8] {
289        self.0.as_bytes()
290    }
291}
292
293impl std::fmt::Display for CellId {
294    #[inline]
295    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
296        std::fmt::Display::fmt(&self.0, f)
297    }
298}
299
300impl std::str::FromStr for CellId {
301    type Err = ();
302
303    fn from_str(s: &str) -> Result<Self, Self::Err> {
304        Ok(CellId(s.to_string()))
305    }
306}
307
308/// A Cell for which we have full access since we have the private key.
309#[derive(Clone)]
310pub struct FullCell {
311    cell: Cell,
312    keypair: Keypair,
313}
314
315impl FullCell {
316    pub fn generate(local_node: LocalNode) -> Result<FullCell, Error> {
317        let keypair = Keypair::generate_ed25519();
318        let config = CellConfig {
319            keypair: keypair.encode_base58_string(),
320            public_key: keypair.public().encode_base58_string(),
321            ..Default::default()
322        };
323
324        let cell = Cell::build(config.clone(), local_node)?;
325        let full_cell = Self::build(keypair, cell);
326        full_cell.cell().save_config(&config)?;
327
328        Ok(full_cell)
329    }
330
331    fn build(keypair: Keypair, cell: Cell) -> FullCell {
332        FullCell { cell, keypair }
333    }
334
335    pub fn keypair(&self) -> &Keypair {
336        &self.keypair
337    }
338
339    pub fn cell(&self) -> &Cell {
340        &self.cell
341    }
342
343    #[cfg(any(test, feature = "tests-utils"))]
344    pub fn with_local_node(self, local_node: LocalNode) -> FullCell {
345        let cell = Cell::from_config(self.cell.config().clone(), local_node)
346            .expect("Couldn't rebuild cell from current cell");
347        cell.unwrap_full()
348    }
349}
350
351/// Enum wrapping a full or non-full cell
352#[derive(Clone)]
353pub enum EitherCell {
354    Full(Box<FullCell>),
355    Cell(Box<Cell>),
356}
357
358impl EitherCell {
359    pub fn nodes(&self) -> CellNodesRead {
360        match self {
361            EitherCell::Full(full_cell) => full_cell.cell().nodes(),
362            EitherCell::Cell(cell) => cell.nodes(),
363        }
364    }
365
366    pub fn nodes_mut(&self) -> CellNodesWrite {
367        match self {
368            EitherCell::Full(full_cell) => full_cell.cell().nodes_mut(),
369            EitherCell::Cell(cell) => cell.nodes_mut(),
370        }
371    }
372
373    pub fn cell(&self) -> &Cell {
374        match self {
375            EitherCell::Full(cell) => cell.cell(),
376            EitherCell::Cell(cell) => cell,
377        }
378    }
379
380    pub fn unwrap_full(self) -> FullCell {
381        match self {
382            EitherCell::Full(cell) => cell.as_ref().clone(),
383            _ => panic!("Tried to unwrap EitherCell into Full, but wasn't"),
384        }
385    }
386
387    pub fn unwrap_cell(self) -> Cell {
388        match self {
389            EitherCell::Cell(cell) => cell.as_ref().clone(),
390            _ => panic!("Tried to unwrap EitherCell into Cell, but wasn't"),
391        }
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use exocore_protos::core::CellApplicationConfig;
398
399    use super::*;
400    use crate::{
401        cell::{Application, CellApplicationConfigExt},
402        dir::{ram::RamDirectory, Directory},
403    };
404
405    #[test]
406    fn test_save_load_directory() {
407        let dir = RamDirectory::new();
408        let node = LocalNode::generate_in_directory(dir.clone()).unwrap();
409
410        let cell1 = FullCell::generate(node.clone()).unwrap();
411        let cell_dir = cell1.cell().directory().clone();
412
413        let cell2 = Cell::from_directory(cell_dir, node).unwrap();
414        assert_eq!(cell1.cell().id(), cell2.cell().id());
415    }
416
417    #[test]
418    fn test_load_inlined_cell_apps() {
419        let dir = RamDirectory::new();
420        let node = LocalNode::generate_in_directory(dir.clone()).unwrap();
421
422        let full_cell = FullCell::generate(node.clone()).unwrap();
423        let cell = full_cell.cell();
424
425        // Crate an application in memory
426        let mem_dir = RamDirectory::default();
427        let (_kp, app) = Application::generate(mem_dir.clone(), "some app".to_string()).unwrap();
428        app.save_manifest(app.manifest()).unwrap();
429
430        // Copy application to cell app directory
431        let app_dir = cell.app_directory(app.manifest()).unwrap();
432        mem_dir.copy_to(app_dir).unwrap();
433
434        // Add it to the cell
435        let mut cell_config = cell.config().clone();
436        cell_config.add_application(CellApplicationConfig::from_manifest(app.manifest().clone()));
437        cell.save_config(&cell_config).unwrap();
438
439        // Reload cell
440        let full_cell = Cell::from_directory(cell.directory().clone(), node.clone()).unwrap();
441        let cell = full_cell.cell();
442        let apps = cell.applications().get();
443        assert_eq!(apps.len(), 1);
444        assert!(apps[0].is_loaded());
445
446        // Load cell from config directly. Should still have the app, but unloaded.
447        let dir = RamDirectory::new();
448        let node_config = node.config().clone();
449        let node_prime = LocalNode::from_config(dir, node_config).unwrap();
450        let full_cell_prime = Cell::from_config(cell_config, node_prime)
451            .unwrap()
452            .unwrap_full();
453        let apps = full_cell_prime.cell().applications().get();
454        assert_eq!(apps.len(), 1);
455        assert!(!apps[0].is_loaded());
456    }
457}