Optimistic sync spec tests (v1.2.0) (#3564)

## Issue Addressed

Implements new optimistic sync test format from https://github.com/ethereum/consensus-specs/pull/2982.

## Proposed Changes

- Add parsing and runner support for the new test format.
- Extend the mock EL with a set of canned responses keyed by block hash. Although this doubles up on some of the existing functionality I think it's really nice to use compared to the `preloaded_responses` or static responses. I think we could write novel new opt sync tests using these primtives much more easily than the previous ones. Forks are natively supported, and different responses to `forkchoiceUpdated` and `newPayload` are also straight-forward.

## Additional Info

Blocked on merge of the spec PR and release of new test vectors.
This commit is contained in:
Michael Sproul
2022-10-15 22:25:52 +00:00
parent ca9dc8e094
commit e4cbdc1c77
11 changed files with 177 additions and 19 deletions

View File

@@ -7,7 +7,7 @@ use serde_derive::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregateSigs {
pub input: Vec<String>,
pub output: String,
pub output: Option<String>,
}
impl_bls_load_case!(BlsAggregateSigs);
@@ -25,14 +25,13 @@ impl Case for BlsAggregateSigs {
aggregate_signature.add_assign(&sig);
}
// Check for YAML null value, indicating invalid input. This is a bit of a hack,
// as our mutating `aggregate_signature.add` API doesn't play nicely with aggregating 0
// inputs.
let output_bytes = if self.output == "~" {
AggregateSignature::infinity().serialize().to_vec()
} else {
hex::decode(&self.output[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?
let output_bytes = match self.output.as_deref() {
// Check for YAML null value, indicating invalid input. This is a bit of a hack,
// as our mutating `aggregate_signature.add` API doesn't play nicely with aggregating 0
// inputs.
Some("~") | None => AggregateSignature::infinity().serialize().to_vec(),
Some(output) => hex::decode(&output[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?,
};
let aggregate_signature = Ok(aggregate_signature.serialize().to_vec());

View File

@@ -9,7 +9,8 @@ use beacon_chain::{
test_utils::{BeaconChainHarness, EphemeralHarnessType},
BeaconChainTypes, CachedHead, CountUnrealized,
};
use serde_derive::Deserialize;
use execution_layer::{json_structures::JsonPayloadStatusV1Status, PayloadStatusV1};
use serde::Deserialize;
use ssz_derive::Decode;
use state_processing::state_advance::complete_state_advance;
use std::future::Future;
@@ -50,16 +51,53 @@ pub struct Checks {
proposer_boost_root: Option<Hash256>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadStatus {
status: JsonPayloadStatusV1Status,
latest_valid_hash: Option<ExecutionBlockHash>,
validation_error: Option<String>,
}
impl From<PayloadStatus> for PayloadStatusV1 {
fn from(status: PayloadStatus) -> Self {
PayloadStatusV1 {
status: status.status.into(),
latest_valid_hash: status.latest_valid_hash,
validation_error: status.validation_error,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
pub enum Step<B, A, AS, P> {
Tick { tick: u64 },
ValidBlock { block: B },
MaybeValidBlock { block: B, valid: bool },
Attestation { attestation: A },
AttesterSlashing { attester_slashing: AS },
PowBlock { pow_block: P },
Checks { checks: Box<Checks> },
Tick {
tick: u64,
},
ValidBlock {
block: B,
},
MaybeValidBlock {
block: B,
valid: bool,
},
Attestation {
attestation: A,
},
AttesterSlashing {
attester_slashing: AS,
},
PowBlock {
pow_block: P,
},
OnPayloadInfo {
block_hash: ExecutionBlockHash,
payload_status: PayloadStatus,
},
Checks {
checks: Box<Checks>,
},
}
#[derive(Debug, Clone, Deserialize)]
@@ -119,6 +157,13 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
ssz_decode_file(&path.join(format!("{}.ssz_snappy", pow_block)))
.map(|pow_block| Step::PowBlock { pow_block })
}
Step::OnPayloadInfo {
block_hash,
payload_status,
} => Ok(Step::OnPayloadInfo {
block_hash,
payload_status,
}),
Step::Checks { checks } => Ok(Step::Checks { checks }),
})
.collect::<Result<_, _>>()?;
@@ -168,6 +213,14 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
tester.process_attester_slashing(attester_slashing)
}
Step::PowBlock { pow_block } => tester.process_pow_block(pow_block),
Step::OnPayloadInfo {
block_hash,
payload_status,
} => {
let el = tester.harness.mock_execution_layer.as_ref().unwrap();
el.server
.set_payload_statuses(*block_hash, payload_status.clone().into());
}
Step::Checks { checks } => {
let Checks {
head,

View File

@@ -117,6 +117,11 @@ impl<E: EthSpec> Operation<E> for Deposit {
ssz_decode_file(path)
}
fn is_enabled_for_fork(_: ForkName) -> bool {
// Some deposit tests require signature verification but are not marked as such.
cfg!(not(feature = "fake_crypto"))
}
fn apply_to(
&self,
state: &mut BeaconState<E>,

View File

@@ -546,6 +546,37 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct OptimisticSyncHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for OptimisticSyncHandler<E> {
type Case = cases::ForkChoiceTest<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"sync"
}
fn handler_name(&self) -> String {
"optimistic".into()
}
fn use_rayon() -> bool {
// The opt sync tests use `block_on` which can cause panics with rayon.
false
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
fork_name != ForkName::Base
&& fork_name != ForkName::Altair
&& cfg!(not(feature = "fake_crypto"))
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct GenesisValidityHandler<E>(PhantomData<E>);