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#[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 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 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#[derive(PartialEq, Eq, Clone, Debug, Hash)]
270pub struct CellId(String);
271
272impl CellId {
273 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#[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#[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 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 let app_dir = cell.app_directory(app.manifest()).unwrap();
432 mem_dir.copy_to(app_dir).unwrap();
433
434 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 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 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}