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 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 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 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 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 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, 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}