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

This commit is contained in:
Michael Sproul
2023-09-13 11:25:18 +10:00
250 changed files with 13730 additions and 5455 deletions

View File

@@ -1,14 +1,14 @@
use beacon_chain::test_utils::RelativeSyncCommittee;
use beacon_chain::{
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType},
BeaconChain, StateSkipConfig, WhenSlotSkipped, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
BeaconChain, ChainConfig, StateSkipConfig, WhenSlotSkipped,
};
use environment::null_logger;
use eth2::{
mixin::{RequestAccept, ResponseForkName, ResponseOptional},
reqwest::RequestBuilder,
types::{BlockId as CoreBlockId, ForkChoiceNode, StateId as CoreStateId, *},
BeaconNodeHttpClient, Error, Timeouts,
BeaconNodeHttpClient, Error, StatusCode, Timeouts,
};
use execution_layer::test_utils::TestingBuilder;
use execution_layer::test_utils::DEFAULT_BUILDER_THRESHOLD_WEI;
@@ -28,9 +28,9 @@ use sensitive_url::SensitiveUrl;
use slot_clock::SlotClock;
use state_processing::per_block_processing::get_expected_withdrawals;
use state_processing::per_slot_processing;
use state_processing::state_advance::partial_state_advance;
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::oneshot;
use tokio::time::Duration;
use tree_hash::TreeHash;
use types::application_domain::ApplicationDomain;
@@ -70,7 +70,6 @@ struct ApiTester {
attester_slashing: AttesterSlashing<E>,
proposer_slashing: ProposerSlashing,
voluntary_exit: SignedVoluntaryExit,
_server_shutdown: oneshot::Sender<()>,
network_rx: NetworkReceivers<E>,
local_enr: Enr,
external_peer_id: PeerId,
@@ -79,6 +78,7 @@ struct ApiTester {
struct ApiTesterConfig {
spec: ChainSpec,
retain_historic_states: bool,
builder_threshold: Option<u128>,
}
@@ -88,11 +88,19 @@ impl Default for ApiTesterConfig {
spec.shard_committee_period = 2;
Self {
spec,
retain_historic_states: false,
builder_threshold: None,
}
}
}
impl ApiTesterConfig {
fn retain_historic_states(mut self) -> Self {
self.retain_historic_states = true;
self
}
}
impl ApiTester {
pub async fn new() -> Self {
// This allows for testing voluntary exits without building out a massive chain.
@@ -120,6 +128,10 @@ impl ApiTester {
let harness = Arc::new(
BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.clone())
.chain_config(ChainConfig {
reconstruct_historic_states: config.retain_historic_states,
..ChainConfig::default()
})
.logger(logging::test_logger())
.deterministic_keypairs(VALIDATOR_COUNT)
.fresh_ephemeral_store()
@@ -234,11 +246,10 @@ impl ApiTester {
let ApiServer {
server,
listening_socket: _,
shutdown_tx,
network_rx,
local_enr,
external_peer_id,
} = create_api_server_on_port(chain.clone(), log, port).await;
} = create_api_server_on_port(chain.clone(), &harness.runtime, log, port).await;
harness.runtime.task_executor.spawn(server, "api_server");
@@ -266,7 +277,6 @@ impl ApiTester {
attester_slashing,
proposer_slashing,
voluntary_exit,
_server_shutdown: shutdown_tx,
network_rx,
local_enr,
external_peer_id,
@@ -320,11 +330,10 @@ impl ApiTester {
let ApiServer {
server,
listening_socket,
shutdown_tx,
network_rx,
local_enr,
external_peer_id,
} = create_api_server(chain.clone(), log).await;
} = create_api_server(chain.clone(), &harness.runtime, log).await;
harness.runtime.task_executor.spawn(server, "api_server");
@@ -349,7 +358,6 @@ impl ApiTester {
attester_slashing,
proposer_slashing,
voluntary_exit,
_server_shutdown: shutdown_tx,
network_rx,
local_enr,
external_peer_id,
@@ -381,6 +389,7 @@ impl ApiTester {
pub async fn new_mev_tester_no_builder_threshold() -> Self {
let mut config = ApiTesterConfig {
builder_threshold: Some(0),
retain_historic_states: false,
spec: E::default_spec(),
};
config.spec.altair_fork_epoch = Some(Epoch::new(0));
@@ -1247,6 +1256,22 @@ impl ApiTester {
self
}
pub async fn test_post_beacon_blocks_ssz_valid(mut self) -> Self {
let next_block = &self.next_block;
self.client
.post_beacon_blocks_ssz(next_block)
.await
.unwrap();
assert!(
self.network_rx.network_recv.recv().await.is_some(),
"valid blocks should be sent to network"
);
self
}
pub async fn test_post_beacon_blocks_invalid(mut self) -> Self {
let block = self
.harness
@@ -1270,6 +1295,86 @@ impl ApiTester {
self
}
pub async fn test_post_beacon_blocks_ssz_invalid(mut self) -> Self {
let block = self
.harness
.make_block_with_modifier(
self.harness.get_current_state(),
self.harness.get_current_slot(),
|b| {
*b.state_root_mut() = Hash256::zero();
},
)
.await
.0;
assert!(self.client.post_beacon_blocks_ssz(&block).await.is_err());
assert!(
self.network_rx.network_recv.recv().await.is_some(),
"gossip valid blocks should be sent to network"
);
self
}
pub async fn test_post_beacon_blocks_duplicate(self) -> Self {
let block = self
.harness
.make_block(
self.harness.get_current_state(),
self.harness.get_current_slot(),
)
.await
.0;
assert!(self.client.post_beacon_blocks(&block).await.is_ok());
let blinded_block = block.clone_as_blinded();
// Test all the POST methods in sequence, they should all behave the same.
let responses = vec![
self.client.post_beacon_blocks(&block).await.unwrap_err(),
self.client
.post_beacon_blocks_v2(&block, None)
.await
.unwrap_err(),
self.client
.post_beacon_blocks_ssz(&block)
.await
.unwrap_err(),
self.client
.post_beacon_blocks_v2_ssz(&block, None)
.await
.unwrap_err(),
self.client
.post_beacon_blinded_blocks(&blinded_block)
.await
.unwrap_err(),
self.client
.post_beacon_blinded_blocks_v2(&blinded_block, None)
.await
.unwrap_err(),
self.client
.post_beacon_blinded_blocks_ssz(&blinded_block)
.await
.unwrap_err(),
self.client
.post_beacon_blinded_blocks_v2_ssz(&blinded_block, None)
.await
.unwrap_err(),
];
for (i, response) in responses.into_iter().enumerate() {
assert_eq!(
response.status().unwrap(),
StatusCode::ACCEPTED,
"response {i}"
);
}
self
}
pub async fn test_beacon_blocks(self) -> Self {
for block_id in self.interesting_block_ids() {
let expected = block_id
@@ -2274,7 +2379,9 @@ impl ApiTester {
.unwrap();
self.chain.slot_clock.set_current_time(
current_epoch_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY - Duration::from_millis(1),
current_epoch_start
- self.chain.spec.maximum_gossip_clock_disparity()
- Duration::from_millis(1),
);
let dependent_root = self
@@ -2311,9 +2418,9 @@ impl ApiTester {
"should not get attester duties outside of tolerance"
);
self.chain
.slot_clock
.set_current_time(current_epoch_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY);
self.chain.slot_clock.set_current_time(
current_epoch_start - self.chain.spec.maximum_gossip_clock_disparity(),
);
self.client
.get_validator_duties_proposer(current_epoch)
@@ -2537,6 +2644,66 @@ impl ApiTester {
}
}
pub async fn test_blinded_block_production_ssz<Payload: AbstractExecPayload<E>>(&self) {
let fork = self.chain.canonical_head.cached_head().head_fork();
let genesis_validators_root = self.chain.genesis_validators_root;
for _ in 0..E::slots_per_epoch() * 3 {
let slot = self.chain.slot().unwrap();
let epoch = self.chain.epoch().unwrap();
let proposer_pubkey_bytes = self
.client
.get_validator_duties_proposer(epoch)
.await
.unwrap()
.data
.into_iter()
.find(|duty| duty.slot == slot)
.map(|duty| duty.pubkey)
.unwrap();
let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap();
let sk = self
.validator_keypairs()
.iter()
.find(|kp| kp.pk == proposer_pubkey)
.map(|kp| kp.sk.clone())
.unwrap();
let randao_reveal = {
let domain = self.chain.spec.get_domain(
epoch,
Domain::Randao,
&fork,
genesis_validators_root,
);
let message = epoch.signing_root(domain);
sk.sign(message).into()
};
let block = self
.client
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
.await
.unwrap()
.data;
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
self.client
.post_beacon_blinded_blocks_ssz(&signed_block)
.await
.unwrap();
// This converts the generic `Payload` to a concrete type for comparison.
let head_block = SignedBeaconBlock::from(signed_block.clone());
assert_eq!(head_block, signed_block);
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
}
}
pub async fn test_blinded_block_production_no_verify_randao<Payload: AbstractExecPayload<E>>(
self,
) -> Self {
@@ -2980,6 +3147,69 @@ impl ApiTester {
self
}
pub async fn test_post_validator_liveness_epoch(self) -> Self {
let epoch = self.chain.epoch().unwrap();
let head_state = self.chain.head_beacon_state_cloned();
let indices = (0..head_state.validators().len())
.map(|i| i as u64)
.collect::<Vec<_>>();
// Construct the expected response
let expected: Vec<StandardLivenessResponseData> = head_state
.validators()
.iter()
.enumerate()
.map(|(index, _)| StandardLivenessResponseData {
index: index as u64,
is_live: false,
})
.collect();
let result = self
.client
.post_validator_liveness_epoch(epoch, indices.clone())
.await
.unwrap()
.data;
assert_eq!(result, expected);
// Attest to the current slot
self.client
.post_beacon_pool_attestations(self.attestations.as_slice())
.await
.unwrap();
let result = self
.client
.post_validator_liveness_epoch(epoch, indices.clone())
.await
.unwrap()
.data;
let committees = head_state
.get_beacon_committees_at_slot(self.chain.slot().unwrap())
.unwrap();
let attesting_validators: Vec<usize> = committees
.into_iter()
.flat_map(|committee| committee.committee.iter().cloned())
.collect();
// All attesters should now be considered live
let expected = expected
.into_iter()
.map(|mut a| {
if attesting_validators.contains(&(a.index as usize)) {
a.is_live = true;
}
a
})
.collect::<Vec<_>>();
assert_eq!(result, expected);
self
}
// Helper function for tests that require a valid RANDAO signature.
async fn get_test_randao(&self, slot: Slot, epoch: Epoch) -> (u64, SignatureBytes) {
let fork = self.chain.canonical_head.cached_head().head_fork();
@@ -4169,6 +4399,72 @@ impl ApiTester {
self
}
pub async fn test_get_expected_withdrawals_invalid_state(self) -> Self {
let state_id = CoreStateId::Root(Hash256::zero());
let result = self.client.get_expected_withdrawals(&state_id).await;
match result {
Err(e) => {
assert_eq!(e.status().unwrap(), 404);
}
_ => panic!("query did not fail correctly"),
}
self
}
pub async fn test_get_expected_withdrawals_capella(self) -> Self {
let slot = self.chain.slot().unwrap();
let state_id = CoreStateId::Slot(slot);
// calculate the expected withdrawals
let (mut state, _, _) = StateId(state_id).state(&self.chain).unwrap();
let proposal_slot = state.slot() + 1;
let proposal_epoch = proposal_slot.epoch(E::slots_per_epoch());
let (state_root, _, _) = StateId(state_id).root(&self.chain).unwrap();
if proposal_epoch != state.current_epoch() {
let _ = partial_state_advance(
&mut state,
Some(state_root),
proposal_slot,
&self.chain.spec,
);
}
let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec).unwrap();
// fetch expected withdrawals from the client
let result = self.client.get_expected_withdrawals(&state_id).await;
match result {
Ok(withdrawal_response) => {
assert_eq!(withdrawal_response.execution_optimistic, Some(false));
assert_eq!(withdrawal_response.finalized, Some(false));
assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec());
}
Err(e) => {
println!("{:?}", e);
panic!("query failed incorrectly");
}
}
self
}
pub async fn test_get_expected_withdrawals_pre_capella(self) -> Self {
let state_id = CoreStateId::Head;
let result = self.client.get_expected_withdrawals(&state_id).await;
match result {
Err(e) => {
assert_eq!(e.status().unwrap(), 400);
}
_ => panic!("query did not fail correctly"),
}
self
}
pub async fn test_get_events_altair(self) -> Self {
let topics = vec![EventTopic::ContributionAndProof];
let mut events_future = self
@@ -4388,6 +4684,22 @@ async fn post_beacon_blocks_valid() {
ApiTester::new().await.test_post_beacon_blocks_valid().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn post_beacon_blocks_ssz_valid() {
ApiTester::new()
.await
.test_post_beacon_blocks_ssz_valid()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_post_beacon_blocks_ssz_invalid() {
ApiTester::new()
.await
.test_post_beacon_blocks_ssz_invalid()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn post_beacon_blocks_invalid() {
ApiTester::new()
@@ -4396,6 +4708,14 @@ async fn post_beacon_blocks_invalid() {
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn post_beacon_blocks_duplicate() {
ApiTester::new()
.await
.test_post_beacon_blocks_duplicate()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn beacon_pools_post_attestations_valid() {
ApiTester::new()
@@ -4531,7 +4851,7 @@ async fn get_validator_duties_attester_with_skip_slots() {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_duties_proposer() {
ApiTester::new()
ApiTester::new_from_config(ApiTesterConfig::default().retain_historic_states())
.await
.test_get_validator_duties_proposer()
.await;
@@ -4539,7 +4859,7 @@ async fn get_validator_duties_proposer() {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_duties_proposer_with_skip_slots() {
ApiTester::new()
ApiTester::new_from_config(ApiTesterConfig::default().retain_historic_states())
.await
.skip_slots(E::slots_per_epoch() * 2)
.test_get_validator_duties_proposer()
@@ -4584,6 +4904,14 @@ async fn blinded_block_production_full_payload_premerge() {
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_ssz_full_payload_premerge() {
ApiTester::new()
.await
.test_blinded_block_production_ssz::<FullPayload<_>>()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
ApiTester::new()
@@ -4593,6 +4921,15 @@ async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_ssz_with_skip_slots_full_payload_premerge() {
ApiTester::new()
.await
.skip_slots(E::slots_per_epoch() * 2)
.test_blinded_block_production_ssz::<FullPayload<_>>()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn blinded_block_production_no_verify_randao_full_payload_premerge() {
ApiTester::new()
@@ -4854,6 +5191,7 @@ async fn builder_payload_chosen_by_profit() {
async fn builder_works_post_capella() {
let mut config = ApiTesterConfig {
builder_threshold: Some(0),
retain_historic_states: false,
spec: E::default_spec(),
};
config.spec.altair_fork_epoch = Some(Epoch::new(0));
@@ -4870,6 +5208,14 @@ async fn builder_works_post_capella() {
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn post_validator_liveness_epoch() {
ApiTester::new()
.await
.test_post_validator_liveness_epoch()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn lighthouse_endpoints() {
ApiTester::new()
@@ -4909,3 +5255,37 @@ async fn optimistic_responses() {
.test_check_optimistic_responses()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn expected_withdrawals_invalid_pre_capella() {
let mut config = ApiTesterConfig::default();
config.spec.altair_fork_epoch = Some(Epoch::new(0));
ApiTester::new_from_config(config)
.await
.test_get_expected_withdrawals_pre_capella()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn expected_withdrawals_invalid_state() {
let mut config = ApiTesterConfig::default();
config.spec.altair_fork_epoch = Some(Epoch::new(0));
config.spec.bellatrix_fork_epoch = Some(Epoch::new(0));
config.spec.capella_fork_epoch = Some(Epoch::new(0));
ApiTester::new_from_config(config)
.await
.test_get_expected_withdrawals_invalid_state()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn expected_withdrawals_valid_capella() {
let mut config = ApiTesterConfig::default();
config.spec.altair_fork_epoch = Some(Epoch::new(0));
config.spec.bellatrix_fork_epoch = Some(Epoch::new(0));
config.spec.capella_fork_epoch = Some(Epoch::new(0));
ApiTester::new_from_config(config)
.await
.test_get_expected_withdrawals_capella()
.await;
}