use crate::attestation_id::AttestationId; use crate::sync_aggregate_id::SyncAggregateId; use crate::OpPoolError; use crate::OperationPool; use derivative::Derivative; use parking_lot::RwLock; use serde_derive::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use store::{DBColumn, Error as StoreError, StoreItem}; use types::*; type PersistedSyncContributions = Vec<(SyncAggregateId, Vec>)>; /// SSZ-serializable version of `OperationPool`. /// /// Operations are stored in arbitrary order, so it's not a good idea to compare instances /// of this type (or its encoded form) for equality. Convert back to an `OperationPool` first. #[superstruct( variants(Base, Altair), variant_attributes( derive(Derivative, PartialEq, Debug, Serialize, Deserialize, Encode, Decode), serde(bound = "T: EthSpec", deny_unknown_fields), derivative(Clone), ), partial_getter_error(ty = "OpPoolError", expr = "OpPoolError::IncorrectOpPoolVariant") )] #[derive(PartialEq, Debug, Serialize, Deserialize, Encode)] #[serde(untagged)] #[serde(bound = "T: EthSpec")] #[ssz(enum_behaviour = "transparent")] pub struct PersistedOperationPool { /// Mapping from attestation ID to attestation mappings. // We could save space by not storing the attestation ID, but it might // be difficult to make that roundtrip due to eager aggregation. attestations: Vec<(AttestationId, Vec>)>, /// Mapping from sync contribution ID to sync contributions and aggregate. #[superstruct(only(Altair))] sync_contributions: PersistedSyncContributions, /// Attester slashings. attester_slashings: Vec<(AttesterSlashing, ForkVersion)>, /// Proposer slashings. proposer_slashings: Vec, /// Voluntary exits. voluntary_exits: Vec, } impl PersistedOperationPool { /// Convert an `OperationPool` into serializable form. Always converts to /// `PersistedOperationPool::Altair` because the v3 to v4 database schema migration ensures /// the op pool is always persisted as the Altair variant. pub fn from_operation_pool(operation_pool: &OperationPool) -> Self { let attestations = operation_pool .attestations .read() .iter() .map(|(att_id, att)| (att_id.clone(), att.clone())) .collect(); let sync_contributions = operation_pool .sync_contributions .read() .iter() .map(|(id, contribution)| (id.clone(), contribution.clone())) .collect(); let attester_slashings = operation_pool .attester_slashings .read() .iter() .cloned() .collect(); let proposer_slashings = operation_pool .proposer_slashings .read() .iter() .map(|(_, slashing)| slashing.clone()) .collect(); let voluntary_exits = operation_pool .voluntary_exits .read() .iter() .map(|(_, exit)| exit.clone()) .collect(); PersistedOperationPool::Altair(PersistedOperationPoolAltair { attestations, sync_contributions, attester_slashings, proposer_slashings, voluntary_exits, }) } /// Reconstruct an `OperationPool`. Sets `sync_contributions` to its `Default` if `self` matches /// `PersistedOperationPool::Base`. pub fn into_operation_pool(self) -> Result, OpPoolError> { let attestations = RwLock::new(self.attestations().iter().cloned().collect()); let attester_slashings = RwLock::new(self.attester_slashings().iter().cloned().collect()); let proposer_slashings = RwLock::new( self.proposer_slashings() .iter() .cloned() .map(|slashing| (slashing.signed_header_1.message.proposer_index, slashing)) .collect(), ); let voluntary_exits = RwLock::new( self.voluntary_exits() .iter() .cloned() .map(|exit| (exit.message.validator_index, exit)) .collect(), ); let op_pool = match self { PersistedOperationPool::Base(_) => OperationPool { attestations, sync_contributions: <_>::default(), attester_slashings, proposer_slashings, voluntary_exits, _phantom: Default::default(), }, PersistedOperationPool::Altair(_) => { let sync_contributions = RwLock::new(self.sync_contributions()?.iter().cloned().collect()); OperationPool { attestations, sync_contributions, attester_slashings, proposer_slashings, voluntary_exits, _phantom: Default::default(), } } }; Ok(op_pool) } /// Convert the `PersistedOperationPool::Base` variant to `PersistedOperationPool::Altair` by /// setting `sync_contributions` to its default. pub fn base_to_altair(self) -> Self { match self { PersistedOperationPool::Base(_) => { PersistedOperationPool::Altair(PersistedOperationPoolAltair { attestations: self.attestations().to_vec(), sync_contributions: <_>::default(), attester_slashings: self.attester_slashings().to_vec(), proposer_slashings: self.proposer_slashings().to_vec(), voluntary_exits: self.voluntary_exits().to_vec(), }) } PersistedOperationPool::Altair(_) => self, } } } /// This `StoreItem` implementation is necessary for migrating the `PersistedOperationPool` /// in the v3 to v4 database schema migration. impl StoreItem for PersistedOperationPoolBase { fn db_column() -> DBColumn { DBColumn::OpPool } fn as_store_bytes(&self) -> Vec { self.as_ssz_bytes() } fn from_store_bytes(bytes: &[u8]) -> Result { Self::from_ssz_bytes(bytes).map_err(Into::into) } } /// Deserialization for `PersistedOperationPool` defaults to `PersistedOperationPool::Altair` /// because the v3 to v4 database schema migration ensures the persisted op pool is always stored /// in the Altair format. impl StoreItem for PersistedOperationPool { fn db_column() -> DBColumn { DBColumn::OpPool } fn as_store_bytes(&self) -> Vec { self.as_ssz_bytes() } fn from_store_bytes(bytes: &[u8]) -> Result { // Default deserialization to the Altair variant. PersistedOperationPoolAltair::from_ssz_bytes(bytes) .map(Self::Altair) .map_err(Into::into) } }