simperby_repository/interpret/
create.rs

1use super::*;
2use read::*;
3
4pub async fn approve(
5    raw: &mut RawRepository,
6    agenda_hash: &Hash256,
7    proof: Vec<TypedSignature<Agenda>>,
8    timestamp: Timestamp,
9) -> Result<CommitHash, Error> {
10    let approved_agendas = read::read_governance_approved_agendas(raw).await?;
11
12    for (commit_hash, _) in approved_agendas {
13        if let Commit::AgendaProof(agenda_proof) = read::read_commit(raw, commit_hash).await? {
14            if agenda_proof.agenda_hash == *agenda_hash {
15                // already approved
16                return Ok(commit_hash);
17            }
18        } else {
19            return Err(eyre!(IntegrityError::new(format!(
20                "commit {} is not an agenda proof",
21                commit_hash
22            ))));
23        }
24    }
25
26    // Check if the agenda branch is rebased on top of the `finalized` branch.
27    let last_header_commit = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
28    let agenda_branch_name = format!("a-{}", &agenda_hash.to_string()[0..BRANCH_NAME_HASH_DIGITS]);
29    let agenda_commit_hash = raw.locate_branch(agenda_branch_name.clone()).await?;
30    let find_merge_base_result = raw
31        .find_merge_base(last_header_commit, agenda_commit_hash)
32        .await
33        .map_err(|e| match e {
34            raw::Error::NotFound(_) => {
35                eyre!(IntegrityError::new(format!(
36                    "cannot find merge base for branch {agenda_branch_name} and finalized branch"
37                )))
38            }
39            _ => eyre!(e),
40        })?;
41
42    if last_header_commit != find_merge_base_result {
43        return Err(eyre!(
44            "branch {} should be rebased on {}",
45            agenda_branch_name,
46            FINALIZED_BRANCH_NAME
47        ));
48    }
49
50    // Verify all the incoming commits
51    let finalized_header = read_last_finalized_block_header(raw).await?;
52    let reserved_state = read_last_finalized_reserved_state(raw).await?;
53    let finalized_commit_hash = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
54    let commits = read_commits(raw, finalized_commit_hash, agenda_commit_hash).await?;
55    let mut verifier =
56        CommitSequenceVerifier::new(finalized_header.clone(), reserved_state.clone())
57            .map_err(|e| eyre!("failed to create a commit sequence verifier: {}", e))?;
58    for (commit, hash) in commits.iter() {
59        verifier
60            .apply_commit(commit)
61            .map_err(|e| eyre!("verification error on commit {}: {}", hash, e))?;
62    }
63    // Verify agenda with agenda proof
64    let agenda_commit = commits.iter().map(|(commit, _)| commit).last().unwrap();
65    let agenda = match agenda_commit {
66        Commit::Agenda(agenda) => agenda,
67        _ => return Err(eyre::eyre!("not an agenda commit")),
68    };
69    // Delete past `a-(trimmed agenda hash)` branch and create new `a-(trimmed agenda proof hash)` branch
70    raw.delete_branch(agenda_branch_name.clone()).await?;
71    // Create agenda proof commit
72    let agenda_proof = AgendaProof {
73        height: agenda.height,
74        agenda_hash: agenda_commit.to_hash256(),
75        proof,
76        timestamp,
77    };
78
79    let agenda_proof_commit = Commit::AgendaProof(agenda_proof.clone());
80    let agenda_proof_semantic_commit =
81        format::to_semantic_commit(&agenda_proof_commit, reserved_state)?;
82    let agenda_proof_branch_name = format!(
83        "a-{}",
84        &agenda_proof_commit.to_hash256().to_string()[0..BRANCH_NAME_HASH_DIGITS]
85    );
86    // Check if it is already approved.
87    if raw
88        .list_branches()
89        .await?
90        .contains(&agenda_proof_branch_name)
91    {
92        return Ok(raw.locate_branch(agenda_proof_branch_name.clone()).await?);
93    }
94    raw.create_branch(agenda_proof_branch_name.clone(), agenda_commit_hash)
95        .await?;
96    raw.checkout(agenda_proof_branch_name).await?;
97    let agenda_proof_commit_hash = raw
98        .create_semantic_commit(agenda_proof_semantic_commit, true)
99        .await?;
100
101    Ok(agenda_proof_commit_hash)
102}
103
104pub async fn create_transaction(
105    raw: &mut RawRepository,
106    transaction: Transaction,
107) -> Result<CommitHash, Error> {
108    let reserved_state = read_last_finalized_reserved_state(raw).await?;
109    Ok(raw
110        .create_semantic_commit(
111            format::to_semantic_commit(&Commit::Transaction(transaction), reserved_state)?,
112            true,
113        )
114        .await?)
115}
116
117pub async fn create_agenda(
118    raw: &mut RawRepository,
119    author: MemberName,
120) -> Result<(Agenda, CommitHash), Error> {
121    let last_header = read_last_finalized_block_header(raw).await?;
122    raw.check_clean()
123        .await
124        .map_err(|e| eyre!("repository is not clean: {e}"))?;
125    let head = raw.get_head().await?;
126    let last_header_commit = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
127
128    // Check if HEAD is rebased on top of the `finalized` branch.
129    if raw.find_merge_base(last_header_commit, head).await? != last_header_commit {
130        return Err(eyre!("HEAD should be rebased on {}", FINALIZED_BRANCH_NAME));
131    }
132    // Check the validity of the commit sequence
133    let reserved_state = read_last_finalized_reserved_state(raw).await?;
134    let mut verifier = CommitSequenceVerifier::new(last_header.clone(), reserved_state.clone())
135        .map_err(|e| eyre!("failed to create a commit sequence verifier: {}", e))?;
136    let commits = read_commits(raw, last_header_commit, head).await?;
137    for (commit, hash) in commits.iter() {
138        verifier
139            .apply_commit(commit)
140            .map_err(|e| eyre!("verification error on commit {}: {}", hash, e))?;
141    }
142
143    // Create agenda commit
144    let mut transactions = Vec::new();
145    for (commit, _) in commits {
146        if let Commit::Transaction(t) = commit {
147            transactions.push(t.clone());
148        }
149    }
150    let agenda = Agenda {
151        author,
152        timestamp: get_timestamp(),
153        transactions_hash: Agenda::calculate_transactions_hash(&transactions),
154        height: last_header.height + 1,
155        previous_block_hash: last_header.to_hash256(),
156    };
157    let agenda_commit = Commit::Agenda(agenda.clone());
158    verifier.apply_commit(&agenda_commit).map_err(|_| {
159        eyre!("agenda commit cannot be created on top of the current commit sequence")
160    })?;
161
162    let semantic_commit = to_semantic_commit(&agenda_commit, reserved_state)?;
163
164    raw.checkout_clean().await?;
165    let result = raw.create_semantic_commit(semantic_commit, true).await?;
166    let mut agenda_branch_name = agenda_commit.to_hash256().to_string();
167    agenda_branch_name.truncate(BRANCH_NAME_HASH_DIGITS);
168    let agenda_branch_name = format!("a-{agenda_branch_name}");
169    raw.create_branch(agenda_branch_name, result).await?;
170    Ok((agenda, result))
171}
172
173pub async fn create_block(
174    raw: &mut RawRepository,
175    author: PublicKey,
176) -> Result<(BlockHeader, CommitHash), Error> {
177    raw.check_clean()
178        .await
179        .map_err(|e| eyre!("repository is not clean: {e}"))?;
180    let head = raw.get_head().await?;
181    let last_header_commit = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
182
183    // Check if HEAD branch is rebased on top of the `finalized` branch.
184    if raw.find_merge_base(last_header_commit, head).await? != last_header_commit {
185        return Err(eyre!("HEAD should be rebased on {}", FINALIZED_BRANCH_NAME));
186    }
187
188    // Check the validity of the commit sequence
189    let commits = read_commits(raw, last_header_commit, head).await?;
190    let last_header = read_last_finalized_block_header(raw).await?;
191    let reserved_state = read_last_finalized_reserved_state(raw).await?;
192    let mut verifier = CommitSequenceVerifier::new(last_header.clone(), reserved_state.clone())
193        .map_err(|e| eyre!("failed to create a commit sequence verifier: {}", e))?;
194    for (commit, hash) in commits.iter() {
195        verifier
196            .apply_commit(commit)
197            .map_err(|e| eyre!("verification error on commit {}: {}", hash, e))?;
198    }
199
200    // Verify `finalization_proof`
201    let fp_commit_hash = raw.locate_branch(FP_BRANCH_NAME.into()).await?;
202    let fp_semantic_commit = raw.read_semantic_commit(fp_commit_hash).await?;
203    let finalization_proof = fp_from_semantic_commit(fp_semantic_commit).unwrap().proof;
204
205    // Create block commit
206    let block_header = BlockHeader {
207        author: author.clone(),
208        prev_block_finalization_proof: finalization_proof,
209        previous_hash: last_header.to_hash256(),
210        height: last_header.height + 1,
211        timestamp: get_timestamp(),
212        commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
213            &commits
214                .iter()
215                .map(|(commit, _)| commit.clone())
216                .collect::<Vec<_>>(),
217        ),
218        repository_merkle_root: Hash256::zero(), // TODO
219        validator_set: reserved_state.get_validator_set().unwrap(),
220        version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
221    };
222    let block_commit = Commit::Block(block_header.clone());
223    verifier.apply_commit(&block_commit).map_err(|e| {
224        eyre!("block commit cannot be created on top of the current commit sequence: {e}")
225    })?;
226
227    let semantic_commit = to_semantic_commit(&block_commit, reserved_state)?;
228
229    raw.checkout_clean().await?;
230    raw.checkout_detach(head).await?;
231    let result = raw.create_semantic_commit(semantic_commit, true).await?;
232    let mut block_branch_name = block_commit.to_hash256().to_string();
233    block_branch_name.truncate(BRANCH_NAME_HASH_DIGITS);
234    let block_branch_name = format!("b-{block_branch_name}");
235    raw.create_branch(block_branch_name.clone(), result).await?;
236    raw.checkout(block_branch_name).await?;
237    Ok((block_header, result))
238}
239
240pub async fn create_extra_agenda_transaction(
241    raw: &mut RawRepository,
242    transaction: &ExtraAgendaTransaction,
243) -> Result<CommitHash, Error> {
244    raw.check_clean()
245        .await
246        .map_err(|e| eyre!("repository is not clean: {e}"))?;
247    let head = raw.get_head().await?;
248    let last_header_commit = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
249    let reserved_state = read_last_finalized_reserved_state(raw).await?;
250
251    // Check if HEAD branch is rebased on top of the `finalized` branch.
252    if raw.find_merge_base(last_header_commit, head).await? != last_header_commit {
253        return Err(eyre!("HEAD should be rebased on {}", FINALIZED_BRANCH_NAME));
254    }
255
256    // Check the validity of the commit sequence
257    let commits = read_commits(raw, last_header_commit, head).await?;
258    let last_header = read_last_finalized_block_header(raw).await?;
259    let mut verifier = CommitSequenceVerifier::new(last_header.clone(), reserved_state.clone())
260        .map_err(|e| eyre!("failed to create a commit sequence verifier: {}", e))?;
261    for (commit, hash) in commits.iter() {
262        verifier
263            .apply_commit(commit)
264            .map_err(|e| eyre!("verification error on commit {}: {}", hash, e))?;
265    }
266
267    let extra_agenda_tx_commit = Commit::ExtraAgendaTransaction(transaction.clone());
268    verifier.apply_commit(&extra_agenda_tx_commit).map_err(|_| {
269            eyre!(
270                "extra-agenda transaction commit cannot be created on top of the current commit sequence"
271            )
272        })?;
273
274    let semantic_commit = to_semantic_commit(&extra_agenda_tx_commit, reserved_state)?;
275
276    raw.checkout_clean().await?;
277    let result = raw.create_semantic_commit(semantic_commit, false).await?;
278    Ok(result)
279}
280
281pub async fn finalize(
282    raw: &mut RawRepository,
283    block_commit_hash: CommitHash,
284    proof: FinalizationProof,
285) -> Result<CommitHash, Error> {
286    let csv = read_and_verify_commits_from_last_finalized_block(raw, block_commit_hash).await??;
287    if let Commit::Block(block) = csv
288        .get_total_commits()
289        .last()
290        .expect("there must be at least one commit in CSV")
291    {
292        csv.verify_last_header_finalization(&proof)?;
293        raw.checkout_clean().await?;
294        raw.move_branch(FP_BRANCH_NAME.to_string(), block_commit_hash)
295            .await
296            .map_err(|e| match e {
297                raw::Error::NotFound(_) => {
298                    eyre!(IntegrityError::new(format!(
299                        "failed to find fp branch: {e}"
300                    )))
301                }
302                _ => eyre!(e),
303            })?;
304        raw.checkout(FP_BRANCH_NAME.into())
305            .await
306            .map_err(|e| match e {
307                raw::Error::NotFound(_) => {
308                    eyre!(IntegrityError::new(format!(
309                        "failed to find the fp branch: {e}"
310                    )))
311                }
312                _ => eyre!(e),
313            })?;
314        let commit_hash = raw
315            .create_semantic_commit(
316                format::fp_to_semantic_commit(&LastFinalizationProof {
317                    height: block.height,
318                    proof,
319                }),
320                true,
321            )
322            .await?;
323        sync(raw, commit_hash)
324            .await?
325            .expect("already checked by CSV");
326        Ok(commit_hash)
327    } else {
328        Err(eyre!("commit {} is not a block commit", block_commit_hash))
329    }
330}
331
332pub async fn commit_gitignore(raw: &mut RawRepository) -> Result<(), Error> {
333    raw.check_clean().await?;
334    if check_gitignore(raw).await? {
335        return Err(eyre!(".simperby/ entry already exists in .gitignore"));
336    }
337    let path = raw.get_working_directory_path().await?;
338    let path = std::path::Path::new(&path).join(".gitignore");
339    let mut file = tokio::fs::OpenOptions::new()
340        .create(true)
341        .append(true)
342        .open(path)
343        .await?;
344    file.write_all(b".simperby/\n").await?;
345
346    let commit = RawCommit {
347        message: "Add `.simperby/` entry to .gitignore".to_string(),
348        diff: None,
349        author: "Simperby".to_string(),
350        email: "hi@simperby.net".to_string(),
351        timestamp: get_timestamp() / 1000,
352    };
353    raw.create_commit_all(commit).await?;
354    Ok(())
355}