1use gumdrop::Options;
2use serde::{Deserialize, Serialize};
3use simple_error::*;
4use tendermint::{
5 block::{self, parts::Header as PartSetHeader},
6 signature::{Ed25519Signature, Signature},
7 vote,
8 vote::ValidatorIndex,
9};
10
11use crate::{helpers::*, Generator, Header, Validator};
12
13#[derive(Debug, Options, Serialize, Deserialize, Clone)]
14pub struct Vote {
15 #[options(
16 help = "validator of this vote (required; can be passed via STDIN)",
17 parse(try_from_str = "parse_as::<Validator>")
18 )]
19 pub validator: Option<Validator>,
20 #[options(help = "validator index (default: from commit header)")]
21 pub index: Option<u16>,
22 #[options(help = "header to sign (default: commit header)")]
23 pub header: Option<Header>,
24 #[options(help = "vote type; 'prevote' if set, otherwise 'precommit' (default)")]
25 pub prevote: Option<()>,
26 #[options(help = "block height (default: from header)")]
27 pub height: Option<u64>,
28 #[options(help = "time (default: from header)")]
29 pub time: Option<u64>,
30 #[options(help = "commit round (default: from commit)")]
31 pub round: Option<u32>,
32 #[options(
33 help = "to indicate if the vote is nil; produces a 'BlockIdFlagNil' if set, otherwise 'BlockIdFlagCommit' (default)"
34 )]
35 pub nil: Option<()>,
36}
37
38impl Vote {
39 pub fn new(validator: Validator, header: Header) -> Self {
40 Vote {
41 validator: Some(validator),
42 index: None,
43 header: Some(header),
44 prevote: None,
45 height: None,
46 time: None,
47 round: None,
48 nil: None,
49 }
50 }
51 set_option!(index, u16);
52 set_option!(header, Header);
53 set_option!(prevote, bool, if prevote { Some(()) } else { None });
54 set_option!(height, u64);
55 set_option!(time, u64);
56 set_option!(round, u32);
57 set_option!(nil, bool, if nil { Some(()) } else { None });
58}
59
60impl std::str::FromStr for Vote {
61 type Err = SimpleError;
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 parse_as::<Vote>(s)
64 }
65}
66
67impl Generator<vote::Vote> for Vote {
68 fn merge_with_default(self, default: Self) -> Self {
69 Vote {
70 validator: self.validator.or(default.validator),
71 index: self.index.or(default.index),
72 header: self.header.or(default.header),
73 prevote: self.prevote.or(default.prevote),
74 height: self.height.or(default.height),
75 time: self.time.or(default.time),
76 round: self.round.or(default.round),
77 nil: self.nil.or(default.nil),
78 }
79 }
80
81 fn generate(&self) -> Result<vote::Vote, SimpleError> {
82 let validator = match &self.validator {
83 None => bail!("failed to generate vote: validator is missing"),
84 Some(v) => v,
85 };
86 let header = match &self.header {
87 None => bail!("failed to generate vote: header is missing"),
88 Some(h) => h,
89 };
90 let signing_key = validator.get_private_key()?;
91 let signing_key = ed25519_consensus::SigningKey::try_from(signing_key).unwrap();
92 let block_validator = validator.generate()?;
93 let block_header = header.generate()?;
94 let block_id = if self.nil.is_some() {
95 None
96 } else {
97 Some(block::Id {
98 hash: block_header.hash(),
99 part_set_header: PartSetHeader::new(1, block_header.hash()).unwrap(),
100 })
101 };
102 let validator_index = match self.index {
103 Some(i) => i,
104 None => {
105 let position = header
106 .validators
107 .as_ref()
108 .unwrap()
109 .iter()
110 .position(|v| *v == *validator);
111 match position {
112 Some(i) => i as u16, None => 0, }
115 },
116 };
117 let timestamp = if let Some(t) = self.time {
118 get_time(t)?
119 } else {
120 block_header.time
121 };
122 let mut vote = vote::Vote {
123 vote_type: if self.prevote.is_some() {
124 vote::Type::Prevote
125 } else {
126 vote::Type::Precommit
127 },
128 height: block_header.height,
129 round: block::Round::try_from(self.round.unwrap_or(1)).unwrap(),
130 block_id,
131 timestamp: Some(timestamp),
132 validator_address: block_validator.address,
133 validator_index: ValidatorIndex::try_from(validator_index as u32).unwrap(),
134 signature: Signature::new(vec![0_u8; Ed25519Signature::BYTE_SIZE])
135 .map_err(|e| SimpleError::new(e.to_string()))?,
136 extension: vec![],
137 extension_signature: None,
138 };
139
140 let sign_bytes = get_vote_sign_bytes(block_header.chain_id, &vote);
141 vote.signature = Some(signing_key.sign(sign_bytes.as_slice()).into());
142
143 Ok(vote)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::Time;
151
152 #[test]
153 fn test_vote() {
154 let valset1 = [
155 Validator::new("a"),
156 Validator::new("b"),
157 Validator::new("c"),
158 ];
159 let valset2 = [
160 Validator::new("b"),
161 Validator::new("c"),
162 Validator::new("d"),
163 ];
164
165 let now = Time::new(10).generate().unwrap();
166 let header = Header::new(&valset1)
167 .next_validators(&valset2)
168 .height(10)
169 .time(tendermint::Time::from_unix_timestamp(10, 0).unwrap());
170
171 let val = &valset1[1];
172 let vote = Vote::new(val.clone(), header.clone()).round(2);
173
174 let block_val = val.generate().unwrap();
175 let block_header = header.generate().unwrap();
176 let block_vote = vote.generate().unwrap();
177
178 assert_eq!(block_vote.validator_address, block_val.address);
179 assert_eq!(block_vote.height, block_header.height);
180 assert_eq!(block_vote.round.value(), 2);
181 assert_eq!(block_vote.timestamp.unwrap(), now);
182 assert_eq!(block_vote.validator_index.value(), 1);
183 assert_eq!(block_vote.vote_type, vote::Type::Precommit);
184
185 let sign_bytes = get_vote_sign_bytes(block_header.chain_id, &block_vote);
186 assert!(!verify_signature(
187 &valset1[0].get_public_key().unwrap(),
188 &sign_bytes,
189 block_vote.signature.as_ref().unwrap()
190 ));
191 assert!(verify_signature(
192 &valset1[1].get_public_key().unwrap(),
193 &sign_bytes,
194 block_vote.signature.as_ref().unwrap()
195 ));
196 }
197}