ng_repo/
repo.rs

1// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
2// All rights reserved.
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
5// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! Repository
11
12use core::fmt;
13use std::collections::HashMap;
14use std::collections::HashSet;
15use std::sync::Arc;
16
17use serde::{Deserialize, Serialize};
18
19use crate::errors::*;
20#[allow(unused_imports)]
21use crate::log::*;
22use crate::store::Store;
23use crate::types::*;
24
25impl RepositoryV0 {
26    pub fn new_with_meta(id: &PubKey, metadata: &Vec<u8>) -> RepositoryV0 {
27        RepositoryV0 {
28            id: id.clone(),
29            metadata: metadata.clone(),
30            verification_program: vec![],
31            fork_of: vec![],
32            creator: None,
33        }
34    }
35}
36
37impl Repository {
38    pub fn new(id: &RepoId) -> Self {
39        Repository::V0(RepositoryV0 {
40            id: id.clone(),
41            verification_program: vec![],
42            creator: None,
43            fork_of: vec![],
44            metadata: vec![],
45        })
46    }
47    pub fn new_with_meta(id: &PubKey, metadata: &Vec<u8>) -> Repository {
48        Repository::V0(RepositoryV0::new_with_meta(id, metadata))
49    }
50    pub fn id(&self) -> &PubKey {
51        match self {
52            Self::V0(v0) => &v0.id,
53        }
54    }
55}
56
57#[derive(Debug)]
58pub struct UserInfo {
59    /// list of permissions granted to user, with optional metadata
60    pub permissions: HashMap<PermissionV0, Vec<u8>>,
61    pub id: UserId,
62}
63
64impl UserInfo {
65    pub fn has_any_perm(&self, perms: &HashSet<PermissionV0>) -> Result<(), NgError> {
66        //log_debug!("perms {:?}", perms);
67        if self.has_perm(&PermissionV0::Owner).is_ok() {
68            return Ok(());
69        }
70        let is_admin = self.has_perm(&PermissionV0::Admin).is_ok();
71        //log_debug!("is_admin {}", is_admin);
72        //is_delegated_by_admin
73        let has_perms: HashSet<&PermissionV0> = self.permissions.keys().collect();
74        //log_debug!("has_perms {:?}", has_perms);
75        for perm in perms {
76            if is_admin && perm.is_delegated_by_admin() || has_perms.contains(perm) {
77                return Ok(());
78            }
79        }
80        // if has_perms.intersection(perms).count() > 0 {
81        //     Ok(())
82        // } else {
83        Err(NgError::PermissionDenied)
84    }
85    pub fn has_perm(&self, perm: &PermissionV0) -> Result<&Vec<u8>, NgError> {
86        self.permissions.get(perm).ok_or(NgError::PermissionDenied)
87    }
88}
89
90#[derive(Debug, Clone)]
91pub struct BranchInfo {
92    pub id: BranchId,
93
94    pub branch_type: BranchType,
95
96    pub crdt: BranchCrdt,
97
98    pub topic: Option<TopicId>,
99
100    pub topic_priv_key: Option<BranchWriteCapSecret>,
101
102    pub read_cap: Option<ReadCap>,
103
104    pub fork_of: Option<BranchId>,
105
106    pub merged_in: Option<BranchId>,
107
108    pub current_heads: Vec<ObjectRef>,
109
110    pub commits_nbr: u64,
111}
112
113/// In memory Repository representation. With helper functions that access the underlying UserStorage and keeps proxy of the values
114#[derive(Debug)]
115pub struct Repo {
116    pub id: RepoId,
117    /// Repo definition
118    pub repo_def: Repository,
119
120    pub read_cap: Option<ReadCap>,
121
122    pub write_cap: Option<RepoWriteCapSecret>,
123
124    pub signer: Option<SignerCap>,
125
126    pub certificate_ref: Option<ObjectRef>,
127
128    pub members: HashMap<Digest, UserInfo>,
129
130    pub branches: HashMap<BranchId, BranchInfo>,
131
132    /// if opened_branches is empty, it means the repo has not been opened yet.
133    /// if a branchId is present in the hashmap, it means it is opened.
134    /// the boolean indicates if the branch is opened as publisher or not
135    pub opened_branches: HashMap<BranchId, bool>,
136
137    /*pub main_branch_rc: Option<BranchId>,
138
139    pub chat_branch_rc: Option<BranchId>,
140
141    // only used if it is a StoreRepo
142    pub store_branch_rc: Option<BranchId>,
143    pub overlay_branch_rc: Option<BranchId>,
144
145    // only used if it is a private StoreRepo
146    pub user_branch_rc: Option<BranchId>,*/
147    pub store: Arc<Store>,
148}
149
150impl fmt::Display for Repo {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        writeln!(f, "====== Repo ====== {}", self.id)?;
153
154        write!(f, "== repo_def:    {}", self.repo_def)?;
155
156        if self.signer.is_some() {
157            writeln!(f, "== signer:   {:?}", self.signer)?;
158        }
159
160        writeln!(f, "== members:   {:?}", self.members)?;
161
162        Ok(())
163    }
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
167pub struct CommitInfo {
168    pub past: Vec<ObjectId>,
169    pub key: ObjectKey,
170    pub signature: Option<ObjectRef>,
171    pub author: String,
172    pub timestamp: Timestamp,
173    pub final_consistency: bool,
174    pub commit_type: CommitType,
175    pub branch: Option<ObjectId>,
176    pub x: u32,
177    pub y: u32,
178}
179
180impl Repo {
181    #[cfg(any(test, feature = "testing"))]
182    #[allow(deprecated)]
183    pub fn new_with_perms(perms: &[PermissionV0], store: Arc<Store>) -> Self {
184        let pub_key = PubKey::nil();
185        Self::new_with_member(&pub_key, &pub_key, perms, store)
186    }
187
188    pub(crate) fn get_user_string(&self, user_hash: &Digest) -> String {
189        self.members
190            .get(user_hash)
191            .map_or_else(|| format!("t:{user_hash}"), |info| format!("i:{}", info.id))
192    }
193
194    fn load_causal_past(
195        &self,
196        recursor: &mut Vec<(BlockRef, Option<ObjectId>)>,
197        visited: &mut HashMap<ObjectId, (HashSet<ObjectId>, CommitInfo)>,
198        signatures: &mut HashMap<ObjectId, ObjectRef>,
199    ) -> Result<Option<ObjectId>, VerifierError> {
200        let mut root = None;
201        while let Some((next_ref, future)) = recursor.pop() {
202            if let Ok(cobj) = Commit::load(next_ref, &self.store, true) {
203                let id = cobj.id().unwrap();
204                if let Some((future_set, info)) = visited.get_mut(&id) {
205                    // we update the future
206                    if let Some(f) = future {
207                        future_set.insert(f);
208                    }
209                    if let Some(sign) = signatures.remove(&id) {
210                        info.signature = Some(sign);
211                    }
212                } else {
213                    let commit_type = cobj.get_type().unwrap();
214                    let acks = cobj.acks();
215                    // for a in acks.iter() {
216                    //     log_debug!("ACKS of {} {}", id.to_string(), a.id.to_string());
217                    // }
218                    let (past, real_acks, next_future) = match commit_type {
219                        CommitType::SyncSignature => {
220                            assert_eq!(acks.len(), 1);
221                            let dep = cobj.deps();
222                            assert_eq!(dep.len(), 1);
223                            let mut current_commit = dep[0].clone();
224                            let sign_ref = cobj.get_signature_reference().unwrap();
225                            let real_acks;
226                            let mut future = id;
227                            loop {
228                                let o = Commit::load(current_commit.clone(), &self.store, true)?;
229                                let deps = o.deps();
230                                let commit_info = CommitInfo {
231                                    past: deps.iter().map(|r| r.id.clone()).collect(),
232                                    key: o.key().unwrap(),
233                                    signature: Some(sign_ref.clone()),
234                                    author: self.get_user_string(o.author()),
235                                    timestamp: o.timestamp(),
236                                    final_consistency: o.final_consistency(),
237                                    commit_type: o.get_type().unwrap(),
238                                    branch: None,
239                                    x: 0,
240                                    y: 0,
241                                };
242                                let id = o.id().unwrap();
243
244                                visited.insert(id, ([future].into(), commit_info));
245                                future = id;
246                                if id == acks[0].id {
247                                    real_acks = o.acks();
248                                    break;
249                                }
250                                assert_eq!(deps.len(), 1);
251                                current_commit = deps[0].clone();
252                            }
253                            (vec![dep[0].id], real_acks, future)
254                        }
255                        CommitType::AsyncSignature => {
256                            let past: Vec<ObjectId> = acks.iter().map(|r| r.id.clone()).collect();
257                            let sign = cobj.get_signature_reference().unwrap();
258                            for p in cobj.deps().iter() {
259                                signatures.insert(p.id, sign.clone());
260                                //visited.get_mut(&p.id).unwrap().1.signature = Some(sign.clone());
261                            }
262                            (past, acks, id)
263                        }
264                        _ => (acks.iter().map(|r| r.id.clone()).collect(), acks, id),
265                    };
266
267                    let commit_info = CommitInfo {
268                        past,
269                        key: cobj.key().unwrap(),
270                        signature: signatures.remove(&id),
271                        author: self.get_user_string(cobj.author()),
272                        timestamp: cobj.timestamp(),
273                        final_consistency: cobj.final_consistency(),
274                        commit_type,
275                        branch: None,
276                        x: 0,
277                        y: 0,
278                    };
279                    visited.insert(id, (future.map_or([].into(), |f| [f].into()), commit_info));
280                    if real_acks.is_empty() && root.is_none() {
281                        root = Some(next_future);
282                    }
283                    recursor.extend(real_acks.into_iter().map(|br| (br, Some(next_future))));
284                    // for past_ref in real_acks {
285                    //     let o = Commit::load(past_ref, &self.store, true)?;
286                    //     if let Some(r) = self.load_causal_past(&o, visited, Some(next_future))? {
287                    //         root = Some(r);
288                    //     }
289                    // }
290                }
291            }
292        }
293        Ok(root)
294    }
295
296    fn past_is_all_in(
297        past: &Vec<ObjectId>,
298        already_in: &HashMap<ObjectId, ObjectId>,
299        coming_from: &ObjectId,
300    ) -> bool {
301        for p in past {
302            if !already_in.contains_key(p) && p != coming_from {
303                return false;
304            }
305        }
306        true
307    }
308
309    fn collapse(
310        id: &ObjectId,
311        dag: &mut HashMap<ObjectId, (HashSet<ObjectId>, CommitInfo)>,
312        already_in: &mut HashMap<ObjectId, ObjectId>,
313        branches_order: &mut Vec<Option<ObjectId>>,
314        branches: &mut HashMap<ObjectId, usize>,
315        //swimlanes: &mut Vec<Vec<ObjectId>>,
316    ) -> Vec<(ObjectId, CommitInfo)> {
317        let (_, c) = dag.get(id).unwrap();
318        //log_debug!("processing {id}");
319        if c.past.len() > 1 && !Self::past_is_all_in(&c.past, already_in, id) {
320            // we postpone the merge until all the past commits have been added
321            //log_debug!("postponed {}", id);
322            vec![]
323        } else {
324            let (future, mut info) = dag.remove(id).unwrap();
325            let mut branch = match info.past.len() {
326                0 => *id,
327                _ => info.branch.unwrap(),
328                // _ => {
329                //     we merge on the smallest branch ordinal.
330                //     let smallest_branch = info
331                //         .past
332                //         .iter()
333                //         .map(|past_commit| {
334                //             branches.get(already_in.get(past_commit).unwrap()).unwrap()
335                //         })
336                //         .min()
337                //         .unwrap();
338                //     branches_order
339                //         .get(*smallest_branch)
340                //         .unwrap()
341                //         .unwrap()
342                //         .clone()
343                // }
344            };
345            info.branch = Some(branch.clone());
346            // let swimlane_idx = branches.get(&branch).unwrap();
347            // let swimlane = swimlanes.get_mut(*swimlane_idx).unwrap();
348            // if swimlane.last().map_or(true, |last| last != &branch) {
349            //     swimlane.push(branch.clone());
350            // }
351            let mut res = vec![(*id, info)];
352            let mut first_child_branch = branch.clone();
353            already_in.insert(*id, branch);
354            let mut future = Vec::from_iter(future);
355            future.sort();
356            // the first branch is the continuation as parent.
357            let mut iterator = future.iter().peekable();
358            while let Some(child) = iterator.next() {
359                //log_debug!("child of {} : {}", id, child);
360                {
361                    // we merge on the smallest branch ordinal.
362                    let (_, info) = dag.get_mut(child).unwrap();
363                    if let Some(b) = info.branch.to_owned() {
364                        let previous_ordinal = branches.get(&b).unwrap();
365                        let new_ordinal = branches.get(&branch).unwrap();
366                        let close = if previous_ordinal > new_ordinal {
367                            let _ = info.branch.insert(branch);
368                            // we close the previous branch
369                            // log_debug!(
370                            //     "closing previous {} {} in favor of new {} {}",
371                            //     previous_ordinal,
372                            //     b,
373                            //     new_ordinal,
374                            //     branch
375                            // );
376                            &b
377                        } else {
378                            // otherwise we close the new branch
379                            if first_child_branch == branch {
380                                first_child_branch = b;
381                            }
382                            // log_debug!(
383                            //     "closing new branch {} {} in favor of previous {} {}",
384                            //     new_ordinal,
385                            //     branch,
386                            //     previous_ordinal,
387                            //     b
388                            // );
389                            &branch
390                        };
391                        let i = branches.get(close).unwrap();
392                        branches_order.get_mut(*i).unwrap().take();
393                    } else {
394                        let _ = info.branch.insert(branch);
395                    }
396                }
397                // log_debug!(
398                //     "branches_order before children of {child} {:?}",
399                //     branches_order
400                //         .iter()
401                //         .enumerate()
402                //         .map(|(i, b)| b.map_or(format!("{i}:closed"), |bb| format!("{i}:{bb}")))
403                //         .collect::<Vec<String>>()
404                //         .join(" -- ")
405                // );
406                res.append(&mut Self::collapse(
407                    child,
408                    dag,
409                    already_in,
410                    branches_order,
411                    branches,
412                    //swimlanes,
413                ));
414                // log_debug!(
415                //     "branches_order after children of {child} {:?}",
416                //     branches_order
417                //         .iter()
418                //         .enumerate()
419                //         .map(|(i, b)| b.map_or(format!("{i}:closed"), |bb| format!("{i}:{bb}")))
420                //         .collect::<Vec<String>>()
421                //         .join(" -- ")
422                // );
423                // each other child gets a new branch
424                if let Some(next) = iterator.peek() {
425                    branch = **next;
426                    if branches.contains_key(*next) {
427                        continue;
428                    }
429                    let mut branch_inserted = false;
430                    let mut first_child_branch_passed = false;
431                    for (i, next_branch) in branches_order.iter_mut().enumerate() {
432                        if let Some(b) = next_branch {
433                            if b == &first_child_branch {
434                                first_child_branch_passed = true;
435                                //log_debug!("first_child_branch_passed");
436                            }
437                        }
438                        if next_branch.is_none() && first_child_branch_passed {
439                            //log_debug!("found empty lane {}, putting branch in it {}", i, branch);
440                            let _ = next_branch.insert(branch.clone());
441                            branches.insert(branch, i);
442                            branch_inserted = true;
443                            break;
444                        }
445                    }
446                    if !branch_inserted {
447                        //swimlanes.push(Vec::new());
448                        // log_debug!(
449                        //     "adding new lane {}, for branch {}",
450                        //     branches_order.len(),
451                        //     branch
452                        // );
453                        branches_order.push(Some(branch.clone()));
454                        branches.insert(branch, branches_order.len() - 1);
455                    }
456                }
457            }
458            res
459        }
460    }
461
462    pub fn history_at_heads(
463        &self,
464        heads: &[ObjectRef],
465    ) -> Result<(Vec<(ObjectId, CommitInfo)>, Vec<Option<ObjectId>>), VerifierError> {
466        assert!(!heads.is_empty());
467        // for h in heads {
468        //     log_debug!("HEAD {}", h.id);
469        // }
470        let mut visited = HashMap::new();
471        let mut root = None;
472        let mut recursor: Vec<(BlockRef, Option<ObjectId>)> =
473            heads.iter().map(|h| (h.clone(), None)).collect();
474        let mut signatures: HashMap<ObjectId, ObjectRef> = HashMap::new();
475        let r = self.load_causal_past(&mut recursor, &mut visited, &mut signatures)?;
476        if r.is_some() {
477            root = r;
478        }
479        // for id in heads {
480        //     if let Ok(cobj) = Commit::load(id.clone(), &self.store, true) {
481        //         let r = self.load_causal_past(&cobj, &mut visited, None)?;
482        //         //log_debug!("ROOT? {:?}", r.map(|rr| rr.to_string()));
483        //         if r.is_some() {
484        //             root = r;
485        //         }
486        //     }
487        // }
488
489        // for h in visited.keys() {
490        //     log_debug!("VISITED {}", h);
491        // }
492        if root.is_none() {
493            return Err(VerifierError::MalformedDag);
494        }
495        let root = root.unwrap();
496
497        let mut already_in: HashMap<ObjectId, ObjectId> = HashMap::new();
498        let mut branches_order: Vec<Option<ObjectId>> = vec![Some(root.clone())];
499        let mut branches: HashMap<ObjectId, usize> = HashMap::from([(root.clone(), 0)]);
500        //let mut swimlanes: Vec<Vec<ObjectId>> = vec![vec![root.clone()]];
501        let mut commits = Self::collapse(
502            &root,
503            &mut visited,
504            &mut already_in,
505            &mut branches_order,
506            &mut branches,
507            //&mut swimlanes,
508        );
509        for (i, (_, commit)) in commits.iter_mut().enumerate() {
510            commit.y = i as u32;
511            commit.x = *branches.get(commit.branch.as_ref().unwrap()).unwrap() as u32;
512        }
513        Ok((commits, branches_order))
514    }
515
516    pub fn update_branch_current_heads(
517        &mut self,
518        branch: &BranchId,
519        commit_ref: ObjectRef,
520        past: Vec<ObjectRef>,
521    ) -> Result<Vec<ObjectRef>, VerifierError> {
522        //log_info!("from branch {} HEAD UPDATED TO {}", branch, commit_ref.id);
523        if let Some(branch) = self.branches.get_mut(branch) {
524            let mut set: HashSet<&ObjectRef> = HashSet::from_iter(branch.current_heads.iter());
525            for p in past {
526                set.remove(&p);
527            }
528            let already_in_heads = set.contains(&commit_ref);
529            branch.current_heads = set.into_iter().cloned().collect();
530            if !already_in_heads {
531                branch.current_heads.push(commit_ref);
532                branch.commits_nbr += 1;
533            }
534            // we return the new current heads
535            Ok(branch.current_heads.to_vec())
536        } else {
537            Err(VerifierError::BranchNotFound)
538        }
539    }
540
541    pub fn new_with_member(
542        repo_id: &PubKey,
543        member: &UserId,
544        perms: &[PermissionV0],
545        store: Arc<Store>,
546    ) -> Self {
547        let mut members = HashMap::new();
548        let permissions = HashMap::from_iter(
549            perms
550                .iter()
551                .map(|p| (*p, vec![]))
552                .collect::<Vec<(PermissionV0, Vec<u8>)>>()
553                .iter()
554                .cloned(),
555        );
556        let overlay = store.get_store_repo().overlay_id_for_read_purpose();
557        let member_hash = CommitContent::author_digest(member, overlay);
558        //log_debug!("added member {:?} {:?}", member, member_hash);
559        members.insert(
560            member_hash,
561            UserInfo {
562                id: *member,
563                permissions,
564            },
565        );
566        Self {
567            id: repo_id.clone(),
568            repo_def: Repository::new(&repo_id),
569            members,
570            store,
571            signer: None,
572            certificate_ref: None,
573            read_cap: None,
574            write_cap: None,
575            branches: HashMap::new(),
576            opened_branches: HashMap::new(),
577            //main_branch_rc: None,
578        }
579    }
580
581    pub fn verify_permission(&self, commit: &Commit) -> Result<(), NgError> {
582        let content_author = commit.content_v0().author;
583        let body = commit.load_body(&self.store)?;
584        match self.members.get(&content_author) {
585            Some(info) => return info.has_any_perm(&body.required_permission()),
586            None => {}
587        }
588        Err(NgError::PermissionDenied)
589    }
590
591    pub fn member_pubkey(&self, hash: &Digest) -> Result<UserId, NgError> {
592        match self.members.get(hash) {
593            Some(user_info) => Ok(user_info.id),
594            None => Err(NgError::NotFound),
595        }
596    }
597
598    pub fn branch(&self, id: &BranchId) -> Result<&BranchInfo, NgError> {
599        //TODO: load the BranchInfo from storage
600        self.branches.get(id).ok_or(NgError::BranchNotFound)
601    }
602
603    pub fn branch_mut(&mut self, id: &BranchId) -> Result<&mut BranchInfo, NgError> {
604        //TODO: load the BranchInfo from storage
605        self.branches.get_mut(id).ok_or(NgError::BranchNotFound)
606    }
607
608    pub fn overlay_branch(&self) -> Option<&BranchInfo> {
609        for (_, branch) in self.branches.iter() {
610            if branch.branch_type == BranchType::Overlay {
611                return Some(branch);
612            }
613        }
614        None
615    }
616
617    pub fn user_branch(&self) -> Option<&BranchInfo> {
618        for (_, branch) in self.branches.iter() {
619            if branch.branch_type == BranchType::User {
620                return Some(branch);
621            }
622        }
623        None
624    }
625
626    pub fn main_branch(&self) -> Option<&BranchInfo> {
627        for (_, branch) in self.branches.iter() {
628            if branch.branch_type == BranchType::Main {
629                return Some(branch);
630            }
631        }
632        None
633    }
634
635    pub fn store_branch(&self) -> Option<&BranchInfo> {
636        for (_, branch) in self.branches.iter() {
637            if branch.branch_type == BranchType::Store {
638                return Some(branch);
639            }
640        }
641        None
642    }
643
644    pub fn header_branch(&self) -> Option<&BranchInfo> {
645        for (_, branch) in self.branches.iter() {
646            if branch.branch_type == BranchType::Header {
647                return Some(branch);
648            }
649        }
650        None
651    }
652
653    pub fn root_branch(&self) -> Option<&BranchInfo> {
654        for (_, branch) in self.branches.iter() {
655            if branch.branch_type == BranchType::Root {
656                return Some(branch);
657            }
658        }
659        None
660    }
661
662    pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> {
663        match self.overlay_branch() {
664            Some(bi) => Some(bi.read_cap.as_ref().unwrap()),
665            None => self.read_cap.as_ref(), // this is for private stores that don't have an overlay branch
666        }
667    }
668
669    pub fn branch_is_opened(&self, branch: &BranchId) -> bool {
670        self.opened_branches.contains_key(branch)
671    }
672
673    pub fn branch_is_opened_as_publisher(&self, branch: &BranchId) -> bool {
674        match self.opened_branches.get(branch) {
675            Some(val) => *val,
676            None => false,
677        }
678    }
679
680    // pub(crate) fn get_store(&self) -> &Store {
681    //     self.store.unwrap()
682    // }
683}