simperby_repository/interpret/
works.rs

1use super::*;
2use read::*;
3
4pub(crate) async fn advance_finalized_branch(
5    raw: &mut RawRepository,
6    to_be_finalized_block_commit_hash: CommitHash,
7    finalization_proof: LastFinalizationProof,
8) -> Result<(), Error> {
9    raw.checkout_clean().await?;
10    raw.move_branch(
11        FINALIZED_BRANCH_NAME.into(),
12        to_be_finalized_block_commit_hash,
13    )
14    .await?;
15    raw.move_branch(FP_BRANCH_NAME.into(), to_be_finalized_block_commit_hash)
16        .await?;
17    raw.checkout(FP_BRANCH_NAME.into()).await?;
18    raw.create_semantic_commit(format::fp_to_semantic_commit(&finalization_proof), true)
19        .await?;
20    raw.checkout_detach(to_be_finalized_block_commit_hash)
21        .await?;
22    Ok(())
23}
24
25pub async fn sync(
26    raw: &mut RawRepository,
27    tip_commit_hash: CommitHash,
28) -> Result<Result<(), String>, Error> {
29    let lfi = read_last_finalization_info(raw).await?;
30    let mut csv = CommitSequenceVerifier::new(lfi.header.clone(), lfi.reserved_state.clone())
31        .map_err(|e| {
32            IntegrityError::new(format!("finalized branch is not accepted by CSV: {e}"))
33        })?;
34
35    if raw
36        .find_merge_base(lfi.commit_hash, tip_commit_hash)
37        .await?
38        != lfi.commit_hash
39    {
40        return Ok(Err(
41            "the received branch tip commit is not a descendant of the last finalized block."
42                .to_owned(),
43        ));
44    }
45
46    // If the branch ends with a finalization proof commit
47    if let Ok(last_finalization_proof) =
48        format::fp_from_semantic_commit(raw.read_semantic_commit(tip_commit_hash).await?)
49    {
50        // Now we consider the direct parent as the received commit
51        // (fp is not treated in the CSV)
52        let commit_hash = raw.list_ancestors(tip_commit_hash, Some(1)).await?[0];
53        if commit_hash == lfi.commit_hash {
54            return Ok(Err("the received commit is already finalized.".to_owned()));
55        }
56
57        // Read the commits in the branch and verify them
58        let commits = match read_commits(raw, lfi.commit_hash, commit_hash).await {
59            Ok(x) => x,
60            Err(CommitError::Commit(error, commit)) => {
61                return Ok(Err(format!("failed to parse commit {commit}: {error}")));
62            }
63            Err(e) => return Err(e.into()),
64        };
65        for (commit, commit_hash) in &commits {
66            if let Err(e) = csv.apply_commit(commit) {
67                return Ok(Err(format!(
68                    "commit sequence verification failed: {e} at {commit_hash}",
69                )));
70            }
71        }
72
73        let (last_commit, last_commit_hash) = commits.last().expect(
74            "already checked that the received commit is not same as the last finalized block",
75        );
76        if let Commit::Block(_) = last_commit {
77            if csv
78                .verify_last_header_finalization(&last_finalization_proof.proof)
79                .is_err()
80            {
81                return Ok(Err(
82                    "finalization proof is invalid for the last block.".to_owned()
83                ));
84            }
85            advance_finalized_branch(raw, *last_commit_hash, last_finalization_proof).await?;
86        } else {
87            return Ok(Err("fp commit must be on top of a block commit.".to_owned()));
88        }
89    }
90    // If the branch ends with a block, agenda, or agenda proof commit
91    else {
92        if tip_commit_hash == lfi.commit_hash {
93            return Ok(Err("the received commit is already finalized.".to_owned()));
94        }
95
96        // Read the commits in the branch and verify them
97        let commits = match read_commits(raw, lfi.commit_hash, tip_commit_hash).await {
98            Ok(x) => x,
99            Err(CommitError::Commit(error, commit)) => {
100                return Ok(Err(format!("failed to parse commit {commit}: {error}",)));
101            }
102            Err(e) => return Err(e.into()),
103        };
104        for (commit, commit_hash) in &commits {
105            if let Err(e) = csv.apply_commit(commit) {
106                return Ok(Err(format!(
107                    "commit sequence verification failed: {e} at {commit_hash}",
108                )));
109            }
110        }
111
112        // If the commit sequence contains block commit(s) that can be finalized
113        let headers = csv.get_block_headers();
114        if headers.len() > 2 {
115            let (last_header, _) = headers.last().expect(
116                "already checked that the received commit is not same as the last finalized block",
117            );
118            let (second_to_last_header, index) = headers[headers.len() - 2].clone();
119            advance_finalized_branch(
120                raw,
121                commits[index].1,
122                LastFinalizationProof {
123                    height: second_to_last_header.height,
124                    proof: last_header.prev_block_finalization_proof.clone(),
125                },
126            )
127            .await?;
128        }
129
130        // Create a branch associated to the last commit
131        let branch_name = match &commits
132            .last()
133            .expect("already checked that the received commit is not a finalization proof commit")
134            .0
135        {
136            Commit::Agenda(agenda) => {
137                let approved_agendas = read::read_governance_approved_agendas(raw).await?;
138                for (commit_hash, _) in approved_agendas {
139                    if let Commit::AgendaProof(agenda_proof) =
140                        read::read_commit(raw, commit_hash).await?
141                    {
142                        if agenda_proof.agenda_hash == agenda.to_hash256() {
143                            return Ok(Err("agenda proof already exists.".to_owned()));
144                        }
145                    } else {
146                        return Err(eyre!(IntegrityError::new(format!(
147                            "commit {} is not an agenda proof",
148                            commit_hash
149                        ))));
150                    }
151                }
152
153                format!(
154                    "a-{}",
155                    &agenda.to_hash256().to_string()[0..BRANCH_NAME_HASH_DIGITS]
156                )
157            }
158            Commit::AgendaProof(agenda_proof) => {
159                let agenda_name = match &commits[commits.len() - 2].0 {
160                    Commit::Agenda(agenda) => {
161                        format!(
162                            "a-{}",
163                            &agenda.to_hash256().to_string()[0..BRANCH_NAME_HASH_DIGITS]
164                        )
165                    }
166                    _ => {
167                        return Err(eyre!(IntegrityError::new(
168                            "agenda proof's parent is not an agenda".to_string()
169                        )))
170                    }
171                };
172                if raw.locate_branch(agenda_name.clone()).await.is_ok() {
173                    raw.delete_branch(agenda_name).await?;
174                }
175
176                format!(
177                    "a-{}",
178                    &agenda_proof.to_hash256().to_string()[0..BRANCH_NAME_HASH_DIGITS]
179                )
180            }
181            Commit::Block(block) => {
182                format!(
183                    "b-{}",
184                    &block.to_hash256().to_string()[0..BRANCH_NAME_HASH_DIGITS]
185                )
186            }
187            x => return Ok(Err(format!("commit sequence ends with: {x:?}"))),
188        };
189        if raw.locate_branch(branch_name.clone()).await.is_ok() {
190            return Ok(Err(format!("branch already exists: {branch_name}",)));
191        }
192        raw.create_branch(branch_name, tip_commit_hash).await?;
193    };
194    Ok(Ok(()))
195}
196
197pub async fn sync_all(raw: &mut RawRepository) -> Result<Vec<(String, Result<(), String>)>, Error> {
198    let local_branches: Vec<String> = raw
199        .list_branches()
200        .await?
201        .into_iter()
202        .filter(|s| {
203            s.as_str() != FINALIZED_BRANCH_NAME
204                && s.as_str() != FP_BRANCH_NAME
205                && !s.starts_with("a-")
206                && !s.starts_with("b-")
207                && s.as_str() != "p"
208        })
209        .collect();
210    let remote_tracking_branches = raw.list_remote_tracking_branches().await?;
211
212    let mut result = Vec::new();
213    for branch in local_branches {
214        result.push((
215            branch.to_owned(),
216            sync(raw, raw.locate_branch(branch.to_owned()).await?).await?,
217        ));
218    }
219    for (remote, branch, commit_hash) in remote_tracking_branches {
220        result.push((format!("{remote}/{branch}"), sync(raw, commit_hash).await?));
221    }
222    Ok(result)
223}
224
225pub async fn clean(raw: &mut RawRepository, hard: bool) -> Result<(), Error> {
226    let finalized_branch_commit_hash = raw
227        .locate_branch(FINALIZED_BRANCH_NAME.into())
228        .await
229        .map_err(|e| match e {
230            raw::Error::NotFound(_) => {
231                eyre!(IntegrityError::new(
232                    "cannot locate `finalized` branch".to_string()
233                ))
234            }
235            _ => eyre!(e),
236        })?;
237    let branches = read_local_branches(raw).await?;
238    let last_header = read_last_finalized_block_header(raw).await?;
239    for (branch, branch_commit_hash) in branches {
240        if !(branch.as_str() == FINALIZED_BRANCH_NAME || branch.as_str() == FP_BRANCH_NAME) {
241            if hard {
242                raw.delete_branch(branch.to_string()).await?;
243            } else {
244                // Delete outdated branch
245                let find_merge_base_result = raw
246                    .find_merge_base(branch_commit_hash, finalized_branch_commit_hash)
247                    .await
248                    .map_err(|e| match e {
249                        raw::Error::NotFound(_) => {
250                            eyre!(IntegrityError::new(format!(
251                                "cannot find merge base for branch {branch} and finalized branch"
252                            )))
253                        }
254                        _ => eyre!(e),
255                    })?;
256
257                if finalized_branch_commit_hash != find_merge_base_result {
258                    raw.delete_branch(branch.to_string()).await?;
259                }
260
261                // Delete branch with invalid commit sequence
262                raw.checkout(branch.to_string()).await?;
263                let reserved_state = raw.read_reserved_state().await?;
264                let commits =
265                    read_commits(raw, finalized_branch_commit_hash, branch_commit_hash).await?;
266                let mut verifier =
267                    CommitSequenceVerifier::new(last_header.clone(), reserved_state.clone())
268                        .map_err(|e| eyre!("failed to create a commit sequence verifier: {}", e))?;
269                for (commit, _) in commits.iter() {
270                    if verifier.apply_commit(commit).is_err() {
271                        raw.delete_branch(branch.to_string()).await?;
272                    }
273                }
274            }
275        }
276    }
277
278    // Remove remote repositories
279    // Note that remote branches are automatically removed when the remote repository is removed.
280    let remote_list = raw.list_remotes().await?;
281    for (remote_name, _) in remote_list {
282        raw.remove_remote(remote_name).await?;
283    }
284
285    Ok(())
286}