Kintsugi on_merge_block tests (#2811)

* Start v1.1.5 updates

* Implement new payload creation logic

* Tidy, add comments

* Remove unused error enums

* Add validate payload for gossip

* Refactor validate_merge_block

* Split payload verification in per block processing

* Add execute_payload

* Tidy

* Tidy

* Start working on new fork choice tests

* Fix failing merge block test

* Skip block_lookup_failed test

* Fix failing terminal block test

* Fixes from self-review

* Address review comments
This commit is contained in:
Paul Hauner
2021-11-17 11:45:30 +11:00
parent 44a7b37ce3
commit 5f0fef2d1e
14 changed files with 585 additions and 271 deletions

View File

@@ -39,9 +39,6 @@ excluded_paths = [
"tests/minimal/altair/merkle/single_proof",
"tests/mainnet/merge/merkle/single_proof",
"tests/minimal/merge/merkle/single_proof",
# Fork choice tests featuring PoW blocks
"tests/minimal/merge/fork_choice/on_merge_block/",
"tests/mainnet/merge/fork_choice/on_merge_block/"
]
def normalize_path(path):

View File

@@ -9,13 +9,26 @@ use beacon_chain::{
BeaconChainTypes, HeadInfo,
};
use serde_derive::Deserialize;
use ssz_derive::Decode;
use state_processing::state_advance::complete_state_advance;
use std::time::Duration;
use types::{
Attestation, BeaconBlock, BeaconState, Checkpoint, Epoch, EthSpec, ForkName, Hash256,
IndexedAttestation, SignedBeaconBlock, Slot,
IndexedAttestation, SignedBeaconBlock, Slot, Uint256,
};
#[derive(Default, Debug, PartialEq, Clone, Deserialize, Decode)]
#[serde(deny_unknown_fields)]
pub struct PowBlock {
pub block_hash: Hash256,
pub parent_hash: Hash256,
pub total_difficulty: Uint256,
// This field is not used and I expect it to be removed. See:
//
// https://github.com/ethereum/consensus-specs/pull/2720
pub difficulty: Uint256,
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Head {
@@ -37,11 +50,12 @@ pub struct Checks {
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
pub enum Step<B, A> {
pub enum Step<B, A, P> {
Tick { tick: u64 },
ValidBlock { block: B },
MaybeValidBlock { block: B, valid: bool },
Attestation { attestation: A },
PowBlock { pow_block: P },
Checks { checks: Box<Checks> },
}
@@ -56,7 +70,7 @@ pub struct ForkChoiceTest<E: EthSpec> {
pub description: String,
pub anchor_state: BeaconState<E>,
pub anchor_block: BeaconBlock<E>,
pub steps: Vec<Step<SignedBeaconBlock<E>, Attestation<E>>>,
pub steps: Vec<Step<SignedBeaconBlock<E>, Attestation<E>, PowBlock>>,
}
impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
@@ -69,7 +83,7 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
.expect("path must be valid OsStr")
.to_string();
let spec = &testing_spec::<E>(fork_name);
let steps: Vec<Step<String, String>> = yaml_decode_file(&path.join("steps.yaml"))?;
let steps: Vec<Step<String, String, String>> = yaml_decode_file(&path.join("steps.yaml"))?;
// Resolve the object names in `steps.yaml` into actual decoded block/attestation objects.
let steps = steps
.into_iter()
@@ -91,6 +105,10 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
ssz_decode_file(&path.join(format!("{}.ssz_snappy", attestation)))
.map(|attestation| Step::Attestation { attestation })
}
Step::PowBlock { pow_block } => {
ssz_decode_file(&path.join(format!("{}.ssz_snappy", pow_block)))
.map(|pow_block| Step::PowBlock { pow_block })
}
Step::Checks { checks } => Ok(Step::Checks { checks }),
})
.collect::<Result<_, _>>()?;
@@ -133,7 +151,13 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
// https://github.com/sigp/lighthouse/issues/2741
//
// We should eventually solve the above issue and remove this `SkippedKnownFailure`.
if self.description == "new_finalized_slot_is_justified_checkpoint_ancestor" {
if self.description == "new_finalized_slot_is_justified_checkpoint_ancestor"
// This test is skipped until we can do retrospective confirmations of the terminal
// block after an optimistic sync.
//
// TODO(merge): enable this test before production.
|| self.description == "block_lookup_failed"
{
return Err(Error::SkippedKnownFailure);
};
@@ -145,6 +169,7 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
tester.process_block(block.clone(), *valid)?
}
Step::Attestation { attestation } => tester.process_attestation(attestation)?,
Step::PowBlock { pow_block } => tester.process_pow_block(pow_block),
Step::Checks { checks } => {
let Checks {
head,
@@ -231,6 +256,15 @@ impl<E: EthSpec> Tester<E> {
));
}
// Drop any blocks that might be loaded in the mock execution layer. Some of these tests
// will provide their own blocks and we want to start from a clean state.
harness
.mock_execution_layer
.as_ref()
.unwrap()
.server
.drop_all_blocks();
assert_eq!(
harness.chain.slot_clock.genesis_duration().as_secs(),
genesis_time
@@ -357,6 +391,21 @@ impl<E: EthSpec> Tester<E> {
.map_err(|e| Error::InternalError(format!("attestation import failed with {:?}", e)))
}
pub fn process_pow_block(&self, pow_block: &PowBlock) {
let el = self.harness.mock_execution_layer.as_ref().unwrap();
// The EF tests don't supply a block number. Our mock execution layer is fine with duplicate
// block numbers for the purposes of this test.
let block_number = 0;
el.server.insert_pow_block(
block_number,
pow_block.block_hash,
pow_block.parent_hash,
pow_block.total_difficulty,
);
}
pub fn check_head(&self, expected_head: Head) -> Result<(), Error> {
let chain_head = self.find_head().map(|head| Head {
slot: head.slot,

View File

@@ -491,6 +491,34 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceOnBlockHandler<E> {
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct ForkChoiceOnMergeBlockHandler<E>(PhantomData<E>);
impl<E: EthSpec + TypeName> Handler for ForkChoiceOnMergeBlockHandler<E> {
type Case = cases::ForkChoiceTest<E>;
fn config_name() -> &'static str {
E::name()
}
fn runner_name() -> &'static str {
"fork_choice"
}
fn handler_name(&self) -> String {
"on_merge_block".into()
}
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// These tests check block validity (which may include signatures) and there is no need to
// run them with fake crypto.
cfg!(not(feature = "fake_crypto"))
// These tests only exist for the merge.
&& fork_name == ForkName::Merge
}
}
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct GenesisValidityHandler<E>(PhantomData<E>);

View File

@@ -423,6 +423,12 @@ fn fork_choice_on_block() {
ForkChoiceOnBlockHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn fork_choice_on_merge_block() {
ForkChoiceOnMergeBlockHandler::<MinimalEthSpec>::default().run();
ForkChoiceOnMergeBlockHandler::<MainnetEthSpec>::default().run();
}
#[test]
fn genesis_initialization() {
GenesisInitializationHandler::<MinimalEthSpec>::default().run();