From 3c029d48bf4f4ff41873ed696294caeef69319da Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 15 May 2023 02:10:42 +0000 Subject: [PATCH] DB migration for fork choice cleanup (#4265) ## Issue Addressed #4233 ## Proposed Changes Remove the `best_justified_checkpoint` from the `PersistedForkChoiceStore` type as it is now unused. Additionally, remove the `Option`'s wrapping the `justified_checkpoint` and `finalized_checkpoint` fields on `ProtoNode` which were only present to facilitate a previous migration. Include the necessary code to facilitate the migration to a new DB schema. --- Cargo.lock | 1 + .../src/beacon_fork_choice_store.rs | 53 +++++++++-- .../beacon_chain/src/persisted_fork_choice.rs | 31 ++++++- beacon_node/beacon_chain/src/schema_change.rs | 9 ++ .../src/schema_change/migration_schema_v17.rs | 88 +++++++++++++++++++ beacon_node/http_api/src/lib.rs | 8 +- beacon_node/http_api/tests/tests.rs | 4 +- beacon_node/store/src/metadata.rs | 2 +- common/eth2/src/types.rs | 4 +- consensus/proto_array/Cargo.toml | 1 + consensus/proto_array/src/error.rs | 6 +- consensus/proto_array/src/lib.rs | 2 +- consensus/proto_array/src/proto_array.rs | 87 +++++++++++++++--- .../src/proto_array_fork_choice.rs | 37 +++----- consensus/proto_array/src/ssz_container.rs | 53 ++++++++++- 15 files changed, 322 insertions(+), 64 deletions(-) create mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v17.rs diff --git a/Cargo.lock b/Cargo.lock index 6bfa6035a..52e15630d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6196,6 +6196,7 @@ dependencies = [ "serde", "serde_derive", "serde_yaml", + "superstruct 0.5.0", "types", ] diff --git a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs index 71160fcb6..9b2edbd8b 100644 --- a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs +++ b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs @@ -218,7 +218,6 @@ where finalized_checkpoint: self.finalized_checkpoint, justified_checkpoint: self.justified_checkpoint, justified_balances: self.justified_balances.effective_balances.clone(), - best_justified_checkpoint: JUNK_BEST_JUSTIFIED_CHECKPOINT, unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, proposer_boost_root: self.proposer_boost_root, @@ -355,24 +354,62 @@ where } } +pub type PersistedForkChoiceStore = PersistedForkChoiceStoreV17; + /// A container which allows persisting the `BeaconForkChoiceStore` to the on-disk database. -#[superstruct(variants(V11), variant_attributes(derive(Encode, Decode)), no_enum)] +#[superstruct( + variants(V11, V17), + variant_attributes(derive(Encode, Decode)), + no_enum +)] pub struct PersistedForkChoiceStore { - #[superstruct(only(V11))] + #[superstruct(only(V11, V17))] pub balances_cache: BalancesCacheV8, pub time: Slot, pub finalized_checkpoint: Checkpoint, pub justified_checkpoint: Checkpoint, pub justified_balances: Vec, + #[superstruct(only(V11))] pub best_justified_checkpoint: Checkpoint, - #[superstruct(only(V11))] + #[superstruct(only(V11, V17))] pub unrealized_justified_checkpoint: Checkpoint, - #[superstruct(only(V11))] + #[superstruct(only(V11, V17))] pub unrealized_finalized_checkpoint: Checkpoint, - #[superstruct(only(V11))] + #[superstruct(only(V11, V17))] pub proposer_boost_root: Hash256, - #[superstruct(only(V11))] + #[superstruct(only(V11, V17))] pub equivocating_indices: BTreeSet, } -pub type PersistedForkChoiceStore = PersistedForkChoiceStoreV11; +impl Into for PersistedForkChoiceStoreV11 { + fn into(self) -> PersistedForkChoiceStore { + PersistedForkChoiceStore { + balances_cache: self.balances_cache, + time: self.time, + finalized_checkpoint: self.finalized_checkpoint, + justified_checkpoint: self.justified_checkpoint, + justified_balances: self.justified_balances, + unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, + unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, + proposer_boost_root: self.proposer_boost_root, + equivocating_indices: self.equivocating_indices, + } + } +} + +impl Into for PersistedForkChoiceStore { + fn into(self) -> PersistedForkChoiceStoreV11 { + PersistedForkChoiceStoreV11 { + balances_cache: self.balances_cache, + time: self.time, + finalized_checkpoint: self.finalized_checkpoint, + justified_checkpoint: self.justified_checkpoint, + justified_balances: self.justified_balances, + best_justified_checkpoint: JUNK_BEST_JUSTIFIED_CHECKPOINT, + unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, + unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, + proposer_boost_root: self.proposer_boost_root, + equivocating_indices: self.equivocating_indices, + } + } +} diff --git a/beacon_node/beacon_chain/src/persisted_fork_choice.rs b/beacon_node/beacon_chain/src/persisted_fork_choice.rs index 829dc2a8a..8297ea934 100644 --- a/beacon_node/beacon_chain/src/persisted_fork_choice.rs +++ b/beacon_node/beacon_chain/src/persisted_fork_choice.rs @@ -1,17 +1,41 @@ -use crate::beacon_fork_choice_store::PersistedForkChoiceStoreV11; +use crate::beacon_fork_choice_store::{PersistedForkChoiceStoreV11, PersistedForkChoiceStoreV17}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use store::{DBColumn, Error, StoreItem}; use superstruct::superstruct; // If adding a new version you should update this type alias and fix the breakages. -pub type PersistedForkChoice = PersistedForkChoiceV11; +pub type PersistedForkChoice = PersistedForkChoiceV17; -#[superstruct(variants(V11), variant_attributes(derive(Encode, Decode)), no_enum)] +#[superstruct( + variants(V11, V17), + variant_attributes(derive(Encode, Decode)), + no_enum +)] pub struct PersistedForkChoice { pub fork_choice: fork_choice::PersistedForkChoice, #[superstruct(only(V11))] pub fork_choice_store: PersistedForkChoiceStoreV11, + #[superstruct(only(V17))] + pub fork_choice_store: PersistedForkChoiceStoreV17, +} + +impl Into for PersistedForkChoiceV11 { + fn into(self) -> PersistedForkChoice { + PersistedForkChoice { + fork_choice: self.fork_choice, + fork_choice_store: self.fork_choice_store.into(), + } + } +} + +impl Into for PersistedForkChoice { + fn into(self) -> PersistedForkChoiceV11 { + PersistedForkChoiceV11 { + fork_choice: self.fork_choice, + fork_choice_store: self.fork_choice_store.into(), + } + } } macro_rules! impl_store_item { @@ -33,3 +57,4 @@ macro_rules! impl_store_item { } impl_store_item!(PersistedForkChoiceV11); +impl_store_item!(PersistedForkChoiceV17); diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 5808e648a..7b398db2f 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -4,6 +4,7 @@ mod migration_schema_v13; mod migration_schema_v14; mod migration_schema_v15; mod migration_schema_v16; +mod migration_schema_v17; use crate::beacon_chain::{BeaconChainTypes, ETH1_CACHE_DB_KEY}; use crate::eth1_chain::SszEth1; @@ -141,6 +142,14 @@ pub fn migrate_schema( let ops = migration_schema_v16::downgrade_from_v16::(db.clone(), log)?; db.store_schema_version_atomically(to, ops) } + (SchemaVersion(16), SchemaVersion(17)) => { + let ops = migration_schema_v17::upgrade_to_v17::(db.clone(), log)?; + db.store_schema_version_atomically(to, ops) + } + (SchemaVersion(17), SchemaVersion(16)) => { + let ops = migration_schema_v17::downgrade_from_v17::(db.clone(), log)?; + db.store_schema_version_atomically(to, ops) + } // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { target_version: to, diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v17.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v17.rs new file mode 100644 index 000000000..770cbb8ab --- /dev/null +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v17.rs @@ -0,0 +1,88 @@ +use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY}; +use crate::persisted_fork_choice::{PersistedForkChoiceV11, PersistedForkChoiceV17}; +use proto_array::core::{SszContainerV16, SszContainerV17}; +use slog::{debug, Logger}; +use ssz::{Decode, Encode}; +use std::sync::Arc; +use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; + +pub fn upgrade_fork_choice( + mut fork_choice: PersistedForkChoiceV11, +) -> Result { + let ssz_container_v16 = SszContainerV16::from_ssz_bytes( + &fork_choice.fork_choice.proto_array_bytes, + ) + .map_err(|e| { + Error::SchemaMigrationError(format!( + "Failed to decode ProtoArrayForkChoice during schema migration: {:?}", + e + )) + })?; + + let ssz_container_v17: SszContainerV17 = ssz_container_v16.try_into().map_err(|e| { + Error::SchemaMigrationError(format!( + "Missing checkpoint during schema migration: {:?}", + e + )) + })?; + fork_choice.fork_choice.proto_array_bytes = ssz_container_v17.as_ssz_bytes(); + + Ok(fork_choice.into()) +} + +pub fn downgrade_fork_choice( + mut fork_choice: PersistedForkChoiceV17, +) -> Result { + let ssz_container_v17 = SszContainerV17::from_ssz_bytes( + &fork_choice.fork_choice.proto_array_bytes, + ) + .map_err(|e| { + Error::SchemaMigrationError(format!( + "Failed to decode ProtoArrayForkChoice during schema migration: {:?}", + e + )) + })?; + + let ssz_container_v16: SszContainerV16 = ssz_container_v17.into(); + fork_choice.fork_choice.proto_array_bytes = ssz_container_v16.as_ssz_bytes(); + + Ok(fork_choice.into()) +} + +pub fn upgrade_to_v17( + db: Arc>, + log: Logger, +) -> Result, Error> { + // Get persisted_fork_choice. + let v11 = db + .get_item::(&FORK_CHOICE_DB_KEY)? + .ok_or_else(|| Error::SchemaMigrationError("fork choice missing from database".into()))?; + + let v17 = upgrade_fork_choice(v11)?; + + debug!( + log, + "Removing unused best_justified_checkpoint from fork choice store." + ); + + Ok(vec![v17.as_kv_store_op(FORK_CHOICE_DB_KEY)]) +} + +pub fn downgrade_from_v17( + db: Arc>, + log: Logger, +) -> Result, Error> { + // Get persisted_fork_choice. + let v17 = db + .get_item::(&FORK_CHOICE_DB_KEY)? + .ok_or_else(|| Error::SchemaMigrationError("fork choice missing from database".into()))?; + + let v11 = downgrade_fork_choice(v17)?; + + debug!( + log, + "Adding junk best_justified_checkpoint to fork choice store." + ); + + Ok(vec![v11.as_kv_store_op(FORK_CHOICE_DB_KEY)]) +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index d19187cb4..096d99f3f 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2202,12 +2202,8 @@ pub fn serve( .parent .and_then(|index| proto_array.nodes.get(index)) .map(|parent| parent.root), - justified_epoch: node - .justified_checkpoint - .map(|checkpoint| checkpoint.epoch), - finalized_epoch: node - .finalized_checkpoint - .map(|checkpoint| checkpoint.epoch), + justified_epoch: node.justified_checkpoint.epoch, + finalized_epoch: node.finalized_checkpoint.epoch, weight: node.weight, validity: execution_status, execution_block_hash: node diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index a54f17e96..fc78b2a9b 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1956,8 +1956,8 @@ impl ApiTester { .parent .and_then(|index| expected_proto_array.nodes.get(index)) .map(|parent| parent.root), - justified_epoch: node.justified_checkpoint.map(|checkpoint| checkpoint.epoch), - finalized_epoch: node.finalized_checkpoint.map(|checkpoint| checkpoint.epoch), + justified_epoch: node.justified_checkpoint.epoch, + finalized_epoch: node.finalized_checkpoint.epoch, weight: node.weight, validity: execution_status, execution_block_hash: node diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index eca8fc834..6f50d7038 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -4,7 +4,7 @@ use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use types::{Checkpoint, Hash256, Slot}; -pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(16); +pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(17); // All the keys that get stored under the `BeaconMeta` column. // diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 5545bf45d..f58dc8e2a 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1229,8 +1229,8 @@ pub struct ForkChoiceNode { pub slot: Slot, pub block_root: Hash256, pub parent_root: Option, - pub justified_epoch: Option, - pub finalized_epoch: Option, + pub justified_epoch: Epoch, + pub finalized_epoch: Epoch, #[serde(with = "serde_utils::quoted_u64")] pub weight: u64, pub validity: Option, diff --git a/consensus/proto_array/Cargo.toml b/consensus/proto_array/Cargo.toml index cd43c566f..81a535e34 100644 --- a/consensus/proto_array/Cargo.toml +++ b/consensus/proto_array/Cargo.toml @@ -16,3 +16,4 @@ serde = "1.0.116" serde_derive = "1.0.116" serde_yaml = "0.8.13" safe_arith = { path = "../safe_arith" } +superstruct = "0.5.0" \ No newline at end of file diff --git a/consensus/proto_array/src/error.rs b/consensus/proto_array/src/error.rs index 1fe45fd0f..35cb4007b 100644 --- a/consensus/proto_array/src/error.rs +++ b/consensus/proto_array/src/error.rs @@ -14,6 +14,8 @@ pub enum Error { InvalidBestDescendant(usize), InvalidParentDelta(usize), InvalidNodeDelta(usize), + MissingJustifiedCheckpoint, + MissingFinalizedCheckpoint, DeltaOverflow(usize), ProposerBoostOverflow(usize), ReOrgThresholdOverflow, @@ -67,6 +69,6 @@ pub struct InvalidBestNodeInfo { pub justified_checkpoint: Checkpoint, pub finalized_checkpoint: Checkpoint, pub head_root: Hash256, - pub head_justified_checkpoint: Option, - pub head_finalized_checkpoint: Option, + pub head_justified_checkpoint: Checkpoint, + pub head_finalized_checkpoint: Checkpoint, } diff --git a/consensus/proto_array/src/lib.rs b/consensus/proto_array/src/lib.rs index 481daba47..780563954 100644 --- a/consensus/proto_array/src/lib.rs +++ b/consensus/proto_array/src/lib.rs @@ -16,5 +16,5 @@ pub use error::Error; pub mod core { pub use super::proto_array::{ProposerBoost, ProtoArray, ProtoNode}; pub use super::proto_array_fork_choice::VoteTracker; - pub use super::ssz_container::SszContainer; + pub use super::ssz_container::{SszContainer, SszContainerV16, SszContainerV17}; } diff --git a/consensus/proto_array/src/proto_array.rs b/consensus/proto_array/src/proto_array.rs index 1cc34beff..88111b461 100644 --- a/consensus/proto_array/src/proto_array.rs +++ b/consensus/proto_array/src/proto_array.rs @@ -5,6 +5,7 @@ use ssz::four_byte_option_impl; use ssz::Encode; use ssz_derive::{Decode, Encode}; use std::collections::{HashMap, HashSet}; +use superstruct::superstruct; use types::{ AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256, Slot, @@ -66,7 +67,13 @@ impl InvalidationOperation { } } -#[derive(Clone, PartialEq, Debug, Encode, Decode, Serialize, Deserialize)] +pub type ProtoNode = ProtoNodeV17; + +#[superstruct( + variants(V16, V17), + variant_attributes(derive(Clone, PartialEq, Debug, Encode, Decode, Serialize, Deserialize)), + no_enum +)] pub struct ProtoNode { /// The `slot` is not necessary for `ProtoArray`, it just exists so external components can /// easily query the block slot. This is useful for upstream fork choice logic. @@ -85,10 +92,16 @@ pub struct ProtoNode { pub root: Hash256, #[ssz(with = "four_byte_option_usize")] pub parent: Option, + #[superstruct(only(V16))] #[ssz(with = "four_byte_option_checkpoint")] pub justified_checkpoint: Option, + #[superstruct(only(V16))] #[ssz(with = "four_byte_option_checkpoint")] pub finalized_checkpoint: Option, + #[superstruct(only(V17))] + pub justified_checkpoint: Checkpoint, + #[superstruct(only(V17))] + pub finalized_checkpoint: Checkpoint, pub weight: u64, #[ssz(with = "four_byte_option_usize")] pub best_child: Option, @@ -103,6 +116,57 @@ pub struct ProtoNode { pub unrealized_finalized_checkpoint: Option, } +impl TryInto for ProtoNodeV16 { + type Error = Error; + + fn try_into(self) -> Result { + let result = ProtoNode { + slot: self.slot, + state_root: self.state_root, + target_root: self.target_root, + current_epoch_shuffling_id: self.current_epoch_shuffling_id, + next_epoch_shuffling_id: self.next_epoch_shuffling_id, + root: self.root, + parent: self.parent, + justified_checkpoint: self + .justified_checkpoint + .ok_or(Error::MissingJustifiedCheckpoint)?, + finalized_checkpoint: self + .finalized_checkpoint + .ok_or(Error::MissingFinalizedCheckpoint)?, + weight: self.weight, + best_child: self.best_child, + best_descendant: self.best_descendant, + execution_status: self.execution_status, + unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, + unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, + }; + Ok(result) + } +} + +impl Into for ProtoNode { + fn into(self) -> ProtoNodeV16 { + ProtoNodeV16 { + slot: self.slot, + state_root: self.state_root, + target_root: self.target_root, + current_epoch_shuffling_id: self.current_epoch_shuffling_id, + next_epoch_shuffling_id: self.next_epoch_shuffling_id, + root: self.root, + parent: self.parent, + justified_checkpoint: Some(self.justified_checkpoint), + finalized_checkpoint: Some(self.finalized_checkpoint), + weight: self.weight, + best_child: self.best_child, + best_descendant: self.best_descendant, + execution_status: self.execution_status, + unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, + unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, + } + } +} + #[derive(PartialEq, Debug, Encode, Decode, Serialize, Deserialize, Copy, Clone)] pub struct ProposerBoost { pub root: Hash256, @@ -320,8 +384,8 @@ impl ProtoArray { parent: block .parent_root .and_then(|parent| self.indices.get(&parent).copied()), - justified_checkpoint: Some(block.justified_checkpoint), - finalized_checkpoint: Some(block.finalized_checkpoint), + justified_checkpoint: block.justified_checkpoint, + finalized_checkpoint: block.finalized_checkpoint, weight: 0, best_child: None, best_descendant: None, @@ -883,14 +947,7 @@ impl ProtoArray { let genesis_epoch = Epoch::new(0); let current_epoch = current_slot.epoch(E::slots_per_epoch()); let node_epoch = node.slot.epoch(E::slots_per_epoch()); - let node_justified_checkpoint = - if let Some(justified_checkpoint) = node.justified_checkpoint { - justified_checkpoint - } else { - // The node does not have any information about the justified - // checkpoint. This indicates an inconsistent proto-array. - return false; - }; + let node_justified_checkpoint = node.justified_checkpoint; let voting_source = if current_epoch > node_epoch { // The block is from a prior epoch, the voting source will be pulled-up. @@ -998,9 +1055,13 @@ impl ProtoArray { // Run this check once, outside of the loop rather than inside the loop. // If the conditions don't match for this node then they're unlikely to // start matching for its ancestors. + for checkpoint in &[node.finalized_checkpoint, node.justified_checkpoint] { + if checkpoint == &self.finalized_checkpoint { + return true; + } + } + for checkpoint in &[ - node.finalized_checkpoint, - node.justified_checkpoint, node.unrealized_finalized_checkpoint, node.unrealized_justified_checkpoint, ] { diff --git a/consensus/proto_array/src/proto_array_fork_choice.rs b/consensus/proto_array/src/proto_array_fork_choice.rs index d376e62e8..fe831b3c3 100644 --- a/consensus/proto_array/src/proto_array_fork_choice.rs +++ b/consensus/proto_array/src/proto_array_fork_choice.rs @@ -754,29 +754,20 @@ impl ProtoArrayForkChoice { .and_then(|i| self.proto_array.nodes.get(i)) .map(|parent| parent.root); - // If a node does not have a `finalized_checkpoint` or `justified_checkpoint` populated, - // it means it is not a descendant of the finalized checkpoint, so it is valid to return - // `None` here. - if let (Some(justified_checkpoint), Some(finalized_checkpoint)) = - (block.justified_checkpoint, block.finalized_checkpoint) - { - Some(Block { - slot: block.slot, - root: block.root, - parent_root, - state_root: block.state_root, - target_root: block.target_root, - current_epoch_shuffling_id: block.current_epoch_shuffling_id.clone(), - next_epoch_shuffling_id: block.next_epoch_shuffling_id.clone(), - justified_checkpoint, - finalized_checkpoint, - execution_status: block.execution_status, - unrealized_justified_checkpoint: block.unrealized_justified_checkpoint, - unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint, - }) - } else { - None - } + Some(Block { + slot: block.slot, + root: block.root, + parent_root, + state_root: block.state_root, + target_root: block.target_root, + current_epoch_shuffling_id: block.current_epoch_shuffling_id.clone(), + next_epoch_shuffling_id: block.next_epoch_shuffling_id.clone(), + justified_checkpoint: block.justified_checkpoint, + finalized_checkpoint: block.finalized_checkpoint, + execution_status: block.execution_status, + unrealized_justified_checkpoint: block.unrealized_justified_checkpoint, + unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint, + }) } /// Returns the `block.execution_status` field, if the block is present. diff --git a/consensus/proto_array/src/ssz_container.rs b/consensus/proto_array/src/ssz_container.rs index ed1efaae1..de7fa70d6 100644 --- a/consensus/proto_array/src/ssz_container.rs +++ b/consensus/proto_array/src/ssz_container.rs @@ -1,6 +1,6 @@ use crate::proto_array::ProposerBoost; use crate::{ - proto_array::{ProtoArray, ProtoNode}, + proto_array::{ProtoArray, ProtoNodeV16, ProtoNodeV17}, proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker}, Error, JustifiedBalances, }; @@ -8,24 +8,71 @@ use ssz::{four_byte_option_impl, Encode}; use ssz_derive::{Decode, Encode}; use std::collections::HashMap; use std::convert::TryFrom; +use superstruct::superstruct; use types::{Checkpoint, Hash256}; // Define a "legacy" implementation of `Option` which uses four bytes for encoding the union // selector. four_byte_option_impl!(four_byte_option_checkpoint, Checkpoint); -#[derive(Encode, Decode)] +pub type SszContainer = SszContainerV17; + +#[superstruct( + variants(V16, V17), + variant_attributes(derive(Encode, Decode)), + no_enum +)] pub struct SszContainer { pub votes: Vec, pub balances: Vec, pub prune_threshold: usize, pub justified_checkpoint: Checkpoint, pub finalized_checkpoint: Checkpoint, - pub nodes: Vec, + #[superstruct(only(V16))] + pub nodes: Vec, + #[superstruct(only(V17))] + pub nodes: Vec, pub indices: Vec<(Hash256, usize)>, pub previous_proposer_boost: ProposerBoost, } +impl TryInto for SszContainerV16 { + type Error = Error; + + fn try_into(self) -> Result { + let nodes: Result, Error> = + self.nodes.into_iter().map(TryInto::try_into).collect(); + + Ok(SszContainer { + votes: self.votes, + balances: self.balances, + prune_threshold: self.prune_threshold, + justified_checkpoint: self.justified_checkpoint, + finalized_checkpoint: self.finalized_checkpoint, + nodes: nodes?, + indices: self.indices, + previous_proposer_boost: self.previous_proposer_boost, + }) + } +} + +impl Into for SszContainer { + fn into(self) -> SszContainerV16 { + let nodes = self.nodes.into_iter().map(Into::into).collect(); + + SszContainerV16 { + votes: self.votes, + balances: self.balances, + prune_threshold: self.prune_threshold, + justified_checkpoint: self.justified_checkpoint, + finalized_checkpoint: self.finalized_checkpoint, + nodes, + indices: self.indices, + previous_proposer_boost: self.previous_proposer_boost, + } + } +} + impl From<&ProtoArrayForkChoice> for SszContainer { fn from(from: &ProtoArrayForkChoice) -> Self { let proto_array = &from.proto_array;