use crate::policy::types::{PolicyDecision, RefusalCode};
use crate::state::validator::ValidatorState;
pub struct EthereumPolicy;
impl EthereumPolicy {
pub fn new() -> Self {
Self
}
pub fn check_block_proposal(
&self,
state: &ValidatorState,
slot: u64,
signing_root: &[u8; 32],
) -> PolicyDecision {
if let Some(existing_root) = state.get_block_signing_root(slot) {
if existing_root == signing_root {
PolicyDecision::Allow
} else {
tracing::warn!(
slot = slot,
"Double proposal detected: different block for same slot"
);
PolicyDecision::Refuse(RefusalCode::DoubleProposal)
}
} else {
PolicyDecision::Allow
}
}
pub fn check_attestation(
&self,
state: &ValidatorState,
source_epoch: u64,
target_epoch: u64,
signing_root: &[u8; 32],
) -> PolicyDecision {
if let Some(existing_root) = state.get_attestation_signing_root(source_epoch, target_epoch)
{
if existing_root == signing_root {
return PolicyDecision::Allow;
} else {
tracing::warn!(
source_epoch = source_epoch,
target_epoch = target_epoch,
"Double vote detected: different signing root for same source and target epoch"
);
return PolicyDecision::Refuse(RefusalCode::DoubleVote);
}
}
if self.has_attestation_for_target(state, target_epoch) {
tracing::warn!(
target_epoch = target_epoch,
"Double vote detected: attestation already exists for target epoch"
);
return PolicyDecision::Refuse(RefusalCode::DoubleVote);
}
if self.is_surround_vote(state, source_epoch, target_epoch) {
tracing::warn!(
source_epoch = source_epoch,
target_epoch = target_epoch,
"Surround vote detected"
);
return PolicyDecision::Refuse(RefusalCode::SurroundVote);
}
PolicyDecision::Allow
}
fn has_attestation_for_target(&self, state: &ValidatorState, target_epoch: u64) -> bool {
state
.attestation_history
.iter()
.any(|((_, target), _)| target == target_epoch)
}
fn is_surround_vote(&self, state: &ValidatorState, source_epoch: u64, target_epoch: u64) -> bool {
if let Some(min_target) = state.attestation_history.get_min_target_for_source_gt(source_epoch)
{
if min_target < target_epoch {
return true;
}
}
if let Some(max_target) = state.attestation_history.get_max_target_for_source_lt(source_epoch)
{
if max_target > target_epoch {
return true;
}
}
false
}
}
impl Default for EthereumPolicy {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_root(val: u8) -> [u8; 32] {
let mut root = [0u8; 32];
root[0] = val;
root
}
#[test]
fn test_block_proposal_first_sign() {
let policy = EthereumPolicy::new();
let state = ValidatorState::new([0u8; 48]);
let root = make_root(1);
let decision = policy.check_block_proposal(&state, 100, &root);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_block_proposal_same_block() {
let policy = EthereumPolicy::new();
let mut state = ValidatorState::new([0u8; 48]);
let root = make_root(1);
state.record_block_signing(100, root);
let decision = policy.check_block_proposal(&state, 100, &root);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_block_proposal_double_proposal() {
let policy = EthereumPolicy::new();
let mut state = ValidatorState::new([0u8; 48]);
let root1 = make_root(1);
let root2 = make_root(2);
state.record_block_signing(100, root1);
let decision = policy.check_block_proposal(&state, 100, &root2);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::DoubleProposal));
}
#[test]
fn test_attestation_first_sign() {
let policy = EthereumPolicy::new();
let state = ValidatorState::new([0u8; 48]);
let root = make_root(1);
let decision = policy.check_attestation(&state, 10, 11, &root);
assert_eq!(decision, PolicyDecision::Allow);
}
#[test]
fn test_attestation_double_vote() {
let policy = EthereumPolicy::new();
let mut state = ValidatorState::new([0u8; 48]);
let root1 = make_root(1);
let root2 = make_root(2);
state.record_attestation_signing(10, 11, root1);
let decision = policy.check_attestation(&state, 10, 11, &root2);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::DoubleVote));
}
#[test]
fn test_attestation_surround_vote_new_surrounds_old() {
let policy = EthereumPolicy::new();
let mut state = ValidatorState::new([0u8; 48]);
let root1 = make_root(1);
let root2 = make_root(2);
state.record_attestation_signing(5, 10, root1);
let decision = policy.check_attestation(&state, 3, 12, &root2);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::SurroundVote));
}
#[test]
fn test_attestation_surround_vote_old_surrounds_new() {
let policy = EthereumPolicy::new();
let mut state = ValidatorState::new([0u8; 48]);
let root1 = make_root(1);
let root2 = make_root(2);
state.record_attestation_signing(3, 12, root1);
let decision = policy.check_attestation(&state, 5, 10, &root2);
assert_eq!(decision, PolicyDecision::Refuse(RefusalCode::SurroundVote));
}
}