op_pool: finish persistence support
This commit is contained in:
		
							parent
							
								
									7fe458af45
								
							
						
					
					
						commit
						73c4171b52
					
				| @ -26,3 +26,6 @@ state_processing = { path = "../../eth2/state_processing" } | ||||
| tree_hash = { path = "../../eth2/utils/tree_hash" } | ||||
| types = { path = "../../eth2/types" } | ||||
| lmd_ghost = { path = "../../eth2/lmd_ghost" } | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| rand = "0.5.5" | ||||
|  | ||||
| @ -14,6 +14,8 @@ use types::{ | ||||
|     Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot, | ||||
| }; | ||||
| 
 | ||||
| pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; | ||||
| 
 | ||||
| /// Indicates how the `BeaconChainHarness` should produce blocks.
 | ||||
| #[derive(Clone, Copy, Debug)] | ||||
| pub enum BlockStrategy { | ||||
| @ -68,8 +70,8 @@ where | ||||
|     E: EthSpec, | ||||
| { | ||||
|     pub chain: BeaconChain<CommonTypes<L, E>>, | ||||
|     keypairs: Vec<Keypair>, | ||||
|     spec: ChainSpec, | ||||
|     pub keypairs: Vec<Keypair>, | ||||
|     pub spec: ChainSpec, | ||||
| } | ||||
| 
 | ||||
| impl<L, E> BeaconChainHarness<L, E> | ||||
|  | ||||
| @ -1,16 +1,21 @@ | ||||
| #![cfg(not(debug_assertions))] | ||||
| 
 | ||||
| use beacon_chain::test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy}; | ||||
| use beacon_chain::test_utils::{ | ||||
|     AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain, | ||||
|     BEACON_CHAIN_DB_KEY, | ||||
| }; | ||||
| use lmd_ghost::ThreadSafeReducedTree; | ||||
| use store::MemoryStore; | ||||
| use types::{EthSpec, MinimalEthSpec, Slot}; | ||||
| use rand::Rng; | ||||
| use store::{MemoryStore, Store}; | ||||
| use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; | ||||
| use types::{Deposit, EthSpec, Hash256, MinimalEthSpec, Slot}; | ||||
| 
 | ||||
| // Should ideally be divisible by 3.
 | ||||
| pub const VALIDATOR_COUNT: usize = 24; | ||||
| 
 | ||||
| fn get_harness( | ||||
|     validator_count: usize, | ||||
| ) -> BeaconChainHarness<ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>, MinimalEthSpec> { | ||||
| type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>; | ||||
| 
 | ||||
| fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> { | ||||
|     let harness = BeaconChainHarness::new(validator_count); | ||||
| 
 | ||||
|     // Move past the zero slot.
 | ||||
| @ -225,3 +230,38 @@ fn does_not_finalize_without_attestation() { | ||||
|         "no epoch should have been finalized" | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn roundtrip_operation_pool() { | ||||
|     let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; | ||||
| 
 | ||||
|     let harness = get_harness(VALIDATOR_COUNT); | ||||
| 
 | ||||
|     // Add some attestations
 | ||||
|     harness.extend_chain( | ||||
|         num_blocks_produced as usize, | ||||
|         BlockStrategy::OnCanonicalHead, | ||||
|         AttestationStrategy::AllValidators, | ||||
|     ); | ||||
|     assert!(harness.chain.op_pool.num_attestations() > 0); | ||||
| 
 | ||||
|     // Add some deposits
 | ||||
|     let rng = &mut XorShiftRng::from_seed([66; 16]); | ||||
|     for _ in 0..rng.gen_range(1, VALIDATOR_COUNT) { | ||||
|         harness | ||||
|             .chain | ||||
|             .process_deposit(Deposit::random_for_test(rng)) | ||||
|             .unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: could add some other operations
 | ||||
|     harness.chain.persist().unwrap(); | ||||
| 
 | ||||
|     let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes()); | ||||
|     let p: PersistedBeaconChain<CommonTypes<TestForkChoice, MinimalEthSpec>> = | ||||
|         harness.chain.store.get(&key).unwrap().unwrap(); | ||||
| 
 | ||||
|     let restored_op_pool = p.op_pool.into_operation_pool(&p.state, &harness.spec); | ||||
| 
 | ||||
|     assert_eq!(harness.chain.op_pool, restored_op_pool); | ||||
| } | ||||
|  | ||||
| @ -27,7 +27,7 @@ use types::{ | ||||
|     Transfer, Validator, VoluntaryExit, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Default)] | ||||
| #[derive(Default, Debug)] | ||||
| pub struct OperationPool<T: EthSpec + Default> { | ||||
|     /// Map from attestation ID (see below) to vectors of attestations.
 | ||||
|     attestations: RwLock<HashMap<AttestationId, Vec<Attestation>>>, | ||||
| @ -442,6 +442,18 @@ fn prune_validator_hash_map<T, F, E: EthSpec>( | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /// Compare two operation pools.
 | ||||
| impl<T: EthSpec + Default> PartialEq for OperationPool<T> { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         *self.attestations.read() == *other.attestations.read() | ||||
|             && *self.deposits.read() == *other.deposits.read() | ||||
|             && *self.attester_slashings.read() == *other.attester_slashings.read() | ||||
|             && *self.proposer_slashings.read() == *other.proposer_slashings.read() | ||||
|             && *self.voluntary_exits.read() == *other.voluntary_exits.read() | ||||
|             && *self.transfers.read() == *other.transfers.read() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::DepositInsertStatus::*; | ||||
|  | ||||
| @ -1,12 +1,127 @@ | ||||
| use crate::attestation_id::AttestationId; | ||||
| use crate::OperationPool; | ||||
| use itertools::Itertools; | ||||
| use parking_lot::RwLock; | ||||
| use ssz::{Decode, Encode}; | ||||
| use ssz_derive::{Decode, Encode}; | ||||
| use types::*; | ||||
| 
 | ||||
| /// Tuples for SSZ
 | ||||
| /// 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.
 | ||||
| #[derive(Encode, Decode)] | ||||
| 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<SszPair<AttestationId, Vec<Attestation>>>, | ||||
|     deposits: Vec<Deposit>, | ||||
|     /// Attester slashings.
 | ||||
|     attester_slashings: Vec<AttesterSlashing>, | ||||
|     /// Proposer slashings.
 | ||||
|     proposer_slashings: Vec<ProposerSlashing>, | ||||
|     /// Voluntary exits.
 | ||||
|     voluntary_exits: Vec<VoluntaryExit>, | ||||
|     /// Transfers.
 | ||||
|     transfers: Vec<Transfer>, | ||||
| } | ||||
| 
 | ||||
| impl PersistedOperationPool { | ||||
|     /// Convert an `OperationPool` into serializable form.
 | ||||
|     pub fn from_operation_pool<T: EthSpec>(operation_pool: &OperationPool<T>) -> Self { | ||||
|         let attestations = operation_pool | ||||
|             .attestations | ||||
|             .read() | ||||
|             .iter() | ||||
|             .map(|(att_id, att)| SszPair::new(att_id.clone(), att.clone())) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let deposits = operation_pool | ||||
|             .deposits | ||||
|             .read() | ||||
|             .iter() | ||||
|             .map(|(_, d)| d.clone()) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let attester_slashings = operation_pool | ||||
|             .attester_slashings | ||||
|             .read() | ||||
|             .iter() | ||||
|             .map(|(_, slashing)| slashing.clone()) | ||||
|             .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(); | ||||
| 
 | ||||
|         let transfers = operation_pool.transfers.read().iter().cloned().collect(); | ||||
| 
 | ||||
|         Self { | ||||
|             attestations, | ||||
|             deposits, | ||||
|             attester_slashings, | ||||
|             proposer_slashings, | ||||
|             voluntary_exits, | ||||
|             transfers, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Reconstruct an `OperationPool`.
 | ||||
|     pub fn into_operation_pool<T: EthSpec>( | ||||
|         self, | ||||
|         state: &BeaconState<T>, | ||||
|         spec: &ChainSpec, | ||||
|     ) -> OperationPool<T> { | ||||
|         let attestations = RwLock::new(self.attestations.into_iter().map(SszPair::into).collect()); | ||||
|         let deposits = RwLock::new(self.deposits.into_iter().map(|d| (d.index, d)).collect()); | ||||
|         let attester_slashings = RwLock::new( | ||||
|             self.attester_slashings | ||||
|                 .into_iter() | ||||
|                 .map(|slashing| { | ||||
|                     ( | ||||
|                         OperationPool::attester_slashing_id(&slashing, state, spec), | ||||
|                         slashing, | ||||
|                     ) | ||||
|                 }) | ||||
|                 .collect(), | ||||
|         ); | ||||
|         let proposer_slashings = RwLock::new( | ||||
|             self.proposer_slashings | ||||
|                 .into_iter() | ||||
|                 .map(|slashing| (slashing.proposer_index, slashing)) | ||||
|                 .collect(), | ||||
|         ); | ||||
|         let voluntary_exits = RwLock::new( | ||||
|             self.voluntary_exits | ||||
|                 .into_iter() | ||||
|                 .map(|exit| (exit.validator_index, exit)) | ||||
|                 .collect(), | ||||
|         ); | ||||
|         let transfers = RwLock::new(self.transfers.into_iter().collect()); | ||||
| 
 | ||||
|         OperationPool { | ||||
|             attestations, | ||||
|             deposits, | ||||
|             attester_slashings, | ||||
|             proposer_slashings, | ||||
|             voluntary_exits, | ||||
|             transfers, | ||||
|             _phantom: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Tuples for SSZ.
 | ||||
| #[derive(Encode, Decode)] | ||||
| struct SszPair<X: Encode + Decode, Y: Encode + Decode> { | ||||
|     x: X, | ||||
| @ -38,75 +153,3 @@ where | ||||
|         (self.x, self.y) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Encode, Decode)] | ||||
| pub struct PersistedOperationPool { | ||||
|     /// Mapping from attestation ID to attestation mappings, sorted by ID.
 | ||||
|     // TODO: 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<SszPair<AttestationId, Vec<Attestation>>>, | ||||
|     deposits: Vec<Deposit>, | ||||
|     /// Attester slashings sorted by their pair of attestation IDs (not stored).
 | ||||
|     attester_slashings: Vec<AttesterSlashing>, | ||||
| } | ||||
| 
 | ||||
| impl PersistedOperationPool { | ||||
|     pub fn from_operation_pool<T: EthSpec>(operation_pool: &OperationPool<T>) -> Self { | ||||
|         let attestations = operation_pool | ||||
|             .attestations | ||||
|             .read() | ||||
|             .iter() | ||||
|             .map(|(att_id, att)| SszPair::new(att_id.clone(), att.clone())) | ||||
|             .sorted_by(|att1, att2| Ord::cmp(&att1.x, &att2.x)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let deposits = operation_pool | ||||
|             .deposits | ||||
|             .read() | ||||
|             .iter() | ||||
|             .map(|(_, d)| d.clone()) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let attester_slashings = operation_pool | ||||
|             .attester_slashings | ||||
|             .read() | ||||
|             .iter() | ||||
|             .sorted_by(|(id1, _), (id2, _)| Ord::cmp(&id1, &id2)) | ||||
|             .map(|(_, slashing)| slashing.clone()) | ||||
|             .collect(); | ||||
| 
 | ||||
|         Self { | ||||
|             attestations, | ||||
|             deposits, | ||||
|             attester_slashings, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_operation_pool<T: EthSpec>( | ||||
|         self, | ||||
|         state: &BeaconState<T>, | ||||
|         spec: &ChainSpec, | ||||
|     ) -> OperationPool<T> { | ||||
|         let attestations = RwLock::new(self.attestations.into_iter().map(SszPair::into).collect()); | ||||
|         let deposits = RwLock::new(self.deposits.into_iter().map(|d| (d.index, d)).collect()); | ||||
|         let attester_slashings = RwLock::new( | ||||
|             self.attester_slashings | ||||
|                 .into_iter() | ||||
|                 .map(|slashing| { | ||||
|                     ( | ||||
|                         OperationPool::attester_slashing_id(&slashing, state, spec), | ||||
|                         slashing, | ||||
|                     ) | ||||
|                 }) | ||||
|                 .collect(), | ||||
|         ); | ||||
| 
 | ||||
|         OperationPool { | ||||
|             attestations, | ||||
|             deposits, | ||||
|             attester_slashings, | ||||
|             // TODO
 | ||||
|             ..OperationPool::new() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user