simperby_repository/interpret/
create.rs1use 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 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 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 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 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 raw.delete_branch(agenda_branch_name.clone()).await?;
71 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 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 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 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 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 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 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 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 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(), 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 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 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}