use rialo_s_pubkey::Pubkey;
use serde::{Deserialize, Serialize};
use crate::{
consensus_crypto::{AuthorityPublicKey, NetworkPublicKey, ProtocolPublicKey},
Multiaddr,
};
pub const BLAKE3_HASH_SIZE: usize = 32;
pub type Blake3Hash = [u8; BLAKE3_HASH_SIZE];
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
)]
#[repr(transparent)]
pub struct EpochIdentifier(u64);
impl EpochIdentifier {
pub const fn new(epoch: u64) -> Self {
Self(epoch)
}
pub const fn as_u64(self) -> u64 {
self.0
}
pub const fn to_le_bytes(self) -> [u8; 8] {
self.0.to_le_bytes()
}
}
impl From<u64> for EpochIdentifier {
fn from(epoch: u64) -> Self {
Self(epoch)
}
}
impl From<EpochIdentifier> for u64 {
fn from(epoch: EpochIdentifier) -> Self {
epoch.0
}
}
impl std::fmt::Display for EpochIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub const BLS12381_PUBLIC_KEY_SIZE: usize = 96;
pub const ED25519_PUBLIC_KEY_SIZE: usize = 32;
pub const ED25519_SIGNATURE_SIZE: usize = 64;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct EpochChangeConfig {
pub current_epoch: EpochIdentifier,
pub new_epoch: EpochIdentifier,
pub validators: Vec<ValidatorInfo>,
pub consensus_config: Option<EpochConsensusConfig>,
}
impl EpochChangeConfig {
pub fn deterministic_hash(&self) -> [u8; BLAKE3_HASH_SIZE] {
let mut hasher = blake3::Hasher::new();
hasher.update(&self.current_epoch.to_le_bytes());
hasher.update(&self.new_epoch.to_le_bytes());
hasher.update(&(self.validators.len() as u64).to_le_bytes());
for validator in &self.validators {
hasher.update(&validator.stake.to_le_bytes());
let consensus_addr_bytes = validator.consensus_address.to_string();
hasher.update(&(consensus_addr_bytes.len() as u64).to_le_bytes());
hasher.update(consensus_addr_bytes.as_bytes());
let state_sync_addr_bytes = validator.state_sync_address.to_string();
hasher.update(&(state_sync_addr_bytes.len() as u64).to_le_bytes());
hasher.update(state_sync_addr_bytes.as_bytes());
hasher.update(&(validator.hostname.len() as u64).to_le_bytes());
hasher.update(validator.hostname.as_bytes());
hasher.update(&validator.authority_key.to_bytes());
hasher.update(&validator.protocol_key.to_bytes());
hasher.update(&validator.network_key.to_bytes());
hasher.update(validator.signing_key.as_ref());
}
match &self.consensus_config {
Some(config) => {
hasher.update(&[1u8]);
Self::hash_optional_usize(&mut hasher, config.num_leaders_per_round);
Self::hash_optional_u64(&mut hasher, config.max_transactions_in_block_bytes);
Self::hash_optional_u64(&mut hasher, config.max_num_transactions_in_block);
Self::hash_optional_u64(&mut hasher, config.max_transaction_size_bytes);
Self::hash_optional_u32(&mut hasher, config.gc_depth);
Self::hash_optional_bool(&mut hasher, config.zstd_compression);
}
None => {
hasher.update(&[0u8]); }
}
*hasher.finalize().as_bytes()
}
fn hash_optional_usize(hasher: &mut blake3::Hasher, value: Option<usize>) {
match value {
Some(v) => {
hasher.update(&[1u8]);
hasher.update(&(v as u64).to_le_bytes());
}
None => {
hasher.update(&[0u8]);
}
}
}
fn hash_optional_u64(hasher: &mut blake3::Hasher, value: Option<u64>) {
match value {
Some(v) => {
hasher.update(&[1u8]);
hasher.update(&v.to_le_bytes());
}
None => {
hasher.update(&[0u8]);
}
}
}
fn hash_optional_u32(hasher: &mut blake3::Hasher, value: Option<u32>) {
match value {
Some(v) => {
hasher.update(&[1u8]);
hasher.update(&v.to_le_bytes());
}
None => {
hasher.update(&[0u8]);
}
}
}
fn hash_optional_bool(hasher: &mut blake3::Hasher, value: Option<bool>) {
match value {
Some(v) => {
hasher.update(&[1u8]);
hasher.update(&[v as u8]);
}
None => {
hasher.update(&[0u8]);
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ValidatorInfo {
pub stake: u64,
pub consensus_address: Multiaddr,
pub state_sync_address: Multiaddr,
pub hostname: String,
pub authority_key: AuthorityPublicKey,
pub protocol_key: ProtocolPublicKey,
pub network_key: NetworkPublicKey,
pub signing_key: Pubkey,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct EpochConsensusConfig {
pub num_leaders_per_round: Option<usize>,
pub max_transactions_in_block_bytes: Option<u64>,
pub max_num_transactions_in_block: Option<u64>,
pub max_transaction_size_bytes: Option<u64>,
pub gc_depth: Option<u32>,
pub zstd_compression: Option<bool>,
}
pub const READY_MESSAGE_SIGNING_SIZE: usize = 8 + BLAKE3_HASH_SIZE;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ValidatorReadyMessage {
pub current_epoch: EpochIdentifier,
pub epoch_config_hash: [u8; BLAKE3_HASH_SIZE],
pub authority_key: AuthorityPublicKey,
pub protocol_key: ProtocolPublicKey,
#[serde(with = "serde_big_array::BigArray")]
pub signature: [u8; ED25519_SIGNATURE_SIZE],
}
impl ValidatorReadyMessage {
pub fn signing_message(
current_epoch: EpochIdentifier,
epoch_config_hash: &[u8; BLAKE3_HASH_SIZE],
) -> [u8; READY_MESSAGE_SIGNING_SIZE] {
let mut msg = [0u8; READY_MESSAGE_SIGNING_SIZE];
msg[0..8].copy_from_slice(¤t_epoch.to_le_bytes());
msg[8..READY_MESSAGE_SIGNING_SIZE].copy_from_slice(epoch_config_hash);
msg
}
pub fn message_bytes(&self) -> [u8; READY_MESSAGE_SIGNING_SIZE] {
Self::signing_message(self.current_epoch, &self.epoch_config_hash)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct HandoverCertificate {
pub block_index: u64,
pub source_epoch: EpochIdentifier,
pub dest_epoch: EpochIdentifier,
pub epoch_config: EpochChangeConfig,
pub previous_handover_hash: Blake3Hash,
}
impl HandoverCertificate {
pub fn deterministic_hash(&self) -> Blake3Hash {
let mut hasher = blake3::Hasher::new();
hasher.update(&self.block_index.to_le_bytes());
hasher.update(&self.source_epoch.to_le_bytes());
hasher.update(&self.dest_epoch.to_le_bytes());
hasher.update(&self.epoch_config.deterministic_hash());
hasher.update(&self.previous_handover_hash);
*hasher.finalize().as_bytes()
}
pub fn genesis(epoch: EpochIdentifier, epoch_config: EpochChangeConfig) -> Self {
Self {
block_index: 0,
source_epoch: epoch,
dest_epoch: epoch,
epoch_config,
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
}
}
pub fn is_genesis(&self) -> bool {
self.source_epoch == EpochIdentifier::from(0) && self.dest_epoch == EpochIdentifier::from(0)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SnapshotAttestation {
pub block_height: u64,
pub keysvals_hash: [u8; BLAKE3_HASH_SIZE],
pub authority_key: AuthorityPublicKey,
pub protocol_key: ProtocolPublicKey,
#[serde(with = "serde_big_array::BigArray")]
pub signature: [u8; ED25519_SIGNATURE_SIZE],
}
pub const SNAPSHOT_ATTESTATION_SIGNING_SIZE: usize = 8 + BLAKE3_HASH_SIZE;
impl SnapshotAttestation {
pub fn signing_message(
block_height: u64,
keysvals_hash: &[u8; BLAKE3_HASH_SIZE],
) -> [u8; SNAPSHOT_ATTESTATION_SIGNING_SIZE] {
let mut msg = [0u8; SNAPSHOT_ATTESTATION_SIGNING_SIZE];
msg[0..8].copy_from_slice(&block_height.to_le_bytes());
msg[8..SNAPSHOT_ATTESTATION_SIGNING_SIZE].copy_from_slice(keysvals_hash);
msg
}
pub fn message_bytes(&self) -> [u8; SNAPSHOT_ATTESTATION_SIGNING_SIZE] {
Self::signing_message(self.block_height, &self.keysvals_hash)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum AdminTransaction {
EpochChange(Box<EpochChangeConfig>),
Ready(Box<ValidatorReadyMessage>),
Handover(Box<HandoverCertificate>),
SnapshotAttestation(Box<SnapshotAttestation>),
}
#[cfg(test)]
mod tests {
use fastcrypto::{ed25519, traits::KeyPair};
use rand::SeedableRng;
use super::*;
use crate::consensus_crypto::{AuthorityPublicKey, NetworkPublicKey, ProtocolPublicKey};
fn create_test_keys(seed: u64) -> (AuthorityPublicKey, ProtocolPublicKey, NetworkPublicKey) {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let authority_key = AuthorityPublicKey::new(
fastcrypto::bls12381::min_sig::BLS12381KeyPair::generate(&mut rng)
.public()
.clone(),
);
let protocol_key =
ProtocolPublicKey::new(ed25519::Ed25519KeyPair::generate(&mut rng).public().clone());
let network_key =
NetworkPublicKey::new(ed25519::Ed25519KeyPair::generate(&mut rng).public().clone());
(authority_key, protocol_key, network_key)
}
#[test]
fn test_epoch_change_config_serialization() {
let (authority_key, protocol_key, network_key) = create_test_keys(42);
let config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(1),
validators: vec![ValidatorInfo {
stake: 1000,
consensus_address: "/ip4/127.0.0.1/udp/10100".parse().unwrap(),
state_sync_address: "/ip4/127.0.0.1/udp/20100".parse().unwrap(),
hostname: "validator-0".to_string(),
authority_key,
protocol_key,
network_key,
signing_key: Pubkey::new_unique(),
}],
consensus_config: None,
};
let serialized = serde_json::to_string(&config).expect("serialization should succeed");
let deserialized: EpochChangeConfig =
serde_json::from_str(&serialized).expect("deserialization should succeed");
assert_eq!(deserialized.current_epoch, config.current_epoch);
assert_eq!(deserialized.new_epoch, config.new_epoch);
assert_eq!(deserialized.validators.len(), 1);
assert_eq!(deserialized.validators[0].stake, 1000);
}
#[test]
fn test_admin_transaction_serialization() {
let config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(4),
new_epoch: EpochIdentifier::new(5),
validators: vec![],
consensus_config: Some(EpochConsensusConfig {
num_leaders_per_round: Some(2),
..Default::default()
}),
};
let admin_tx = AdminTransaction::EpochChange(Box::new(config));
let serialized = serde_json::to_string(&admin_tx).expect("serialization should succeed");
let deserialized: AdminTransaction =
serde_json::from_str(&serialized).expect("deserialization should succeed");
match deserialized {
AdminTransaction::EpochChange(cfg) => {
assert_eq!(cfg.new_epoch, EpochIdentifier::new(5));
assert!(cfg.consensus_config.is_some());
}
_ => panic!("Expected EpochChange"),
}
}
#[test]
fn test_validator_ready_message_serialization() {
let (authority_key, protocol_key, _) = create_test_keys(43);
let epoch_config_hash = [0xABu8; BLAKE3_HASH_SIZE];
let ready_msg = ValidatorReadyMessage {
current_epoch: EpochIdentifier::new(0),
epoch_config_hash,
authority_key: authority_key.clone(),
protocol_key: protocol_key.clone(),
signature: [2u8; ED25519_SIGNATURE_SIZE],
};
let serialized = serde_json::to_string(&ready_msg).expect("serialization should succeed");
let deserialized: ValidatorReadyMessage =
serde_json::from_str(&serialized).expect("deserialization should succeed");
assert_eq!(deserialized.current_epoch, EpochIdentifier::new(0));
assert_eq!(deserialized.epoch_config_hash, epoch_config_hash);
assert_eq!(deserialized.authority_key, authority_key);
assert_eq!(deserialized.protocol_key, protocol_key);
assert_eq!(deserialized.signature, ready_msg.signature);
}
#[test]
fn test_validator_ready_message_signing_message() {
let (authority_key, protocol_key, _) = create_test_keys(44);
let epoch_config_hash = [0xABu8; BLAKE3_HASH_SIZE];
let msg =
ValidatorReadyMessage::signing_message(EpochIdentifier::new(0), &epoch_config_hash);
let mut expected = [0u8; READY_MESSAGE_SIGNING_SIZE];
expected[0..8].copy_from_slice(&0u64.to_le_bytes());
expected[8..READY_MESSAGE_SIGNING_SIZE].copy_from_slice(&epoch_config_hash);
assert_eq!(msg, expected);
let ready_msg = ValidatorReadyMessage {
current_epoch: EpochIdentifier::new(5),
epoch_config_hash,
authority_key,
protocol_key,
signature: [2u8; ED25519_SIGNATURE_SIZE],
};
let msg_bytes = ready_msg.message_bytes();
let mut expected2 = [0u8; READY_MESSAGE_SIGNING_SIZE];
expected2[0..8].copy_from_slice(&5u64.to_le_bytes());
expected2[8..READY_MESSAGE_SIGNING_SIZE].copy_from_slice(&epoch_config_hash);
assert_eq!(msg_bytes, expected2);
}
#[test]
fn test_admin_transaction_ready_serialization() {
let (authority_key, protocol_key, _) = create_test_keys(45);
let epoch_config_hash = [0xABu8; BLAKE3_HASH_SIZE];
let ready_msg = ValidatorReadyMessage {
current_epoch: EpochIdentifier::new(0),
epoch_config_hash,
authority_key,
protocol_key,
signature: [2u8; ED25519_SIGNATURE_SIZE],
};
let admin_tx = AdminTransaction::Ready(Box::new(ready_msg));
let serialized = serde_json::to_string(&admin_tx).expect("serialization should succeed");
let deserialized: AdminTransaction =
serde_json::from_str(&serialized).expect("deserialization should succeed");
match deserialized {
AdminTransaction::Ready(msg) => {
assert_eq!(msg.current_epoch, EpochIdentifier::new(0));
assert_eq!(msg.epoch_config_hash, epoch_config_hash);
}
_ => panic!("Expected Ready"),
}
}
#[test]
fn test_handover_certificate_serialization() {
let epoch_config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(1),
validators: vec![],
consensus_config: None,
};
let cert = HandoverCertificate {
block_index: 42,
source_epoch: EpochIdentifier::new(0),
dest_epoch: EpochIdentifier::new(1),
epoch_config,
previous_handover_hash: [0xABu8; BLAKE3_HASH_SIZE],
};
let serialized = serde_json::to_string(&cert).expect("serialization should succeed");
let deserialized: HandoverCertificate =
serde_json::from_str(&serialized).expect("deserialization should succeed");
assert_eq!(deserialized.block_index, 42);
assert_eq!(deserialized.source_epoch, EpochIdentifier::new(0));
assert_eq!(deserialized.dest_epoch, EpochIdentifier::new(1));
assert_eq!(
deserialized.previous_handover_hash,
[0xABu8; BLAKE3_HASH_SIZE]
);
}
#[test]
fn test_handover_certificate_deterministic_hash() {
let epoch_config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(1),
validators: vec![],
consensus_config: None,
};
let cert1 = HandoverCertificate {
block_index: 42,
source_epoch: EpochIdentifier::new(0),
dest_epoch: EpochIdentifier::new(1),
epoch_config: epoch_config.clone(),
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
};
let cert2 = cert1.clone();
assert_eq!(cert1.deterministic_hash(), cert2.deterministic_hash());
let cert3 = HandoverCertificate {
block_index: 43,
..cert1.clone()
};
assert_ne!(cert1.deterministic_hash(), cert3.deterministic_hash());
let cert4 = HandoverCertificate {
previous_handover_hash: [1u8; BLAKE3_HASH_SIZE],
..cert1.clone()
};
assert_ne!(cert1.deterministic_hash(), cert4.deterministic_hash());
}
#[test]
fn test_handover_certificate_genesis() {
let epoch_config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(0),
validators: vec![],
consensus_config: None,
};
let genesis = HandoverCertificate::genesis(EpochIdentifier::new(0), epoch_config.clone());
assert_eq!(genesis.block_index, 0);
assert_eq!(genesis.source_epoch, EpochIdentifier::new(0));
assert_eq!(genesis.dest_epoch, EpochIdentifier::new(0));
assert_eq!(genesis.previous_handover_hash, [0u8; BLAKE3_HASH_SIZE]);
assert_eq!(genesis.epoch_config, epoch_config);
}
#[test]
fn test_handover_certificate_is_genesis() {
let epoch_config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(0),
validators: vec![],
consensus_config: None,
};
let genesis = HandoverCertificate::genesis(EpochIdentifier::new(0), epoch_config.clone());
assert!(genesis.is_genesis());
let handover_0_to_1 = HandoverCertificate {
block_index: 100,
source_epoch: EpochIdentifier::new(0),
dest_epoch: EpochIdentifier::new(1),
epoch_config: epoch_config.clone(),
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
};
assert!(!handover_0_to_1.is_genesis());
let handover_1_to_1 = HandoverCertificate {
block_index: 200,
source_epoch: EpochIdentifier::new(1),
dest_epoch: EpochIdentifier::new(1),
epoch_config: epoch_config.clone(),
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
};
assert!(!handover_1_to_1.is_genesis());
let handover_1_to_2 = HandoverCertificate {
block_index: 300,
source_epoch: EpochIdentifier::new(1),
dest_epoch: EpochIdentifier::new(2),
epoch_config,
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
};
assert!(!handover_1_to_2.is_genesis());
}
#[test]
fn test_admin_transaction_handover_serialization() {
let epoch_config = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(1),
validators: vec![],
consensus_config: None,
};
let cert = HandoverCertificate {
block_index: 100,
source_epoch: EpochIdentifier::new(0),
dest_epoch: EpochIdentifier::new(1),
epoch_config,
previous_handover_hash: [0u8; BLAKE3_HASH_SIZE],
};
let admin_tx = AdminTransaction::Handover(Box::new(cert));
let serialized = serde_json::to_string(&admin_tx).expect("serialization should succeed");
let deserialized: AdminTransaction =
serde_json::from_str(&serialized).expect("deserialization should succeed");
match deserialized {
AdminTransaction::Handover(c) => {
assert_eq!(c.block_index, 100);
assert_eq!(c.source_epoch, EpochIdentifier::new(0));
assert_eq!(c.dest_epoch, EpochIdentifier::new(1));
}
_ => panic!("Expected Handover"),
}
}
#[test]
fn test_epoch_change_config_deterministic_hash() {
let (authority_key, protocol_key, network_key) = create_test_keys(50);
let config1 = EpochChangeConfig {
current_epoch: EpochIdentifier::new(0),
new_epoch: EpochIdentifier::new(1),
validators: vec![ValidatorInfo {
stake: 1000,
consensus_address: "/ip4/127.0.0.1/udp/10100".parse().unwrap(),
state_sync_address: "/ip4/127.0.0.1/udp/20100".parse().unwrap(),
hostname: "validator-0".to_string(),
authority_key: authority_key.clone(),
protocol_key: protocol_key.clone(),
network_key: network_key.clone(),
signing_key: Pubkey::new_from_array([3u8; 32]),
}],
consensus_config: None,
};
let config2 = config1.clone();
assert_eq!(config1.deterministic_hash(), config2.deterministic_hash());
let config3 = EpochChangeConfig {
new_epoch: EpochIdentifier::new(2),
..config1.clone()
};
assert_ne!(config1.deterministic_hash(), config3.deterministic_hash());
let config4 = EpochChangeConfig {
validators: vec![ValidatorInfo {
stake: 2000, consensus_address: "/ip4/127.0.0.1/udp/10100".parse().unwrap(),
state_sync_address: "/ip4/127.0.0.1/udp/20100".parse().unwrap(),
hostname: "validator-0".to_string(),
authority_key,
protocol_key,
network_key,
signing_key: Pubkey::new_from_array([3u8; 32]),
}],
..config1.clone()
};
assert_ne!(config1.deterministic_hash(), config4.deterministic_hash());
let config5 = EpochChangeConfig {
consensus_config: Some(EpochConsensusConfig {
num_leaders_per_round: Some(2),
..Default::default()
}),
..config1.clone()
};
assert_ne!(config1.deterministic_hash(), config5.deterministic_hash());
}
#[test]
fn test_snapshot_attestation_serialization() {
let (authority_key, protocol_key, _) = create_test_keys(60);
let attestation = SnapshotAttestation {
block_height: 100,
keysvals_hash: [0xABu8; BLAKE3_HASH_SIZE],
authority_key: authority_key.clone(),
protocol_key: protocol_key.clone(),
signature: [3u8; ED25519_SIGNATURE_SIZE],
};
let serialized = serde_json::to_string(&attestation).expect("serialization should succeed");
let deserialized: SnapshotAttestation =
serde_json::from_str(&serialized).expect("deserialization should succeed");
assert_eq!(deserialized.block_height, 100);
assert_eq!(deserialized.keysvals_hash, [0xABu8; BLAKE3_HASH_SIZE]);
assert_eq!(deserialized.authority_key, authority_key);
assert_eq!(deserialized.protocol_key, protocol_key);
assert_eq!(deserialized.signature, [3u8; ED25519_SIGNATURE_SIZE]);
}
#[test]
fn test_snapshot_attestation_signing_message() {
let keysvals_hash = [0xCDu8; BLAKE3_HASH_SIZE];
let msg = SnapshotAttestation::signing_message(42, &keysvals_hash);
assert_eq!(msg.len(), SNAPSHOT_ATTESTATION_SIGNING_SIZE);
assert_eq!(msg[0..8], 42u64.to_le_bytes());
assert_eq!(msg[8..SNAPSHOT_ATTESTATION_SIGNING_SIZE], keysvals_hash);
let msg2 = SnapshotAttestation::signing_message(1000, &keysvals_hash);
assert_eq!(msg2[0..8], 1000u64.to_le_bytes());
assert_eq!(msg2[8..SNAPSHOT_ATTESTATION_SIGNING_SIZE], keysvals_hash);
assert_ne!(msg, msg2);
}
#[test]
fn test_snapshot_attestation_message_bytes() {
let (authority_key, protocol_key, _) = create_test_keys(61);
let keysvals_hash = [0xEFu8; BLAKE3_HASH_SIZE];
let attestation = SnapshotAttestation {
block_height: 12345,
keysvals_hash,
authority_key,
protocol_key,
signature: [0u8; ED25519_SIGNATURE_SIZE],
};
let msg_bytes = attestation.message_bytes();
let expected = SnapshotAttestation::signing_message(12345, &keysvals_hash);
assert_eq!(msg_bytes, expected);
assert_eq!(msg_bytes[0..8], 12345u64.to_le_bytes());
assert_eq!(
msg_bytes[8..SNAPSHOT_ATTESTATION_SIGNING_SIZE],
keysvals_hash
);
}
#[test]
fn test_admin_transaction_snapshot_attestation_serialization() {
let (authority_key, protocol_key, _) = create_test_keys(62);
let attestation = SnapshotAttestation {
block_height: 999,
keysvals_hash: [0x12u8; BLAKE3_HASH_SIZE],
authority_key: authority_key.clone(),
protocol_key: protocol_key.clone(),
signature: [0x78u8; ED25519_SIGNATURE_SIZE],
};
let admin_tx = AdminTransaction::SnapshotAttestation(Box::new(attestation.clone()));
let serialized = serde_json::to_string(&admin_tx).expect("serialization should succeed");
let deserialized: AdminTransaction =
serde_json::from_str(&serialized).expect("deserialization should succeed");
match deserialized {
AdminTransaction::SnapshotAttestation(att) => {
assert_eq!(att.block_height, 999);
assert_eq!(att.keysvals_hash, [0x12u8; BLAKE3_HASH_SIZE]);
assert_eq!(att.authority_key, authority_key);
assert_eq!(att.protocol_key, protocol_key);
assert_eq!(att.signature, [0x78u8; ED25519_SIGNATURE_SIZE]);
}
_ => panic!("Expected SnapshotAttestation"),
}
}
}