use crate::policy::types::{PolicyDecision, RefusalCode};
use crate::state::validator::{CosmosSignedMsgType, CosmosState};
pub struct CosmosPolicy;
impl CosmosPolicy {
pub fn new() -> Self {
Self
}
pub fn check_prevote(
&self,
state: &CosmosState,
height: i64,
round: i32,
block_hash: Option<[u8; 32]>,
) -> PolicyDecision {
self.check_vote(state, height, round, CosmosSignedMsgType::Prevote, block_hash)
}
pub fn check_precommit(
&self,
state: &CosmosState,
height: i64,
round: i32,
block_hash: Option<[u8; 32]>,
) -> PolicyDecision {
self.check_vote(state, height, round, CosmosSignedMsgType::Precommit, block_hash)
}
pub fn check_proposal(
&self,
state: &CosmosState,
height: i64,
round: i32,
block_hash: [u8; 32],
) -> PolicyDecision {
self.check_vote(
state,
height,
round,
CosmosSignedMsgType::Proposal,
Some(block_hash),
)
}
fn check_vote(
&self,
state: &CosmosState,
height: i64,
round: i32,
msg_type: CosmosSignedMsgType,
block_hash: Option<[u8; 32]>,
) -> PolicyDecision {
if let Some(existing) = state.get_signed_vote(height, round, msg_type) {
if existing.block_hash == block_hash {
PolicyDecision::Allow
} else {
tracing::warn!(
height = height,
round = round,
msg_type = ?msg_type,
"Cosmos double signing detected: different block for same height/round"
);
PolicyDecision::Refuse(RefusalCode::CosmosDoubleSigning)
}
} else {
PolicyDecision::Allow
}
}
pub fn validate_request(&self, height: i64, round: i32) -> bool {
if height <= 0 {
return false;
}
if round < 0 {
return false;
}
true
}
}
impl Default for CosmosPolicy {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_hash(val: u8) -> [u8; 32] {
let mut hash = [0u8; 32];
hash[0] = val;
hash
}
#[test]
fn test_prevote_first_sign() {
let policy = CosmosPolicy::new();
let state = CosmosState::new();
let hash = make_hash(1);
let decision = policy.check_prevote(&state, 100, 0, Some(hash));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_prevote_nil_first_sign() {
let policy = CosmosPolicy::new();
let state = CosmosState::new();
let decision = policy.check_prevote(&state, 100, 0, None);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_prevote_same_block_allowed() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash = make_hash(1);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash));
let decision = policy.check_prevote(&state, 100, 0, Some(hash));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_prevote_double_sign_rejected() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash1));
let decision = policy.check_prevote(&state, 100, 0, Some(hash2));
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::CosmosDoubleSigning));
}
#[test]
fn test_prevote_nil_then_block_rejected() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash = make_hash(1);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, None);
let decision = policy.check_prevote(&state, 100, 0, Some(hash));
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::CosmosDoubleSigning));
}
#[test]
fn test_prevote_block_then_nil_rejected() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash = make_hash(1);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash));
let decision = policy.check_prevote(&state, 100, 0, None);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::CosmosDoubleSigning));
}
#[test]
fn test_different_round_allowed() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash1));
let decision = policy.check_prevote(&state, 100, 1, Some(hash2));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_different_height_allowed() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash1));
let decision = policy.check_prevote(&state, 101, 0, Some(hash2));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_height_regression_allowed() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash1));
let decision = policy.check_prevote(&state, 50, 0, Some(hash2));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_precommit_independent_of_prevote() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Prevote, Some(hash1));
let decision = policy.check_precommit(&state, 100, 0, Some(hash2));
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_proposal_double_sign_rejected() {
let policy = CosmosPolicy::new();
let mut state = CosmosState::new();
let hash1 = make_hash(1);
let hash2 = make_hash(2);
state.record_vote(100, 0, CosmosSignedMsgType::Proposal, Some(hash1));
let decision = policy.check_proposal(&state, 100, 0, hash2);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::CosmosDoubleSigning));
}
#[test]
fn test_validate_request() {
let policy = CosmosPolicy::new();
assert!(policy.validate_request(1, 0));
assert!(policy.validate_request(100, 5));
assert!(!policy.validate_request(0, 0)); assert!(!policy.validate_request(-1, 0)); assert!(!policy.validate_request(1, -1)); }
}