use alloc::vec::Vec;
use core::{fmt, marker::PhantomData};
use serde::{Deserialize, Serialize};
use tendermint::{
account,
block::CommitSig,
chain,
crypto::signature,
trust_threshold::TrustThreshold as _,
validator,
vote::{SignedVote, ValidatorIndex, Vote},
};
use crate::{
errors::VerificationError,
prelude::*,
types::{Commit, SignedHeader, TrustThreshold, ValidatorSet},
};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq)]
pub struct VotingPowerTally {
pub total: u64,
pub tallied: u64,
pub trust_threshold: TrustThreshold,
}
impl VotingPowerTally {
fn new(total: u64, trust_threshold: TrustThreshold) -> Self {
Self {
total,
tallied: 0,
trust_threshold,
}
}
fn tally(&mut self, power: u64) {
self.tallied += power;
debug_assert!(self.tallied <= self.total);
}
fn check(&self) -> Result<(), Self> {
if self
.trust_threshold
.is_enough_power(self.tallied, self.total)
{
Ok(())
} else {
Err(*self)
}
}
}
impl fmt::Display for VotingPowerTally {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"VotingPower(total={} tallied={} trust_threshold={})",
self.total, self.tallied, self.trust_threshold
)
}
}
pub trait VotingPowerCalculator: Send + Sync {
fn total_power_of(&self, validator_set: &ValidatorSet) -> u64 {
validator_set
.validators()
.iter()
.fold(0u64, |total, val_info| total + val_info.power.value())
}
fn check_enough_trust_and_signers(
&self,
untrusted_header: &SignedHeader,
trusted_validators: &ValidatorSet,
trust_threshold: TrustThreshold,
untrusted_validators: &ValidatorSet,
) -> Result<(), VerificationError> {
let (trusted_power, untrusted_power) = self.voting_power_in_sets(
untrusted_header,
(trusted_validators, trust_threshold),
(untrusted_validators, TrustThreshold::TWO_THIRDS),
)?;
trusted_power
.check()
.map_err(VerificationError::not_enough_trust)?;
untrusted_power
.check()
.map_err(VerificationError::insufficient_signers_overlap)?;
Ok(())
}
fn check_signers_overlap(
&self,
untrusted_header: &SignedHeader,
untrusted_validators: &ValidatorSet,
) -> Result<(), VerificationError> {
let trust_threshold = TrustThreshold::TWO_THIRDS;
self.voting_power_in(untrusted_header, untrusted_validators, trust_threshold)?
.check()
.map_err(VerificationError::insufficient_signers_overlap)
}
fn voting_power_in(
&self,
signed_header: &SignedHeader,
validator_set: &ValidatorSet,
trust_threshold: TrustThreshold,
) -> Result<VotingPowerTally, VerificationError>;
fn voting_power_in_sets(
&self,
signed_header: &SignedHeader,
first_set: (&ValidatorSet, TrustThreshold),
second_set: (&ValidatorSet, TrustThreshold),
) -> Result<(VotingPowerTally, VotingPowerTally), VerificationError>;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ProvidedVotingPowerCalculator<V> {
_verifier: PhantomData<V>,
}
unsafe impl<V> Send for ProvidedVotingPowerCalculator<V> {}
unsafe impl<V> Sync for ProvidedVotingPowerCalculator<V> {}
impl<V> Default for ProvidedVotingPowerCalculator<V> {
fn default() -> Self {
Self {
_verifier: PhantomData,
}
}
}
struct NonAbsentCommitVote {
signed_vote: SignedVote,
verified: bool,
}
impl NonAbsentCommitVote {
pub fn new(
commit_sig: &CommitSig,
validator_index: ValidatorIndex,
commit: &Commit,
chain_id: &chain::Id,
) -> Option<Result<Self, VerificationError>> {
let (validator_address, timestamp, signature) = match commit_sig {
CommitSig::BlockIdFlagAbsent { .. } => return None,
CommitSig::BlockIdFlagCommit {
validator_address,
timestamp,
signature,
} => (*validator_address, *timestamp, signature),
CommitSig::BlockIdFlagNil { .. } => return None,
};
let vote = Vote {
vote_type: tendermint::vote::Type::Precommit,
height: commit.height,
round: commit.round,
block_id: Some(commit.block_id),
timestamp: Some(timestamp),
validator_address,
validator_index,
signature: signature.clone(),
extension: Default::default(),
extension_signature: None,
};
Some(
SignedVote::from_vote(vote, chain_id.clone())
.ok_or_else(VerificationError::missing_signature)
.map(|signed_vote| Self {
signed_vote,
verified: false,
}),
)
}
pub fn validator_id(&self) -> account::Id {
self.signed_vote.validator_id()
}
}
struct NonAbsentCommitVotes {
votes: Vec<NonAbsentCommitVote>,
sign_bytes: Vec<u8>,
}
impl NonAbsentCommitVotes {
const SIGN_BYTES_INITIAL_CAPACITY: usize = 166;
pub fn new(signed_header: &SignedHeader) -> Result<Self, VerificationError> {
let mut votes = signed_header
.commit
.signatures
.iter()
.enumerate()
.flat_map(|(idx, signature)| {
let idx = ValidatorIndex::try_from(idx).unwrap();
NonAbsentCommitVote::new(
signature,
idx,
&signed_header.commit,
&signed_header.header.chain_id,
)
})
.collect::<Result<Vec<_>, VerificationError>>()?;
votes.sort_unstable_by_key(NonAbsentCommitVote::validator_id);
let duplicate = votes
.windows(2)
.find(|pair| pair[0].validator_id() == pair[1].validator_id());
if let Some(pair) = duplicate {
Err(VerificationError::duplicate_validator(
pair[0].validator_id(),
))
} else {
Ok(Self {
votes,
sign_bytes: Vec::with_capacity(Self::SIGN_BYTES_INITIAL_CAPACITY),
})
}
}
pub fn has_voted<V: signature::Verifier>(
&mut self,
validator: &validator::Info,
) -> Result<bool, VerificationError> {
let idx = self
.votes
.binary_search_by_key(&validator.address, NonAbsentCommitVote::validator_id);
let vote = match idx {
Ok(idx) => &mut self.votes[idx],
Err(_) => return Ok(false),
};
if !vote.verified {
self.sign_bytes.truncate(0);
vote.signed_vote
.sign_bytes_into(&mut self.sign_bytes)
.expect("buffer is resized if needed and encoding never fails");
let sign_bytes = self.sign_bytes.as_slice();
validator
.verify_signature::<V>(sign_bytes, vote.signed_vote.signature())
.map_err(|_| {
VerificationError::invalid_signature(
vote.signed_vote.signature().as_bytes().to_vec(),
Box::new(validator.clone()),
sign_bytes.to_vec(),
)
})?;
}
vote.verified = true;
Ok(true)
}
}
#[cfg(feature = "rust-crypto")]
pub type ProdVotingPowerCalculator =
ProvidedVotingPowerCalculator<tendermint::crypto::default::signature::Verifier>;
impl<V: signature::Verifier> VotingPowerCalculator for ProvidedVotingPowerCalculator<V> {
fn voting_power_in(
&self,
signed_header: &SignedHeader,
validator_set: &ValidatorSet,
trust_threshold: TrustThreshold,
) -> Result<VotingPowerTally, VerificationError> {
let mut votes = NonAbsentCommitVotes::new(signed_header)?;
voting_power_in_impl::<V>(
&mut votes,
validator_set,
trust_threshold,
self.total_power_of(validator_set),
)
}
fn voting_power_in_sets(
&self,
signed_header: &SignedHeader,
first_set: (&ValidatorSet, TrustThreshold),
second_set: (&ValidatorSet, TrustThreshold),
) -> Result<(VotingPowerTally, VotingPowerTally), VerificationError> {
let mut votes = NonAbsentCommitVotes::new(signed_header)?;
let first_tally = voting_power_in_impl::<V>(
&mut votes,
first_set.0,
first_set.1,
self.total_power_of(first_set.0),
)?;
let second_tally = voting_power_in_impl::<V>(
&mut votes,
second_set.0,
second_set.1,
self.total_power_of(second_set.0),
)?;
Ok((first_tally, second_tally))
}
}
fn voting_power_in_impl<V: signature::Verifier>(
votes: &mut NonAbsentCommitVotes,
validator_set: &ValidatorSet,
trust_threshold: TrustThreshold,
total_voting_power: u64,
) -> Result<VotingPowerTally, VerificationError> {
let mut power = VotingPowerTally::new(total_voting_power, trust_threshold);
for validator in validator_set.validators() {
if votes.has_voted::<V>(validator)? {
power.tally(validator.power());
if power.check().is_ok() {
break;
}
}
}
Ok(power)
}
#[cfg(test)]
mod tests {
use tendermint::trust_threshold::TrustThresholdFraction;
use tendermint_testgen::{
light_block::generate_signed_header, Commit, Generator, Header,
LightBlock as TestgenLightBlock, ValidatorSet, Vote as TestgenVote,
};
use super::*;
use crate::{errors::VerificationErrorDetail, types::LightBlock};
const EXPECTED_RESULT: VotingPowerTally = VotingPowerTally {
total: 100,
tallied: 0,
trust_threshold: TrustThresholdFraction::ONE_THIRD,
};
#[test]
fn test_empty_signatures() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let mut light_block: LightBlock = TestgenLightBlock::new_default(10)
.generate()
.unwrap()
.into();
light_block.signed_header.commit.signatures = vec![];
let result_ok = vp_calculator.voting_power_in(
&light_block.signed_header,
&light_block.validators,
trust_threshold,
);
assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
}
#[test]
fn test_all_signatures_absent() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let mut testgen_lb = TestgenLightBlock::new_default(10);
let mut commit = testgen_lb.commit.clone().unwrap();
commit.votes = Some(vec![]);
testgen_lb.commit = Some(commit);
let light_block: LightBlock = testgen_lb.generate().unwrap().into();
let result_ok = vp_calculator.voting_power_in(
&light_block.signed_header,
&light_block.validators,
trust_threshold,
);
assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
}
#[test]
fn test_all_signatures_nil() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let validator_set = ValidatorSet::new(vec!["a", "b"]);
let vals = validator_set.clone().validators.unwrap();
let header = Header::new(&vals);
let votes = vec![
TestgenVote::new(vals[0].clone(), header.clone()).nil(true),
TestgenVote::new(vals[1].clone(), header.clone()).nil(true),
];
let commit = Commit::new_with_votes(header.clone(), 1, votes);
let signed_header = generate_signed_header(&header, &commit).unwrap();
let valset = validator_set.generate().unwrap();
let result_ok = vp_calculator.voting_power_in(&signed_header, &valset, trust_threshold);
assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
}
#[test]
fn test_one_invalid_signature() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let mut testgen_lb = TestgenLightBlock::new_default(10);
let mut commit = testgen_lb.commit.clone().unwrap();
let mut votes = commit.votes.unwrap();
let vote = votes.pop().unwrap();
let header = vote.clone().header.unwrap().chain_id("bad-chain");
votes.push(vote.header(header));
commit.votes = Some(votes);
testgen_lb.commit = Some(commit);
let light_block: LightBlock = testgen_lb.generate().unwrap().into();
let result_err = vp_calculator.voting_power_in(
&light_block.signed_header,
&light_block.validators,
trust_threshold,
);
match result_err {
Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {},
_ => panic!("expected InvalidSignature error"),
}
}
#[test]
fn test_all_signatures_invalid() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let mut testgen_lb = TestgenLightBlock::new_default(10);
let header = testgen_lb.header.unwrap().chain_id("bad-chain");
testgen_lb.header = Some(header);
let light_block: LightBlock = testgen_lb.generate().unwrap().into();
let result_err = vp_calculator.voting_power_in(
&light_block.signed_header,
&light_block.validators,
trust_threshold,
);
match result_err {
Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {},
_ => panic!("expected InvalidSignature error"),
}
}
#[test]
fn test_signatures_from_diff_valset() {
let vp_calculator = ProdVotingPowerCalculator::default();
let trust_threshold = TrustThreshold::default();
let mut light_block: LightBlock = TestgenLightBlock::new_default(10)
.generate()
.unwrap()
.into();
light_block.validators = ValidatorSet::new(vec!["bad-val1", "bad-val2"])
.generate()
.unwrap();
let result_ok = vp_calculator.voting_power_in(
&light_block.signed_header,
&light_block.validators,
trust_threshold,
);
assert_eq!(result_ok.unwrap(), EXPECTED_RESULT);
}
}