diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 1fd18336d..4a48e968f 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1494,6 +1494,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # Verify that the slots match assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot # Verify that proposer index is the correct index assert block.proposer_index == get_beacon_proposer_index(state) # Verify that the parent matches diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py index a2eb744b9..b57090568 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py @@ -9,7 +9,7 @@ def prepare_state_for_header_processing(spec, state): spec.process_slots(state, state.slot + 1) -def run_block_header_processing(spec, state, block, valid=True): +def run_block_header_processing(spec, state, block, prepare_state=True, valid=True): """ Run ``process_block_header``, yielding: - pre-state ('pre') @@ -17,7 +17,8 @@ def run_block_header_processing(spec, state, block, valid=True): - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ - prepare_state_for_header_processing(spec, state) + if prepare_state: + prepare_state_for_header_processing(spec, state) yield 'pre', state yield 'block', block @@ -68,6 +69,22 @@ def test_invalid_parent_root(spec, state): yield from run_block_header_processing(spec, state, block, valid=False) +@with_all_phases +@spec_state_test +def test_invalid_multiple_blocks_single_slot(spec, state): + block = build_empty_block_for_next_slot(spec, state) + + prepare_state_for_header_processing(spec, state) + spec.process_block_header(state, block) + + assert state.latest_block_header.slot == state.slot + + child_block = block.copy() + child_block.parent_root = block.hash_tree_root() + + yield from run_block_header_processing(spec, state, child_block, prepare_state=False, valid=False) + + @with_all_phases @spec_state_test def test_proposer_slashed(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index a0678dbb3..71ee5141d 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -79,6 +79,34 @@ def test_empty_block_transition(spec, state): assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32() +def process_and_sign_block_without_header_validations(spec, state, block): + """ + Artificially bypass the restrictions in the state transition to transition and sign block + + WARNING UNSAFE: Only use when generating valid-looking invalid blocks for test vectors + """ + + # Perform single mutation in `process_block_header` + state.latest_block_header = spec.BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=spec.Bytes32(), + body_root=block.body.hash_tree_root(), + ) + + # Perform rest of process_block transitions + spec.process_randao(state, block.body) + spec.process_eth1_data(state, block.body) + spec.process_operations(state, block.body) + + # Insert post-state rot + block.state_root = state.hash_tree_root() + + # Sign block + return sign_block(spec, state, block) + + @with_phases(['phase0']) @spec_state_test def test_proposal_for_genesis_slot(spec, state): @@ -95,10 +123,8 @@ def test_proposal_for_genesis_slot(spec, state): lambda: spec.state_transition(failed_state, spec.SignedBeaconBlock(message=block), validate_result=False) ) - # Artifically bypass the restriction in the state transition to transition and sign block for test vectors - spec.process_block(state, block) - block.state_root = state.hash_tree_root() - signed_block = sign_block(spec, state, block) + # Artificially bypass the restriction in the state transition to transition and sign block for test vectors + signed_block = process_and_sign_block_without_header_validations(spec, state, block) yield 'blocks', [signed_block] yield 'post', None @@ -121,10 +147,8 @@ def test_parent_from_same_slot(spec, state): lambda: spec.state_transition(failed_state, spec.SignedBeaconBlock(message=child_block), validate_result=False) ) - # Artifically bypass the restriction in the state transition to transition and sign block for test vectors - spec.process_block(state, child_block) - child_block.state_root = state.hash_tree_root() - signed_child_block = sign_block(spec, state, child_block) + # Artificially bypass the restriction in the state transition to transition and sign block for test vectors + signed_child_block = process_and_sign_block_without_header_validations(spec, state, child_block) yield 'blocks', [signed_parent_block, signed_child_block] yield 'post', None