simperby_repository/interpret/
read.rs1use super::*;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum CommitError {
6 #[error("raw repo error: {0}")]
7 Raw(#[from] raw::Error),
8 #[error("failed to parse commit ({1}): {0}")]
9 Commit(eyre::Error, CommitHash),
10 #[error("reserved state error: {0}")]
11 ReservedState(#[from] super::Error),
12}
13
14pub async fn read_local_branches(
15 raw: &RawRepository,
16) -> Result<HashSet<(Branch, CommitHash)>, Error> {
17 let local_branches = raw.list_branches().await?;
18 let mut result = HashSet::new();
19 for b in local_branches {
21 result.insert((b.clone(), raw.locate_branch(b).await?));
22 }
23 Ok(result)
24}
25
26pub async fn get_last_finalized_block_commit_hash(
27 raw: &RawRepository,
28) -> Result<CommitHash, Error> {
29 raw.locate_branch(FINALIZED_BRANCH_NAME.into())
30 .await
31 .map_err(|e| match e {
32 raw::Error::NotFound(_) => {
33 eyre!(IntegrityError::new(
34 "cannot locate `finalized` branch".to_string()
35 ))
36 }
37 _ => eyre!(e),
38 })
39}
40
41pub async fn read_last_finalized_reserved_state(
42 raw: &RawRepository,
43) -> Result<ReservedState, Error> {
44 Ok(raw
45 .read_reserved_state_at_commit(get_last_finalized_block_commit_hash(raw).await?)
46 .await?)
47}
48
49pub async fn read_last_finalized_block_header(raw: &RawRepository) -> Result<BlockHeader, Error> {
50 let commit_hash = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
51 let semantic_commit = raw.read_semantic_commit(commit_hash).await?;
52 let commit = format::from_semantic_commit(semantic_commit).map_err(|e| eyre!(e))?;
53 if let Commit::Block(block_header) = commit {
54 Ok(block_header)
55 } else {
56 Err(eyre!(IntegrityError {
57 msg: "`finalized` branch is not on a block".to_owned(),
58 }))
59 }
60}
61
62pub async fn read_last_finalization_proof(
63 raw: &RawRepository,
64) -> Result<LastFinalizationProof, Error> {
65 if let Ok(last_finalization_proof) = format::fp_from_semantic_commit(
66 raw.read_semantic_commit(raw.locate_branch(FP_BRANCH_NAME.into()).await.map_err(
67 |e| match e {
68 raw::Error::NotFound(_) => {
69 eyre!(IntegrityError::new("cannot locate `fp` branch".to_string()))
70 }
71 _ => eyre!(e),
72 },
73 )?)
74 .await?,
75 ) {
76 Ok(last_finalization_proof)
77 } else {
78 Err(eyre!(IntegrityError {
79 msg: "`fp` branch is not on a finalization proof".to_owned(),
80 }))
81 }
82}
83
84pub async fn read_finalization_info(
85 raw: &RawRepository,
86 height: BlockHeight,
87) -> Result<FinalizationInfo, Error> {
88 let finalized_block_header = read_last_finalized_block_header(raw).await?;
89 let last_block_height = finalized_block_header.height;
90
91 if height == last_block_height {
92 read_last_finalization_info(raw).await
93 } else {
94 let initial_commit = raw.get_initial_commit().await?;
95 let finalized_commit_hash = get_last_finalized_block_commit_hash(raw).await?;
96
97 let commits = raw
98 .query_commit_path(initial_commit, finalized_commit_hash)
99 .await?;
100 let commits = stream::iter(
101 commits
102 .iter()
103 .cloned()
104 .map(|c| async move { raw.read_semantic_commit(c).await.map(|x| (x, c)) }),
105 )
106 .buffered(256)
107 .collect::<Vec<_>>()
108 .await;
109 let commits = commits.into_iter().collect::<Result<Vec<_>, _>>()?;
110 let commits = commits
111 .into_iter()
112 .filter(|(commit, _hash)| {
113 commit.title == format!(">block: {height}")
114 || commit.title == format!(">block: {}", height + 1)
115 })
116 .collect::<Vec<_>>();
117
118 let header: BlockHeader = serde_spb::from_str(&commits[0].0.body)?;
119 let next_header: BlockHeader = serde_spb::from_str(&commits[1].0.body)?;
120 let commit_hash = commits[0].1;
121 let reserved_state = raw.read_reserved_state_at_commit(commit_hash).await?;
122 let proof = next_header.prev_block_finalization_proof;
123 Ok(FinalizationInfo {
124 header,
125 commit_hash,
126 reserved_state,
127 proof,
128 })
129 }
130}
131
132pub async fn read_last_finalization_info(raw: &RawRepository) -> Result<FinalizationInfo, Error> {
133 let header = read_last_finalized_block_header(raw).await?;
134 let commit_hash = get_last_finalized_block_commit_hash(raw).await?;
135 let reserved_state = read_last_finalized_reserved_state(raw).await?;
136 let proof = read_last_finalization_proof(raw).await?.proof;
137 Ok(FinalizationInfo {
138 header,
139 commit_hash,
140 reserved_state,
141 proof,
142 })
143}
144
145pub(crate) async fn read_raw_commits(
146 raw: &RawRepository,
147 ancestor: CommitHash,
148 descendant: CommitHash,
149) -> Result<Vec<(RawCommit, CommitHash)>, Error> {
150 let commits = raw.query_commit_path(ancestor, descendant).await?;
151 let commits = stream::iter(
152 commits
153 .iter()
154 .cloned()
155 .map(|c| async move { raw.read_commit(c).await.map(|x| (x, c)) }),
156 )
157 .buffered(256)
158 .collect::<Vec<_>>()
159 .await;
160 let commits = commits.into_iter().collect::<Result<Vec<_>, _>>()?;
161 Ok(commits)
162}
163
164pub(crate) async fn read_commits(
165 raw: &RawRepository,
166 ancestor: CommitHash,
167 descendant: CommitHash,
168) -> Result<Vec<(Commit, CommitHash)>, CommitError> {
169 let commits = raw.query_commit_path(ancestor, descendant).await?;
170 let commits = stream::iter(
171 commits
172 .iter()
173 .cloned()
174 .map(|c| async move { raw.read_semantic_commit(c).await.map(|x| (x, c)) }),
175 )
176 .buffered(256)
177 .collect::<Vec<_>>()
178 .await;
179 let commits = commits.into_iter().collect::<Result<Vec<_>, _>>()?;
180 let commits = commits
181 .into_iter()
182 .map(|(commit, hash)| {
183 from_semantic_commit(commit)
184 .map_err(|e| (e, hash))
185 .map(|x| (x, hash))
186 })
187 .collect::<Result<Vec<_>, _>>()
188 .map_err(|(e, c)| CommitError::Commit(e, c))?;
189 Ok(commits)
190}
191
192pub async fn read_and_verify_commits_from_last_finalized_block(
200 raw: &RawRepository,
201 commit_hash: CommitHash,
202) -> Result<Result<CommitSequenceVerifier, Error>, Error> {
203 let lfi = read_last_finalization_info(raw).await?;
204
205 if raw.find_merge_base(lfi.commit_hash, commit_hash).await? != lfi.commit_hash {
206 return Ok(Err(eyre!(
207 "the given commit is not a descendant of the last finalized block."
208 )));
209 }
210
211 if format::fp_from_semantic_commit(raw.read_semantic_commit(commit_hash).await?).is_ok() {
212 return Ok(Err(eyre!(
213 "the given commit is a finalization proof commit."
214 )));
215 }
216
217 let commits = read_commits(raw, lfi.commit_hash, commit_hash).await?;
218 let mut csv = CommitSequenceVerifier::new(lfi.header, lfi.reserved_state).map_err(|e| {
219 IntegrityError::new(format!("finalized branch is not accepted by CSV: {e}"))
220 })?;
221
222 for commit in commits {
223 if let Err(e) = csv.apply_commit(&commit.0) {
224 return Ok(Err(eyre!(e)));
225 }
226 }
227 Ok(Ok(csv))
228}
229
230pub async fn read_commit(raw: &RawRepository, commit_hash: CommitHash) -> Result<Commit, Error> {
231 let semantic_commit = raw.read_semantic_commit(commit_hash).await?;
232 format::from_semantic_commit(semantic_commit).map_err(|e| eyre!(e))
233}
234
235pub async fn read_agendas(raw: &RawRepository) -> Result<Vec<(CommitHash, Hash256)>, Error> {
236 let mut agendas: Vec<(CommitHash, Hash256)> = vec![];
237 let branches = read_local_branches(raw).await?;
238 let last_header_commit_hash = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
239 for (branch, branch_commit_hash) in branches {
240 if branch.as_str().starts_with("a-") {
242 let find_merge_base_result = raw
244 .find_merge_base(last_header_commit_hash, branch_commit_hash)
245 .await
246 .map_err(|e| match e {
247 raw::Error::NotFound(_) => {
248 eyre!(IntegrityError::new(format!(
249 "cannot find merge base for branch {branch} and finalized branch"
250 )))
251 }
252 _ => eyre!(e),
253 })?;
254
255 if last_header_commit_hash != find_merge_base_result {
256 log::warn!(
257 "branch {} should be rebased on top of the {} branch",
258 branch,
259 FINALIZED_BRANCH_NAME
260 );
261 continue;
262 }
263
264 let commits = read_commits(raw, last_header_commit_hash, branch_commit_hash).await?;
266 let last_header = read_last_finalized_block_header(raw).await?;
267 for (commit, hash) in commits {
268 if let Commit::Agenda(agenda) = commit {
269 if agenda.height == last_header.height + 1 {
270 agendas.push((hash, agenda.to_hash256()));
271 }
272 }
273 }
274 }
275 }
276 Ok(agendas)
277}
278
279pub async fn read_governance_approved_agendas(
280 raw: &RawRepository,
281) -> Result<Vec<(CommitHash, Hash256)>, Error> {
282 let mut agendas: Vec<(CommitHash, Hash256)> = vec![];
283 let branches = read_local_branches(raw).await?;
284 let last_header_commit_hash = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
285 for (branch, branch_commit_hash) in branches {
286 if branch.as_str().starts_with("a-") {
288 let find_merge_base_result = raw
290 .find_merge_base(last_header_commit_hash, branch_commit_hash)
291 .await
292 .map_err(|e| match e {
293 raw::Error::NotFound(_) => {
294 eyre!(IntegrityError::new(format!(
295 "cannot find merge base for branch {branch} and finalized branch"
296 )))
297 }
298 _ => eyre!(e),
299 })?;
300
301 if last_header_commit_hash != find_merge_base_result {
302 log::warn!(
303 "branch {} should be rebased on top of the {} branch",
304 branch,
305 FINALIZED_BRANCH_NAME
306 );
307 continue;
308 }
309
310 let commits = read_commits(raw, last_header_commit_hash, branch_commit_hash).await?;
312 let last_header = read_last_finalized_block_header(raw).await?;
313 for (commit, hash) in commits {
314 if let Commit::AgendaProof(agenda_proof) = commit {
315 if agenda_proof.height == last_header.height + 1 {
316 agendas.push((hash, agenda_proof.to_hash256()));
317 }
318 }
319 }
320 }
321 }
322 Ok(agendas)
323}
324
325pub async fn read_blocks(raw: &RawRepository) -> Result<Vec<(CommitHash, Hash256)>, Error> {
326 let mut blocks: Vec<(CommitHash, Hash256)> = vec![];
327 let branches = read_local_branches(raw).await?;
328 let last_header_commit_hash = raw.locate_branch(FINALIZED_BRANCH_NAME.into()).await?;
329 for (branch, branch_commit_hash) in branches {
330 if branch.as_str().starts_with("b-") {
332 let find_merge_base_result = raw
334 .find_merge_base(last_header_commit_hash, branch_commit_hash)
335 .await
336 .map_err(|e| match e {
337 raw::Error::NotFound(_) => {
338 eyre!(IntegrityError::new(format!(
339 "cannot find merge base for branch {branch} and finalized branch"
340 )))
341 }
342 _ => eyre!(e),
343 })?;
344 if last_header_commit_hash != find_merge_base_result {
345 log::warn!(
346 "branch {} should be rebased on top of the {} branch",
347 branch,
348 FINALIZED_BRANCH_NAME
349 );
350 continue;
351 }
352
353 let commits = read_commits(raw, last_header_commit_hash, branch_commit_hash).await?;
355 let last_header = read_last_finalized_block_header(raw).await?;
356 for (commit, hash) in commits {
357 if let Commit::Block(block_header) = commit {
358 if block_header.height == last_header.height + 1 {
359 blocks.push((hash, block_header.to_hash256()));
360 }
361 }
362 }
363 }
364 }
365 Ok(blocks)
366}
367
368pub async fn check_gitignore(raw: &RawRepository) -> Result<bool, Error> {
369 let path = raw.get_working_directory_path().await?;
370 let path = std::path::Path::new(&path).join(".gitignore");
371 if !path.exists() {
372 return Ok(false);
373 }
374 let file = tokio::fs::File::open(path).await?;
375 let mut lines = tokio::io::BufReader::new(file).lines();
376 while let Some(line) = lines.next_line().await? {
377 if line == ".simperby/" {
378 return Ok(true);
379 }
380 }
381 Ok(false)
382}