2021-06-21 05:46:36 +00:00
|
|
|
use crate::InterchangeError;
|
2020-10-02 01:42:27 +00:00
|
|
|
use serde_derive::{Deserialize, Serialize};
|
2021-06-21 05:46:36 +00:00
|
|
|
use std::cmp::max;
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use std::io;
|
2021-03-17 05:09:57 +00:00
|
|
|
use types::{Epoch, Hash256, PublicKeyBytes, Slot};
|
2020-10-02 01:42:27 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct InterchangeMetadata {
|
|
|
|
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
|
|
|
|
pub interchange_format_version: u64,
|
|
|
|
pub genesis_validators_root: Hash256,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
2020-11-09 05:04:01 +00:00
|
|
|
pub struct InterchangeData {
|
2021-03-17 05:09:57 +00:00
|
|
|
pub pubkey: PublicKeyBytes,
|
2020-10-02 01:42:27 +00:00
|
|
|
pub signed_blocks: Vec<SignedBlock>,
|
|
|
|
pub signed_attestations: Vec<SignedAttestation>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct SignedBlock {
|
|
|
|
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
|
|
|
|
pub slot: Slot,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub signing_root: Option<Hash256>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
pub struct SignedAttestation {
|
|
|
|
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
|
|
|
|
pub source_epoch: Epoch,
|
|
|
|
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
|
|
|
|
pub target_epoch: Epoch,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub signing_root: Option<Hash256>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
|
|
pub struct Interchange {
|
|
|
|
pub metadata: InterchangeMetadata,
|
2020-11-09 05:04:01 +00:00
|
|
|
pub data: Vec<InterchangeData>,
|
2020-10-02 01:42:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Interchange {
|
|
|
|
pub fn from_json_str(json: &str) -> Result<Self, serde_json::Error> {
|
|
|
|
serde_json::from_str(json)
|
|
|
|
}
|
|
|
|
|
2021-06-21 05:46:36 +00:00
|
|
|
pub fn from_json_reader(mut reader: impl std::io::Read) -> Result<Self, io::Error> {
|
|
|
|
// We read the entire file into memory first, as this is *a lot* faster than using
|
|
|
|
// `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160
|
|
|
|
let mut json_str = String::new();
|
|
|
|
reader.read_to_string(&mut json_str)?;
|
|
|
|
Ok(Interchange::from_json_str(&json_str)?)
|
2020-10-02 01:42:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_to(&self, writer: impl std::io::Write) -> Result<(), serde_json::Error> {
|
|
|
|
serde_json::to_writer(writer, self)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Do these two `Interchange`s contain the same data (ignoring ordering)?
|
|
|
|
pub fn equiv(&self, other: &Self) -> bool {
|
2021-01-19 00:34:28 +00:00
|
|
|
let self_set = self.data.iter().collect::<HashSet<_>>();
|
|
|
|
let other_set = other.data.iter().collect::<HashSet<_>>();
|
2020-10-02 01:42:27 +00:00
|
|
|
self.metadata == other.metadata && self_set == other_set
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The number of entries in `data`.
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.data.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is the `data` part of the interchange completely empty?
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.len() == 0
|
|
|
|
}
|
2021-06-21 05:46:36 +00:00
|
|
|
|
|
|
|
/// Minify an interchange by constructing a synthetic block & attestation for each validator.
|
|
|
|
pub fn minify(&self) -> Result<Self, InterchangeError> {
|
|
|
|
// Map from pubkey to optional max block and max attestation.
|
|
|
|
let mut validator_data =
|
|
|
|
HashMap::<PublicKeyBytes, (Option<SignedBlock>, Option<SignedAttestation>)>::new();
|
|
|
|
|
|
|
|
for data in self.data.iter() {
|
|
|
|
// Existing maximum attestation and maximum block.
|
|
|
|
let (max_block, max_attestation) = validator_data
|
|
|
|
.entry(data.pubkey)
|
|
|
|
.or_insert_with(|| (None, None));
|
|
|
|
|
|
|
|
// Find maximum source and target epochs.
|
|
|
|
let max_source_epoch = data
|
|
|
|
.signed_attestations
|
|
|
|
.iter()
|
|
|
|
.map(|attestation| attestation.source_epoch)
|
|
|
|
.max();
|
|
|
|
let max_target_epoch = data
|
|
|
|
.signed_attestations
|
|
|
|
.iter()
|
|
|
|
.map(|attestation| attestation.target_epoch)
|
|
|
|
.max();
|
|
|
|
|
|
|
|
match (max_source_epoch, max_target_epoch) {
|
|
|
|
(Some(source_epoch), Some(target_epoch)) => {
|
|
|
|
if let Some(prev_max) = max_attestation {
|
|
|
|
prev_max.source_epoch = max(prev_max.source_epoch, source_epoch);
|
|
|
|
prev_max.target_epoch = max(prev_max.target_epoch, target_epoch);
|
|
|
|
} else {
|
|
|
|
*max_attestation = Some(SignedAttestation {
|
|
|
|
source_epoch,
|
|
|
|
target_epoch,
|
|
|
|
signing_root: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(None, None) => {}
|
|
|
|
_ => return Err(InterchangeError::MinAndMaxInconsistent),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Find maximum block slot.
|
|
|
|
let max_block_slot = data.signed_blocks.iter().map(|block| block.slot).max();
|
|
|
|
|
|
|
|
if let Some(max_slot) = max_block_slot {
|
|
|
|
if let Some(prev_max) = max_block {
|
|
|
|
prev_max.slot = max(prev_max.slot, max_slot);
|
|
|
|
} else {
|
|
|
|
*max_block = Some(SignedBlock {
|
|
|
|
slot: max_slot,
|
|
|
|
signing_root: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = validator_data
|
|
|
|
.into_iter()
|
|
|
|
.map(|(pubkey, (maybe_block, maybe_att))| InterchangeData {
|
|
|
|
pubkey,
|
|
|
|
signed_blocks: maybe_block.into_iter().collect(),
|
|
|
|
signed_attestations: maybe_att.into_iter().collect(),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
metadata: self.metadata.clone(),
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
}
|
2020-10-02 01:42:27 +00:00
|
|
|
}
|