simperby_core/
verify.rs

1use crate::reserved::ReservedState;
2use crate::*;
3use std::collections::HashMap;
4use std::collections::HashSet;
5use thiserror::Error;
6
7#[derive(Error, Debug, Clone)]
8pub enum Error {
9    #[error("invalid argument: {0}")]
10    InvalidArgument(String),
11    #[error("invalid proof: {0}")]
12    InvalidProof(String),
13    #[error("crypto error: {0}")]
14    CryptoError(String, CryptoError),
15    #[error("invalid commit: applied {0} commit cannot be applied at {1} phase")]
16    PhaseMismatch(String, String),
17}
18
19/// Verifies whether `h2` can be the direct child of `h1`.
20///
21/// Note that you still need to verify
22/// 1. block body (other commits)
23/// 2. finalization proof
24/// 3. protocol version of the node binary.
25pub fn verify_header_to_header(h1: &BlockHeader, h2: &BlockHeader) -> Result<(), Error> {
26    if h2.height != h1.height + 1 {
27        return Err(Error::InvalidArgument(format!(
28            "invalid height: expected {}, got {}",
29            h1.height + 1,
30            h2.height
31        )));
32    }
33    if h2.previous_hash != h1.to_hash256() {
34        return Err(Error::InvalidArgument(format!(
35            "invalid previous hash: expected {}, got {}",
36            h1.to_hash256(),
37            h2.previous_hash
38        )));
39    }
40    if !h1
41        .validator_set
42        .iter()
43        .any(|(public_key, _)| public_key == &h2.author)
44    {
45        return Err(Error::InvalidArgument(format!(
46            "invalid author: {} is not in the validator set",
47            h2.author
48        )));
49    }
50    if h2.timestamp < h1.timestamp {
51        return Err(Error::InvalidArgument(format!(
52            "invalid timestamp: expected larger than or equal to {}, got {}",
53            h1.timestamp, h2.timestamp
54        )));
55    }
56    verify_finalization_proof(h1, &h2.prev_block_finalization_proof)?;
57    Ok(())
58}
59
60/// Verifies the finalization proof of the given block header.
61pub fn verify_finalization_proof(
62    header: &BlockHeader,
63    block_finalization_proof: &FinalizationProof,
64) -> Result<(), Error> {
65    let total_voting_power: VotingPower = header.validator_set.iter().map(|(_, v)| v).sum();
66    let mut voted_validators = HashSet::new();
67    for signature in &block_finalization_proof.signatures {
68        signature
69            .verify(&FinalizationSignTarget {
70                block_hash: header.to_hash256(),
71                round: block_finalization_proof.round,
72            })
73            .map_err(|e| Error::CryptoError("invalid finalization proof".to_string(), e))?;
74        voted_validators.insert(signature.signer());
75    }
76    let voted_voting_power: VotingPower = header
77        .validator_set
78        .iter()
79        .filter(|(v, _)| voted_validators.contains(v))
80        .map(|(_, power)| power)
81        .sum();
82    if voted_voting_power * 3 <= total_voting_power * 2 {
83        return Err(Error::InvalidProof(format!(
84            "invalid finalization proof - voted voting power is too low: {voted_voting_power} / {total_voting_power}"
85        )));
86    }
87    Ok(())
88}
89
90// Phases of the `CommitSequenceVerifier`.
91//
92// Note that `Phase::X` is agenda phase where `Commit::X` is the last commit.
93#[derive(Debug, Clone, PartialEq, Eq)]
94enum Phase {
95    // The transaction phase.
96    // Note that there can be agendas without transactions.
97    Transaction {
98        last_transaction: Transaction,
99        preceding_transactions: Vec<Transaction>,
100    },
101    // The agenda phase.
102    Agenda {
103        agenda: Agenda,
104    },
105    // The agenda proof phase.
106    AgendaProof {
107        agenda_proof: AgendaProof,
108    },
109    // The extra phase.
110    // Extra phase consists of `ExtraAgendaTransaction`s and `ChatLog`s.
111    ExtraAgendaTransaction {
112        last_extra_agenda_timestamp: Timestamp,
113        // TODO: add `ChatLog` here.
114    },
115    // The block phase.
116    Block,
117}
118
119/// Verifies whether the given sequence of commits can be a partial sequence of a valid finalized chain.
120///
121/// It may accept sequences that contain more than one `BlockHeader`.
122#[derive(Debug, Clone)]
123pub struct CommitSequenceVerifier {
124    header: BlockHeader,
125    phase: Phase,
126    reserved_state: ReservedState,
127    commits_for_next_block: Vec<Commit>,
128    total_commits: Vec<Commit>,
129}
130
131impl CommitSequenceVerifier {
132    /// Creates a new `CommitSequenceVerifier` with the given block header.
133    pub fn new(start_header: BlockHeader, reserved_state: ReservedState) -> Result<Self, Error> {
134        Ok(Self {
135            header: start_header.clone(),
136            phase: Phase::Block,
137            reserved_state,
138            commits_for_next_block: vec![],
139            total_commits: vec![Commit::Block(start_header)],
140        })
141    }
142
143    pub fn get_header(&self) -> &BlockHeader {
144        &self.header
145    }
146
147    /// Returns the commits received so far.
148    pub fn get_total_commits(&self) -> &[Commit] {
149        &self.total_commits
150    }
151
152    pub fn get_reserved_state(&self) -> &ReservedState {
153        &self.reserved_state
154    }
155
156    /// Returns the block headers received so far, with the index of the commit.
157    ///
158    /// It returns `[start_header]` if no block header has been received.
159    pub fn get_block_headers(&self) -> Vec<(BlockHeader, usize)> {
160        self.total_commits
161            .iter()
162            .enumerate()
163            .filter_map(|(i, commit)| match commit {
164                Commit::Block(header) => Some((header.clone(), i)),
165                _ => None,
166            })
167            .collect()
168    }
169
170    /// Verifies finalization of the last header with the given proof.
171    ///
172    /// Note that due to the nature of the finalization proof (included in the next block)
173    /// there is always an unverified last header (which may even not be the last commit).
174    pub fn verify_last_header_finalization(&self, proof: &FinalizationProof) -> Result<(), Error> {
175        verify_finalization_proof(&self.header, proof)
176    }
177
178    /// Verifies whether the given reserved state is valid from the current state.
179    pub fn verify_reserved_state(&self, rs: &ReservedState) -> Result<(), Error> {
180        // Check that the number of members is at least 4.
181        if rs.members.len() < 4 {
182            return Err(Error::InvalidArgument(
183                "the number of members is less than 4".to_string(),
184            ));
185        }
186        // Check that `consensus_leader_order` is correct.
187        // 1. consensus_leader_order should be the subset of members.
188        // 2. every consensus leader should not be expelled.
189        // 3. consensus_leader_order should consist of more than 1 unique members to avoid a SPoF.
190        let valid_leader_candidates: HashSet<&MemberName> = rs
191            .members
192            .iter()
193            .filter(|m| !m.expelled)
194            .map(|m| &m.name)
195            .collect();
196        if !rs
197            .consensus_leader_order
198            .iter()
199            .all(|m| valid_leader_candidates.contains(m))
200        {
201            return Err(Error::InvalidArgument(
202                "Some consensus leaders are not valid candidates".to_string(),
203            ));
204        }
205        if rs
206            .consensus_leader_order
207            .iter()
208            .collect::<HashSet<&MemberName>>()
209            .len()
210            <= 1
211        {
212            return Err(Error::InvalidArgument(
213                "consensus_leader_order should consist of more than 1 unique members".to_string(),
214            ));
215        }
216        // Check that `genesis_info` stays the same.
217        if rs.genesis_info != self.reserved_state.genesis_info {
218            return Err(Error::InvalidArgument("genesis_info changes".to_string()));
219        }
220        // Check that `Member::name` and `Member::public_key` are unique.
221        let mut member_names = HashSet::new();
222        let mut public_keys = HashSet::new();
223        for member in &rs.members {
224            if !member_names.insert(&member.name) {
225                return Err(Error::InvalidArgument(format!(
226                    "member name '{}' already exists",
227                    member.name
228                )));
229            }
230            if !public_keys.insert(&member.public_key) {
231                return Err(Error::InvalidArgument(format!(
232                    "the public key of '{}' already exists",
233                    member.name
234                )));
235            }
236        }
237        // Check that `member` monotonically increases (refer to `Member::expelled`).
238        // Once a member is added, it cannot be removed, even if it is expelled.
239        let member_names: HashSet<String> = rs.members.iter().map(|m| m.name.clone()).collect();
240        for existing_member in &self.reserved_state.members {
241            if !member_names.contains(&existing_member.name) {
242                return Err(Error::InvalidArgument(format!(
243                    "{} doesn't not exist in members",
244                    &existing_member.name
245                )));
246            }
247        }
248        Ok(())
249    }
250
251    /// Verifies the given commit and updates the internal reserved_state of CommitSequenceVerifier.
252    pub fn apply_commit(&mut self, commit: &Commit) -> Result<(), Error> {
253        match (commit, &self.phase) {
254            (Commit::Block(block_header), Phase::AgendaProof { agenda_proof: _ }) => {
255                verify_header_to_header(&self.header, block_header)?;
256                // Verify commit merkle root
257                let commit_merkle_root =
258                    BlockHeader::calculate_commit_merkle_root(&self.commits_for_next_block);
259                if commit_merkle_root != block_header.commit_merkle_root {
260                    return Err(Error::InvalidArgument(format!(
261                        "invalid commit merkle root: expected {}, got {}",
262                        commit_merkle_root, block_header.commit_merkle_root
263                    )));
264                };
265                self.header = block_header.clone();
266                self.phase = Phase::Block;
267                self.commits_for_next_block = vec![];
268            }
269            (
270                Commit::Block(block_header),
271                Phase::ExtraAgendaTransaction {
272                    last_extra_agenda_timestamp,
273                },
274            ) => {
275                verify_header_to_header(&self.header, block_header)?;
276                // Check if the block contains all the extra-agenda transactions.
277                if block_header.timestamp < *last_extra_agenda_timestamp {
278                    return Err(Error::InvalidArgument(format!(
279                        "invalid block timestamp: expected larger than or equal to the last extra-agenda transaction timestamp {}, got {}",
280                        last_extra_agenda_timestamp, block_header.timestamp
281                    )));
282                }
283                // Verify commit hash
284                let commit_merkle_root =
285                    BlockHeader::calculate_commit_merkle_root(&self.commits_for_next_block);
286                if commit_merkle_root != block_header.commit_merkle_root {
287                    return Err(Error::InvalidArgument(format!(
288                        "invalid commit merkle root: expected {}, got {}",
289                        commit_merkle_root, block_header.commit_merkle_root
290                    )));
291                };
292                self.header = block_header.clone();
293                self.phase = Phase::Block;
294                self.commits_for_next_block = vec![];
295            }
296            (Commit::Transaction(tx), Phase::Block) => {
297                // Update reserved_state for reserved-diff transactions.
298                if let Diff::Reserved(rs) = &tx.diff {
299                    self.verify_reserved_state(rs)?;
300                    self.reserved_state = *rs.clone();
301                } else if let Diff::General(rs, _) = &tx.diff {
302                    self.verify_reserved_state(rs)?;
303                    self.reserved_state = *rs.clone();
304                }
305                self.phase = Phase::Transaction {
306                    last_transaction: tx.clone(),
307                    preceding_transactions: vec![],
308                };
309            }
310            (
311                Commit::Transaction(tx),
312                Phase::Transaction {
313                    last_transaction,
314                    preceding_transactions,
315                },
316            ) => {
317                // Check if transactions are in chronological order
318                if tx.timestamp < last_transaction.timestamp {
319                    return Err(Error::InvalidArgument(format!(
320                        "invalid transaction timestamp: expected larger than or equal to the last transaction timestamp {}, got {}",
321                        last_transaction.timestamp, tx.timestamp
322                    )));
323                }
324                // Update reserved_state for reserved-diff transactions.
325                if let Diff::Reserved(rs) = &tx.diff {
326                    self.verify_reserved_state(rs)?;
327                    self.reserved_state = *rs.clone();
328                } else if let Diff::General(rs, _) = &tx.diff {
329                    self.verify_reserved_state(rs)?;
330                    self.reserved_state = *rs.clone();
331                }
332                let mut preceding_transactions = preceding_transactions.clone();
333                preceding_transactions.push(last_transaction.clone());
334                self.phase = Phase::Transaction {
335                    last_transaction: tx.clone(),
336                    preceding_transactions,
337                };
338            }
339            (Commit::Agenda(agenda), Phase::Block) => {
340                // Check if agenda is associated with the current block sequence.
341                if agenda.height != self.header.height + 1 {
342                    return Err(Error::InvalidArgument(format!(
343                        "invalid agenda block height: expected {}, got {}",
344                        self.header.height + 1,
345                        agenda.height
346                    )));
347                }
348                // Verify agenda without transactions
349                if agenda.transactions_hash != Agenda::calculate_transactions_hash(&[]) {
350                    return Err(Error::InvalidArgument(format!(
351                        "invalid agenda transactions_hash: expected {}, got {}",
352                        Agenda::calculate_transactions_hash(&[]),
353                        agenda.transactions_hash
354                    )));
355                }
356                // Verify if agenda's last previous_block_hash matches with the actual previous block hash to prevent replay attacks
357                if agenda.previous_block_hash != self.header.to_hash256() {
358                    return Err(Error::InvalidArgument(format!(
359                        "invalid agenda previous_block_hash: expected {}, got {}",
360                        self.header.to_hash256(),
361                        agenda.previous_block_hash
362                    )));
363                }
364                self.phase = Phase::Agenda {
365                    agenda: agenda.clone(),
366                };
367            }
368            (
369                Commit::Agenda(agenda),
370                Phase::Transaction {
371                    last_transaction,
372                    preceding_transactions,
373                },
374            ) => {
375                // Check if agenda is associated with the current block sequence.
376                if agenda.height != self.header.height + 1 {
377                    return Err(Error::InvalidArgument(format!(
378                        "invalid agenda block height: expected {}, got {}",
379                        self.header.height + 1,
380                        agenda.height
381                    )));
382                }
383                // Check if agenda is in chronological order
384                if agenda.timestamp < last_transaction.timestamp {
385                    return Err(Error::InvalidArgument(
386                        format!("invalid agenda timestamp: expected larger than or equal to the last transaction timestamp {}, got {}", last_transaction.timestamp, agenda.timestamp)
387                    ));
388                }
389                // Verify agenda
390                let transactions = [
391                    preceding_transactions.clone(),
392                    vec![last_transaction.clone()],
393                ]
394                .concat();
395                if agenda.transactions_hash != Agenda::calculate_transactions_hash(&transactions) {
396                    return Err(Error::InvalidArgument(format!(
397                        "invalid agenda transactions_hash: expected {}, got {}",
398                        Agenda::calculate_transactions_hash(&transactions),
399                        agenda.transactions_hash
400                    )));
401                }
402                // Verify if agenda's last previous_block_hash matches with the actual previous block hash to prevent replay attacks
403                if agenda.previous_block_hash != self.header.to_hash256() {
404                    return Err(Error::InvalidArgument(format!(
405                        "invalid agenda previous_block_hash: expected {}, got {}",
406                        self.header.to_hash256(),
407                        agenda.previous_block_hash
408                    )));
409                }
410                self.phase = Phase::Agenda {
411                    agenda: agenda.clone(),
412                };
413            }
414            (Commit::AgendaProof(agenda_proof), Phase::Agenda { agenda }) => {
415                // Check if agenda proof is associated with the current block sequence.
416                if agenda_proof.height != self.header.height + 1 {
417                    return Err(Error::InvalidArgument(format!(
418                        "invalid agenda proof block height: expected {}, got {}",
419                        self.header.height + 1,
420                        agenda_proof.height
421                    )));
422                }
423                // Check if agenda hash matches
424                if agenda_proof.agenda_hash != agenda.to_hash256() {
425                    return Err(Error::InvalidArgument(format!(
426                        "invalid agenda proof: invalid agenda hash expected {}, got {}",
427                        agenda.to_hash256(),
428                        agenda_proof.agenda_hash
429                    )));
430                }
431                // Verify the agenda proof
432                for signature in agenda_proof.proof.iter() {
433                    signature.verify(agenda).map_err(|e| {
434                        Error::CryptoError("invalid agenda proof: invalid signature".to_string(), e)
435                    })?;
436                }
437                // Check if the agenda proof is signed by the majority of the governance participants
438                let governance_set = self
439                    .reserved_state
440                    .get_governance_set()
441                    .unwrap()
442                    .into_iter()
443                    .collect::<HashMap<_, _>>();
444                let total_weight = governance_set.values().sum::<u64>();
445                let signed_weight = agenda_proof
446                    .proof
447                    .iter()
448                    .map(|s| {
449                        if let Some(weight) = governance_set.get(s.signer()) {
450                            Ok(*weight)
451                        } else {
452                            Err(Error::InvalidArgument(format!(
453                                "invalid agenda proof: invalid signer {}",
454                                s.signer()
455                            )))
456                        }
457                    })
458                    .collect::<Result<Vec<_>, Error>>()?
459                    .iter()
460                    .sum::<u64>();
461                if signed_weight * 2 <= total_weight {
462                    return Err(Error::InvalidArgument(
463                        "invalid agenda proof: insufficient signed weight".to_string(),
464                    ));
465                }
466                self.phase = Phase::AgendaProof {
467                    agenda_proof: agenda_proof.clone(),
468                };
469            }
470            (Commit::ExtraAgendaTransaction(tx), Phase::AgendaProof { agenda_proof: _ }) => {
471                match tx {
472                    ExtraAgendaTransaction::Delegate(tx) => {
473                        // Update reserved reserved_state by applying delegation
474                        self.reserved_state.apply_delegate(tx).map_err(|e| {
475                            Error::InvalidArgument(format!("invalid delegation: {e}"))
476                        })?;
477                        self.phase = Phase::ExtraAgendaTransaction {
478                            last_extra_agenda_timestamp: tx.data.timestamp,
479                        };
480                    }
481                    ExtraAgendaTransaction::Undelegate(tx) => {
482                        // Update reserved reserved_state by applying undelegation
483                        self.reserved_state.apply_undelegate(tx).map_err(|e| {
484                            Error::InvalidArgument(format!("invalid undelegation: {e}"))
485                        })?;
486                        self.phase = Phase::ExtraAgendaTransaction {
487                            last_extra_agenda_timestamp: tx.data.timestamp,
488                        };
489                    }
490                    ExtraAgendaTransaction::Report(_tx) => unimplemented!(),
491                }
492            }
493            (
494                Commit::ExtraAgendaTransaction(tx),
495                Phase::ExtraAgendaTransaction {
496                    last_extra_agenda_timestamp,
497                },
498            ) => {
499                match tx {
500                    ExtraAgendaTransaction::Delegate(tx) => {
501                        // Update reserved reserved_state by applying delegation
502                        self.reserved_state.apply_delegate(tx).map_err(|e| {
503                            Error::InvalidArgument(format!("invalid delegation: {e}"))
504                        })?;
505                        // Check if extra-agenda transactions are in chronological order
506                        if tx.data.timestamp < *last_extra_agenda_timestamp {
507                            return Err(Error::InvalidArgument(
508                                format!("invalid extra-agenda transaction timestamp: expected larger than or equal to the last transaction timestamp {}, got {}", last_extra_agenda_timestamp, tx.data.timestamp)
509                            ));
510                        }
511                        self.phase = Phase::ExtraAgendaTransaction {
512                            last_extra_agenda_timestamp: tx.data.timestamp,
513                        };
514                    }
515                    ExtraAgendaTransaction::Undelegate(tx) => {
516                        // Update reserved reserved_state by applying undelegation
517                        self.reserved_state.apply_undelegate(tx).map_err(|e| {
518                            Error::InvalidArgument(format!("invalid undelegation: {e}"))
519                        })?;
520                        // Check if extra-agenda transactions are in chronological order
521                        if tx.data.timestamp < *last_extra_agenda_timestamp {
522                            return Err(Error::InvalidArgument(
523                                format!("invalid extra-agenda transaction timestamp: expected larger than or equal to the last transaction timestamp {}, got {}", last_extra_agenda_timestamp, tx.data.timestamp)
524                            ));
525                        }
526                        self.phase = Phase::ExtraAgendaTransaction {
527                            last_extra_agenda_timestamp: tx.data.timestamp,
528                        };
529                    }
530                    ExtraAgendaTransaction::Report(_tx) => unimplemented!(),
531                }
532            }
533            (Commit::ChatLog(_chat_log), _) => unimplemented!(),
534            (commit, phase) => {
535                return Err(Error::PhaseMismatch(
536                    format!("{commit:?}"),
537                    format!("{phase:?}"),
538                ));
539            }
540        }
541        self.commits_for_next_block.push(commit.clone());
542        self.total_commits.push(commit.clone());
543        Ok(())
544    }
545}
546
547#[cfg(test)]
548mod test {
549    use super::*;
550    use crate::merkle_tree::OneshotMerkleTree;
551    use serde_json::json;
552
553    fn generate_validator_keypair(size: u8) -> Vec<(PublicKey, PrivateKey)> {
554        let mut validator_keypair: Vec<(PublicKey, PrivateKey)> = vec![];
555        for i in 0..size {
556            validator_keypair.push(generate_keypair([i]))
557        }
558        validator_keypair
559    }
560
561    fn generate_block_header(
562        validator_keypair: &[(PublicKey, PrivateKey)],
563        author_index: usize,
564        finalization_proof: FinalizationProof,
565        previous_hash_value: Hash256,
566        block_height: BlockHeight,
567        time: Timestamp,
568        commit_merkle_root_value: Hash256,
569    ) -> BlockHeader {
570        let validator_set: Vec<(PublicKey, u64)> = validator_keypair
571            .iter()
572            .map(|(public_key, _)| (public_key.clone(), 1))
573            .collect();
574        BlockHeader {
575            author: validator_set[author_index].0.clone(),
576            prev_block_finalization_proof: finalization_proof,
577            previous_hash: previous_hash_value,
578            height: block_height,
579            timestamp: time,
580            commit_merkle_root: commit_merkle_root_value,
581            repository_merkle_root: Hash256::zero(),
582            validator_set: validator_set.to_vec(),
583            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
584        }
585    }
586
587    fn get_members(validator_set: &[(PublicKey, VotingPower)]) -> Vec<Member> {
588        let mut members = vec![];
589        for (i, (public_key, voting_power)) in validator_set.iter().enumerate() {
590            members.push(Member {
591                public_key: public_key.clone(),
592                name: format!("member{i}").to_string(),
593                governance_voting_power: *voting_power,
594                consensus_voting_power: *voting_power,
595                governance_delegatee: None,
596                consensus_delegatee: None,
597                expelled: false,
598            });
599        }
600        members
601    }
602
603    fn generate_reserved_state(
604        validator_keypair: &[(PublicKey, PrivateKey)],
605        author_index: usize,
606        time: Timestamp,
607    ) -> ReservedState {
608        let genesis_header: BlockHeader = BlockHeader {
609            author: validator_keypair[author_index].0.clone(),
610            prev_block_finalization_proof: FinalizationProof::genesis(),
611            previous_hash: Hash256::zero(),
612            height: 0,
613            timestamp: time,
614            commit_merkle_root: OneshotMerkleTree::create(vec![]).root(),
615            repository_merkle_root: Hash256::zero(),
616            validator_set: validator_keypair
617                .iter()
618                .map(|(public_key, _)| (public_key.clone(), 1))
619                .collect(),
620            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
621        };
622        let members = get_members(&genesis_header.validator_set);
623        let mut consensus_leader_order: Vec<MemberName> =
624            members.iter().map(|member| member.name.clone()).collect();
625        consensus_leader_order.sort();
626        ReservedState {
627            genesis_info: GenesisInfo {
628                header: genesis_header.clone(),
629                genesis_proof: generate_unanimous_finalization_proof(
630                    validator_keypair,
631                    &genesis_header,
632                    0,
633                ),
634                chain_name: "PDAO Chain".to_string(),
635            },
636            members, // TODO: fix to not use genesis header
637            consensus_leader_order,
638            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
639        }
640    }
641
642    fn generate_empty_transaction_commit(time: Timestamp) -> Commit {
643        Commit::Transaction(Transaction {
644            author: "doesn't matter".to_owned(),
645            timestamp: time,
646            head: "Test empty commit".to_string(),
647            body: "This is important!".to_string(),
648            diff: Diff::None,
649        })
650    }
651
652    fn generate_non_reserved_diff_transaction_commit(time: Timestamp) -> Commit {
653        Commit::Transaction(Transaction {
654            author: "doesn't matter".to_owned(),
655            timestamp: time,
656            head: "Test non-reserved-diff commit".to_string(),
657            body: serde_spb::to_string(&json!({
658                "type": "transfer-ft",
659                "asset": "ETH",
660                "amount": "0.1",
661                "recipient": "<key:some-addr-in-ethereum>",
662            }))
663            .unwrap(),
664            diff: Diff::NonReserved(Hash256::hash("The actual content of the diff".as_bytes())),
665        })
666    }
667
668    fn generate_reserved_diff_transaction_commit(
669        validator_keypair: &mut Vec<(PublicKey, PrivateKey)>,
670        reserved_state: &mut ReservedState,
671        seed: u8,
672        time: Timestamp,
673    ) -> Commit {
674        // Update reserved reserved_state
675        validator_keypair.push(generate_keypair([seed]));
676        let new_member_name = format!("member{}", validator_keypair.len() - 1);
677        reserved_state.members.push(Member {
678            public_key: validator_keypair.last().unwrap().0.clone(),
679            name: new_member_name.clone(),
680            governance_voting_power: 1,
681            consensus_voting_power: 1,
682            governance_delegatee: None,
683            consensus_delegatee: None,
684            expelled: false,
685        });
686        reserved_state.consensus_leader_order.push(new_member_name);
687        reserved_state.consensus_leader_order.sort();
688        Commit::Transaction(Transaction {
689            author: "doesn't matter".to_owned(),
690            timestamp: time,
691            head: "Test reserved-diff commit".to_string(),
692            body: String::new(),
693            diff: Diff::Reserved(Box::new(reserved_state.clone())),
694        })
695    }
696
697    fn genearte_general_diff_transaction_commit(
698        validator_keypair: &mut Vec<(PublicKey, PrivateKey)>,
699        reserved_state: &mut ReservedState,
700        seed: u8,
701        time: Timestamp,
702    ) -> Commit {
703        // Update reserved reserved_state
704        validator_keypair.push(generate_keypair([seed]));
705        let new_member_name = format!("member{}", validator_keypair.len() - 1);
706        reserved_state.members.push(Member {
707            public_key: validator_keypair.last().unwrap().0.clone(),
708            name: new_member_name.clone(),
709            governance_voting_power: 1,
710            consensus_voting_power: 1,
711            governance_delegatee: None,
712            consensus_delegatee: None,
713            expelled: false,
714        });
715        reserved_state.consensus_leader_order.push(new_member_name);
716        reserved_state.consensus_leader_order.sort();
717        Commit::Transaction(Transaction {
718            author: "doesn't matter".to_owned(),
719            timestamp: time,
720            head: "Test non-reserved-diff commit".to_string(),
721            body: serde_spb::to_string(&json!({
722                "type": "transfer-ft",
723                "asset": "ETH",
724                "amount": "0.1",
725                "recipient": "<key:some-addr-in-ethereum>",
726            }))
727            .unwrap(),
728            diff: Diff::General(
729                Box::new(reserved_state.clone()),
730                Hash256::hash("The actual content of the diff".as_bytes()),
731            ),
732        })
733    }
734
735    fn generate_agenda_commit(agenda: &Agenda) -> Commit {
736        Commit::Agenda(agenda.clone())
737    }
738
739    fn generate_agenda_proof_commit(
740        validator_keypair: &[(PublicKey, PrivateKey)],
741        agenda: &Agenda,
742        agenda_hash_value: Hash256,
743    ) -> Commit {
744        let mut agenda_proof: Vec<TypedSignature<Agenda>> = vec![];
745        for (_, private_key) in validator_keypair {
746            agenda_proof.push(TypedSignature::sign(agenda, private_key).unwrap())
747        }
748        Commit::AgendaProof(AgendaProof {
749            agenda_hash: agenda_hash_value,
750            proof: agenda_proof,
751            height: agenda.height,
752            timestamp: 0,
753        })
754    }
755
756    fn generate_delegation_transaction_commit(
757        data: &DelegationTransactionData,
758        proof: TypedSignature<DelegationTransactionData>,
759    ) -> Commit {
760        Commit::ExtraAgendaTransaction(ExtraAgendaTransaction::Delegate(TxDelegate {
761            data: data.clone(),
762            proof,
763        }))
764    }
765
766    fn generate_undelegation_transaction_commit(
767        data: &UndelegationTransactionData,
768        proof: TypedSignature<UndelegationTransactionData>,
769    ) -> Commit {
770        Commit::ExtraAgendaTransaction(ExtraAgendaTransaction::Undelegate(TxUndelegate {
771            data: data.clone(),
772            proof,
773        }))
774    }
775
776    fn generate_unanimous_finalization_proof(
777        validator_keypair: &[(PublicKey, PrivateKey)],
778        header: &BlockHeader,
779        round: ConsensusRound,
780    ) -> FinalizationProof {
781        let mut signatures: Vec<TypedSignature<FinalizationSignTarget>> = vec![];
782        for (_, private_key) in validator_keypair {
783            signatures.push(
784                TypedSignature::sign(
785                    &FinalizationSignTarget {
786                        round,
787                        block_hash: header.to_hash256(),
788                    },
789                    private_key,
790                )
791                .unwrap(),
792            );
793        }
794        FinalizationProof { round, signatures }
795    }
796
797    fn generate_block_commit(
798        validator_keypair: &[(PublicKey, PrivateKey)],
799        author_index: usize,
800        previous_header: BlockHeader,
801        time: Timestamp,
802        commit_merkle_root_value: Hash256,
803        repository_merkle_root_value: Hash256,
804    ) -> Commit {
805        Commit::Block(BlockHeader {
806            author: validator_keypair[author_index].0.clone(),
807            prev_block_finalization_proof: generate_unanimous_finalization_proof(
808                validator_keypair,
809                &previous_header,
810                0,
811            ),
812            previous_hash: Commit::Block(previous_header.clone()).to_hash256(),
813            height: previous_header.height + 1,
814            timestamp: time,
815            commit_merkle_root: commit_merkle_root_value,
816            repository_merkle_root: repository_merkle_root_value,
817            validator_set: validator_keypair
818                .iter()
819                .map(|(public_key, _)| (public_key.clone(), 1))
820                .collect(),
821            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
822        })
823    }
824
825    fn setup_test(
826        validator_set_size: u8,
827    ) -> (
828        Vec<(PublicKey, PrivateKey)>,
829        ReservedState,
830        CommitSequenceVerifier,
831    ) {
832        let validator_keypair: Vec<(PublicKey, PrivateKey)> =
833            generate_validator_keypair(validator_set_size);
834        let start_header: BlockHeader = generate_block_header(
835            &validator_keypair,
836            0,
837            FinalizationProof::genesis(),
838            Hash256::zero(),
839            0,
840            0,
841            OneshotMerkleTree::create(vec![]).root(),
842        );
843        let reserved_state: ReservedState = generate_reserved_state(&validator_keypair, 0, 0);
844        let csv: CommitSequenceVerifier =
845            CommitSequenceVerifier::new(start_header, reserved_state.clone()).unwrap();
846        (validator_keypair, reserved_state, csv)
847    }
848
849    fn calculate_agenda_transactions_hash(phase: Phase) -> Hash256 {
850        if let Phase::Transaction {
851            ref last_transaction,
852            ref preceding_transactions,
853        } = phase
854        {
855            Agenda::calculate_transactions_hash(
856                &[
857                    preceding_transactions.clone(),
858                    vec![last_transaction.clone()],
859                ]
860                .concat(),
861            )
862        } else {
863            Agenda::calculate_transactions_hash(&[])
864        }
865    }
866
867    #[test]
868    /// Test the case where the commit sequence is correct.
869    fn correct_commit_sequence1() {
870        let (mut validator_keypair, mut reserved_state, mut csv) = setup_test(4);
871        // Apply empty transaction commit
872        csv.apply_commit(&generate_empty_transaction_commit(1))
873            .unwrap();
874        // Apply non-reserved-diff commit
875        csv.apply_commit(&generate_non_reserved_diff_transaction_commit(2))
876            .unwrap();
877        // Apply reserved-diff commit
878        csv.apply_commit(&generate_reserved_diff_transaction_commit(
879            &mut validator_keypair,
880            &mut reserved_state,
881            4,
882            3,
883        ))
884        .unwrap();
885        // Apply general-diff commit
886        csv.apply_commit(&genearte_general_diff_transaction_commit(
887            &mut validator_keypair,
888            &mut reserved_state,
889            5,
890            4,
891        ))
892        .unwrap();
893        // Apply agenda commit
894        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
895        let agenda: Agenda = Agenda {
896            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
897            timestamp: 5,
898            transactions_hash: agenda_transactions_hash,
899            height: csv.header.height + 1,
900            previous_block_hash: csv.header.to_hash256(),
901        };
902        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
903        // Apply agenda-proof commit
904        csv.apply_commit(&generate_agenda_proof_commit(
905            &validator_keypair,
906            &agenda,
907            agenda.to_hash256(),
908        ))
909        .unwrap();
910    }
911
912    #[test]
913    /// Test the case where the commit sequence is correct but there are no transaction commits.
914    fn correct_commit_sequence2() {
915        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
916        // Apply agenda commit
917        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
918        let agenda: Agenda = Agenda {
919            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
920            timestamp: 1,
921            transactions_hash: agenda_transactions_hash,
922            height: csv.header.height + 1,
923            previous_block_hash: csv.header.to_hash256(),
924        };
925        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
926        // Apply agenda-proof commit
927        csv.apply_commit(&generate_agenda_proof_commit(
928            &validator_keypair,
929            &agenda,
930            agenda.to_hash256(),
931        ))
932        .unwrap();
933    }
934
935    #[test]
936    /// Test the case where the block commit is invalid because the block height is invalid.
937    fn invalid_block_commit_with_invalid_height() {
938        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
939        // Apply agenda commit
940        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
941        let agenda: Agenda = Agenda {
942            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
943            timestamp: 1,
944            transactions_hash: agenda_transactions_hash,
945            height: csv.header.height + 1,
946            previous_block_hash: csv.header.to_hash256(),
947        };
948        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
949        // Apply agenda-proof commit
950        csv.apply_commit(&generate_agenda_proof_commit(
951            &validator_keypair,
952            &agenda,
953            agenda.to_hash256(),
954        ))
955        .unwrap();
956        // Apply block commit with invalid height
957        csv.apply_commit(&Commit::Block(BlockHeader {
958            author: validator_keypair[0].0.clone(),
959            prev_block_finalization_proof: generate_unanimous_finalization_proof(
960                &validator_keypair,
961                &csv.header,
962                0,
963            ),
964            previous_hash: Commit::Block(csv.header.clone()).to_hash256(),
965            height: csv.header.height + 2,
966            timestamp: 2,
967            commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
968                &csv.commits_for_next_block,
969            ),
970            repository_merkle_root: Hash256::zero(),
971            validator_set: validator_keypair
972                .iter()
973                .map(|(public_key, _)| (public_key.clone(), 1))
974                .collect(),
975            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
976        }))
977        .unwrap_err();
978    }
979
980    #[test]
981    /// Test the case where the block commit is invalid because the previous hash is invalid.
982    fn invalid_block_commit_with_invalid_previous_hash() {
983        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
984        // Apply agenda commit
985        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
986        let agenda: Agenda = Agenda {
987            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
988            timestamp: 1,
989            transactions_hash: agenda_transactions_hash,
990            height: csv.header.height + 1,
991            previous_block_hash: csv.header.to_hash256(),
992        };
993        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
994        // Apply agenda-proof commit
995        csv.apply_commit(&generate_agenda_proof_commit(
996            &validator_keypair,
997            &agenda,
998            agenda.to_hash256(),
999        ))
1000        .unwrap();
1001        // Apply block commit with invalid previous hash
1002        csv.apply_commit(&Commit::Block(BlockHeader {
1003            author: validator_keypair[0].0.clone(),
1004            prev_block_finalization_proof: generate_unanimous_finalization_proof(
1005                &validator_keypair,
1006                &csv.header,
1007                0,
1008            ),
1009            previous_hash: Hash256::zero(),
1010            height: csv.header.height + 1,
1011            timestamp: 2,
1012            commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
1013                &csv.commits_for_next_block,
1014            ),
1015            repository_merkle_root: Hash256::zero(),
1016            validator_set: validator_keypair
1017                .iter()
1018                .map(|(public_key, _)| (public_key.clone(), 1))
1019                .collect(),
1020            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1021        }))
1022        .unwrap_err();
1023    }
1024
1025    #[test]
1026    /// Test the case where the block commit is invalid because the author is invalid.
1027    fn invalid_block_commit_with_invalid_author() {
1028        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1029        // Apply agenda commit
1030        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1031        let agenda: Agenda = Agenda {
1032            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1033            timestamp: 1,
1034            transactions_hash: agenda_transactions_hash,
1035            height: csv.header.height + 1,
1036            previous_block_hash: csv.header.to_hash256(),
1037        };
1038        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1039        // Apply agenda-proof commit
1040        csv.apply_commit(&generate_agenda_proof_commit(
1041            &validator_keypair,
1042            &agenda,
1043            agenda.to_hash256(),
1044        ))
1045        .unwrap();
1046        // Apply block commit with invalid author
1047        csv.apply_commit(&Commit::Block(BlockHeader {
1048            author: generate_keypair([42]).0,
1049            prev_block_finalization_proof: generate_unanimous_finalization_proof(
1050                &validator_keypair,
1051                &csv.header,
1052                0,
1053            ),
1054            previous_hash: Commit::Block(csv.header.clone()).to_hash256(),
1055            height: csv.header.height + 1,
1056            timestamp: 2,
1057            commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
1058                &csv.commits_for_next_block,
1059            ),
1060            repository_merkle_root: Hash256::zero(),
1061            validator_set: validator_keypair
1062                .iter()
1063                .map(|(public_key, _)| (public_key.clone(), 1))
1064                .collect(),
1065            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1066        }))
1067        .unwrap_err();
1068    }
1069
1070    #[test]
1071    /// Test the case where the block commit is invalid because the timestamp is invalid.
1072    fn invalid_block_commit_with_invalid_timestamp() {
1073        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1074        // Apply agenda commit
1075        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1076        let agenda: Agenda = Agenda {
1077            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1078            timestamp: 1,
1079            transactions_hash: agenda_transactions_hash,
1080            height: csv.header.height + 1,
1081            previous_block_hash: csv.header.to_hash256(),
1082        };
1083        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1084        // Apply agenda-proof commit
1085        csv.apply_commit(&generate_agenda_proof_commit(
1086            &validator_keypair,
1087            &agenda,
1088            agenda.to_hash256(),
1089        ))
1090        .unwrap();
1091        // Apply block commit with invalid timestamp
1092        csv.apply_commit(&Commit::Block(BlockHeader {
1093            author: validator_keypair[0].0.clone(),
1094            prev_block_finalization_proof: generate_unanimous_finalization_proof(
1095                &validator_keypair,
1096                &csv.header,
1097                0,
1098            ),
1099            previous_hash: Commit::Block(csv.header.clone()).to_hash256(),
1100            height: csv.header.height + 1,
1101            timestamp: -1,
1102            commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
1103                &csv.commits_for_next_block,
1104            ),
1105            repository_merkle_root: Hash256::zero(),
1106            validator_set: validator_keypair
1107                .iter()
1108                .map(|(public_key, _)| (public_key.clone(), 1))
1109                .collect(),
1110            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1111        }))
1112        .unwrap_err();
1113    }
1114
1115    #[test]
1116    /// Test the case where the block commit is invalid because the finalization proof is invalid for invalid signature.
1117    fn invalid_block_commit_with_invalid_finalization_proof_for_invalid_signature() {
1118        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1119        // Apply agenda commit
1120        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1121        let agenda: Agenda = Agenda {
1122            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1123            timestamp: 1,
1124            transactions_hash: agenda_transactions_hash,
1125            height: csv.header.height + 1,
1126            previous_block_hash: csv.header.to_hash256(),
1127        };
1128        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1129        // Apply agenda-proof commit
1130        csv.apply_commit(&generate_agenda_proof_commit(
1131            &validator_keypair,
1132            &agenda,
1133            agenda.to_hash256(),
1134        ))
1135        .unwrap();
1136        // Apply block commit with invalid finalization proof for invalid signature
1137        csv.apply_commit(&Commit::Block(generate_block_header(
1138            &validator_keypair,
1139            0,
1140            generate_unanimous_finalization_proof(
1141                &validator_keypair,
1142                &generate_block_header(
1143                    &validator_keypair[1..],
1144                    0,
1145                    FinalizationProof::genesis(),
1146                    csv.header.to_hash256(),
1147                    csv.header.height + 1,
1148                    2,
1149                    OneshotMerkleTree::create(vec![]).root(),
1150                ),
1151                0,
1152            ),
1153            csv.header.to_hash256(),
1154            csv.header.height + 1,
1155            2,
1156            OneshotMerkleTree::create(vec![]).root(),
1157        )))
1158        .unwrap_err();
1159    }
1160
1161    #[test]
1162    /// Test the case where the block commit is invalid because the finalization proof is invalid for low voting power.
1163    fn invalid_block_commit_with_invalid_finalization_proof_for_low_voting_power() {
1164        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1165        // Apply agenda commit
1166        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1167        let agenda: Agenda = Agenda {
1168            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1169            timestamp: 1,
1170            transactions_hash: agenda_transactions_hash,
1171            height: csv.header.height + 1,
1172            previous_block_hash: csv.header.to_hash256(),
1173        };
1174        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1175        // Apply agenda-proof commit
1176        csv.apply_commit(&generate_agenda_proof_commit(
1177            &validator_keypair,
1178            &agenda,
1179            agenda.to_hash256(),
1180        ))
1181        .unwrap();
1182        // Apply block commit with invalid finalization proof for low voting power
1183        csv.apply_commit(&Commit::Block(BlockHeader {
1184            author: validator_keypair[0].0.clone(),
1185            prev_block_finalization_proof: {
1186                let mut proof =
1187                    generate_unanimous_finalization_proof(&validator_keypair, &csv.header, 0);
1188                proof.signatures = vec![proof.signatures[0].clone()];
1189                proof
1190            },
1191            previous_hash: Commit::Block(csv.header.clone()).to_hash256(),
1192            height: csv.header.height + 1,
1193            timestamp: 2,
1194            commit_merkle_root: BlockHeader::calculate_commit_merkle_root(
1195                &csv.commits_for_next_block,
1196            ),
1197            repository_merkle_root: Hash256::zero(),
1198            validator_set: validator_keypair
1199                .iter()
1200                .map(|(public_key, _)| (public_key.clone(), 1))
1201                .collect(),
1202            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1203        }))
1204        .unwrap_err();
1205    }
1206
1207    #[test]
1208    /// Test the case where the block commit is invalid because the commit merkle root is invalid.
1209    fn invalid_block_commit_with_invalid_commit_merkle_root() {
1210        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1211        // Apply agenda commit
1212        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1213        let agenda: Agenda = Agenda {
1214            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1215            timestamp: 1,
1216            transactions_hash: agenda_transactions_hash,
1217            height: csv.header.height + 1,
1218            previous_block_hash: csv.header.to_hash256(),
1219        };
1220        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1221        // Apply agenda-proof commit
1222        csv.apply_commit(&generate_agenda_proof_commit(
1223            &validator_keypair,
1224            &agenda,
1225            agenda.to_hash256(),
1226        ))
1227        .unwrap();
1228        // Apply block commit with invalid commit merkle root
1229        csv.apply_commit(&Commit::Block(BlockHeader {
1230            author: validator_keypair[0].0.clone(),
1231            prev_block_finalization_proof: generate_unanimous_finalization_proof(
1232                &validator_keypair,
1233                &csv.header,
1234                0,
1235            ),
1236            previous_hash: Commit::Block(csv.header.clone()).to_hash256(),
1237            height: csv.header.height + 1,
1238            timestamp: 2,
1239            commit_merkle_root: OneshotMerkleTree::create(vec![]).root(),
1240            repository_merkle_root: Hash256::zero(),
1241            validator_set: validator_keypair
1242                .iter()
1243                .map(|(public_key, _)| (public_key.clone(), 1))
1244                .collect(),
1245            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1246        }))
1247        .unwrap_err();
1248    }
1249
1250    #[test]
1251    /// Test the case where the block commit is invalid because block commit already exists.
1252    fn phase_mismatch_for_block_commit1() {
1253        let (validator_keypair, _, mut csv) = setup_test(4);
1254        // Apply block commit at block phase
1255        csv.apply_commit(&generate_block_commit(
1256            &validator_keypair,
1257            0,
1258            csv.header.clone(),
1259            1,
1260            OneshotMerkleTree::create(vec![]).root(),
1261            Hash256::zero(),
1262        ))
1263        .unwrap_err();
1264    }
1265
1266    #[test]
1267    /// Test the case where the block commit is invalid because it is transaction phase.
1268    fn phase_mismatch_for_block_commit2() {
1269        let (validator_keypair, _, mut csv) = setup_test(4);
1270        // Apply empty transaction commit
1271        csv.apply_commit(&generate_empty_transaction_commit(1))
1272            .unwrap();
1273        // Apply block commit at transaction phase
1274        csv.apply_commit(&generate_block_commit(
1275            &validator_keypair,
1276            0,
1277            csv.header.clone(),
1278            2,
1279            OneshotMerkleTree::create(vec![]).root(),
1280            Hash256::zero(),
1281        ))
1282        .unwrap_err();
1283    }
1284
1285    #[test]
1286    /// Test the case where the block commit is invalid because it is agenda phase.
1287    fn phase_mismatch_for_block_commit3() {
1288        let (mut validator_keypair, mut reserved_state, mut csv) = setup_test(4);
1289        // Apply empty transaction commit
1290        csv.apply_commit(&generate_empty_transaction_commit(1))
1291            .unwrap();
1292        // Apply non-reserved-diff commit
1293        csv.apply_commit(&generate_non_reserved_diff_transaction_commit(2))
1294            .unwrap();
1295        // Apply reserved-diff commit
1296        csv.apply_commit(&generate_reserved_diff_transaction_commit(
1297            &mut validator_keypair,
1298            &mut reserved_state,
1299            4,
1300            3,
1301        ))
1302        .unwrap();
1303        // Apply general-diff commit
1304        csv.apply_commit(&genearte_general_diff_transaction_commit(
1305            &mut validator_keypair,
1306            &mut reserved_state,
1307            5,
1308            4,
1309        ))
1310        .unwrap();
1311        // Apply agenda commit
1312        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1313        let agenda: Agenda = Agenda {
1314            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1315            timestamp: 5,
1316            transactions_hash: agenda_transactions_hash,
1317            height: csv.header.height + 1,
1318            previous_block_hash: csv.header.to_hash256(),
1319        };
1320        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1321        // Apply block commit at agenda phase
1322        csv.apply_commit(&generate_block_commit(
1323            &validator_keypair,
1324            0,
1325            csv.header.clone(),
1326            5,
1327            OneshotMerkleTree::create(vec![]).root(),
1328            Hash256::zero(),
1329        ))
1330        .unwrap_err();
1331    }
1332
1333    #[test]
1334    /// Test the case where the transaction commit is invalid because it is agenda phase.
1335    fn phase_mismatch_for_transaction_commit1() {
1336        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1337        // Apply agenda commit
1338        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1339        let agenda: Agenda = Agenda {
1340            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1341            timestamp: 1,
1342            transactions_hash: agenda_transactions_hash,
1343            height: csv.header.height + 1,
1344            previous_block_hash: csv.header.to_hash256(),
1345        };
1346        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1347        // Apply transaction commit at agenda phase
1348        csv.apply_commit(&generate_empty_transaction_commit(2))
1349            .unwrap_err();
1350    }
1351
1352    #[test]
1353    /// Test the case where the transaction commit is invalid because it is agenda proof phase.
1354    fn phase_mismatch_for_transaction_commit2() {
1355        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1356        // Apply agenda commit
1357        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1358        let agenda: Agenda = Agenda {
1359            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1360            timestamp: 1,
1361            transactions_hash: agenda_transactions_hash,
1362            height: csv.header.height + 1,
1363            previous_block_hash: csv.header.to_hash256(),
1364        };
1365        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1366        // Apply agenda-proof commit
1367        csv.apply_commit(&generate_agenda_proof_commit(
1368            &validator_keypair,
1369            &agenda,
1370            agenda.to_hash256(),
1371        ))
1372        .unwrap();
1373        // Apply transaction commit at agenda proof phase
1374        csv.apply_commit(&generate_empty_transaction_commit(2))
1375            .unwrap_err();
1376    }
1377
1378    #[ignore]
1379    #[test]
1380    /// Test the case where the transaction commit is invalid because it is extra-agenda transaction phase.
1381    /// This test case is ignored because the extra-agenda transaction is not implemented yet.
1382    fn phase_mismatch_for_transaction_commit3() {
1383        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1384        // Apply agenda commit
1385        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1386        let agenda: Agenda = Agenda {
1387            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1388            timestamp: 1,
1389            transactions_hash: agenda_transactions_hash,
1390            height: csv.header.height + 1,
1391            previous_block_hash: csv.header.to_hash256(),
1392        };
1393        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1394        // Apply agenda-proof commit
1395        csv.apply_commit(&generate_agenda_proof_commit(
1396            &validator_keypair,
1397            &agenda,
1398            agenda.to_hash256(),
1399        ))
1400        .unwrap();
1401        // Apply extra-agenda transaction commit
1402        // delegator: member-0, delegatee: member-1
1403        let delegator = reserved_state.members[0].clone();
1404        let delegator_private_key = validator_keypair[0].1.clone();
1405        let delegatee = reserved_state.members[1].clone();
1406        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
1407            delegator: delegator.name,
1408            delegatee: delegatee.name,
1409            governance: true,
1410            block_height: csv.header.height + 1,
1411            timestamp: 2,
1412            chain_name: reserved_state.genesis_info.chain_name,
1413        };
1414        let proof =
1415            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
1416        csv.apply_commit(&generate_delegation_transaction_commit(
1417            &delegation_transaction_data,
1418            proof,
1419        ))
1420        .unwrap();
1421        // Apply transaction commit at extra-agenda transaction phase
1422        csv.apply_commit(&generate_empty_transaction_commit(3))
1423            .unwrap_err();
1424    }
1425
1426    #[test]
1427    /// Test the case where the agenda commit is invalid because the agenda height is invalid.
1428    /// The agenda height should be the next height of the last header height.
1429    fn invalid_agenda_commit_with_invalid_height() {
1430        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1431        // Apply agenda commit with invalid height
1432        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1433        let agenda: Agenda = Agenda {
1434            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1435            timestamp: 1,
1436            transactions_hash: agenda_transactions_hash,
1437            height: 0,
1438            previous_block_hash: csv.header.to_hash256(),
1439        };
1440        csv.apply_commit(&generate_agenda_commit(&agenda))
1441            .unwrap_err();
1442    }
1443
1444    #[test]
1445    /// Test the case where the agenda commit is invalid because the agenda hash is invalid.
1446    fn invalid_agenda_commit_with_invalid_agenda_hash1() {
1447        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1448        // Apply agenda commit with invalid agenda hash
1449        let agenda_transactions_hash = if let Commit::Transaction(transaction) =
1450            generate_empty_transaction_commit(1)
1451        {
1452            Agenda::calculate_transactions_hash(&[transaction])
1453        } else {
1454            panic!("generate_empty_transaction_commit should return Commit::Transaction type value")
1455        };
1456        let agenda: Agenda = Agenda {
1457            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1458            timestamp: 2,
1459            transactions_hash: agenda_transactions_hash,
1460            height: csv.header.height + 1,
1461            previous_block_hash: csv.header.to_hash256(),
1462        };
1463        csv.apply_commit(&generate_agenda_commit(&agenda))
1464            .unwrap_err();
1465    }
1466
1467    #[test]
1468    /// Test the case where the agenda commit is invalid because the agenda hash is invalid.
1469    fn invalid_agenda_commit_with_invalid_agenda_hash2() {
1470        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1471        // Apply empty transaction commit
1472        csv.apply_commit(&generate_empty_transaction_commit(1))
1473            .unwrap();
1474        // Apply agenda commit with invalid agenda hash
1475        let agenda_transactions_hash = Agenda::calculate_transactions_hash(&[]);
1476        let agenda: Agenda = Agenda {
1477            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1478            timestamp: 2,
1479            transactions_hash: agenda_transactions_hash,
1480            height: csv.header.height + 1,
1481            previous_block_hash: csv.header.to_hash256(),
1482        };
1483        csv.apply_commit(&generate_agenda_commit(&agenda))
1484            .unwrap_err();
1485    }
1486
1487    #[test]
1488    /// Test the case where the agenda commit is invalid because the timestamp is invalid.
1489    fn invalid_agenda_commit_with_invalid_timestamp() {
1490        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1491        // Apply empty transaction commit
1492        csv.apply_commit(&generate_empty_transaction_commit(1))
1493            .unwrap();
1494        // Apply agenda commit with invalid timestamp
1495        let agenda: Agenda = Agenda {
1496            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1497            timestamp: 0,
1498            transactions_hash: Agenda::calculate_transactions_hash(&[]),
1499            height: csv.header.height + 1,
1500            previous_block_hash: csv.header.to_hash256(),
1501        };
1502        csv.apply_commit(&generate_agenda_commit(&agenda))
1503            .unwrap_err();
1504    }
1505
1506    #[test]
1507    /// Test the case where the agenda commit is invalid because agenda commit already exists.
1508    fn phase_mismatch_for_agenda_commit1() {
1509        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1510        // Apply agenda commit
1511        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1512        let agenda: Agenda = Agenda {
1513            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1514            timestamp: 1,
1515            transactions_hash: agenda_transactions_hash,
1516            height: csv.header.height + 1,
1517            previous_block_hash: csv.header.to_hash256(),
1518        };
1519        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1520        // Apply agenda commit again
1521        csv.apply_commit(&generate_agenda_commit(&agenda))
1522            .unwrap_err();
1523    }
1524
1525    #[test]
1526    /// Test the case where the agenda commit is invalid because it is in agenda proof phase.
1527    fn phase_mismatch_for_agenda_commit2() {
1528        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1529        // Apply agenda commit
1530        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1531        let agenda: Agenda = Agenda {
1532            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1533            timestamp: 1,
1534            transactions_hash: agenda_transactions_hash,
1535            height: csv.header.height + 1,
1536            previous_block_hash: csv.header.to_hash256(),
1537        };
1538        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1539        // Apply agenda-proof commit
1540        csv.apply_commit(&generate_agenda_proof_commit(
1541            &validator_keypair,
1542            &agenda,
1543            agenda.to_hash256(),
1544        ))
1545        .unwrap();
1546        // Apply agenda commit at agenda proof phase
1547        csv.apply_commit(&generate_agenda_commit(&agenda))
1548            .unwrap_err();
1549    }
1550
1551    #[ignore]
1552    #[test]
1553    // Test the case where the agenda commit is invalid because it is extra-agenda transaction phase.
1554    fn phase_mismatch_for_agenda_commit3() {
1555        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1556        // Apply agenda commit
1557        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1558        let agenda: Agenda = Agenda {
1559            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1560            timestamp: 1,
1561            transactions_hash: agenda_transactions_hash,
1562            height: csv.header.height + 1,
1563            previous_block_hash: csv.header.to_hash256(),
1564        };
1565        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1566        // Apply agenda-proof commit
1567        csv.apply_commit(&generate_agenda_proof_commit(
1568            &validator_keypair,
1569            &agenda,
1570            agenda.to_hash256(),
1571        ))
1572        .unwrap();
1573        // Apply extra-agenda transaction commit
1574        // delegator: member-0, delegatee: member-1
1575        let delegator = reserved_state.members[0].clone();
1576        let delegator_private_key = validator_keypair[0].1.clone();
1577        let delegatee = reserved_state.members[1].clone();
1578        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
1579            delegator: delegator.name,
1580            delegatee: delegatee.name,
1581            governance: true,
1582            block_height: csv.header.height + 1,
1583            timestamp: 2,
1584            chain_name: reserved_state.genesis_info.chain_name.clone(),
1585        };
1586        let proof =
1587            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
1588        csv.apply_commit(&generate_delegation_transaction_commit(
1589            &delegation_transaction_data,
1590            proof,
1591        ))
1592        .unwrap();
1593        // Apply agenda commit at extra agenda-transaction phase
1594        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1595        let agenda: Agenda = Agenda {
1596            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1597            timestamp: 3,
1598            transactions_hash: agenda_transactions_hash,
1599            height: csv.header.height + 1,
1600            previous_block_hash: csv.header.to_hash256(),
1601        };
1602        csv.apply_commit(&generate_agenda_commit(&agenda))
1603            .unwrap_err();
1604    }
1605
1606    #[test]
1607    /// Test the case where the agenda proof commit is invalid because the agenda proof height is invalid.
1608    /// The agenda proof height should be the next height of the last header height.
1609    fn invalid_agenda_proof_commit_with_invalid_height() {
1610        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1611        // Apply agenda commit
1612        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1613        let agenda: Agenda = Agenda {
1614            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1615            timestamp: 1,
1616            transactions_hash: agenda_transactions_hash,
1617            height: csv.header.height + 1,
1618            previous_block_hash: csv.header.to_hash256(),
1619        };
1620        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1621        // Apply agenda-proof commit with invalid height
1622        csv.apply_commit(&generate_agenda_proof_commit(
1623            &validator_keypair,
1624            &Agenda {
1625                author: reserved_state.query_name(&validator_keypair[1].0).unwrap(),
1626                timestamp: 1,
1627                transactions_hash: agenda_transactions_hash,
1628                height: 0,
1629                previous_block_hash: csv.header.to_hash256(),
1630            },
1631            agenda.to_hash256(),
1632        ))
1633        .unwrap_err();
1634    }
1635
1636    #[test]
1637    /// Test the case where the agenda proof commit is invalid because the agenda hash is invalid.
1638    fn invalid_agenda_proof_with_invalid_agenda_hash() {
1639        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1640        // Apply agenda commit
1641        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1642        let agenda: Agenda = Agenda {
1643            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1644            timestamp: 1,
1645            transactions_hash: agenda_transactions_hash,
1646            height: csv.header.height + 1,
1647            previous_block_hash: csv.header.to_hash256(),
1648        };
1649        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1650        // Apply agenda-proof commit with invalid agenda hash
1651        csv.apply_commit(&generate_agenda_proof_commit(
1652            &validator_keypair,
1653            &agenda,
1654            Hash256::zero(),
1655        ))
1656        .unwrap_err();
1657    }
1658
1659    #[test]
1660    /// Test the case where the agenda proof commit is invalid because the signature is invalid.
1661    fn invalid_agenda_proof_with_invalid_signature() {
1662        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1663        // Apply agenda commit
1664        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1665        let agenda: Agenda = Agenda {
1666            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1667            timestamp: 1,
1668            transactions_hash: agenda_transactions_hash,
1669            height: csv.header.height + 1,
1670            previous_block_hash: csv.header.to_hash256(),
1671        };
1672        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1673        // Apply agenda-proof commit with invalid signature
1674        csv.apply_commit(&generate_agenda_proof_commit(
1675            &validator_keypair,
1676            &Agenda {
1677                author: reserved_state.query_name(&validator_keypair[1].0).unwrap(),
1678                timestamp: 0,
1679                transactions_hash: Hash256::zero(),
1680                height: csv.header.height + 1,
1681                previous_block_hash: csv.header.to_hash256(),
1682            },
1683            agenda.to_hash256(),
1684        ))
1685        .unwrap_err();
1686    }
1687
1688    #[test]
1689    /// Test the case where the agenda proof commit is invalid because agenda proof already exists.
1690    fn phase_mismatch_for_agenda_proof_commit1() {
1691        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1692        // Apply agenda commit
1693        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1694        let agenda: Agenda = Agenda {
1695            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1696            timestamp: 1,
1697            transactions_hash: agenda_transactions_hash,
1698            height: csv.header.height + 1,
1699            previous_block_hash: csv.header.to_hash256(),
1700        };
1701        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1702        // Apply agenda-proof commit
1703        csv.apply_commit(&generate_agenda_proof_commit(
1704            &validator_keypair,
1705            &agenda,
1706            agenda.to_hash256(),
1707        ))
1708        .unwrap();
1709        // Apply agenda-proof commit again
1710        csv.apply_commit(&generate_agenda_proof_commit(
1711            &validator_keypair,
1712            &agenda,
1713            agenda.to_hash256(),
1714        ))
1715        .unwrap_err();
1716    }
1717
1718    #[test]
1719    /// Test the case where the agenda proof commit is invalid because it is transaction phase.
1720    fn phase_mismatch_for_agenda_proof_commit2() {
1721        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1722        // Apply empty transaction commit
1723        csv.apply_commit(&generate_empty_transaction_commit(1))
1724            .unwrap();
1725        // Apply agenda-proof commit at transaction phase
1726        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1727        let agenda: Agenda = Agenda {
1728            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1729            timestamp: 2,
1730            transactions_hash: agenda_transactions_hash,
1731            height: csv.header.height + 1,
1732            previous_block_hash: csv.header.to_hash256(),
1733        };
1734        csv.apply_commit(&generate_agenda_proof_commit(
1735            &validator_keypair,
1736            &agenda,
1737            agenda.to_hash256(),
1738        ))
1739        .unwrap_err();
1740    }
1741
1742    #[test]
1743    /// Test the case where the member count of reserved state is less than 4.
1744    fn invalid_reserved_state_with_too_few_members() {
1745        // set validator_set_size to 3
1746        let (_, reserved_state, mut csv) = setup_test(3);
1747        // Apply reserved-diff commit to verify the reserved state
1748        csv.apply_commit(&Commit::Transaction(Transaction {
1749            author: "doesn't matter".to_owned(),
1750            timestamp: 3,
1751            head: "Test reserved-diff commit".to_string(),
1752            body: String::new(),
1753            diff: Diff::Reserved(Box::new(reserved_state)),
1754        }))
1755        .unwrap_err();
1756    }
1757
1758    #[test]
1759    /// Test the case where a consensus leader is not the one of members.
1760    fn invalid_reserved_state_with_consensus_leader_not_in_members() {
1761        let (_, mut reserved_state, mut csv) = setup_test(4);
1762        // Add a member name that is not the one of members' names to consensus_leader_order
1763        reserved_state
1764            .consensus_leader_order
1765            .push("stranger".to_string());
1766        // Apply reserved-diff commit to verify the reserved state
1767        csv.apply_commit(&Commit::Transaction(Transaction {
1768            author: "doesn't matter".to_owned(),
1769            timestamp: 3,
1770            head: "Test reserved-diff commit".to_string(),
1771            body: String::new(),
1772            diff: Diff::Reserved(Box::new(reserved_state.clone())),
1773        }))
1774        .unwrap_err();
1775    }
1776
1777    #[test]
1778    /// Test the case where an expelled member is included in the consensus leader order.
1779    fn invalid_reserved_state_with_expelled_member_in_consensus_leader_order() {
1780        let (_, mut reserved_state, mut csv) = setup_test(4);
1781        // Expel the first member
1782        reserved_state.members[0].expelled = true;
1783        // Apply reserved-diff commit to verify the reserved state
1784        csv.apply_commit(&Commit::Transaction(Transaction {
1785            author: "doesn't matter".to_owned(),
1786            timestamp: 3,
1787            head: "Test reserved-diff commit".to_string(),
1788            body: String::new(),
1789            diff: Diff::Reserved(Box::new(reserved_state)),
1790        }))
1791        .unwrap_err();
1792    }
1793
1794    #[test]
1795    /// Test the case where the genesis info is changed.
1796    fn invalid_reserved_state_with_changed_genesis_info() {
1797        let (validator_keypair, mut reserved_state, mut csv) = setup_test(4);
1798        // Generate a new genesis header with different author_index
1799        let author_index = 1;
1800        let new_genesis_header: BlockHeader = BlockHeader {
1801            author: validator_keypair[author_index].0.clone(),
1802            prev_block_finalization_proof: FinalizationProof::genesis(),
1803            previous_hash: Hash256::zero(),
1804            height: 0,
1805            timestamp: 0,
1806            commit_merkle_root: OneshotMerkleTree::create(vec![]).root(),
1807            repository_merkle_root: Hash256::zero(),
1808            validator_set: validator_keypair
1809                .iter()
1810                .map(|(public_key, _)| (public_key.clone(), 1))
1811                .collect(),
1812            version: SIMPERBY_CORE_PROTOCOL_VERSION.to_string(),
1813        };
1814        // Change genesis info of the reserved state
1815        reserved_state.genesis_info = GenesisInfo {
1816            header: new_genesis_header.clone(),
1817            genesis_proof: generate_unanimous_finalization_proof(
1818                &validator_keypair,
1819                &new_genesis_header,
1820                0,
1821            ),
1822            chain_name: "PDAO Chain".to_string(),
1823        };
1824        // Apply reserved-diff commit to verify the reserved state
1825        csv.apply_commit(&Commit::Transaction(Transaction {
1826            author: "doesn't matter".to_owned(),
1827            timestamp: 3,
1828            head: "Test reserved-diff commit".to_string(),
1829            body: String::new(),
1830            diff: Diff::Reserved(Box::new(reserved_state)),
1831        }))
1832        .unwrap_err();
1833    }
1834
1835    #[test]
1836    /// Test the case where there is a duplicate member name.
1837    fn invalid_reserved_state_with_duplicate_member_name() {
1838        let (mut validator_keypair, mut reserved_state, mut csv) = setup_test(4);
1839        // Generate a new member with duplicate name
1840        validator_keypair.push(generate_keypair([4]));
1841        reserved_state.members.push(Member {
1842            public_key: validator_keypair.last().unwrap().0.clone(),
1843            name: "member0".to_string(), // duplicate name
1844            governance_voting_power: 1,
1845            consensus_voting_power: 1,
1846            governance_delegatee: None,
1847            consensus_delegatee: None,
1848            expelled: false,
1849        });
1850        // Apply reserved-diff commit to verify the reserved state
1851        csv.apply_commit(&Commit::Transaction(Transaction {
1852            author: "doesn't matter".to_owned(),
1853            timestamp: 3,
1854            head: "Test reserved-diff commit".to_string(),
1855            body: String::new(),
1856            diff: Diff::Reserved(Box::new(reserved_state.clone())),
1857        }))
1858        .unwrap_err();
1859    }
1860
1861    #[test]
1862    /// Test the case where there is a duplicate public key.
1863    fn invalid_reserved_state_with_duplicate_public_key() {
1864        let (validator_keypair, mut reserved_state, mut csv) = setup_test(4);
1865        // Generate a new member with duplicate public key
1866        reserved_state.members.push(Member {
1867            public_key: validator_keypair[0].0.clone(), // duplicate public key
1868            name: format!("member{}", validator_keypair.len() - 1),
1869            governance_voting_power: 1,
1870            consensus_voting_power: 1,
1871            governance_delegatee: None,
1872            consensus_delegatee: None,
1873            expelled: false,
1874        });
1875        // Apply reserved-diff commit to verify the reserved state
1876        csv.apply_commit(&Commit::Transaction(Transaction {
1877            author: "doesn't matter".to_owned(),
1878            timestamp: 3,
1879            head: "Test reserved-diff commit".to_string(),
1880            body: String::new(),
1881            diff: Diff::Reserved(Box::new(reserved_state.clone())),
1882        }))
1883        .unwrap_err();
1884    }
1885
1886    #[test]
1887    /// Test the case where the member names don't monotonically increase.
1888    fn invalid_reserved_state_with_non_monotonic_increased_member_names() {
1889        let (_, mut reserved_state, mut csv) = setup_test(5);
1890        // Remove one from members instead of setting Member::expelled to true
1891        reserved_state.members.pop();
1892        reserved_state.consensus_leader_order.pop();
1893        // Apply reserved-diff commit to verify the reserved state
1894        csv.apply_commit(&Commit::Transaction(Transaction {
1895            author: "doesn't matter".to_owned(),
1896            timestamp: 3,
1897            head: "Test reserved-diff commit".to_string(),
1898            body: String::new(),
1899            diff: Diff::Reserved(Box::new(reserved_state.clone())),
1900        }))
1901        .unwrap_err();
1902    }
1903
1904    #[test]
1905    fn test_verify_reserved_state_version_advance() {
1906        // configuring the test
1907        let (mut _validator_keypair, reserved_state, csv) = setup_test(4);
1908
1909        // rendering the reserved state that is expected to be valid
1910        let mut valid_rs = reserved_state;
1911        valid_rs.version = "1.1.0".to_string(); // set a version that is greater than the previous version
1912
1913        assert!(csv.verify_reserved_state(&valid_rs).is_ok());
1914    }
1915
1916    #[ignore]
1917    #[test]
1918    /// Test the case where the agenda proof commit is invalid because it is extra-agenda transaction phase.
1919    fn phase_mismatch_for_agenda_proof_commit3() {
1920        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1921        // Apply agenda commit
1922        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1923        let agenda: Agenda = Agenda {
1924            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1925            timestamp: 1,
1926            transactions_hash: agenda_transactions_hash,
1927            height: csv.header.height + 1,
1928            previous_block_hash: csv.header.to_hash256(),
1929        };
1930        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
1931        // Apply agenda-proof commit
1932        csv.apply_commit(&generate_agenda_proof_commit(
1933            &validator_keypair,
1934            &agenda,
1935            agenda.to_hash256(),
1936        ))
1937        .unwrap();
1938        // Apply extra-agenda transaction commit
1939        // delegator: member-0, delegatee: member-1
1940        let delegator = reserved_state.members[0].clone();
1941        let delegator_private_key = validator_keypair[0].1.clone();
1942        let delegatee = reserved_state.members[1].clone();
1943        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
1944            delegator: delegator.name,
1945            delegatee: delegatee.name,
1946            governance: true,
1947            block_height: csv.header.height + 1,
1948            timestamp: 2,
1949            chain_name: reserved_state.genesis_info.chain_name.clone(),
1950        };
1951        let proof =
1952            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
1953        csv.apply_commit(&generate_delegation_transaction_commit(
1954            &delegation_transaction_data,
1955            proof,
1956        ))
1957        .unwrap();
1958        // Apply agenda-proof commit at extra-agenda transaction phase
1959        let agenda_transactions_hash = Agenda::calculate_transactions_hash(&[]);
1960        let agenda: Agenda = Agenda {
1961            author: reserved_state.query_name(&validator_keypair[2].0).unwrap(),
1962            timestamp: 3,
1963            transactions_hash: agenda_transactions_hash,
1964            height: csv.header.height + 1,
1965            previous_block_hash: csv.header.to_hash256(),
1966        };
1967        csv.apply_commit(&generate_agenda_proof_commit(
1968            &validator_keypair,
1969            &agenda,
1970            agenda.to_hash256(),
1971        ))
1972        .unwrap_err();
1973    }
1974
1975    #[test]
1976    /// Test the case where the agenda proof commit is invalid because it is block phase.
1977    fn phase_mismatch_for_agenda_proof_commit4() {
1978        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
1979        // Apply agenda-proof commit at block phase
1980        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
1981        let agenda: Agenda = Agenda {
1982            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
1983            timestamp: 1,
1984            transactions_hash: agenda_transactions_hash,
1985            height: csv.header.height + 1,
1986            previous_block_hash: csv.header.to_hash256(),
1987        };
1988        csv.apply_commit(&generate_agenda_proof_commit(
1989            &validator_keypair,
1990            &agenda,
1991            agenda.to_hash256(),
1992        ))
1993        .unwrap_err();
1994    }
1995
1996    #[ignore]
1997    #[test]
1998    // Test the case where the `Delegate` extra-agenda transaction is invalid because the delegator is not a member.
1999    fn invalid_delegate_transaction_with_invalid_delegator1() {
2000        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
2001        // Apply agenda commit
2002        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
2003        let agenda: Agenda = Agenda {
2004            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
2005            timestamp: 1,
2006            transactions_hash: agenda_transactions_hash,
2007            height: csv.header.height + 1,
2008            previous_block_hash: csv.header.to_hash256(),
2009        };
2010        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
2011        // Apply agenda-proof commit
2012        csv.apply_commit(&generate_agenda_proof_commit(
2013            &validator_keypair,
2014            &agenda,
2015            agenda.to_hash256(),
2016        ))
2017        .unwrap();
2018        // Apply extra-agenda transaction commit
2019        // delegator: not-a-member, delegatee: member-0
2020        let (delegator_public_key, delegator_private_key) = generate_keypair_random();
2021        let delegator = Member {
2022            public_key: delegator_public_key,
2023            name: "not-a-member".to_string(),
2024            governance_voting_power: 100,
2025            consensus_voting_power: 100,
2026            governance_delegatee: None,
2027            consensus_delegatee: None,
2028            expelled: false,
2029        };
2030        let delegatee = reserved_state.members[0].clone();
2031        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2032            delegator: delegator.name,
2033            delegatee: delegatee.name,
2034            governance: true,
2035            block_height: csv.header.height + 1,
2036            timestamp: 2,
2037            chain_name: reserved_state.genesis_info.chain_name,
2038        };
2039        let proof: TypedSignature<DelegationTransactionData> =
2040            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
2041        csv.apply_commit(&generate_delegation_transaction_commit(
2042            &delegation_transaction_data,
2043            proof,
2044        ))
2045        .unwrap_err();
2046    }
2047
2048    #[ignore]
2049    #[test]
2050    // Test the case where the `Delegate` extra-agenda transaction is invalid because the delegator has already delegated.
2051    fn invalid_delegate_transaction_with_invalid_delegator2() {
2052        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
2053        // Apply agenda commit
2054        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
2055        let agenda: Agenda = Agenda {
2056            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
2057            timestamp: 1,
2058            transactions_hash: agenda_transactions_hash,
2059            height: csv.header.height + 1,
2060            previous_block_hash: csv.header.to_hash256(),
2061        };
2062        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
2063        // Apply agenda-proof commit
2064        csv.apply_commit(&generate_agenda_proof_commit(
2065            &validator_keypair,
2066            &agenda,
2067            agenda.to_hash256(),
2068        ))
2069        .unwrap();
2070        // Apply extra-agenda transaction commit
2071        // delegator: member-1, delegatee: member-2
2072        let delegator = reserved_state.members[1].clone();
2073        let delegator_private_key = validator_keypair[1].1.clone();
2074        let delegatee = reserved_state.members[2].clone();
2075        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2076            delegator: delegator.name.clone(),
2077            delegatee: delegatee.name,
2078            governance: true,
2079            block_height: csv.header.height + 1,
2080            timestamp: 2,
2081            chain_name: reserved_state.genesis_info.chain_name.clone(),
2082        };
2083        let proof: TypedSignature<DelegationTransactionData> =
2084            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
2085        csv.apply_commit(&generate_delegation_transaction_commit(
2086            &delegation_transaction_data,
2087            proof,
2088        ))
2089        .unwrap();
2090        // Apply extra-agenda transaction commit
2091        // delegator: member-1, delegatee: member-3
2092        let delegatee = reserved_state.members[3].clone();
2093        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2094            delegator: delegator.name,
2095            delegatee: delegatee.name,
2096            governance: true,
2097            block_height: csv.header.height + 1,
2098            timestamp: 3,
2099            chain_name: reserved_state.genesis_info.chain_name,
2100        };
2101        let proof: TypedSignature<DelegationTransactionData> =
2102            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
2103        csv.apply_commit(&generate_delegation_transaction_commit(
2104            &delegation_transaction_data,
2105            proof,
2106        ))
2107        .unwrap_err();
2108    }
2109
2110    #[ignore]
2111    #[test]
2112    // Test the case where the `Delegate` extra-agenda transaction is invalid because the delegatee is not a member.
2113    fn invalid_delegate_transaction_with_invalid_delegatee() {
2114        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
2115        // Apply agenda commit
2116        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
2117        let agenda: Agenda = Agenda {
2118            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
2119            timestamp: 1,
2120            transactions_hash: agenda_transactions_hash,
2121            height: csv.header.height + 1,
2122            previous_block_hash: csv.header.to_hash256(),
2123        };
2124        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
2125        // Apply agenda-proof commit
2126        csv.apply_commit(&generate_agenda_proof_commit(
2127            &validator_keypair,
2128            &agenda,
2129            agenda.to_hash256(),
2130        ))
2131        .unwrap();
2132        // Apply extra-agenda transaction commit
2133        // delegator: member-1, delegatee: not-a-member
2134        let delegator = reserved_state.members[1].clone();
2135        let delegator_private_key = validator_keypair[1].1.clone();
2136        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2137            delegator: delegator.name,
2138            delegatee: "not-a-member".to_string(),
2139            governance: true,
2140            block_height: csv.header.height + 1,
2141            timestamp: 2,
2142            chain_name: reserved_state.genesis_info.chain_name,
2143        };
2144        let proof =
2145            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
2146        csv.apply_commit(&generate_delegation_transaction_commit(
2147            &delegation_transaction_data,
2148            proof,
2149        ))
2150        .unwrap_err();
2151    }
2152
2153    #[ignore]
2154    #[test]
2155    // Test the case where the `Delegate` extra-agenda transaction is invalid because the signature is invalid.
2156    fn invalid_delegate_transaction_with_invalid_signature() {
2157        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
2158        // Apply agenda commit
2159        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
2160        let agenda: Agenda = Agenda {
2161            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
2162            timestamp: 1,
2163            transactions_hash: agenda_transactions_hash,
2164            height: csv.header.height + 1,
2165            previous_block_hash: csv.header.to_hash256(),
2166        };
2167        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
2168        // Apply agenda-proof commit
2169        csv.apply_commit(&generate_agenda_proof_commit(
2170            &validator_keypair,
2171            &agenda,
2172            agenda.to_hash256(),
2173        ))
2174        .unwrap();
2175        // Apply extra-agenda transaction commit
2176        // delegator: member-1, delegatee: member-2
2177        // The signature is signed by member-3's private key
2178        let delegator = reserved_state.members[1].clone();
2179        let non_delegator_private_key = validator_keypair[3].1.clone();
2180        let delegatee = reserved_state.members[2].clone();
2181        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2182            delegator: delegator.name,
2183            delegatee: delegatee.name,
2184            governance: true,
2185            block_height: csv.header.height + 1,
2186            timestamp: 2,
2187            chain_name: reserved_state.genesis_info.chain_name,
2188        };
2189        let proof =
2190            TypedSignature::sign(&delegation_transaction_data, &non_delegator_private_key).unwrap();
2191        csv.apply_commit(&generate_delegation_transaction_commit(
2192            &delegation_transaction_data,
2193            proof,
2194        ))
2195        .unwrap_err();
2196    }
2197
2198    #[ignore]
2199    #[test]
2200    // Test the case where the `Delegate` extra-agenda transaction is invalid because the timestamp is invalid.
2201    /// This test case is ignored because the extra-agenda transaction is not implemented yet.
2202    // TODO: enable this test case when the extra-agenda transaction is implemented.
2203    fn invalid_delegate_transaction_with_invalid_timestamp() {
2204        todo!("Implement this test")
2205    }
2206
2207    #[ignore]
2208    #[test]
2209    // Test the case where the `Undelegate` extra-agenda transaction is invalid because the delegator is not a member.
2210    /// This test case is ignored because the extra-agenda transaction is not implemented yet.
2211    /// TODO: enable this test case when the extra-agenda transaction is implemented.
2212    fn invalid_undelegate_transaction_with_invalid_delegator1() {
2213        todo!("Implement this test")
2214    }
2215
2216    #[ignore]
2217    #[test]
2218    // Test the case where the `Undelegate` extra-agenda transaction is invalid because the delegator has not delegated.
2219    /// This test case is ignored because the extra-agenda transaction is not implemented yet.
2220    /// TODO: enable this test case when the extra-agenda transaction is implemented.
2221    fn invalid_undelegate_transaction_with_invalid_delegator2() {
2222        todo!("Implement this test")
2223    }
2224
2225    #[ignore]
2226    #[test]
2227    // Test the case where the `Undelegate` extra-agenda transaction is invalid because the signature is invalid.
2228    fn invalid_undelegate_transaction_with_invalid_signature() {
2229        let (validator_keypair, reserved_state, mut csv) = setup_test(4);
2230        // Apply agenda commit
2231        let agenda_transactions_hash = calculate_agenda_transactions_hash(csv.phase.clone());
2232        let agenda: Agenda = Agenda {
2233            author: reserved_state.query_name(&validator_keypair[0].0).unwrap(),
2234            timestamp: 1,
2235            transactions_hash: agenda_transactions_hash,
2236            height: csv.header.height + 1,
2237            previous_block_hash: csv.header.to_hash256(),
2238        };
2239        csv.apply_commit(&generate_agenda_commit(&agenda)).unwrap();
2240        // Apply agenda-proof commit
2241        csv.apply_commit(&generate_agenda_proof_commit(
2242            &validator_keypair,
2243            &agenda,
2244            agenda.to_hash256(),
2245        ))
2246        .unwrap();
2247        // Apply extra-agenda transaction commit
2248        // delegator: member-1, delegatee: member-2
2249        let delegator = reserved_state.members[1].clone();
2250        let delegator_private_key = validator_keypair[1].1.clone();
2251        let delegatee = reserved_state.members[2].clone();
2252        let delegation_transaction_data: DelegationTransactionData = DelegationTransactionData {
2253            delegator: delegator.name.clone(),
2254            delegatee: delegatee.name,
2255            governance: true,
2256            block_height: csv.header.height + 1,
2257            timestamp: 2,
2258            chain_name: reserved_state.genesis_info.chain_name.clone(),
2259        };
2260        let proof =
2261            TypedSignature::sign(&delegation_transaction_data, &delegator_private_key).unwrap();
2262        csv.apply_commit(&generate_delegation_transaction_commit(
2263            &delegation_transaction_data,
2264            proof,
2265        ))
2266        .unwrap();
2267        // Apply `Undelegate` extra-agenda transaction commit with invalid signature
2268        let non_delegator_private_key = validator_keypair[3].1.clone();
2269        let undelegation_transaction_data: UndelegationTransactionData =
2270            UndelegationTransactionData {
2271                delegator: delegator.name,
2272                block_height: csv.header.height + 1,
2273                timestamp: 2,
2274                chain_name: reserved_state.genesis_info.chain_name,
2275            };
2276        let invalid_proof =
2277            TypedSignature::sign(&undelegation_transaction_data, &non_delegator_private_key)
2278                .unwrap();
2279        csv.apply_commit(&generate_undelegation_transaction_commit(
2280            &undelegation_transaction_data,
2281            invalid_proof,
2282        ))
2283        .unwrap_err();
2284    }
2285
2286    #[ignore]
2287    #[test]
2288    // Test the case where the `Undelegate` extra-agenda transaction is invalid because the timestamp is invalid.
2289    /// This test case is ignored because the extra-agenda transaction is not implemented yet.
2290    /// TODO: enable this test case when the extra-agenda transaction is implemented.
2291    fn invalid_undelegate_transaction_with_invalid_timestamp() {
2292        todo!("Implement this test")
2293    }
2294
2295    // TODO: add test cases where the `Report` extra-agenda transactions are invalid.
2296    // These test cases are TODO because the `Report` extra-agenda transaction is not implemented yet.
2297}