use chia_sha2::Sha256;
use dig_protocol::Bytes32;
use serde::{Deserialize, Serialize};
use crate::constants::DOMAIN_BEACON_ATTESTER;
use crate::evidence::checkpoint::Checkpoint;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct AttestationData {
pub slot: u64,
pub index: u64,
pub beacon_block_root: Bytes32,
pub source: Checkpoint,
pub target: Checkpoint,
}
impl AttestationData {
pub fn signing_root(&self, network_id: &Bytes32) -> Bytes32 {
let mut h = Sha256::new();
h.update(DOMAIN_BEACON_ATTESTER);
h.update(network_id.as_ref());
h.update(self.slot.to_le_bytes());
h.update(self.index.to_le_bytes());
h.update(self.beacon_block_root.as_ref());
h.update(self.source.epoch.to_le_bytes());
h.update(self.source.root.as_ref());
h.update(self.target.epoch.to_le_bytes());
h.update(self.target.root.as_ref());
let out: [u8; 32] = h.finalize();
Bytes32::new(out)
}
#[must_use]
pub(crate) fn is_slashable_against(&self, other: &AttestationData) -> bool {
let double_vote = self.target.epoch == other.target.epoch && self != other;
let surround_vote = (self.source.epoch < other.source.epoch
&& self.target.epoch > other.target.epoch)
|| (other.source.epoch < self.source.epoch && other.target.epoch > self.target.epoch);
double_vote || surround_vote
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::evidence::checkpoint::Checkpoint;
fn att(source_epoch: u64, target_epoch: u64) -> AttestationData {
AttestationData {
slot: target_epoch,
index: 0,
beacon_block_root: Bytes32::new([0u8; 32]),
source: Checkpoint {
epoch: source_epoch,
root: Bytes32::new([source_epoch as u8; 32]),
},
target: Checkpoint {
epoch: target_epoch,
root: Bytes32::new([target_epoch as u8; 32]),
},
}
}
#[test]
fn double_vote_same_target_different_data_is_slashable() {
let a = att(1, 5);
let mut b = att(1, 5);
b.beacon_block_root = Bytes32::new([9u8; 32]);
assert!(a.is_slashable_against(&b));
assert!(b.is_slashable_against(&a));
}
#[test]
fn byte_identical_attestations_are_not_slashable() {
let a = att(1, 5);
let b = att(1, 5);
assert!(!a.is_slashable_against(&b));
}
#[test]
fn surround_vote_is_slashable_both_directions() {
let a = att(1, 6);
let b = att(2, 5);
assert!(a.is_slashable_against(&b));
assert!(b.is_slashable_against(&a));
}
#[test]
fn distinct_non_surrounding_non_double_vote_is_not_slashable() {
let a = att(1, 2);
let b = att(3, 4);
assert!(!a.is_slashable_against(&b));
assert!(!b.is_slashable_against(&a));
}
#[test]
fn predicate_is_symmetric_across_a_matrix() {
for sa in 0..4u64 {
for ta in sa..sa + 4 {
for sb in 0..4u64 {
for tb in sb..sb + 4 {
let a = att(sa, ta);
let b = att(sb, tb);
assert_eq!(
a.is_slashable_against(&b),
b.is_slashable_against(&a),
"asymmetry at a=({sa},{ta}) b=({sb},{tb})"
);
}
}
}
}
}
}