simperby_repository/
lib.rs

1pub mod format;
2pub mod interpret;
3pub mod raw;
4// TODO: integrate the server feature with `DistributedRepository`
5mod network;
6pub mod server;
7
8use eyre::eyre;
9use format::*;
10use futures::prelude::*;
11use interpret::*;
12use raw::{RawCommit, RawRepository};
13use serde::{Deserialize, Serialize};
14use simperby_core::reserved::ReservedState;
15use simperby_core::utils::get_timestamp;
16use simperby_core::verify::CommitSequenceVerifier;
17use simperby_core::*;
18use simperby_network::*;
19use std::sync::Arc;
20use std::{collections::HashSet, fmt};
21use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
22use tokio::sync::RwLock;
23
24pub use network::RepositoryMessage;
25
26pub type Branch = String;
27pub type Tag = String;
28
29pub const FINALIZED_BRANCH_NAME: &str = "finalized";
30pub const FP_BRANCH_NAME: &str = "fp";
31pub const COMMIT_TITLE_HASH_DIGITS: usize = 8;
32pub const TAG_NAME_HASH_DIGITS: usize = 8;
33pub const BRANCH_NAME_HASH_DIGITS: usize = 8;
34pub const UNKNOWN_COMMIT_AUTHOR: &str = "unknown";
35
36pub type Error = eyre::Error;
37
38#[derive(thiserror::Error, Debug)]
39#[error("repository integrity broken: {msg}")]
40pub struct IntegrityError {
41    pub msg: String,
42}
43
44impl IntegrityError {
45    pub fn new(msg: String) -> Self {
46        Self { msg }
47    }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Config {
52    /// The distance that if a commit is past this far,
53    /// any forked branch starting from the commit
54    /// will be considered a long range attack and thus ignored.
55    ///
56    /// If zero, fork can be detected only from the currently last-finalized commit.
57    pub long_range_attack_distance: usize,
58}
59
60/// The local Simperby blockchain data repository.
61///
62/// It automatically locks the repository once created.
63///
64/// - It **verifies** all the incoming changes and applies them to the local repository
65/// only if they are valid.
66pub struct DistributedRepository {
67    dms: Option<Arc<RwLock<Dms<RepositoryMessage>>>>,
68    /// We keep the `RawRepository` in a `RwLock` for possible concurrent accesses in some operations.
69    raw: Arc<RwLock<RawRepository>>,
70    _config: Config,
71    private_key: Option<PrivateKey>,
72}
73
74impl DistributedRepository {
75    pub fn get_raw(&self) -> Arc<RwLock<RawRepository>> {
76        Arc::clone(&self.raw)
77    }
78
79    pub fn get_dms(&self) -> Option<Arc<RwLock<Dms<RepositoryMessage>>>> {
80        self.dms.as_ref().map(Arc::clone)
81    }
82
83    pub async fn new(
84        dms: Option<Arc<RwLock<Dms<RepositoryMessage>>>>,
85        raw: Arc<RwLock<RawRepository>>,
86        config: Config,
87        private_key: Option<PrivateKey>,
88    ) -> Result<Self, Error> {
89        Ok(Self {
90            dms,
91            raw,
92            _config: config,
93            private_key,
94        })
95    }
96
97    /// Initializes the genesis repository, leaving a genesis header.
98    ///
99    /// It also
100    /// - creates `fp` branch and its commit (for the genesis block).
101    /// - creates the `finalized` branch.
102    ///
103    /// Note that `genesis` can be called on any commit except a merge commit.
104    pub async fn genesis(mut raw: RawRepository) -> Result<(), Error> {
105        genesis(&mut raw).await
106    }
107
108    // ---------------
109    // Read-only operations
110    // ---------------
111
112    /// Reads the last finalization information from the repository.
113    pub async fn read_last_finalization_info(&self) -> Result<FinalizationInfo, Error> {
114        read_last_finalization_info(&*self.raw.read().await).await
115    }
116
117    /// Reads the finalization information at specific height.
118    pub async fn read_finalization_info(
119        &self,
120        height: BlockHeight,
121    ) -> Result<FinalizationInfo, Error> {
122        read_finalization_info(&*self.raw.read().await, height).await
123    }
124
125    /// Reads the given commit.
126    pub async fn read_commit(&self, commit_hash: CommitHash) -> Result<Commit, Error> {
127        read_commit(&*self.raw.read().await, commit_hash).await
128    }
129
130    /// Returns the currently valid and height-acceptable agendas in the repository.
131    pub async fn read_agendas(&self) -> Result<Vec<(CommitHash, Hash256)>, Error> {
132        read_agendas(&*self.raw.read().await).await
133    }
134
135    /// Returns governance-approved agendas in the repository.
136    /// The result will be a list of agenda proofs, not just agendas.
137    pub async fn read_governance_approved_agendas(
138        &self,
139    ) -> Result<Vec<(CommitHash, Hash256)>, Error> {
140        read_governance_approved_agendas(&*self.raw.read().await).await
141    }
142
143    /// Returns the currently valid and height-acceptable blocks in the repository.
144    pub async fn read_blocks(&self) -> Result<Vec<(CommitHash, Hash256)>, Error> {
145        read_blocks(&*self.raw.read().await).await
146    }
147
148    /// Checks the validity of the repository, starting from the given height.
149    ///
150    /// It checks
151    /// 1. all the reserved branches and tags
152    /// 2. the finalization proof in the `fp` branch.
153    /// 3. the existence of merge commits
154    /// 4. the canonical history of the `finalized` branch.
155    /// 5. the reserved state in a valid format.
156    pub async fn check(&self, _starting_height: BlockHeight) -> Result<bool, Error> {
157        // TODO
158        Ok(true)
159    }
160
161    /// Checks the existence of `.gitignore` file and `.simperby/` entry in `.gitignore`.
162    /// This returns true if both exist.
163    pub async fn check_gitignore(&self) -> Result<bool, Error> {
164        check_gitignore(&*self.raw.read().await).await
165    }
166
167    // ---------------
168    // Operations that interact with possible local works
169    // (manually added commits or remote tracking branches)
170    // ---------------
171
172    /// Synchronizes the repository with the given commit (interpreted as a branch tip).
173    /// - Returns `Ok(Ok(()))` if the branch is successfully received.
174    /// - Returns `Ok(Err(_))` if the branch is invalid and thus rejected, with a reason.
175    /// - Returns `Err(_)` if an error occurs.
176    ///
177    /// 1. Finalization: move the `finalized` and `fp` branch to the last finalized block commit.
178    /// 2. Block observed: add a `b-#` branch on the block candidate.
179    /// 3. Agenda observed (either governance-approved or not): add an `a-#` branch on the agenda candidate.
180    ///
181    /// This will verify every commit along the way.
182    /// If the given commit is not a descendant of the
183    /// current `finalized` (i.e., cannot be fast-forwarded), it fails.
184    pub async fn sync(&mut self, commit_hash: CommitHash) -> Result<Result<(), String>, Error> {
185        sync(&mut *self.raw.write().await, commit_hash).await
186    }
187
188    /// Performs `sync()` on all local branches and remote tracking branches on the repository.
189    ///
190    /// Returns the list of `(branch name, result of sync())`.
191    pub async fn sync_all(&mut self) -> Result<Vec<(String, Result<(), String>)>, Error> {
192        sync_all(&mut *self.raw.write().await).await
193    }
194
195    /// Tests if the given push request is acceptable.
196    pub async fn test_push_eligibility(
197        &self,
198        commit_hash: CommitHash,
199        branch_name: String,
200        timestamp: Timestamp,
201        signature: TypedSignature<(CommitHash, String, Timestamp)>,
202        _timestamp_to_test: Timestamp,
203    ) -> Result<bool, Error> {
204        test_push_eligibility(
205            &*self.raw.read().await,
206            commit_hash,
207            branch_name,
208            timestamp,
209            signature,
210            _timestamp_to_test,
211        )
212        .await
213    }
214
215    /// Cleans all the outdated commits, remote repositories and branches.
216    ///
217    /// It will leave only
218    /// - the `finalized` branch
219    /// - the `fp` branch
220    /// when `hard` is `true`,
221    ///
222    /// and when `hard` is `false`,
223    /// - the `p` branch
224    /// - the `a-#` branches
225    /// - the `b-#` branches
226    /// will be left as well
227    /// if only the branches have valid commit sequences
228    /// and are not outdated (branched from the last finalized commit).
229    pub async fn clean(&mut self, hard: bool) -> Result<(), Error> {
230        clean(&mut *self.raw.write().await, hard).await
231    }
232
233    /// Broadcasts all the local messages.
234    pub async fn broadcast(&mut self) -> Result<(), Error> {
235        broadcast(&mut *self.raw.write().await, self.private_key.clone()).await
236    }
237
238    // ---------------
239    // DMS-related operations
240    // ---------------
241
242    pub async fn flush(&mut self) -> Result<(), Error> {
243        self.flush_().await
244    }
245
246    /// Updates the repository module with the latest messages from the DMS.
247    ///
248    /// Note that it never finalizes a block.
249    /// Finalization is done by the consensus module, or the `sync` method.
250    pub async fn update(&mut self, _no_network: bool) -> Result<(), Error> {
251        self.update_().await
252    }
253
254    // ---------------
255    // Various operations that (might) create a commit
256    // ---------------
257
258    /// Informs that the given agenda has been approved.
259    ///
260    /// After verification, it will create an agenda-proof commit,
261    /// and update the corresponding `a-#` branch to it
262    ///
263    /// TODO: get `AgendaProof` instead.
264    pub async fn approve(
265        &mut self,
266        agenda_hash: &Hash256,
267        proof: Vec<TypedSignature<Agenda>>,
268        timestamp: Timestamp,
269    ) -> Result<CommitHash, Error> {
270        approve(&mut *self.raw.write().await, agenda_hash, proof, timestamp).await
271    }
272
273    /// Creates a transaction commit on top of the HEAD.
274    pub async fn create_transaction(
275        &mut self,
276        transaction: Transaction,
277    ) -> Result<CommitHash, Error> {
278        create_transaction(&mut *self.raw.write().await, transaction).await
279    }
280
281    /// Creates an agenda commit on top of the HEAD.
282    pub async fn create_agenda(
283        &mut self,
284        author: MemberName,
285    ) -> Result<(Agenda, CommitHash), Error> {
286        create_agenda(&mut *self.raw.write().await, author).await
287    }
288
289    /// Creates a block commit on top of the HEAD.
290    pub async fn create_block(
291        &mut self,
292        author: PublicKey,
293    ) -> Result<(BlockHeader, CommitHash), Error> {
294        create_block(&mut *self.raw.write().await, author).await
295    }
296
297    /// Creates an extra-agenda transaction commit on top of the HEAD.
298    pub async fn create_extra_agenda_transaction(
299        &mut self,
300        transaction: &ExtraAgendaTransaction,
301    ) -> Result<CommitHash, Error> {
302        create_extra_agenda_transaction(&mut *self.raw.write().await, transaction).await
303    }
304
305    /// Finalizes the block with the given proof. Returns the commit hash of the updated `fp` branch.
306    pub async fn finalize(
307        &mut self,
308        block_commit_hash: CommitHash,
309        proof: FinalizationProof,
310    ) -> Result<CommitHash, Error> {
311        finalize(&mut *self.raw.write().await, block_commit_hash, proof).await
312    }
313
314    /// Creates a commit that adds `.simperby/` entry to `.gitignore`.
315    /// It fails if it exists normally.
316    pub async fn commit_gitignore(&mut self) -> Result<(), Error> {
317        commit_gitignore(&mut *self.raw.write().await).await
318    }
319
320    // ---------------
321    // Tag-related operations
322    // ---------------
323
324    /// Puts a 'vote' tag on the commit.
325    pub async fn vote(&mut self, commit_hash: CommitHash) -> Result<(), Error> {
326        vote(&mut *self.raw.write().await, commit_hash).await
327    }
328
329    /// Puts a 'veto' tag on the commit.
330    pub async fn veto(&mut self, commit_hash: CommitHash) -> Result<(), Error> {
331        veto(&mut *self.raw.write().await, commit_hash).await
332    }
333}