cometbft_testgen/
vote.rs

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, // Todo: possible overflow
115                    None => 0,           // we allow non-present validators for testing purposes
116                }
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}