tendermint_testgen/
commit.rs

1use std::collections::BTreeSet;
2
3use gumdrop::Options;
4use serde::{Deserialize, Serialize};
5use simple_error::*;
6use tendermint::block::{self, parts::Header as PartSetHeader, Round};
7
8use crate::{helpers::*, validator::sort_validators, Generator, Header, Validator, Vote};
9
10#[derive(Debug, Options, Serialize, Deserialize, Clone)]
11pub struct Commit {
12    #[options(help = "header (required)", parse(try_from_str = "parse_as::<Header>"))]
13    pub header: Option<Header>,
14    #[options(
15        help = "votes in this commit (default: from header)",
16        parse(try_from_str = "parse_as::<Vec<Vote>>")
17    )]
18    pub votes: Option<Vec<Vote>>,
19    #[options(help = "commit round (default: 1)")]
20    pub round: Option<u32>,
21}
22
23impl Commit {
24    /// Make a new commit using default votes produced from the header.
25    pub fn new(header: Header, round: u32) -> Self {
26        let commit = Commit {
27            header: Some(header),
28            round: Some(round),
29            votes: None,
30        };
31        commit.generate_default_votes()
32    }
33    /// Make a new commit using explicit votes.
34    pub fn new_with_votes(header: Header, round: u32, votes: Vec<Vote>) -> Self {
35        Commit {
36            header: Some(header),
37            round: Some(round),
38            votes: Some(votes),
39        }
40    }
41    set_option!(header, Header);
42    set_option!(votes, Vec<Vote>);
43    set_option!(round, u32);
44
45    /// Generate commit votes from all validators in the header.
46    /// This function will panic if the header is not present
47    pub fn generate_default_votes(mut self) -> Self {
48        let header = self.header.as_ref().unwrap();
49        let val_to_vote = |(i, v): (usize, &Validator)| -> Vote {
50            Vote::new(v.clone(), header.clone())
51                .index(i as u16)
52                .round(self.round.unwrap_or(1))
53        };
54        let votes = header
55            .validators
56            .as_ref()
57            .unwrap()
58            .iter()
59            .enumerate()
60            .map(val_to_vote)
61            .collect();
62        self.votes = Some(votes);
63        self
64    }
65
66    /// Get a mutable reference to the vote of the given validator.
67    /// This function will panic if the votes or the validator vote is not present
68    pub fn vote_of_validator(&mut self, id: &str) -> &mut Vote {
69        self.votes
70            .as_mut()
71            .unwrap()
72            .iter_mut()
73            .find(|v| *v.validator.as_ref().unwrap() == Validator::new(id))
74            .unwrap()
75    }
76
77    /// Get a mutable reference to the vote at the given index
78    /// This function will panic if the votes or the vote at index is not present
79    pub fn vote_at_index(&mut self, index: usize) -> &mut Vote {
80        self.votes.as_mut().unwrap().get_mut(index).unwrap()
81    }
82}
83
84impl std::str::FromStr for Commit {
85    type Err = SimpleError;
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        let commit = match parse_as::<Commit>(s) {
88            Ok(input) => input,
89            Err(_) => Commit::new(parse_as::<Header>(s)?, 1),
90        };
91        Ok(commit)
92    }
93}
94
95impl Generator<block::Commit> for Commit {
96    fn merge_with_default(self, other: Self) -> Self {
97        Commit {
98            header: self.header.or(other.header),
99            round: self.round.or(other.round),
100            votes: self.votes.or(other.votes),
101        }
102    }
103
104    fn generate(&self) -> Result<block::Commit, SimpleError> {
105        let header = match &self.header {
106            None => bail!("failed to generate commit: header is missing"),
107            Some(h) => h,
108        };
109        let block_header = header.generate()?;
110        let block_id = block::Id {
111            hash: block_header.hash(),
112            part_set_header: PartSetHeader::new(1, block_header.hash()).unwrap(),
113        };
114        let votes = match &self.votes {
115            None => self.clone().generate_default_votes().votes.unwrap(),
116            Some(vs) => vs.to_vec(),
117        };
118
119        let all_vals = header.validators.as_ref().unwrap();
120        let mut all_vals: BTreeSet<&Validator> = BTreeSet::from_iter(all_vals);
121        let votes_vals: Vec<Validator> =
122            votes.iter().map(|v| v.validator.clone().unwrap()).collect();
123        all_vals.append(&mut BTreeSet::from_iter(&votes_vals));
124        let all_vals: Vec<Validator> = all_vals.iter().map(|&x| x.clone()).collect();
125        let all_vals = sort_validators(&all_vals);
126
127        let vote_to_sig = |v: &Vote| -> Result<block::CommitSig, SimpleError> {
128            let vote = v.generate()?;
129            if vote.block_id.is_none() {
130                Ok(block::CommitSig::BlockIdFlagNil {
131                    validator_address: vote.validator_address,
132                    timestamp: vote.timestamp.unwrap(),
133                    signature: vote.signature,
134                })
135            } else {
136                Ok(block::CommitSig::BlockIdFlagCommit {
137                    validator_address: vote.validator_address,
138                    timestamp: vote.timestamp.unwrap(),
139                    signature: vote.signature,
140                })
141            }
142        };
143        let val_to_sig = |val: &Validator| -> Result<block::CommitSig, SimpleError> {
144            let vote = votes
145                .iter()
146                .find(|&vote| vote.validator.as_ref().unwrap() == val);
147            match vote {
148                Some(vote) => vote_to_sig(vote),
149                None => Ok(block::CommitSig::BlockIdFlagAbsent),
150            }
151        };
152        let sigs = all_vals
153            .iter()
154            .map(val_to_sig)
155            .collect::<Result<Vec<block::CommitSig>, SimpleError>>()?;
156        let commit = block::Commit {
157            height: block_header.height,
158            round: Round::try_from(self.round.unwrap_or(1)).unwrap(),
159            block_id, /* TODO do we need at least one part?
160                       * //block::Id::new(hasher.hash_header(&block_header), None), // */
161            signatures: sigs,
162        };
163        Ok(commit)
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use tendermint::Time;
170
171    use super::*;
172
173    #[test]
174    fn test_commit() {
175        let valset1 = sort_validators(&[
176            Validator::new("a"),
177            Validator::new("b"),
178            Validator::new("c"),
179        ]);
180        let valset2 = sort_validators(&[
181            Validator::new("d"),
182            Validator::new("e"),
183            Validator::new("f"),
184        ]);
185
186        let header = Header::new(&valset1)
187            .next_validators(&valset2)
188            .height(10)
189            .time(Time::from_unix_timestamp(11, 0).unwrap());
190
191        let commit = Commit::new(header.clone(), 3);
192
193        let block_header = header.generate().unwrap();
194        let block_commit = commit.generate().unwrap();
195
196        assert_eq!(block_commit.round.value(), 3);
197        assert_eq!(block_commit.height, block_header.height);
198
199        let mut commit = commit;
200        assert_eq!(commit.vote_at_index(1).round, Some(3));
201        assert_eq!(commit.vote_of_validator("b").index, Some(0));
202
203        let votes = commit.votes.as_ref().unwrap();
204
205        for (i, sig) in block_commit.signatures.iter().enumerate() {
206            match sig {
207                block::CommitSig::BlockIdFlagCommit {
208                    validator_address: _,
209                    timestamp: _,
210                    signature,
211                } => {
212                    let block_vote = votes[i].generate().unwrap();
213                    let sign_bytes =
214                        get_vote_sign_bytes(block_header.chain_id.clone(), &block_vote);
215                    assert!(!verify_signature(
216                        &valset2[i].get_public_key().unwrap(),
217                        &sign_bytes,
218                        signature.as_ref().unwrap()
219                    ));
220                    assert!(verify_signature(
221                        &valset1[i].get_public_key().unwrap(),
222                        &sign_bytes,
223                        signature.as_ref().unwrap()
224                    ));
225                },
226                _ => panic!("signature was not a commit"),
227            };
228        }
229    }
230}