1use alloc::vec::Vec;
4use core::{fmt, marker::PhantomData};
5
6use serde::{Deserialize, Serialize};
7use tendermint::{
8 account,
9 block::CommitSig,
10 chain,
11 crypto::signature,
12 trust_threshold::TrustThreshold as _,
13 validator,
14 vote::{SignedVote, ValidatorIndex, Vote},
15};
16
17use crate::{
18 errors::VerificationError,
19 prelude::*,
20 types::{Commit, SignedHeader, TrustThreshold, ValidatorSet},
21};
22
23#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq)]
25pub struct VotingPowerTally {
26 pub total: u64,
28 pub tallied: u64,
30 pub trust_threshold: TrustThreshold,
32}
33
34impl VotingPowerTally {
35 fn new(total: u64, trust_threshold: TrustThreshold) -> Self {
36 Self {
37 total,
38 tallied: 0,
39 trust_threshold,
40 }
41 }
42
43 fn tally(&mut self, power: u64) {
45 self.tallied += power;
46 debug_assert!(self.tallied <= self.total);
47 }
48
49 fn check(&self) -> Result<(), Self> {
51 if self
52 .trust_threshold
53 .is_enough_power(self.tallied, self.total)
54 {
55 Ok(())
56 } else {
57 Err(*self)
58 }
59 }
60}
61
62impl fmt::Display for VotingPowerTally {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(
65 f,
66 "VotingPower(total={} tallied={} trust_threshold={})",
67 self.total, self.tallied, self.trust_threshold
68 )
69 }
70}
71
72pub trait VotingPowerCalculator: Send + Sync {
76 fn total_power_of(&self, validator_set: &ValidatorSet) -> u64 {
78 validator_set
79 .validators()
80 .iter()
81 .fold(0u64, |total, val_info| total + val_info.power.value())
82 }
83
84 fn check_enough_trust_and_signers(
98 &self,
99 untrusted_header: &SignedHeader,
100 trusted_validators: &ValidatorSet,
101 trust_threshold: TrustThreshold,
102 untrusted_validators: &ValidatorSet,
103 ) -> Result<(), VerificationError> {
104 let (trusted_power, untrusted_power) = self.voting_power_in_sets(
105 untrusted_header,
106 (trusted_validators, trust_threshold),
107 (untrusted_validators, TrustThreshold::TWO_THIRDS),
108 )?;
109 trusted_power
110 .check()
111 .map_err(VerificationError::not_enough_trust)?;
112 untrusted_power
113 .check()
114 .map_err(VerificationError::insufficient_signers_overlap)?;
115 Ok(())
116 }
117
118 fn check_signers_overlap(
120 &self,
121 untrusted_header: &SignedHeader,
122 untrusted_validators: &ValidatorSet,
123 ) -> Result<(), VerificationError> {
124 let trust_threshold = TrustThreshold::TWO_THIRDS;
125 self.voting_power_in(untrusted_header, untrusted_validators, trust_threshold)?
126 .check()
127 .map_err(VerificationError::insufficient_signers_overlap)
128 }
129
130 fn voting_power_in(
142 &self,
143 signed_header: &SignedHeader,
144 validator_set: &ValidatorSet,
145 trust_threshold: TrustThreshold,
146 ) -> Result<VotingPowerTally, VerificationError>;
147
148 fn voting_power_in_sets(
179 &self,
180 signed_header: &SignedHeader,
181 first_set: (&ValidatorSet, TrustThreshold),
182 second_set: (&ValidatorSet, TrustThreshold),
183 ) -> Result<(VotingPowerTally, VotingPowerTally), VerificationError>;
184}
185
186#[derive(Copy, Clone, Debug, PartialEq, Eq)]
189pub struct ProvidedVotingPowerCalculator<V> {
190 _verifier: PhantomData<V>,
191}
192
193unsafe impl<V> Send for ProvidedVotingPowerCalculator<V> {}
195unsafe impl<V> Sync for ProvidedVotingPowerCalculator<V> {}
196
197impl<V> Default for ProvidedVotingPowerCalculator<V> {
198 fn default() -> Self {
199 Self {
200 _verifier: PhantomData,
201 }
202 }
203}
204
205struct NonAbsentCommitVote {
207 signed_vote: SignedVote,
208 verified: bool,
210}
211
212impl NonAbsentCommitVote {
213 pub fn new(
220 commit_sig: &CommitSig,
221 validator_index: ValidatorIndex,
222 commit: &Commit,
223 chain_id: &chain::Id,
224 ) -> Option<Result<Self, VerificationError>> {
225 let (validator_address, timestamp, signature) = match commit_sig {
226 CommitSig::BlockIdFlagAbsent { .. } => return None,
227 CommitSig::BlockIdFlagCommit {
228 validator_address,
229 timestamp,
230 signature,
231 } => (*validator_address, *timestamp, signature),
232 CommitSig::BlockIdFlagNil { .. } => return None,
233 };
234
235 let vote = Vote {
236 vote_type: tendermint::vote::Type::Precommit,
237 height: commit.height,
238 round: commit.round,
239 block_id: Some(commit.block_id),
240 timestamp: Some(timestamp),
241 validator_address,
242 validator_index,
243 signature: signature.clone(),
244 extension: Default::default(),
245 extension_signature: None,
246 };
247 Some(
248 SignedVote::from_vote(vote, chain_id.clone())
249 .ok_or_else(VerificationError::missing_signature)
250 .map(|signed_vote| Self {
251 signed_vote,
252 verified: false,
253 }),
254 )
255 }
256
257 pub fn validator_id(&self) -> account::Id {
259 self.signed_vote.validator_id()
260 }
261}
262
263struct NonAbsentCommitVotes {
265 votes: Vec<NonAbsentCommitVote>,
267 sign_bytes: Vec<u8>,
272}
273
274impl NonAbsentCommitVotes {
275 const SIGN_BYTES_INITIAL_CAPACITY: usize = 166;
286
287 pub fn new(signed_header: &SignedHeader) -> Result<Self, VerificationError> {
288 let mut votes = signed_header
289 .commit
290 .signatures
291 .iter()
292 .enumerate()
293 .flat_map(|(idx, signature)| {
294 let idx = ValidatorIndex::try_from(idx).unwrap();
297 NonAbsentCommitVote::new(
298 signature,
299 idx,
300 &signed_header.commit,
301 &signed_header.header.chain_id,
302 )
303 })
304 .collect::<Result<Vec<_>, VerificationError>>()?;
305 votes.sort_unstable_by_key(NonAbsentCommitVote::validator_id);
306
307 let duplicate = votes
310 .windows(2)
311 .find(|pair| pair[0].validator_id() == pair[1].validator_id());
312 if let Some(pair) = duplicate {
313 Err(VerificationError::duplicate_validator(
314 pair[0].validator_id(),
315 ))
316 } else {
317 Ok(Self {
318 votes,
319 sign_bytes: Vec::with_capacity(Self::SIGN_BYTES_INITIAL_CAPACITY),
320 })
321 }
322 }
323
324 pub fn has_voted<V: signature::Verifier>(
330 &mut self,
331 validator: &validator::Info,
332 ) -> Result<Option<usize>, VerificationError> {
333 if let Ok(idx) = self
334 .votes
335 .binary_search_by_key(&validator.address, NonAbsentCommitVote::validator_id)
336 {
337 let vote = &mut self.votes[idx];
338
339 if !vote.verified {
340 self.sign_bytes.clear();
341 vote.signed_vote
342 .sign_bytes_into(&mut self.sign_bytes)
343 .expect("buffer is resized if needed and encoding never fails");
344
345 let sign_bytes = self.sign_bytes.as_slice();
346 validator
347 .verify_signature::<V>(sign_bytes, vote.signed_vote.signature())
348 .map_err(|_| {
349 VerificationError::invalid_signature(
350 vote.signed_vote.signature().as_bytes().to_vec(),
351 Box::new(validator.clone()),
352 sign_bytes.to_vec(),
353 )
354 })?;
355
356 vote.verified = true;
357 }
358
359 Ok(Some(idx))
360 } else {
361 Ok(None)
362 }
363 }
364}
365
366#[cfg(feature = "rust-crypto")]
368pub type ProdVotingPowerCalculator =
369 ProvidedVotingPowerCalculator<tendermint::crypto::default::signature::Verifier>;
370
371impl<V: signature::Verifier> VotingPowerCalculator for ProvidedVotingPowerCalculator<V> {
372 fn voting_power_in(
373 &self,
374 signed_header: &SignedHeader,
375 validator_set: &ValidatorSet,
376 trust_threshold: TrustThreshold,
377 ) -> Result<VotingPowerTally, VerificationError> {
378 let mut votes = NonAbsentCommitVotes::new(signed_header)?;
379 voting_power_in_impl::<V>(
380 &mut votes,
381 validator_set,
382 trust_threshold,
383 self.total_power_of(validator_set),
384 )
385 }
386
387 fn voting_power_in_sets(
388 &self,
389 signed_header: &SignedHeader,
390 first_set: (&ValidatorSet, TrustThreshold),
391 second_set: (&ValidatorSet, TrustThreshold),
392 ) -> Result<(VotingPowerTally, VotingPowerTally), VerificationError> {
393 let mut votes = NonAbsentCommitVotes::new(signed_header)?;
394 let first_tally = voting_power_in_impl::<V>(
395 &mut votes,
396 first_set.0,
397 first_set.1,
398 self.total_power_of(first_set.0),
399 )?;
400 let second_tally = voting_power_in_impl::<V>(
401 &mut votes,
402 second_set.0,
403 second_set.1,
404 self.total_power_of(second_set.0),
405 )?;
406 Ok((first_tally, second_tally))
407 }
408}
409
410fn voting_power_in_impl<V: signature::Verifier>(
411 votes: &mut NonAbsentCommitVotes,
412 validator_set: &ValidatorSet,
413 trust_threshold: TrustThreshold,
414 total_voting_power: u64,
415) -> Result<VotingPowerTally, VerificationError> {
416 let mut power = VotingPowerTally::new(total_voting_power, trust_threshold);
417 let mut seen_vals = Vec::new();
418
419 for validator in validator_set.validators() {
420 if let Some(idx) = votes.has_voted::<V>(validator)? {
421 if seen_vals.contains(&idx) {
425 return Err(VerificationError::duplicate_validator(validator.address));
426 }
427 seen_vals.push(idx);
428
429 power.tally(validator.power());
430
431 if power.check().is_ok() {
433 break;
434 }
435 }
436 }
437
438 Ok(power)
439}
440
441#[cfg(test)]
446mod tests {
447 use tendermint::trust_threshold::TrustThresholdFraction;
448 use tendermint_testgen::{
449 light_block::generate_signed_header, Commit, Generator, Header,
450 LightBlock as TestgenLightBlock, ValidatorSet, Vote as TestgenVote,
451 };
452
453 use super::*;
454 use crate::{errors::VerificationErrorDetail, types::LightBlock};
455
456 const EXPECTED_RESULT: VotingPowerTally = VotingPowerTally {
457 total: 100,
458 tallied: 0,
459 trust_threshold: TrustThresholdFraction::ONE_THIRD,
460 };
461
462 #[test]
463 fn test_empty_signatures() {
464 let vp_calculator = ProdVotingPowerCalculator::default();
465 let trust_threshold = TrustThreshold::default();
466
467 let mut light_block: LightBlock = TestgenLightBlock::new_default(10)
468 .generate()
469 .unwrap()
470 .into();
471 light_block.signed_header.commit.signatures = vec![];
472
473 let result_ok = vp_calculator.voting_power_in(
474 &light_block.signed_header,
475 &light_block.validators,
476 trust_threshold,
477 );
478
479 assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
481 }
482
483 #[test]
484 fn test_all_signatures_absent() {
485 let vp_calculator = ProdVotingPowerCalculator::default();
486 let trust_threshold = TrustThreshold::default();
487
488 let mut testgen_lb = TestgenLightBlock::new_default(10);
489 let mut commit = testgen_lb.commit.clone().unwrap();
490 commit.votes = Some(vec![]);
492 testgen_lb.commit = Some(commit);
493 let light_block: LightBlock = testgen_lb.generate().unwrap().into();
494
495 let result_ok = vp_calculator.voting_power_in(
496 &light_block.signed_header,
497 &light_block.validators,
498 trust_threshold,
499 );
500
501 assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
503 }
504
505 #[test]
506 fn test_all_signatures_nil() {
507 let vp_calculator = ProdVotingPowerCalculator::default();
508 let trust_threshold = TrustThreshold::default();
509
510 let validator_set = ValidatorSet::new(vec!["a", "b"]);
511 let vals = validator_set.clone().validators.unwrap();
512 let header = Header::new(&vals);
513 let votes = vec![
514 TestgenVote::new(vals[0].clone(), header.clone()).nil(true),
515 TestgenVote::new(vals[1].clone(), header.clone()).nil(true),
516 ];
517 let commit = Commit::new_with_votes(header.clone(), 1, votes);
518 let signed_header = generate_signed_header(&header, &commit).unwrap();
519 let valset = validator_set.generate().unwrap();
520
521 let result_ok = vp_calculator.voting_power_in(&signed_header, &valset, trust_threshold);
522
523 assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
525 }
526
527 #[test]
528 fn test_one_invalid_signature() {
529 let vp_calculator = ProdVotingPowerCalculator::default();
530 let trust_threshold = TrustThreshold::default();
531
532 let mut testgen_lb = TestgenLightBlock::new_default(10);
533 let mut commit = testgen_lb.commit.clone().unwrap();
534 let mut votes = commit.votes.unwrap();
535 let vote = votes.pop().unwrap();
536 let header = vote.clone().header.unwrap().chain_id("bad-chain");
537 votes.push(vote.header(header));
538
539 commit.votes = Some(votes);
540 testgen_lb.commit = Some(commit);
541 let light_block: LightBlock = testgen_lb.generate().unwrap().into();
542
543 let result_err = vp_calculator.voting_power_in(
544 &light_block.signed_header,
545 &light_block.validators,
546 trust_threshold,
547 );
548
549 match result_err {
550 Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {},
551 _ => panic!("expected InvalidSignature error"),
552 }
553 }
554
555 #[test]
556 fn test_all_signatures_invalid() {
557 let vp_calculator = ProdVotingPowerCalculator::default();
558 let trust_threshold = TrustThreshold::default();
559
560 let mut testgen_lb = TestgenLightBlock::new_default(10);
561 let header = testgen_lb.header.unwrap().chain_id("bad-chain");
562 testgen_lb.header = Some(header);
563 let light_block: LightBlock = testgen_lb.generate().unwrap().into();
564
565 let result_err = vp_calculator.voting_power_in(
566 &light_block.signed_header,
567 &light_block.validators,
568 trust_threshold,
569 );
570
571 match result_err {
572 Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {},
573 _ => panic!("expected InvalidSignature error"),
574 }
575 }
576
577 #[test]
578 fn test_signatures_from_diff_valset() {
579 let vp_calculator = ProdVotingPowerCalculator::default();
580 let trust_threshold = TrustThreshold::default();
581
582 let mut light_block: LightBlock = TestgenLightBlock::new_default(10)
583 .generate()
584 .unwrap()
585 .into();
586 light_block.validators = ValidatorSet::new(vec!["bad-val1", "bad-val2"])
587 .generate()
588 .unwrap();
589
590 let result_ok = vp_calculator.voting_power_in(
591 &light_block.signed_header,
592 &light_block.validators,
593 trust_threshold,
594 );
595
596 assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
598 }
599}