Merge remote-tracking branch 'origin/unstable' into tree-states

This commit is contained in:
Michael Sproul
2022-09-14 13:51:23 +10:00
404 changed files with 28947 additions and 12000 deletions

View File

@@ -11,10 +11,15 @@ use crate::{
StateSkipConfig,
};
use bls::get_withdrawal_credentials;
use execution_layer::test_utils::DEFAULT_JWT_SECRET;
use execution_layer::{
test_utils::{ExecutionBlockGenerator, MockExecutionLayer, DEFAULT_TERMINAL_BLOCK},
auth::JwtKey,
test_utils::{
ExecutionBlockGenerator, MockExecutionLayer, TestingBuilder, DEFAULT_TERMINAL_BLOCK,
},
ExecutionLayer,
};
use fork_choice::CountUnrealized;
use futures::channel::mpsc::Receiver;
pub use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
use int_to_bytes::int_to_bytes32;
@@ -28,9 +33,11 @@ use rayon::prelude::*;
use sensitive_url::SensitiveUrl;
use slog::Logger;
use slot_clock::TestingSlotClock;
use state_processing::per_block_processing::compute_timestamp_at_slot;
use state_processing::state_advance::complete_state_advance;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
@@ -122,8 +129,7 @@ pub fn test_spec<E: EthSpec>() -> ChainSpec {
FORK_NAME_ENV_VAR, e
)
});
let fork = ForkName::from_str(fork_name.as_str())
.unwrap_or_else(|()| panic!("unknown FORK_NAME: {}", fork_name));
let fork = ForkName::from_str(fork_name.as_str()).unwrap();
fork.make_genesis_spec(E::default_spec())
} else {
E::default_spec()
@@ -144,8 +150,10 @@ pub struct Builder<T: BeaconChainTypes> {
store: Option<Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>>,
initial_mutator: Option<BoxedMutator<T::EthSpec, T::HotStore, T::ColdStore>>,
store_mutator: Option<BoxedMutator<T::EthSpec, T::HotStore, T::ColdStore>>,
execution_layer: Option<ExecutionLayer>,
execution_layer: Option<ExecutionLayer<T::EthSpec>>,
mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
mock_builder: Option<TestingBuilder<T::EthSpec>>,
testing_slot_clock: Option<TestingSlotClock>,
runtime: TestRuntime,
log: Logger,
}
@@ -203,6 +211,20 @@ impl<E: EthSpec> Builder<EphemeralHarnessType<E>> {
self.store = Some(store);
self.store_mutator(Box::new(mutator))
}
/// Manually restore from a given `MemoryStore`.
pub fn resumed_ephemeral_store(
mut self,
store: Arc<HotColdDB<E, MemoryStore<E>, MemoryStore<E>>>,
) -> Self {
let mutator = move |builder: BeaconChainBuilder<_>| {
builder
.resume_from_db()
.expect("should resume from database")
};
self.store = Some(store);
self.store_mutator(Box::new(mutator))
}
}
impl<E: EthSpec> Builder<DiskHarnessType<E>> {
@@ -263,6 +285,8 @@ where
store_mutator: None,
execution_layer: None,
mock_execution_layer: None,
mock_builder: None,
testing_slot_clock: None,
runtime,
log,
}
@@ -358,12 +382,46 @@ where
DEFAULT_TERMINAL_BLOCK,
spec.terminal_block_hash,
spec.terminal_block_hash_activation_epoch,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
None,
);
self.execution_layer = Some(mock.el.clone());
self.mock_execution_layer = Some(mock);
self
}
pub fn mock_execution_layer_with_builder(mut self, beacon_url: SensitiveUrl) -> Self {
// Get a random unused port
let port = unused_port::unused_tcp_port().unwrap();
let builder_url = SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap();
let spec = self.spec.clone().expect("cannot build without spec");
let mock_el = MockExecutionLayer::new(
self.runtime.task_executor.clone(),
spec.terminal_total_difficulty,
DEFAULT_TERMINAL_BLOCK,
spec.terminal_block_hash,
spec.terminal_block_hash_activation_epoch,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
Some(builder_url.clone()),
)
.move_to_terminal_block();
let mock_el_url = SensitiveUrl::parse(mock_el.server.url().as_str()).unwrap();
self.mock_builder = Some(TestingBuilder::new(
mock_el_url,
builder_url,
beacon_url,
spec,
self.runtime.task_executor.clone(),
));
self.execution_layer = Some(mock_el.el.clone());
self.mock_execution_layer = Some(mock_el);
self
}
/// Instruct the mock execution engine to always return a "valid" response to any payload it is
/// asked to execute.
pub fn mock_execution_layer_all_payloads_valid(self) -> Self {
@@ -375,6 +433,11 @@ where
self
}
pub fn testing_slot_clock(mut self, slot_clock: TestingSlotClock) -> Self {
self.testing_slot_clock = Some(slot_clock);
self
}
pub fn build(self) -> BeaconChainHarness<BaseHarnessType<E, Hot, Cold>> {
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
@@ -415,7 +478,9 @@ where
};
// Initialize the slot clock only if it hasn't already been initialized.
builder = if builder.get_slot_clock().is_none() {
builder = if let Some(testing_slot_clock) = self.testing_slot_clock {
builder.slot_clock(testing_slot_clock)
} else if builder.get_slot_clock().is_none() {
builder
.testing_slot_clock(Duration::from_secs(seconds_per_slot))
.expect("should configure testing slot clock")
@@ -432,6 +497,7 @@ where
shutdown_receiver: Arc::new(Mutex::new(shutdown_receiver)),
runtime: self.runtime,
mock_execution_layer: self.mock_execution_layer,
mock_builder: self.mock_builder.map(Arc::new),
rng: make_rng(),
}
}
@@ -450,6 +516,7 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub runtime: TestRuntime,
pub mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
pub mock_builder: Option<Arc<TestingBuilder<T::EthSpec>>>,
pub rng: Mutex<StdRng>,
}
@@ -511,13 +578,40 @@ where
}
pub fn get_current_state(&self) -> BeaconState<E> {
self.chain.head().unwrap().beacon_state
self.chain.head_beacon_state_cloned()
}
pub fn get_timestamp_at_slot(&self) -> u64 {
let state = self.get_current_state();
compute_timestamp_at_slot(&state, &self.spec).unwrap()
}
pub fn get_current_state_and_root(&self) -> (BeaconState<E>, Hash256) {
let head = self.chain.head().unwrap();
let head = self.chain.head_snapshot();
let state_root = head.beacon_state_root();
(head.beacon_state, state_root)
(head.beacon_state.clone(), state_root)
}
pub fn head_slot(&self) -> Slot {
self.chain.canonical_head.cached_head().head_slot()
}
pub fn head_block_root(&self) -> Hash256 {
self.chain.canonical_head.cached_head().head_block_root()
}
pub fn finalized_checkpoint(&self) -> Checkpoint {
self.chain
.canonical_head
.cached_head()
.finalized_checkpoint()
}
pub fn justified_checkpoint(&self) -> Checkpoint {
self.chain
.canonical_head
.cached_head()
.justified_checkpoint()
}
pub fn get_current_slot(&self) -> Slot {
@@ -562,7 +656,7 @@ where
state.get_block_root(slot).unwrap() == state.get_block_root(slot - 1).unwrap()
}
pub fn make_block(
pub async fn make_block(
&self,
mut state: BeaconState<E>,
slot: Slot,
@@ -596,6 +690,7 @@ where
Some(graffiti),
ProduceBlockVerification::VerifyRandao,
)
.await
.unwrap();
let signed_block = block.sign(
@@ -610,7 +705,7 @@ where
/// Useful for the `per_block_processing` tests. Creates a block, and returns the state after
/// caches are built but before the generated block is processed.
pub fn make_block_return_pre_state(
pub async fn make_block_return_pre_state(
&self,
mut state: BeaconState<E>,
slot: Slot,
@@ -646,6 +741,7 @@ where
Some(graffiti),
ProduceBlockVerification::VerifyRandao,
)
.await
.unwrap();
let signed_block = block.sign(
@@ -1072,6 +1168,19 @@ where
}
pub fn make_attester_slashing(&self, validator_indices: Vec<u64>) -> AttesterSlashing<E> {
self.make_attester_slashing_with_epochs(validator_indices, None, None, None, None)
}
pub fn make_attester_slashing_with_epochs(
&self,
validator_indices: Vec<u64>,
source1: Option<Epoch>,
target1: Option<Epoch>,
source2: Option<Epoch>,
target2: Option<Epoch>,
) -> AttesterSlashing<E> {
let fork = self.chain.canonical_head.cached_head().head_fork();
let mut attestation_1 = IndexedAttestation {
attesting_indices: VariableList::new(validator_indices).unwrap(),
data: AttestationData {
@@ -1080,11 +1189,11 @@ where
beacon_block_root: Hash256::zero(),
target: Checkpoint {
root: Hash256::zero(),
epoch: Epoch::new(0),
epoch: target1.unwrap_or(fork.epoch),
},
source: Checkpoint {
root: Hash256::zero(),
epoch: Epoch::new(0),
epoch: source1.unwrap_or(Epoch::new(0)),
},
},
signature: AggregateSignature::infinity(),
@@ -1092,12 +1201,13 @@ where
let mut attestation_2 = attestation_1.clone();
attestation_2.data.index += 1;
attestation_2.data.source.epoch = source2.unwrap_or(Epoch::new(0));
attestation_2.data.target.epoch = target2.unwrap_or(fork.epoch);
for attestation in &mut [&mut attestation_1, &mut attestation_2] {
for &i in &attestation.attesting_indices {
let sk = &self.validator_keypairs[i as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let genesis_validators_root = self.chain.genesis_validators_root;
let domain = self.chain.spec.get_domain(
@@ -1151,11 +1261,11 @@ where
attestation_2.data.index += 1;
let fork = self.chain.canonical_head.cached_head().head_fork();
for attestation in &mut [&mut attestation_1, &mut attestation_2] {
for &i in &attestation.attesting_indices {
let sk = &self.validator_keypairs[i as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let genesis_validators_root = self.chain.genesis_validators_root;
let domain = self.chain.spec.get_domain(
@@ -1177,19 +1287,25 @@ where
}
pub fn make_proposer_slashing(&self, validator_index: u64) -> ProposerSlashing {
let mut block_header_1 = self
.chain
.head_beacon_block()
.unwrap()
.message()
.block_header();
self.make_proposer_slashing_at_slot(validator_index, None)
}
pub fn make_proposer_slashing_at_slot(
&self,
validator_index: u64,
slot_override: Option<Slot>,
) -> ProposerSlashing {
let mut block_header_1 = self.chain.head_beacon_block().message().block_header();
block_header_1.proposer_index = validator_index;
if let Some(slot) = slot_override {
block_header_1.slot = slot;
}
let mut block_header_2 = block_header_1.clone();
block_header_2.state_root = Hash256::zero();
let sk = &self.validator_keypairs[validator_index as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let fork = self.chain.canonical_head.cached_head().head_fork();
let genesis_validators_root = self.chain.genesis_validators_root;
let mut signed_block_headers = vec![block_header_1, block_header_2]
@@ -1207,7 +1323,7 @@ where
pub fn make_voluntary_exit(&self, validator_index: u64, epoch: Epoch) -> SignedVoluntaryExit {
let sk = &self.validator_keypairs[validator_index as usize].sk;
let fork = self.chain.head_info().unwrap().fork;
let fork = self.chain.canonical_head.cached_head().head_fork();
let genesis_validators_root = self.chain.genesis_validators_root;
VoluntaryExit {
@@ -1230,7 +1346,7 @@ where
/// Create a new block, apply `block_modifier` to it, sign it and return it.
///
/// The state returned is a pre-block state at the same slot as the produced block.
pub fn make_block_with_modifier(
pub async fn make_block_with_modifier(
&self,
state: BeaconState<E>,
slot: Slot,
@@ -1239,7 +1355,7 @@ where
assert_ne!(slot, 0, "can't produce a block at slot 0");
assert!(slot >= state.slot());
let (block, state) = self.make_block_return_pre_state(state, slot);
let (block, state) = self.make_block_return_pre_state(state, slot).await;
let (mut block, _) = block.deconstruct();
block_modifier(&mut block);
@@ -1327,23 +1443,31 @@ where
(deposits, state)
}
pub fn process_block(
pub async fn process_block(
&self,
slot: Slot,
block: SignedBeaconBlock<E>,
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
self.set_current_slot(slot);
let block_hash: SignedBeaconBlockHash = self.chain.process_block(block)?.into();
self.chain.fork_choice()?;
let block_hash: SignedBeaconBlockHash = self
.chain
.process_block(Arc::new(block), CountUnrealized::True)
.await?
.into();
self.chain.recompute_head_at_current_slot().await;
Ok(block_hash)
}
pub fn process_block_result(
pub async fn process_block_result(
&self,
block: SignedBeaconBlock<E>,
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
let block_hash: SignedBeaconBlockHash = self.chain.process_block(block)?.into();
self.chain.fork_choice().unwrap();
let block_hash: SignedBeaconBlockHash = self
.chain
.process_block(Arc::new(block), CountUnrealized::True)
.await?
.into();
self.chain.recompute_head_at_current_slot().await;
Ok(block_hash)
}
@@ -1382,7 +1506,7 @@ where
self.chain
.apply_attestation_to_fork_choice(&verified)
.unwrap();
self.chain.add_to_block_inclusion_pool(&verified).unwrap();
self.chain.add_to_block_inclusion_pool(verified).unwrap();
}
}
@@ -1398,14 +1522,14 @@ where
self.chain.slot_clock.set_slot(slot.into());
}
pub fn add_block_at_slot(
pub async fn add_block_at_slot(
&self,
slot: Slot,
state: BeaconState<E>,
) -> Result<(SignedBeaconBlockHash, SignedBeaconBlock<E>, BeaconState<E>), BlockError<E>> {
self.set_current_slot(slot);
let (block, new_state) = self.make_block(state, slot);
let block_hash = self.process_block(slot, block.clone())?;
let (block, new_state) = self.make_block(state, slot).await;
let block_hash = self.process_block(slot, block.clone()).await?;
Ok((block_hash, block, new_state))
}
@@ -1422,19 +1546,19 @@ where
self.process_attestations(attestations);
}
pub fn add_attested_block_at_slot(
pub async fn add_attested_block_at_slot(
&self,
slot: Slot,
state: BeaconState<E>,
state_root: Hash256,
validators: &[usize],
) -> Result<(SignedBeaconBlockHash, BeaconState<E>), BlockError<E>> {
let (block_hash, block, state) = self.add_block_at_slot(slot, state)?;
let (block_hash, block, state) = self.add_block_at_slot(slot, state).await?;
self.attest_block(&state, state_root, block_hash, &block, validators);
Ok((block_hash, state))
}
pub fn add_attested_blocks_at_slots(
pub async fn add_attested_blocks_at_slots(
&self,
state: BeaconState<E>,
state_root: Hash256,
@@ -1443,9 +1567,10 @@ where
) -> AddBlocksResult<E> {
assert!(!slots.is_empty());
self.add_attested_blocks_at_slots_given_lbh(state, state_root, slots, validators, None)
.await
}
fn add_attested_blocks_at_slots_given_lbh(
async fn add_attested_blocks_at_slots_given_lbh(
&self,
mut state: BeaconState<E>,
state_root: Hash256,
@@ -1462,6 +1587,7 @@ where
for slot in slots {
let (block_hash, new_state) = self
.add_attested_block_at_slot(*slot, state, state_root, validators)
.await
.unwrap();
state = new_state;
block_hash_from_slot.insert(*slot, block_hash);
@@ -1483,7 +1609,7 @@ where
/// epoch at a time.
///
/// Chains is a vec of `(state, slots, validators)` tuples.
pub fn add_blocks_on_multiple_chains(
pub async fn add_blocks_on_multiple_chains(
&self,
chains: Vec<(BeaconState<E>, Vec<Slot>, Vec<usize>)>,
) -> Vec<AddBlocksResult<E>> {
@@ -1542,7 +1668,8 @@ where
&epoch_slots,
&validators,
Some(head_block),
);
)
.await;
block_hashes.extend(new_block_hashes);
state_hashes.extend(new_state_hashes);
@@ -1591,18 +1718,18 @@ where
/// Deprecated: Use make_block() instead
///
/// Returns a newly created block, signed by the proposer for the given slot.
pub fn build_block(
pub async fn build_block(
&self,
state: BeaconState<E>,
slot: Slot,
_block_strategy: BlockStrategy,
) -> (SignedBeaconBlock<E>, BeaconState<E>) {
self.make_block(state, slot)
self.make_block(state, slot).await
}
/// Uses `Self::extend_chain` to build the chain out to the `target_slot`.
pub fn extend_to_slot(&self, target_slot: Slot) -> Hash256 {
if self.chain.slot().unwrap() == self.chain.head_info().unwrap().slot {
pub async fn extend_to_slot(&self, target_slot: Slot) -> Hash256 {
if self.chain.slot().unwrap() == self.chain.canonical_head.cached_head().head_slot() {
self.advance_slot();
}
@@ -1613,7 +1740,7 @@ where
.checked_add(1)
.unwrap();
self.extend_slots(num_slots)
self.extend_slots(num_slots).await
}
/// Uses `Self::extend_chain` to `num_slots` blocks.
@@ -1622,8 +1749,8 @@ where
///
/// - BlockStrategy::OnCanonicalHead,
/// - AttestationStrategy::AllValidators,
pub fn extend_slots(&self, num_slots: usize) -> Hash256 {
if self.chain.slot().unwrap() == self.chain.head_info().unwrap().slot {
pub async fn extend_slots(&self, num_slots: usize) -> Hash256 {
if self.chain.slot().unwrap() == self.chain.canonical_head.cached_head().head_slot() {
self.advance_slot();
}
@@ -1632,6 +1759,7 @@ where
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await
}
/// Deprecated: Use add_attested_blocks_at_slots() instead
@@ -1645,7 +1773,7 @@ where
///
/// The `attestation_strategy` dictates which validators will attest to the newly created
/// blocks.
pub fn extend_chain(
pub async fn extend_chain(
&self,
num_blocks: usize,
block_strategy: BlockStrategy,
@@ -1680,8 +1808,9 @@ where
AttestationStrategy::SomeValidators(vals) => vals,
};
let state_root = state.update_tree_hash_cache().unwrap();
let (_, _, last_produced_block_hash, _) =
self.add_attested_blocks_at_slots(state, state_root, &slots, &validators);
let (_, _, last_produced_block_hash, _) = self
.add_attested_blocks_at_slots(state, state_root, &slots, &validators)
.await;
last_produced_block_hash.into()
}
@@ -1695,44 +1824,50 @@ where
/// then built `faulty_fork_blocks`.
///
/// Returns `(honest_head, faulty_head)`, the roots of the blocks at the top of each chain.
pub fn generate_two_forks_by_skipping_a_block(
pub async fn generate_two_forks_by_skipping_a_block(
&self,
honest_validators: &[usize],
faulty_validators: &[usize],
honest_fork_blocks: usize,
faulty_fork_blocks: usize,
) -> (Hash256, Hash256) {
let initial_head_slot = self
.chain
.head()
.expect("should get head")
.beacon_block
.slot();
let initial_head_slot = self.chain.head_snapshot().beacon_block.slot();
// Move to the next slot so we may produce some more blocks on the head.
self.advance_slot();
// Extend the chain with blocks where only honest validators agree.
let honest_head = self.extend_chain(
honest_fork_blocks,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::SomeValidators(honest_validators.to_vec()),
);
let honest_head = self
.extend_chain(
honest_fork_blocks,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::SomeValidators(honest_validators.to_vec()),
)
.await;
// Go back to the last block where all agreed, and build blocks upon it where only faulty nodes
// agree.
let faulty_head = self.extend_chain(
faulty_fork_blocks,
BlockStrategy::ForkCanonicalChainAt {
previous_slot: initial_head_slot,
// `initial_head_slot + 2` means one slot is skipped.
first_slot: initial_head_slot + 2,
},
AttestationStrategy::SomeValidators(faulty_validators.to_vec()),
);
let faulty_head = self
.extend_chain(
faulty_fork_blocks,
BlockStrategy::ForkCanonicalChainAt {
previous_slot: initial_head_slot,
// `initial_head_slot + 2` means one slot is skipped.
first_slot: initial_head_slot + 2,
},
AttestationStrategy::SomeValidators(faulty_validators.to_vec()),
)
.await;
assert_ne!(honest_head, faulty_head, "forks should be distinct");
(honest_head, faulty_head)
}
}
// Junk `Debug` impl to satistfy certain trait bounds during testing.
impl<T: BeaconChainTypes> fmt::Debug for BeaconChainHarness<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BeaconChainHarness")
}
}