use crate::error::{ConsensusError, Result};
use crate::leader_reputation::LeaderReputation;
use crate::voter::Vote;
use dashmap::DashMap;
use serde::{Deserialize, Deserializer, Serialize};
use std::sync::Arc;
use tenzro_crypto::pq::ML_DSA_65_VK_LEN;
use tenzro_crypto::PublicKey;
pub const BLS_G1_COMPRESSED_LEN: usize = 48;
use tenzro_types::primitives::{Address, Hash, Timestamp};
use tenzro_types::tee::{AttestationReport, AttestationResult};
fn deserialize_pq_verifying_key<'de, D>(deserializer: D) -> std::result::Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
if bytes.len() != ML_DSA_65_VK_LEN {
return Err(serde::de::Error::custom(format!(
"validator PQ verifying key has wrong length: expected {} bytes (ML-DSA-65), got {}",
ML_DSA_65_VK_LEN,
bytes.len()
)));
}
Ok(bytes)
}
fn deserialize_bls_verifying_key<'de, D>(deserializer: D) -> std::result::Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let bytes = Vec::<u8>::deserialize(deserializer)?;
if bytes.len() != BLS_G1_COMPRESSED_LEN {
return Err(serde::de::Error::custom(format!(
"validator BLS verifying key has wrong length: expected {} bytes (BLS12-381 G1 compressed), got {}",
BLS_G1_COMPRESSED_LEN,
bytes.len()
)));
}
Ok(bytes)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ValidatorInfo {
pub address: Address,
pub public_key: PublicKey,
#[serde(deserialize_with = "deserialize_pq_verifying_key")]
pub pq_public_key: Vec<u8>,
#[serde(deserialize_with = "deserialize_bls_verifying_key")]
pub bls_public_key: Vec<u8>,
pub stake: u128,
pub tee_attestation: Option<AttestationReport>,
pub tee_attestation_result: Option<AttestationResult>,
pub status: ValidatorStatus,
pub registered_at: Timestamp,
pub last_attestation_update: Option<Timestamp>,
}
impl ValidatorInfo {
pub fn new(
address: Address,
public_key: PublicKey,
pq_public_key: Vec<u8>,
bls_public_key: Vec<u8>,
stake: u128,
) -> Self {
assert_eq!(
pq_public_key.len(),
ML_DSA_65_VK_LEN,
"validator PQ verifying key has wrong length: expected {} bytes (ML-DSA-65), got {}",
ML_DSA_65_VK_LEN,
pq_public_key.len()
);
assert_eq!(
bls_public_key.len(),
BLS_G1_COMPRESSED_LEN,
"validator BLS verifying key has wrong length: expected {} bytes (BLS12-381 G1 compressed), got {}",
BLS_G1_COMPRESSED_LEN,
bls_public_key.len()
);
Self {
address,
public_key,
pq_public_key,
bls_public_key,
stake,
tee_attestation: None,
tee_attestation_result: None,
status: ValidatorStatus::Active,
registered_at: Timestamp::now(),
last_attestation_update: None,
}
}
pub fn composite_public_key(&self) -> tenzro_crypto::composite::CompositePublicKey {
tenzro_crypto::composite::CompositePublicKey::new(
self.public_key.clone(),
self.pq_public_key.clone(),
)
}
pub fn with_tee_attestation(
mut self,
attestation: AttestationReport,
result: AttestationResult,
) -> Self {
self.tee_attestation = Some(attestation);
self.tee_attestation_result = Some(result);
self.last_attestation_update = Some(Timestamp::now());
self
}
pub fn has_valid_tee_attestation(&self) -> bool {
self.tee_attestation_result
.as_ref()
.map(|r| r.valid)
.unwrap_or(false)
}
pub fn is_active(&self) -> bool {
self.status == ValidatorStatus::Active
}
pub fn voting_power(&self) -> u128 {
if self.is_active() {
self.stake
} else {
0
}
}
pub fn leader_priority(&self) -> u128 {
self.voting_power()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValidatorStatus {
Active,
Inactive,
Jailed,
Unbonding,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidatorSet {
pub epoch: u64,
validators: Vec<ValidatorInfo>,
total_stake: u128,
pub epoch_start: Timestamp,
}
impl ValidatorSet {
pub fn new(epoch: u64, validators: Vec<ValidatorInfo>) -> Result<Self> {
if validators.is_empty() {
return Err(ConsensusError::InvalidValidatorSet(
"Validator set cannot be empty".to_string(),
));
}
let total_stake = validators.iter().map(|v| v.voting_power()).sum();
Ok(Self {
epoch,
validators,
total_stake,
epoch_start: Timestamp::now(),
})
}
pub fn get(&self, index: usize) -> Option<&ValidatorInfo> {
self.validators.get(index)
}
pub fn get_by_address(&self, address: &Address) -> Option<&ValidatorInfo> {
self.validators.iter().find(|v| &v.address == address)
}
pub fn index_of(&self, address: &Address) -> Option<usize> {
self.validators.iter().position(|v| &v.address == address)
}
pub fn active_validators(&self) -> &[ValidatorInfo] {
&self.validators
}
pub fn len(&self) -> usize {
self.validators.len()
}
pub fn is_empty(&self) -> bool {
self.validators.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<'_, ValidatorInfo> {
self.validators.iter()
}
pub fn total_stake(&self) -> u128 {
self.total_stake
}
pub fn total_voting_power(&self) -> u128 {
self.validators.iter().map(|v| v.voting_power()).sum()
}
pub fn is_validator(&self, address: &Address) -> bool {
self.get_by_address(address).is_some()
}
pub fn select_leader_round_robin(&self, view: u64) -> Result<&ValidatorInfo> {
if self.validators.is_empty() {
return Err(ConsensusError::InvalidValidatorSet(
"No validators available".to_string(),
));
}
let index = (view as usize) % self.validators.len();
Ok(&self.validators[index])
}
pub fn quorum_threshold(&self) -> usize {
let n = self.validators.len();
let f = (n.saturating_sub(1)) / 3;
2 * f + 1
}
pub fn tee_attested_validators(&self) -> Vec<&ValidatorInfo> {
self.validators
.iter()
.filter(|v| v.has_valid_tee_attestation())
.collect()
}
pub fn tee_attestation_rate(&self) -> f64 {
if self.validators.is_empty() {
return 0.0;
}
let attested = self.tee_attested_validators().len();
(attested as f64 / self.validators.len() as f64) * 100.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EquivocationEvidence {
pub validator: Address,
pub view: u64,
pub vote1: Vote,
pub vote2: Vote,
pub detected_at: Timestamp,
}
impl EquivocationEvidence {
pub fn new(validator: Address, view: u64, vote1: Vote, vote2: Vote) -> Self {
Self {
validator,
view,
vote1,
vote2,
detected_at: Timestamp::now(),
}
}
pub fn is_valid(&self) -> bool {
self.vote1.view == self.vote2.view
&& self.vote1.view == self.view
&& self.vote1.voter == self.vote2.voter
&& self.vote1.voter == self.validator
&& self.vote1.block_hash != self.vote2.block_hash
&& self.vote1.vote_type == self.vote2.vote_type
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ValidatorViewKey {
validator: Address,
view: u64,
}
pub struct EquivocationDetector {
votes: Arc<DashMap<ValidatorViewKey, (Hash, crate::voter::VoteType)>>,
evidence: Arc<DashMap<(Address, u64), EquivocationEvidence>>,
storage: Option<Arc<dyn tenzro_storage::KvStore>>,
}
impl EquivocationDetector {
pub fn new() -> Self {
Self {
votes: Arc::new(DashMap::new()),
evidence: Arc::new(DashMap::new()),
storage: None,
}
}
pub fn with_storage(storage: Arc<dyn tenzro_storage::KvStore>) -> Self {
let d = Self {
votes: Arc::new(DashMap::new()),
evidence: Arc::new(DashMap::new()),
storage: Some(storage),
};
d.hydrate();
d
}
fn vote_key(validator: &Address, view: u64) -> Vec<u8> {
let mut k = b"equivocation/votes/".to_vec();
k.extend_from_slice(validator.as_bytes());
k.push(b'/');
k.extend_from_slice(&view.to_le_bytes());
k
}
fn evidence_key(validator: &Address, view: u64) -> Vec<u8> {
let mut k = b"equivocation/evidence/".to_vec();
k.extend_from_slice(validator.as_bytes());
k.push(b'/');
k.extend_from_slice(&view.to_le_bytes());
k
}
fn hydrate(&self) {
let Some(ref storage) = self.storage else {
return;
};
if let Ok(entries) =
storage.scan_prefix(tenzro_storage::CF_AUDIT, b"equivocation/votes/")
{
for (key, value) in entries {
if let Some(vk) = Self::parse_vote_key(&key) {
if let Ok(payload) =
serde_json::from_slice::<(Hash, crate::voter::VoteType)>(&value)
{
self.votes.insert(vk, payload);
}
}
}
}
if let Ok(entries) =
storage.scan_prefix(tenzro_storage::CF_AUDIT, b"equivocation/evidence/")
{
for (key, value) in entries {
if let Some((addr, view)) = Self::parse_evidence_key(&key) {
if let Ok(ev) =
serde_json::from_slice::<EquivocationEvidence>(&value)
{
self.evidence.insert((addr, view), ev);
}
}
}
}
}
fn parse_vote_key(key: &[u8]) -> Option<ValidatorViewKey> {
let prefix = b"equivocation/votes/";
if !key.starts_with(prefix) {
return None;
}
let rest = &key[prefix.len()..];
if rest.len() < 9 {
return None;
}
let addr_end = rest.len() - 9;
if rest[addr_end] != b'/' {
return None;
}
let validator = Address::from_bytes(&rest[..addr_end])?;
let mut view_buf = [0u8; 8];
view_buf.copy_from_slice(&rest[addr_end + 1..]);
let view = u64::from_le_bytes(view_buf);
Some(ValidatorViewKey { validator, view })
}
fn parse_evidence_key(key: &[u8]) -> Option<(Address, u64)> {
let prefix = b"equivocation/evidence/";
if !key.starts_with(prefix) {
return None;
}
let rest = &key[prefix.len()..];
if rest.len() < 9 {
return None;
}
let addr_end = rest.len() - 9;
if rest[addr_end] != b'/' {
return None;
}
let validator = Address::from_bytes(&rest[..addr_end])?;
let mut view_buf = [0u8; 8];
view_buf.copy_from_slice(&rest[addr_end + 1..]);
let view = u64::from_le_bytes(view_buf);
Some((validator, view))
}
fn persist_vote(
&self,
vk: &ValidatorViewKey,
payload: &(Hash, crate::voter::VoteType),
) {
if let Some(ref storage) = self.storage {
if let Ok(bytes) = serde_json::to_vec(payload) {
let _ = storage.put(
tenzro_storage::CF_AUDIT,
&Self::vote_key(&vk.validator, vk.view),
&bytes,
);
}
}
}
fn persist_evidence(&self, ev: &EquivocationEvidence) {
if let Some(ref storage) = self.storage {
if let Ok(bytes) = serde_json::to_vec(ev) {
let _ = storage.put(
tenzro_storage::CF_AUDIT,
&Self::evidence_key(&ev.validator, ev.view),
&bytes,
);
}
}
}
pub fn check_vote(&self, vote: &Vote) -> Result<Option<EquivocationEvidence>> {
let key = ValidatorViewKey {
validator: vote.voter,
view: vote.view,
};
if let Some(existing) = self.votes.get(&key) {
let (existing_hash, existing_type) = *existing;
if existing_hash == vote.block_hash && existing_type == vote.vote_type {
return Err(ConsensusError::AlreadyVoted(vote.view));
}
if existing_type == vote.vote_type {
let placeholder_sig = tenzro_crypto::composite::CompositeSignature::new(
Vec::new(),
Vec::new(),
);
let placeholder_bls = vote.bls_signature.clone();
let vote1 = Vote::new(
vote.view,
vote.height,
existing_hash,
vote.voter,
placeholder_sig,
vote.public_key.clone(),
placeholder_bls,
existing_type,
vote.high_qc_view,
);
let evidence = EquivocationEvidence::new(
vote.voter,
vote.view,
vote1,
vote.clone(),
);
self.evidence.insert((vote.voter, vote.view), evidence.clone());
self.persist_evidence(&evidence);
tracing::warn!(
validator = %vote.voter,
view = vote.view,
block1 = %existing_hash,
block2 = %vote.block_hash,
"Equivocation detected"
);
return Ok(Some(evidence));
}
}
let payload = (vote.block_hash, vote.vote_type);
self.votes.insert(key.clone(), payload);
self.persist_vote(&key, &payload);
Ok(None)
}
pub fn get_all_evidence(&self) -> Vec<EquivocationEvidence> {
self.evidence.iter().map(|entry| entry.value().clone()).collect()
}
pub fn get_evidence(&self, validator: &Address, view: u64) -> Option<EquivocationEvidence> {
self.evidence.get(&(*validator, view)).map(|e| e.clone())
}
pub fn cleanup_old_votes(&self, min_view: u64) {
let mut pruned: Vec<ValidatorViewKey> = Vec::new();
self.votes.retain(|key, _| {
let keep = key.view >= min_view;
if !keep {
pruned.push(key.clone());
}
keep
});
if let Some(ref storage) = self.storage {
for key in &pruned {
let _ = storage.delete(
tenzro_storage::CF_AUDIT,
&Self::vote_key(&key.validator, key.view),
);
}
}
}
pub fn vote_count(&self) -> usize {
self.votes.len()
}
pub fn evidence_count(&self) -> usize {
self.evidence.len()
}
}
impl Default for EquivocationDetector {
fn default() -> Self {
Self::new()
}
}
pub trait ProposerElection: Send + Sync {
fn select_leader(
&self,
round: u64,
epoch: u64,
prev_block_id: [u8; 32],
validator_set: &ValidatorSet,
) -> Result<Address>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RoundRobinProposer;
impl RoundRobinProposer {
pub const fn new() -> Self {
Self
}
}
impl ProposerElection for RoundRobinProposer {
fn select_leader(
&self,
round: u64,
_epoch: u64,
_prev_block_id: [u8; 32],
validator_set: &ValidatorSet,
) -> Result<Address> {
validator_set
.select_leader_round_robin(round)
.map(|v| v.address)
}
}
#[derive(Clone)]
pub struct ReputationProposer {
reputation: Arc<LeaderReputation>,
}
impl ReputationProposer {
pub fn new(reputation: Arc<LeaderReputation>) -> Self {
Self { reputation }
}
pub fn reputation(&self) -> &Arc<LeaderReputation> {
&self.reputation
}
}
impl ProposerElection for ReputationProposer {
fn select_leader(
&self,
round: u64,
epoch: u64,
prev_block_id: [u8; 32],
validator_set: &ValidatorSet,
) -> Result<Address> {
let prev_hash = Hash::new(prev_block_id);
self.reputation
.select_leader(round, epoch, &prev_hash, validator_set)
.map(|v| v.address)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tenzro_crypto::bls::BlsKeyPair;
use tenzro_crypto::pq::MlDsaSigningKey;
use tenzro_crypto::{KeyPair, KeyType};
fn create_test_validator(stake: u128) -> ValidatorInfo {
let keypair = KeyPair::generate(KeyType::Ed25519).unwrap();
let crypto_addr = keypair.address();
let mut addr_bytes = [0u8; 32];
addr_bytes[..20].copy_from_slice(crypto_addr.as_bytes());
let address = Address::new(addr_bytes);
let pq = MlDsaSigningKey::generate();
let bls = BlsKeyPair::generate().unwrap();
ValidatorInfo::new(
address,
keypair.public_key().clone(),
pq.verifying_key_bytes().to_vec(),
bls.public_key().to_bytes().to_vec(),
stake,
)
}
#[test]
fn test_validator_info() {
let validator = create_test_validator(1000);
assert_eq!(validator.voting_power(), 1000);
assert!(validator.is_active());
assert!(!validator.has_valid_tee_attestation());
}
#[test]
fn test_validator_set_creation() {
let validators = vec![
create_test_validator(1000),
create_test_validator(2000),
create_test_validator(3000),
];
let set = ValidatorSet::new(1, validators).unwrap();
assert_eq!(set.len(), 3);
assert_eq!(set.total_voting_power(), 6000);
assert_eq!(set.quorum_threshold(), 1); }
#[test]
fn test_leader_selection() {
let validators = vec![
create_test_validator(1000),
create_test_validator(2000),
create_test_validator(3000),
];
let set = ValidatorSet::new(1, validators).unwrap();
let leader0 = set.select_leader_round_robin(0).unwrap();
let leader1 = set.select_leader_round_robin(1).unwrap();
let leader2 = set.select_leader_round_robin(2).unwrap();
let leader3 = set.select_leader_round_robin(3).unwrap();
assert_eq!(leader0.address, set.validators[0].address);
assert_eq!(leader1.address, set.validators[1].address);
assert_eq!(leader2.address, set.validators[2].address);
assert_eq!(leader3.address, set.validators[0].address); }
#[test]
fn test_empty_validator_set() {
let result = ValidatorSet::new(1, vec![]);
assert!(result.is_err());
}
#[test]
fn test_equivocation_detection() {
use crate::voter::{Vote, VoteType};
use tenzro_crypto::composite::{CompositePublicKey, CompositeSignature};
use tenzro_types::primitives::BlockHeight;
let detector = EquivocationDetector::new();
let validator = create_test_validator(1000);
let placeholder_pk = CompositePublicKey::new(
validator.public_key.clone(),
validator.pq_public_key.clone(),
);
let placeholder_sig = CompositeSignature::new(vec![0u8; 64], vec![0u8; 3309]);
let placeholder_bls_kp = BlsKeyPair::generate().unwrap();
let placeholder_bls = placeholder_bls_kp.sign(b"__placeholder__");
let vote1 = Vote::new(
1,
BlockHeight::from(10),
Hash::default(),
validator.address,
placeholder_sig.clone(),
placeholder_pk.clone(),
placeholder_bls.clone(),
VoteType::Prepare,
0,
);
let result = detector.check_vote(&vote1);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let result = detector.check_vote(&vote1);
assert!(result.is_err());
let mut different_hash = [0u8; 32];
different_hash[0] = 1;
let vote2 = Vote::new(
1,
BlockHeight::from(10),
Hash::new(different_hash),
validator.address,
placeholder_sig.clone(),
placeholder_pk.clone(),
placeholder_bls.clone(),
VoteType::Prepare,
0,
);
let result = detector.check_vote(&vote2);
assert!(result.is_ok());
let evidence = result.unwrap();
assert!(evidence.is_some());
let evidence = evidence.unwrap();
assert_eq!(evidence.validator, validator.address);
assert_eq!(evidence.view, 1);
assert!(evidence.is_valid());
}
#[test]
fn test_equivocation_detector_cleanup() {
use crate::voter::{Vote, VoteType};
use tenzro_crypto::composite::{CompositePublicKey, CompositeSignature};
use tenzro_types::primitives::BlockHeight;
let detector = EquivocationDetector::new();
let validator = create_test_validator(1000);
let placeholder_pk = CompositePublicKey::new(
validator.public_key.clone(),
validator.pq_public_key.clone(),
);
let placeholder_sig = CompositeSignature::new(vec![0u8; 64], vec![0u8; 3309]);
let placeholder_bls_kp = BlsKeyPair::generate().unwrap();
let placeholder_bls = placeholder_bls_kp.sign(b"__placeholder__");
for view in 1..=3 {
let vote = Vote::new(
view,
BlockHeight::from(10),
Hash::default(),
validator.address,
placeholder_sig.clone(),
placeholder_pk.clone(),
placeholder_bls.clone(),
VoteType::Prepare,
0,
);
let _ = detector.check_vote(&vote);
}
assert_eq!(detector.vote_count(), 3);
detector.cleanup_old_votes(2);
assert_eq!(detector.vote_count(), 2);
}
}