1use crate::{raw::RawCommit, raw::SemanticCommit, UNKNOWN_COMMIT_AUTHOR};
2use eyre::{eyre, Error};
3use regex::Regex;
4use simperby_core::{reserved::ReservedState, *};
5
6pub fn to_semantic_commit(
8 commit: &Commit,
9 mut reserved_state: ReservedState,
10) -> Result<SemanticCommit, Error> {
11 match commit {
12 Commit::Agenda(agenda) => {
13 let title = format!(">agenda: {}", agenda.height);
14 let body = serde_spb::to_string(agenda).unwrap();
15 Ok(SemanticCommit {
16 title,
17 body,
18 diff: Diff::None,
19 author: agenda.author.clone(),
20 timestamp: agenda.timestamp,
21 })
22 }
23 Commit::Block(block_header) => {
24 let title = format!(">block: {}", block_header.height);
25 let body = serde_spb::to_string(block_header).unwrap();
26 Ok(SemanticCommit {
27 title,
28 body,
29 diff: Diff::None,
30 author: if block_header.author == PublicKey::zero() {
31 "genesis".to_owned()
32 } else {
33 reserved_state
34 .query_name(&block_header.author)
35 .ok_or_else(|| {
36 eyre!(
37 "failed to query the name of the author: {}",
38 block_header.author
39 )
40 })?
41 },
42 timestamp: block_header.timestamp,
43 })
44 }
45 Commit::Transaction(transaction) => Ok(SemanticCommit {
46 title: transaction.head.clone(),
47 body: transaction.body.clone(),
48 diff: transaction.diff.clone(),
49 author: transaction.author.clone(),
50 timestamp: transaction.timestamp,
51 }),
52 Commit::AgendaProof(agenda_proof) => {
53 let title = format!(">agenda-proof: {}", agenda_proof.height);
54 let body = serde_spb::to_string(agenda_proof).unwrap();
55 Ok(SemanticCommit {
56 title,
57 body,
58 diff: Diff::None,
59 author: UNKNOWN_COMMIT_AUTHOR.to_owned(),
60 timestamp: agenda_proof.timestamp,
61 })
62 }
63 Commit::ExtraAgendaTransaction(tx) => {
64 let body = serde_spb::to_string(tx).unwrap();
65 match tx {
66 ExtraAgendaTransaction::Delegate(tx) => {
67 let title = format!(
68 ">tx-delegate: {} to {}",
69 tx.data.delegator, tx.data.delegatee
70 );
71 let diff = Diff::Reserved(Box::new(reserved_state.apply_delegate(tx).unwrap()));
72 Ok(SemanticCommit {
73 title,
74 body,
75 diff,
76 author: tx.data.delegator.clone(),
77 timestamp: tx.data.timestamp,
78 })
79 }
80 ExtraAgendaTransaction::Undelegate(tx) => {
81 let title = format!(">tx-undelegate: {}", tx.data.delegator);
82 let diff =
83 Diff::Reserved(Box::new(reserved_state.apply_undelegate(tx).unwrap()));
84 Ok(SemanticCommit {
85 title,
86 body,
87 diff,
88 author: tx.data.delegator.clone(),
89 timestamp: tx.data.timestamp,
90 })
91 }
92 ExtraAgendaTransaction::Report(_) => {
93 unimplemented!("report is not implemented yet.")
94 }
95 }
96 }
97 Commit::ChatLog(_) => unimplemented!(),
98 }
99}
100
101pub fn from_semantic_commit(semantic_commit: SemanticCommit) -> Result<Commit, Error> {
105 let pattern = Regex::new(
106 r"^>(((agenda)|(block)|(agenda-proof)): (\d+))|((tx-delegate): ((\D+)-(\d+)) to ((\D+)-(\d+)))|((tx-undelegate): ((\D+)-(\d+)))$"
107 )
108 .unwrap();
109 let captures = pattern.captures(&semantic_commit.title);
110 if let Some(captures) = captures {
111 let commit_type = captures
112 .get(2)
113 .or_else(|| captures.get(8))
114 .or_else(|| captures.get(16))
115 .map(|m| m.as_str())
116 .ok_or_else(|| {
117 eyre!(
118 "failed to parse commit type from the commit title: {}",
119 semantic_commit.title
120 )
121 })?;
122 match commit_type {
123 "agenda" => {
124 let agenda: Agenda = serde_spb::from_str(&semantic_commit.body)?;
125 let height = captures.get(6).map(|m| m.as_str()).ok_or_else(|| {
126 eyre!(
127 "failed to parse height from the commit title: {}",
128 semantic_commit.title
129 )
130 })?;
131 let height = height.parse::<u64>()?;
132 if height != agenda.height {
133 return Err(eyre!(
134 "agenda height mismatch: expected {}, got {}",
135 agenda.height,
136 height
137 ));
138 }
139 Ok(Commit::Agenda(agenda))
140 }
141 "block" => {
142 let block_header: BlockHeader = serde_spb::from_str(&semantic_commit.body)?;
143 let height = captures.get(6).map(|m| m.as_str()).ok_or_else(|| {
144 eyre!(
145 "failed to parse height from the commit title: {}",
146 semantic_commit.title
147 )
148 })?;
149 let height = height.parse::<u64>()?;
150 if height != block_header.height {
151 return Err(eyre!(
152 "block height mismatch: expected {}, got {}",
153 block_header.height,
154 height
155 ));
156 }
157 Ok(Commit::Block(block_header))
158 }
159 "agenda-proof" => {
160 let agenda_proof: AgendaProof = serde_spb::from_str(&semantic_commit.body)?;
161 let height = captures.get(6).map(|m| m.as_str()).ok_or_else(|| {
162 eyre!(
163 "failed to parse height from the commit title: {}",
164 semantic_commit.title
165 )
166 })?;
167 let height = height.parse::<u64>()?;
168 if height != agenda_proof.height {
169 return Err(eyre!(
170 "agenda-proof height mismatch: expected {}, got {}",
171 agenda_proof.height,
172 height
173 ));
174 }
175 Ok(Commit::AgendaProof(agenda_proof))
176 }
177 "tx-delegate" => {
178 let tx: ExtraAgendaTransaction = serde_spb::from_str(&semantic_commit.body)?;
179 match tx {
180 ExtraAgendaTransaction::Delegate(ref tx) => {
181 let delegator = captures.get(9).map(|m| m.as_str()).ok_or_else(|| {
182 eyre!(
183 "failed to parse delegator from the commit title: {}",
184 semantic_commit.title
185 )
186 })?;
187 if delegator != tx.data.delegator {
188 return Err(eyre!(
189 "delegator mismatch: expected {}, got {}",
190 delegator,
191 tx.data.delegator
192 ));
193 }
194 let delegatee = captures.get(12).map(|m| m.as_str()).ok_or_else(|| {
195 eyre!(
196 "failed to parse delegatee from the commit title: {}",
197 semantic_commit.title
198 )
199 })?;
200 if delegatee != tx.data.delegatee {
201 return Err(eyre!(
202 "delegatee mismatch: expected {}, got {}",
203 delegatee,
204 tx.data.delegatee
205 ));
206 }
207 Ok(Commit::ExtraAgendaTransaction(
208 ExtraAgendaTransaction::Delegate(tx.clone()),
209 ))
210 }
211 _ => Err(eyre!("expected delegation transaction, got {:?}", tx)),
212 }
213 }
214 "tx-undelegate" => {
215 let tx: ExtraAgendaTransaction = serde_spb::from_str(&semantic_commit.body)?;
216 match tx {
217 ExtraAgendaTransaction::Undelegate(ref tx) => {
218 let delegator = captures.get(17).map(|m| m.as_str()).ok_or_else(|| {
219 eyre!(
220 "failed to parse delegator from the commit title: {}",
221 semantic_commit.title
222 )
223 })?;
224 if delegator != tx.data.delegator {
225 return Err(eyre!(
226 "delegator mismatch: expected {}, got {}",
227 delegator,
228 tx.data.delegator
229 ));
230 }
231 Ok(Commit::ExtraAgendaTransaction(
232 ExtraAgendaTransaction::Undelegate(tx.clone()),
233 ))
234 }
235 _ => Err(eyre!("expected undelegation transaction, got {:?}", tx)),
236 }
237 }
238 _ => Err(eyre!("unknown commit type: {}", commit_type)),
239 }
240 } else {
241 Ok(Commit::Transaction(Transaction {
242 author: semantic_commit.author,
243 timestamp: semantic_commit.timestamp,
244 head: semantic_commit.title,
245 body: semantic_commit.body,
246 diff: semantic_commit.diff,
247 }))
248 }
249}
250
251pub fn fp_to_semantic_commit(fp: &LastFinalizationProof) -> SemanticCommit {
252 let title = format!(">fp: {}", fp.height);
253 let body = serde_spb::to_string(&fp).unwrap();
254 SemanticCommit {
255 title,
256 body,
257 diff: Diff::None,
258 author: UNKNOWN_COMMIT_AUTHOR.to_owned(),
259 timestamp: 0,
260 }
261}
262
263pub fn fp_from_semantic_commit(
264 semantic_commit: SemanticCommit,
265) -> Result<LastFinalizationProof, Error> {
266 let pattern = Regex::new(r"^>fp: (\d+)$").unwrap();
267 let captures = pattern.captures(&semantic_commit.title);
268 if let Some(captures) = captures {
269 let height = captures.get(1).map(|m| m.as_str()).ok_or_else(|| {
270 eyre!(
271 "Failed to parse commit height from commit title: {}",
272 semantic_commit.title
273 )
274 })?;
275 let height = height.parse::<u64>()?;
276 let proof: LastFinalizationProof = serde_spb::from_str(&semantic_commit.body)?;
277 if height != proof.height {
278 return Err(eyre!(
279 "proof height mismatch: expected {}, got {}",
280 proof.height,
281 height
282 ));
283 }
284 Ok(proof)
285 } else {
286 Err(eyre!("unknown commit type: {}", semantic_commit.title))
287 }
288}
289
290pub fn raw_commit_to_semantic_commit(raw_commit: RawCommit) -> SemanticCommit {
291 let (title, body) = if let Some((title, body)) = raw_commit.message.split_once("\n\n") {
292 (title.to_string(), body.to_string())
293 } else {
294 (String::new(), String::new())
295 };
296 SemanticCommit {
297 title,
298 body,
299 diff: if raw_commit.diff.is_none() {
300 Diff::None
301 } else {
302 unimplemented!()
304 },
305 author: raw_commit.author,
306 timestamp: raw_commit.timestamp / 1000,
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use simperby_core::test_utils::generate_standard_genesis;
314
315 #[test]
316 fn format_transaction_commit() {
317 let (reserved_state, _) = generate_standard_genesis(4);
318 let transaction = Commit::Transaction(Transaction {
319 author: "doesn't matter".to_owned(),
320 timestamp: 0,
321 head: "abc".to_string(),
322 body: "def".to_string(),
323 diff: Diff::None,
324 });
325 assert_eq!(
326 transaction,
327 from_semantic_commit(to_semantic_commit(&transaction, reserved_state).unwrap(),)
328 .unwrap()
329 );
330 }
331
332 #[test]
333 fn format_agenda_commit() {
334 let (reserved_state, _) = generate_standard_genesis(4);
335 let agenda = Commit::Agenda(Agenda {
336 height: 3,
337 author: "doesn't matter".to_owned(),
338 timestamp: 123,
339 transactions_hash: Hash256::hash("hello"),
340 previous_block_hash: Hash256::hash("hello"),
341 });
342 assert_eq!(
343 agenda,
344 from_semantic_commit(to_semantic_commit(&agenda, reserved_state).unwrap(),).unwrap()
345 );
346 }
347
348 #[test]
349 fn format_block_commit() {
350 let (reserved_state, _) = generate_standard_genesis(4);
351 let block = Commit::Block(BlockHeader {
352 height: 3,
353 author: PublicKey::zero(),
354 prev_block_finalization_proof: FinalizationProof {
355 round: 0,
356 signatures: vec![TypedSignature::new(Signature::zero(), PublicKey::zero())],
357 },
358 previous_hash: Hash256::hash("hello1"),
359 timestamp: 0,
360 commit_merkle_root: Hash256::hash("hello2"),
361 repository_merkle_root: Hash256::hash("hello3"),
362 validator_set: vec![(PublicKey::zero(), 1)],
363 version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
364 });
365 assert_eq!(
366 block,
367 from_semantic_commit(to_semantic_commit(&block, reserved_state).unwrap(),).unwrap()
368 );
369 }
370
371 #[test]
372 fn format_agenda_proof_commit() {
373 let (reserved_state, _) = generate_standard_genesis(4);
374 let agenda_proof = Commit::AgendaProof(AgendaProof {
375 height: 3,
376 agenda_hash: Hash256::hash("hello1"),
377 proof: vec![TypedSignature::new(Signature::zero(), PublicKey::zero())],
378 timestamp: 0,
379 });
380 assert_eq!(
381 agenda_proof,
382 from_semantic_commit(to_semantic_commit(&agenda_proof, reserved_state).unwrap(),)
383 .unwrap()
384 );
385 }
386
387 #[test]
388 fn format_extra_agenda_transaction_commit1() {
389 let (reserved_state, keys) = generate_standard_genesis(4);
390 let delegation_transaction_data = DelegationTransactionData {
391 delegator: reserved_state.members[0].name.clone(),
392 delegatee: reserved_state.members[1].name.clone(),
393 governance: true,
394 block_height: 0,
395 timestamp: 0,
396 chain_name: reserved_state.genesis_info.chain_name.clone(),
397 };
398 let delegation_transaction =
399 Commit::ExtraAgendaTransaction(ExtraAgendaTransaction::Delegate(TxDelegate {
400 data: delegation_transaction_data.clone(),
401 proof: TypedSignature::sign(&delegation_transaction_data, &keys[0].1).unwrap(),
402 }));
403 assert_eq!(
404 delegation_transaction,
405 from_semantic_commit(
406 to_semantic_commit(&delegation_transaction, reserved_state).unwrap()
407 )
408 .unwrap()
409 );
410 }
411
412 #[test]
413 fn format_extra_agenda_transaction_commit2() {
414 let (mut reserved_state, keys) = generate_standard_genesis(4);
415 reserved_state.members[0].governance_delegatee = Option::from("member-0000".to_string());
416 reserved_state.members[0].consensus_delegatee = Option::from("member-0000".to_string());
417 let undelegation_transaction_data = UndelegationTransactionData {
418 delegator: reserved_state.members[0].name.clone(),
419 block_height: 0,
420 timestamp: 0,
421 chain_name: reserved_state.genesis_info.chain_name.clone(),
422 };
423 let undelegation_transaction =
424 Commit::ExtraAgendaTransaction(ExtraAgendaTransaction::Undelegate(TxUndelegate {
425 data: undelegation_transaction_data.clone(),
426 proof: TypedSignature::sign(&undelegation_transaction_data, &keys[0].1).unwrap(),
427 }));
428 assert_eq!(
429 undelegation_transaction,
430 from_semantic_commit(
431 to_semantic_commit(&undelegation_transaction, reserved_state.clone()).unwrap()
432 )
433 .unwrap()
434 );
435 }
436
437 #[test]
438 fn format_fp() {
439 let fp = LastFinalizationProof {
440 height: 3,
441 proof: FinalizationProof {
442 round: 0,
443 signatures: vec![
444 TypedSignature::new(Signature::zero(), PublicKey::zero()),
445 TypedSignature::new(Signature::zero(), PublicKey::zero()),
446 ],
447 },
448 };
449 assert_eq!(
450 fp,
451 fp_from_semantic_commit(fp_to_semantic_commit(&fp)).unwrap()
452 );
453 }
454}