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