Repository: ethereum/consensus-specs

Issue:
Remove shard check in `verify_shard_block_message`
The shard field consistency check in `verify_shard_block_message` is redundant
```python
shard = block.shard
assert block.shard == shard
```
Given the input argument, I'm not able to find a proper consistency check the for block's shard field. 
One can argue we don't need to check for the shard field because `shard_parent_root` indirectly validates `shard`.  The shard field is most useful to looking up shard blocks are coming in from the correct p2p subnet (`shard_{shard}_block`)

Opening this PR for suggestion


Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -94,9 +94,9 @@ def get_spec(file_name: str) -> SpecObject:
 
 from lru import LRU
 
-from eth2spec.utils.ssz.ssz_impl import hash_tree_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
 from eth2spec.utils.ssz.ssz_typing import (
-    View, boolean, Container, List, Vector, uint64,
+    View, boolean, Container, List, Vector, uint8, uint32, uint64,
     Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
 )
 from eth2spec.utils import bls
@@ -118,10 +118,10 @@ def get_spec(file_name: str) -> SpecObject:
 
 from lru import LRU
 
-from eth2spec.utils.ssz.ssz_impl import hash_tree_root
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
 from eth2spec.utils.ssz.ssz_typing import (
-    View, boolean, Container, List, Vector, uint64, uint8, bit,
-    ByteList, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
+    View, boolean, Container, List, Vector, uint8, uint32, uint64, bit,
+    ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
 )
 from eth2spec.utils import bls
 
@@ -140,12 +140,7 @@ def get_spec(file_name: str) -> SpecObject:
 def ceillog2(x: uint64) -> int:
     return (x - 1).bit_length()
 '''
-SUNDRY_FUNCTIONS = '''
-# Monkey patch hash cache
-_hash = hash
-hash_cache: Dict[bytes, Bytes32] = {}
-
-
+PHASE0_SUNDRY_FUNCTIONS = '''
 def get_eth1_data(block: Eth1Block) -> Eth1Data:
     """
     A stub function return mocking Eth1Data.
@@ -156,12 +151,6 @@ def get_eth1_data(block: Eth1Block) -> Eth1Data:
         block_hash=hash_tree_root(block))
 
 
-def hash(x: bytes) -> Bytes32:  # type: ignore
-    if x not in hash_cache:
-        hash_cache[x] = Bytes32(_hash(x))
-    return hash_cache[x]
-
-
 def cache_this(key_fn, value_fn, lru_size):  # type: ignore
     cache_dict = LRU(size=lru_size)
 
@@ -189,10 +178,10 @@ def wrapper(*args, **kw):  # type: ignore
     lambda state, index: (state.validators.hash_tree_root(), state.slot, index),
     _get_base_reward, lru_size=2048)
 
-_get_committee_count_at_slot = get_committee_count_at_slot
-get_committee_count_at_slot = cache_this(
+_get_committee_count_per_slot = get_committee_count_per_slot
+get_committee_count_per_slot = cache_this(
     lambda state, epoch: (state.validators.hash_tree_root(), epoch),
-    _get_committee_count_at_slot, lru_size=SLOTS_PER_EPOCH * 3)
+    _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3)
 
 _get_active_validator_indices = get_active_validator_indices
 get_active_validator_indices = cache_this(
@@ -216,10 +205,27 @@ def wrapper(*args, **kw):  # type: ignore
 
 _get_attesting_indices = get_attesting_indices
 get_attesting_indices = cache_this(
-    lambda state, data, bits: (state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root()),
+    lambda state, data, bits: (
+        state.randao_mixes.hash_tree_root(),
+        state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root()
+    ),
     _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
 
 
+PHASE1_SUNDRY_FUNCTIONS = '''
+
+def get_block_data_merkle_root(data: ByteList) -> Root:
+    # To get the Merkle root of the block data, we need the Merkle root without the length mix-in
+    # The below implements this in the Remerkleable framework
+    return Root(data.get_backing().get_left().merkle_root())
+
+
+_get_start_shard = get_start_shard
+get_start_shard = cache_this(
+    lambda state, slot: (state.validators.hash_tree_root(), slot),
+    _get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)'''
+
+
 def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
     """
     Given all the objects that constitute a spec, combine them into a single pyfile.
@@ -250,9 +256,11 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
             + '\n\n' + CONFIG_LOADER
             + '\n\n' + ssz_objects_instantiation_spec
             + '\n\n' + functions_spec
-            + '\n' + SUNDRY_FUNCTIONS
-            + '\n'
+            + '\n' + PHASE0_SUNDRY_FUNCTIONS
     )
+    if fork == 'phase1':
+        spec += '\n' + PHASE1_SUNDRY_FUNCTIONS
+    spec += '\n'
     return spec
 
 
@@ -385,6 +393,8 @@ def finalize_options(self):
                     specs/phase1/shard-transition.md
                     specs/phase1/fork-choice.md
                     specs/phase1/phase1-fork.md
+                    specs/phase1/shard-fork-choice.md
+                    specs/phase1/validator.md
                 """
             else:
                 raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)
@@ -508,7 +518,7 @@ def run(self):
         "py_ecc==4.0.0",
         "milagro_bls_binding==1.3.0",
         "dataclasses==0.6",
-        "remerkleable==0.1.16",
+        "remerkleable==0.1.17",
         "ruamel.yaml==0.16.5",
         "lru-dict==1.1.6"
     ]


Test patch:
diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt
--- a/tests/core/pyspec/eth2spec/VERSION.txt
+++ b/tests/core/pyspec/eth2spec/VERSION.txt
@@ -1 +1 @@
-0.12.1
\ No newline at end of file
+0.12.2
\ No newline at end of file
diff --git a/tests/core/pyspec/eth2spec/config/config_util.py b/tests/core/pyspec/eth2spec/config/config_util.py
--- a/tests/core/pyspec/eth2spec/config/config_util.py
+++ b/tests/core/pyspec/eth2spec/config/config_util.py
@@ -1,8 +1,9 @@
-from ruamel.yaml import YAML
+import os
 from pathlib import Path
-from os.path import join
 from typing import Dict, Any
 
+from ruamel.yaml import YAML
+
 config: Dict[str, Any] = {}
 
 
@@ -35,11 +36,19 @@ def load_config_file(configs_dir: str, presets_name: str) -> Dict[str, Any]:
     :param presets_name: The name of the presets. (lowercase snake_case)
     :return: Dictionary, mapping of constant-name -> constant-value
     """
-    path = Path(join(configs_dir, presets_name + '.yaml'))
-    yaml = YAML(typ='base')
-    loaded = yaml.load(path)
+    present_dir = Path(configs_dir) / presets_name
+    _, _, config_files = next(os.walk(present_dir))
+    config_files.sort()
+    loaded_config = {}
+    for config_file_name in config_files:
+        yaml = YAML(typ='base')
+        path = present_dir / config_file_name
+        loaded = yaml.load(path)
+        loaded_config.update(loaded)
+    assert loaded_config != {}
+
     out: Dict[str, Any] = dict()
-    for k, v in loaded.items():
+    for k, v in loaded_config.items():
         if isinstance(v, list):
             # Clean up integer values. YAML parser renders lists of ints as list of str
             out[k] = [int(item) if item.isdigit() else item for item in v]
diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py
--- a/tests/core/pyspec/eth2spec/test/conftest.py
+++ b/tests/core/pyspec/eth2spec/test/conftest.py
@@ -59,8 +59,8 @@ def bls_default(request):
 def bls_type(request):
     bls_type = request.config.getoption("--bls-type")
     if bls_type == "py_ecc":
-        bls_utils.bls = bls_utils.py_ecc_bls
+        bls_utils.use_py_ecc()
     elif bls_type == "milagro":
-        bls_utils.bls = bls_utils.milagro_bls
+        bls_utils.use_milagro()
     else:
         raise Exception(f"unrecognized bls type: {bls_type}")
diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py
--- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py
@@ -1,12 +1,14 @@
+from lru import LRU
+
 from typing import List
 
-from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1
-from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot, transition_to
+from eth2spec.test.context import expect_assertion_error, PHASE1
+from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
 from eth2spec.test.helpers.block import build_empty_block_for_next_slot
+from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
 from eth2spec.test.helpers.keys import privkeys
 from eth2spec.utils import bls
 from eth2spec.utils.ssz.ssz_typing import Bitlist
-from lru import LRU
 
 
 def run_attestation_processing(spec, state, attestation, valid=True):
@@ -44,7 +46,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
     yield 'post', state
 
 
-def build_attestation_data(spec, state, slot, index, shard_transition=None, on_time=True):
+def build_attestation_data(spec, state, slot, index, shard=None, shard_transition=None, on_time=True):
     assert state.slot >= slot
 
     if slot == state.slot:
@@ -76,43 +78,26 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t
     )
 
     if spec.fork == PHASE1:
+        if shard is None:
+            shard = spec.compute_shard_from_committee_index(state, attestation_data.index, attestation_data.slot)
+        attestation_data.shard = shard
+
         if shard_transition is not None:
-            lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
-            attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
+            last_offset_index = len(shard_transition.shard_data_roots) - 1
+            attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
             attestation_data.shard_transition_root = shard_transition.hash_tree_root()
         else:
-            # No shard transition
-            shard = spec.get_shard(state, spec.Attestation(data=attestation_data))
             if on_time:
-                temp_state = state.copy()
-                next_slot(spec, temp_state)
-                shard_transition = spec.get_shard_transition(temp_state, shard, [])
-                lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
-                attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
+                shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
+                last_offset_index = len(shard_transition.shard_data_roots) - 1
+                attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
                 attestation_data.shard_transition_root = shard_transition.hash_tree_root()
             else:
-                attestation_data.shard_head_root = state.shard_states[shard].transition_digest
+                attestation_data.shard_head_root = state.shard_states[shard].latest_block_root
                 attestation_data.shard_transition_root = spec.Root()
     return attestation_data
 
 
-def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False):
-    shard = spec.get_shard(state, attestation)
-    offset_slots = spec.compute_offset_slots(
-        spec.get_latest_slot_for_shard(state, shard),
-        attestation.data.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY,
-    )
-    for _ in offset_slots:
-        attestation.custody_bits_blocks.append(
-            Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
-        )
-
-    if signed:
-        sign_attestation(spec, state, attestation)
-
-    return attestation
-
-
 def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
     '''
     Construct on-time attestation for next slot
@@ -133,7 +118,7 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_tran
     )
 
 
-def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False):
+def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None):
     '''
     Construct on-time attestation for next slot
     '''
@@ -142,7 +127,8 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False)
     if index is None:
         index = 0
 
-    return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False)
+    return get_valid_attestation(spec, state, slot=slot, index=index,
+                                 signed=signed, on_time=False, shard_transition=shard_transition)
 
 
 def get_valid_attestation(spec,
@@ -179,9 +165,6 @@ def get_valid_attestation(spec,
     # fill the attestation with (optionally filtered) participants, and optionally sign it
     fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set)
 
-    if spec.fork == PHASE1 and on_time:
-        attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed)
-
     return attestation
 
 
@@ -201,43 +184,9 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
 
 
 def sign_indexed_attestation(spec, state, indexed_attestation):
-    if spec.fork == PHASE0:
-        participants = indexed_attestation.attesting_indices
-        data = indexed_attestation.data
-        indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
-    else:
-        participants = spec.get_indices_from_committee(
-            indexed_attestation.committee,
-            indexed_attestation.attestation.aggregation_bits,
-        )
-        data = indexed_attestation.attestation.data
-        if any(indexed_attestation.attestation.custody_bits_blocks):
-            sign_on_time_attestation(spec, state, indexed_attestation.attestation)
-        else:
-            indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
-
-
-def sign_on_time_attestation(spec, state, attestation):
-    if not any(attestation.custody_bits_blocks):
-        sign_attestation(spec, state, attestation)
-        return
-
-    committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index)
-    signatures = []
-    for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
-        for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits):
-            if not abit:
-                continue
-            signatures.append(get_attestation_custody_signature(
-                spec,
-                state,
-                attestation.data,
-                block_index,
-                cbit,
-                privkeys[participant]
-            ))
-
-    attestation.signature = bls.Aggregate(signatures)
+    participants = indexed_attestation.attesting_indices
+    data = indexed_attestation.data
+    indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
 
 
 def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey):
@@ -254,10 +203,6 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index
 
 
 def sign_attestation(spec, state, attestation):
-    if spec.fork == PHASE1 and any(attestation.custody_bits_blocks):
-        sign_on_time_attestation(spec, state, attestation)
-        return
-
     participants = spec.get_attesting_indices(
         state,
         attestation.data,
@@ -314,23 +259,30 @@ def next_epoch_with_attestations(spec,
         block = build_empty_block_for_next_slot(spec, post_state)
         if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
             slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
-            committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest)
+            committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
             if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)):
                 for index in range(committees_per_slot):
-                    cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True)
+                    if spec.fork == PHASE1:
+                        shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest)
+                        shard_transition = get_shard_transition_of_committee(spec, post_state, index)
+                        block.body.shard_transitions[shard] = shard_transition
+                    else:
+                        shard_transition = None
+
+                    cur_attestation = get_valid_attestation(
+                        spec, post_state, slot_to_attest,
+                        shard_transition=shard_transition, index=index, signed=True, on_time=True
+                    )
                     block.body.attestations.append(cur_attestation)
 
         if fill_prev_epoch:
             slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
-            committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest)
+            committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
             for index in range(committees_per_slot):
                 prev_attestation = get_valid_attestation(
                     spec, post_state, slot_to_attest, index=index, signed=True, on_time=False)
                 block.body.attestations.append(prev_attestation)
 
-        if spec.fork == PHASE1:
-            fill_block_shard_transitions_by_attestations(spec, post_state, block)
-
         signed_block = state_transition_and_sign_block(spec, post_state, block)
         signed_blocks.append(signed_block)
 
@@ -354,7 +306,7 @@ def prepare_state_with_attestations(spec, state, participation_fn=None):
     for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY):
         # create an attestation for each index in each slot in epoch
         if state.slot < next_epoch_start_slot:
-            for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)):
+            for committee_index in range(spec.get_committee_count_per_slot(state, spec.get_current_epoch(state))):
                 def temp_participants_filter(comm):
                     if participation_fn is None:
                         return comm
@@ -396,14 +348,3 @@ def cached_prepare_state_with_attestations(spec, state):
 
     # Put the LRU cache result into the state view, as if we transitioned the original view
     state.set_backing(_prep_state_cache_dict[key])
-
-
-def fill_block_shard_transitions_by_attestations(spec, state, block):
-    block.body.shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
-    for attestation in block.body.attestations:
-        shard = spec.get_shard(state, attestation)
-        if attestation.data.slot == state.slot:
-            temp_state = state.copy()
-            transition_to(spec, temp_state, slot=block.slot)
-            shard_transition = spec.get_shard_transition(temp_state, shard, [])
-            block.body.shard_transitions[shard] = shard_transition
diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py
--- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py
@@ -1,4 +1,3 @@
-from eth2spec.test.context import PHASE1
 from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation
 
 
@@ -41,34 +40,19 @@ def get_indexed_attestation_participants(spec, indexed_att):
     """
     Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
     """
-    if spec.fork == PHASE1:
-        return list(spec.get_indices_from_committee(
-            indexed_att.committee,
-            indexed_att.attestation.aggregation_bits,
-        ))
-    else:
-        return list(indexed_att.attesting_indices)
+    return list(indexed_att.attesting_indices)
 
 
 def set_indexed_attestation_participants(spec, indexed_att, participants):
     """
     Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
     """
-    if spec.fork == PHASE1:
-        indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee]
-    else:
-        indexed_att.attesting_indices = participants
+    indexed_att.attesting_indices = participants
 
 
 def get_attestation_1_data(spec, att_slashing):
-    if spec.fork == PHASE1:
-        return att_slashing.attestation_1.attestation.data
-    else:
-        return att_slashing.attestation_1.data
+    return att_slashing.attestation_1.data
 
 
 def get_attestation_2_data(spec, att_slashing):
-    if spec.fork == PHASE1:
-        return att_slashing.attestation_2.attestation.data
-    else:
-        return att_slashing.attestation_2.data
+    return att_slashing.attestation_2.data
diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py
--- a/tests/core/pyspec/eth2spec/test/helpers/custody.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py
@@ -1,10 +1,7 @@
 from eth2spec.test.helpers.keys import privkeys
 from eth2spec.utils import bls
-from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector
-from eth2spec.utils.ssz.ssz_impl import hash_tree_root
-from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
-from remerkleable.core import pack_bits_to_chunks
-from remerkleable.tree import subtree_fill_to_contents, get_depth
+from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, ByteList
+from remerkleable.tree import gindex_bit_iter
 
 BYTES_PER_CHUNK = 32
 
@@ -37,9 +34,10 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
     )
 
 
-def get_valid_custody_key_reveal(spec, state, period=None):
+def get_valid_custody_key_reveal(spec, state, period=None, validator_index=None):
     current_epoch = spec.get_current_epoch(state)
-    revealer_index = spec.get_active_validator_indices(state, current_epoch)[0]
+    revealer_index = (spec.get_active_validator_indices(state, current_epoch)[0]
+                      if validator_index is None else validator_index)
     revealer = state.validators[revealer_index]
 
     if period is None:
@@ -61,38 +59,54 @@ def bitlist_from_int(max_len, num_bits, n):
     return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)])
 
 
-def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False):
+def get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, data, data_index=0):
     beacon_committee = spec.get_beacon_committee(
         state,
         attestation.data.slot,
-        attestation.data.crosslink.shard,
+        attestation.data.index,
     )
-    responder_index = beacon_committee[0]
-    challenger_index = beacon_committee[-1]
+    malefactor_index = beacon_committee[0]
+    whistleblower_index = beacon_committee[-1]
+
+    slashing = spec.CustodySlashing(
+        data_index=data_index,
+        malefactor_index=malefactor_index,
+        malefactor_secret=custody_secret,
+        whistleblower_index=whistleblower_index,
+        shard_transition=shard_transition,
+        attestation=attestation,
+        data=data,
+    )
+    slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING)
+    slashing_root = spec.compute_signing_root(slashing, slashing_domain)
 
-    epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch,
-                                                     responder_index)
+    signed_slashing = spec.SignedCustodySlashing(
+        message=slashing,
+        signature=bls.Sign(privkeys[whistleblower_index], slashing_root)
+    )
 
-    # Generate the responder key
-    domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch)
-    signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
-    responder_key = bls.Sign(privkeys[responder_index], signing_root)
+    return signed_slashing
 
-    chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink)
 
-    chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0)
+def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_index=None, chunk_index=None):
+    crosslink_committee = spec.get_beacon_committee(
+        state,
+        attestation.data.slot,
+        attestation.data.index
+    )
+    responder_index = crosslink_committee[0]
+    data_index = len(shard_transition.shard_block_lengths) - 1 if not data_index else data_index
 
-    n = 0
-    while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit:
-        chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n)
-        n += 1
+    chunk_count = (shard_transition.shard_block_lengths[data_index]
+                   + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK
+    chunk_index = chunk_count - 1 if not chunk_index else chunk_index
 
-    return spec.CustodyBitChallenge(
+    return spec.CustodyChunkChallenge(
         responder_index=responder_index,
         attestation=attestation,
-        challenger_index=challenger_index,
-        responder_key=responder_key,
-        chunk_bits=chunk_bits,
+        chunk_index=chunk_index,
+        data_index=data_index,
+        shard_transition=shard_transition,
     )
 
 
@@ -102,50 +116,89 @@ def custody_chunkify(spec, x):
     return chunks
 
 
-def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False):
-    chunks = custody_chunkify(spec, custody_data)
-
-    chunk_index = len(chunks) - 1
-    chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
+def build_proof(anchor, leaf_index):
+    if leaf_index <= 1:
+        return []  # Nothing to prove / invalid index
+    node = anchor
+    proof = []
+    # Walk down, top to bottom to the leaf
+    bit_iter, _ = gindex_bit_iter(leaf_index)
+    for bit in bit_iter:
+        # Always take the opposite hand for the proof.
+        # 1 = right as leaf, thus get left
+        if bit:
+            proof.append(node.get_left().merkle_root())
+            node = node.get_right()
+        else:
+            proof.append(node.get_right().merkle_root())
+            node = node.get_left()
 
-    while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit:
-        chunk_index -= 1
-        chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
+    return list(reversed(proof))
 
-    chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
-    chunks_hash_tree_roots += [
-        hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK))
-        for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))]
-    data_tree = get_merkle_tree(chunks_hash_tree_roots)
 
-    data_branch = get_merkle_proof(data_tree, chunk_index)
+def get_valid_custody_chunk_response(spec, state, chunk_challenge, challenge_index,
+                                     block_length_or_custody_data,
+                                     invalid_chunk_data=False):
+    if isinstance(block_length_or_custody_data, int):
+        custody_data = get_custody_test_vector(block_length_or_custody_data)
+    else:
+        custody_data = block_length_or_custody_data
 
-    bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK
-    print(bitlist_chunk_index)
-    bitlist_chunk_nodes = pack_bits_to_chunks(bit_challenge.chunk_bits)
-    bitlist_tree = subtree_fill_to_contents(bitlist_chunk_nodes, get_depth(spec.MAX_CUSTODY_CHUNKS))
-    print(bitlist_tree)
-    bitlist_chunk_branch = None  # TODO; extract proof from merkle tree
+    custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data)
+    chunks = custody_chunkify(spec, custody_data_block)
 
-    bitlist_chunk_index = chunk_index // 256
+    chunk_index = chunk_challenge.chunk_index
 
-    chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256:
-                                     (bitlist_chunk_index + 1) * 256])
+    data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH)
 
-    return spec.CustodyResponse(
+    return spec.CustodyChunkResponse(
         challenge_index=challenge_index,
         chunk_index=chunk_index,
         chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
-        data_branch=data_branch,
-        chunk_bits_branch=bitlist_chunk_branch,
-        chunk_bits_leaf=chunk_bits_leaf,
+        branch=data_branch,
     )
 
 
-def get_custody_test_vector(bytelength):
-    ints = bytelength // 4
-    return b"".join(i.to_bytes(4, "little") for i in range(ints))
+def get_custody_test_vector(bytelength, offset=0):
+    ints = bytelength // 4 + 1
+    return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength]
+
+
+def get_sample_shard_transition(spec, start_slot, block_lengths):
+    b = [spec.get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)))
+         for x in block_lengths]
+    shard_transition = spec.ShardTransition(
+        start_slot=start_slot,
+        shard_block_lengths=block_lengths,
+        shard_data_roots=b,
+        shard_states=[spec.ShardState() for x in block_lengths],
+        proposer_signature_aggregate=spec.BLSSignature(),
+    )
+    return shard_transition
+
+
+def get_custody_secret(spec, state, validator_index, epoch=None):
+    period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
+                                                   else spec.get_current_epoch(state))
+    epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
+    domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
+    signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
+    return bls.Sign(privkeys[validator_index], signing_root)
+
+
+def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True):
+    test_vector = get_custody_test_vector(length)
+    offset = 0
+    while spec.compute_custody_bit(custody_secret, test_vector) != slashable:
+        offset += 1
+        test_vector = get_custody_test_vector(length, offset)
+    return test_vector
 
 
-def get_custody_merkle_root(data):
-    return None  # get_merkle_tree(chunkify(data))[-1][0]
+def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True):
+    shard_transition = get_sample_shard_transition(spec, start_slot, block_lengths)
+    slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret,
+                                                              block_lengths[0], slashable=slashable)
+    block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector)
+    shard_transition.shard_data_roots[0] = spec.get_block_data_merkle_root(block_data)
+    return shard_transition, slashable_test_vector
diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py
@@ -0,0 +1,27 @@
+def get_anchor_root(spec, state):
+    anchor_block_header = state.latest_block_header.copy()
+    if anchor_block_header.state_root == spec.Bytes32():
+        anchor_block_header.state_root = spec.hash_tree_root(state)
+    return spec.hash_tree_root(anchor_block_header)
+
+
+def add_block_to_store(spec, store, signed_block):
+    pre_state = store.block_states[signed_block.message.parent_root]
+    block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
+
+    if store.time < block_time:
+        spec.on_tick(store, block_time)
+
+    spec.on_block(store, signed_block)
+
+
+def add_attestation_to_store(spec, store, attestation):
+    parent_block = store.blocks[attestation.data.beacon_block_root]
+    pre_state = store.block_states[spec.hash_tree_root(parent_block)]
+    block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
+    next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
+
+    if store.time < next_epoch_time:
+        spec.on_tick(store, next_epoch_time)
+
+    spec.on_attestation(store, attestation)
diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py
--- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py
@@ -1,6 +1,4 @@
-from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
 from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot
-from eth2spec.test.helpers.state import transition_to
 from eth2spec.test.helpers.keys import privkeys
 from eth2spec.utils import bls
 from eth2spec.utils.bls import only_with_bls
@@ -23,19 +21,21 @@ def build_shard_block(spec,
                       shard,
                       slot=None,
                       body=None,
+                      shard_parent_state=None,
                       signed=False):
-    shard_state = beacon_state.shard_states[shard]
+    if shard_parent_state is None:
+        shard_parent_state = beacon_state.shard_states[shard]
+
     if slot is None:
-        slot = shard_state.slot + 1
+        slot = shard_parent_state.slot + 1
 
     if body is None:
-        body = b'\x56' * 128
+        body = get_sample_shard_block_body(spec)
 
-    proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
     beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot)
-
+    proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
     block = spec.ShardBlock(
-        shard_parent_root=shard_state.latest_block_root,
+        shard_parent_root=shard_parent_state.latest_block_root,
         beacon_parent_root=beacon_parent_root,
         slot=slot,
         shard=shard,
@@ -52,15 +52,18 @@ def build_shard_block(spec,
     return signed_block
 
 
-def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot):
-    temp_state = state.copy()
-    transition_to(spec, temp_state, on_time_slot)
+def get_shard_transitions(spec, parent_beacon_state, shard_block_dict):
     shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
-    for shard, blocks in shard_blocks.items():
-        offset_slots = spec.get_offset_slots(temp_state, shard)
+    on_time_slot = parent_beacon_state.slot + 1
+    for shard, blocks in shard_block_dict.items():
+        shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks)
+        offset_slots = spec.compute_offset_slots(
+            spec.get_latest_slot_for_shard(parent_beacon_state, shard),
+            on_time_slot,
+        )
         len_offset_slots = len(offset_slots)
-        assert len_offset_slots == on_time_slot - state.shard_states[shard].slot - 1
-        shard_transition = spec.get_shard_transition(temp_state, shard, blocks)
+        shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks)
+
         if len(blocks) > 0:
             shard_block_root = blocks[-1].message.hash_tree_root()
             assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root
@@ -70,17 +73,16 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot):
     return shard_transitions
 
 
-def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition=None):
-    temp_state = state.copy()
-    transition_to(spec, temp_state, on_time_slot - 1)
-    attestation = get_valid_on_time_attestation(
-        spec,
-        temp_state,
-        index=index,
-        shard_transition=shard_transition,
-        signed=True,
-    )
-    assert attestation.data.slot == temp_state.slot
-    if shard_transition is not None:
-        assert attestation.data.shard_transition_root == shard_transition.hash_tree_root()
-    return attestation
+def get_committee_index_of_shard(spec, state, slot, shard):  # Optional[CommitteeIndex]
+    active_shard_count = spec.get_active_shard_count(state)
+    committee_count = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot))
+    start_shard = spec.get_start_shard(state, slot)
+    for committee_index in range(committee_count):
+        if (start_shard + committee_index) % active_shard_count == shard:
+            return committee_index
+    return None
+
+
+def get_sample_shard_block_body(spec, is_max=False):
+    size = spec.MAX_SHARD_BLOCK_SIZE if is_max else 128
+    return b'\x56' * size
diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py
--- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py
+++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py
@@ -26,3 +26,17 @@ def run_shard_transitions_processing(spec, state, shard_transitions, attestation
 
     # yield post-state
     yield 'post', state
+
+
+def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks=None):
+    if shard_blocks is None:
+        shard_blocks = []
+
+    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
+    shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks)
+    return shard_transition
+
+
+def is_full_crosslink(spec, state):
+    epoch = spec.compute_epoch_at_slot(state.slot)
+    return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)
diff --git a/tests/core/pyspec/eth2spec/test/genesis/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/genesis/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py
@@ -139,7 +139,7 @@ def test_wrong_index_for_committee_signature(spec, state):
 @spec_state_test
 @never_bls
 def test_wrong_index_for_slot(spec, state):
-    while spec.get_committee_count_at_slot(state, state.slot) >= spec.MAX_COMMITTEES_PER_SLOT:
+    while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT:
         state.validators = state.validators[:len(state.validators) // 2]
         state.balances = state.balances[:len(state.balances) // 2]
 
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py
similarity index 85%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py
@@ -1,5 +1,5 @@
 from eth2spec.test.context import (
-    PHASE0, PHASE1,
+    PHASE0,
     spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
 )
 from eth2spec.test.helpers.attestations import sign_indexed_attestation
@@ -162,10 +162,7 @@ def test_same_data(spec, state):
 
     indexed_att_1 = attester_slashing.attestation_1
     att_2_data = get_attestation_2_data(spec, attester_slashing)
-    if spec.fork == PHASE1:
-        indexed_att_1.attestation.data = att_2_data
-    else:
-        indexed_att_1.data = att_2_data
+    indexed_att_1.data = att_2_data
     sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
 
     yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@@ -200,6 +197,71 @@ def test_participants_already_slashed(spec, state):
 # Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list.
 
 
+@with_phases([PHASE0])
+@spec_state_test
+@always_bls
+def test_att1_high_index(spec, state):
+    attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
+
+    indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
+    indices.append(spec.ValidatorIndex(len(state.validators)))  # off by 1
+    attester_slashing.attestation_1.attesting_indices = indices
+
+    yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
+
+
+@with_phases([PHASE0])
+@spec_state_test
+@always_bls
+def test_att2_high_index(spec, state):
+    attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
+
+    indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_2)
+    indices.append(spec.ValidatorIndex(len(state.validators)))  # off by 1
+    attester_slashing.attestation_2.attesting_indices = indices
+
+    yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
+
+
+@with_phases([PHASE0])
+@spec_state_test
+@always_bls
+def test_att1_empty_indices(spec, state):
+    attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
+
+    attester_slashing.attestation_1.attesting_indices = []
+    attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE
+
+    yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
+
+
+@with_phases([PHASE0])
+@spec_state_test
+@always_bls
+def test_att2_empty_indices(spec, state):
+    attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)
+
+    attester_slashing.attestation_2.attesting_indices = []
+    attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE
+
+    yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
+
+
+@with_phases([PHASE0])
+@spec_state_test
+@always_bls
+def test_all_empty_indices(spec, state):
+    attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False)
+
+    attester_slashing.attestation_1.attesting_indices = []
+    attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE
+
+    attester_slashing.attestation_2.attesting_indices = []
+    attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE
+
+    yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
+
+
 @with_phases([PHASE0])
 @spec_state_test
 @always_bls
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_deposit.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py
rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py
@@ -1,5 +1,5 @@
 from eth2spec.test.context import spec_state_test, with_all_phases
-from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import (
     run_epoch_processing_with, run_epoch_processing_to
 )
 from eth2spec.test.helpers.state import transition_to
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py
@@ -1,5 +1,5 @@
 from eth2spec.test.context import spec_state_test, with_all_phases
-from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import (
     run_epoch_processing_with
 )
 from eth2spec.test.helpers.state import transition_to
@@ -27,8 +27,8 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support
     remaining_balance = int(total_balance * 2 // 3)  # can become negative
 
     start_slot = spec.compute_start_slot_at_epoch(epoch)
+    committees_per_slot = spec.get_committee_count_per_slot(state, epoch)
     for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH):
-        committees_per_slot = spec.get_committee_count_at_slot(state, slot)
         for index in range(committees_per_slot):
             # Check if we already have had sufficient balance. (and undone if we don't want it).
             # If so, do not create more attestations. (we do not have empty pending attestations normally anyway)
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py
@@ -1,7 +1,7 @@
 from eth2spec.test.helpers.deposits import mock_deposit
 from eth2spec.test.helpers.state import next_epoch, next_slots
 from eth2spec.test.context import spec_state_test, with_all_phases
-from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
 
 
 def run_process_registry_updates(spec, state):
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py
@@ -1,4 +1,5 @@
 from eth2spec.test.context import (
+    PHASE0,
     spec_state_test, spec_test,
     with_all_phases, with_phases, single_phase,
     with_custom_state,
@@ -16,7 +17,7 @@
 )
 from eth2spec.test.helpers.rewards import leaking
 from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants
-from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
 from random import Random
 
 
@@ -24,7 +25,7 @@ def run_process_rewards_and_penalties(spec, state):
     yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties')
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_genesis_epoch_no_attestations_no_penalties(spec, state):
     pre_state = state.copy()
@@ -37,7 +38,7 @@ def test_genesis_epoch_no_attestations_no_penalties(spec, state):
         assert state.balances[index] == pre_state.balances[index]
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_genesis_epoch_full_attestations_no_rewards(spec, state):
     attestations = []
@@ -47,7 +48,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state):
             attestation = get_valid_attestation(spec, state, signed=True)
             attestations.append(attestation)
         # fill each created slot in state after inclusion delay
-        if slot - spec.MIN_ATTESTATION_INCLUSION_DELAY >= 0:
+        if slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
             include_att = attestations[slot - spec.MIN_ATTESTATION_INCLUSION_DELAY]
             add_attestations_to_state(spec, state, [include_att], state.slot)
         next_slot(spec, state)
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py
similarity index 98%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py
rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py
@@ -1,5 +1,5 @@
 from eth2spec.test.context import spec_state_test, with_all_phases
-from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import (
     run_epoch_processing_with, run_epoch_processing_to
 )
 from eth2spec.test.helpers.state import next_epoch
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/finality/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/finality/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/test_finality.py b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py
similarity index 97%
rename from tests/core/pyspec/eth2spec/test/test_finality.py
rename to tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py
--- a/tests/core/pyspec/eth2spec/test/test_finality.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py
@@ -1,4 +1,4 @@
-from eth2spec.test.context import spec_state_test, never_bls, with_all_phases, with_phases
+from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases, with_phases
 from eth2spec.test.helpers.state import next_epoch_via_block
 from eth2spec.test.helpers.attestations import next_epoch_with_attestations
 
@@ -28,9 +28,8 @@ def check_finality(spec,
         assert state.finalized_checkpoint == prev_state.finalized_checkpoint
 
 
-@with_phases(["phase0"])
+@with_phases([PHASE0])
 @spec_state_test
-@never_bls
 def test_finality_no_updates_at_genesis(spec, state):
     assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH
 
@@ -54,7 +53,6 @@ def test_finality_no_updates_at_genesis(spec, state):
 
 @with_all_phases
 @spec_state_test
-@never_bls
 def test_finality_rule_4(spec, state):
     # get past first two epochs that finality does not run on
     next_epoch_via_block(spec, state)
@@ -80,7 +78,6 @@ def test_finality_rule_4(spec, state):
 
 @with_all_phases
 @spec_state_test
-@never_bls
 def test_finality_rule_1(spec, state):
     # get past first two epochs that finality does not run on
     next_epoch_via_block(spec, state)
@@ -108,7 +105,6 @@ def test_finality_rule_1(spec, state):
 
 @with_all_phases
 @spec_state_test
-@never_bls
 def test_finality_rule_2(spec, state):
     # get past first two epochs that finality does not run on
     next_epoch_via_block(spec, state)
@@ -138,7 +134,6 @@ def test_finality_rule_2(spec, state):
 
 @with_all_phases
 @spec_state_test
-@never_bls
 def test_finality_rule_3(spec, state):
     """
     Test scenario described here
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/genesis/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/genesis/test_initialization.py
rename to tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py
diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/genesis/test_validity.py
rename to tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/rewards/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_basic.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_basic.py
rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_leak.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_leak.py
rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_random.py
rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_1/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase0/sanity/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py
similarity index 95%
rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py
rename to tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py
--- a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py
@@ -16,12 +16,13 @@
     get_indexed_attestation_participants,
 )
 from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
-from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations
+from eth2spec.test.helpers.attestations import get_valid_attestation
 from eth2spec.test.helpers.deposits import prepare_state_and_deposit
+from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
 
 from eth2spec.test.context import (
+    PHASE0, PHASE1,
     spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases,
-    PHASE1
 )
 
 
@@ -112,7 +113,7 @@ def process_and_sign_block_without_header_validations(spec, state, block):
     return sign_block(spec, state, block)
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_proposal_for_genesis_slot(spec, state):
     assert state.slot == spec.GENESIS_SLOT
@@ -312,6 +313,28 @@ def test_empty_epoch_transition_not_finalizing(spec, state):
         assert state.balances[index] < pre_balances[index]
 
 
+@with_all_phases
+@spec_state_test
+def test_proposer_self_slashing(spec, state):
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(spec, state)
+    assert not state.validators[block.proposer_index].slashed
+
+    proposer_slashing = get_valid_proposer_slashing(
+        spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True)
+    block.body.proposer_slashings.append(proposer_slashing)
+
+    # The header is processed *before* the block body:
+    # the proposer was not slashed before the body, thus the block is valid.
+    signed_block = state_transition_and_sign_block(spec, state, block)
+    # The proposer slashed themselves.
+    assert state.validators[block.proposer_index].slashed
+
+    yield 'blocks', [signed_block]
+    yield 'post', state
+
+
 @with_all_phases
 @spec_state_test
 def test_proposer_slashing(spec, state):
@@ -483,7 +506,7 @@ def test_duplicate_attester_slashing(spec, state):
 
 # All AttesterSlashing tests should be adopted for Phase 1 but helper support is not yet there
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_multiple_attester_slashings_no_overlap(spec, state):
     # Skip test if config cannot handle multiple AttesterSlashings per block
@@ -524,7 +547,7 @@ def test_multiple_attester_slashings_no_overlap(spec, state):
     check_attester_slashing_effect(spec, pre_state, state, full_indices)
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_multiple_attester_slashings_partial_overlap(spec, state):
     # Skip test if config cannot handle multiple AttesterSlashings per block
@@ -687,14 +710,23 @@ def test_attestation(spec, state):
 
     yield 'pre', state
 
-    attestation = get_valid_attestation(spec, state, signed=True, on_time=True)
+    attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    index = 0
+    if spec.fork == PHASE1:
+        shard = spec.compute_shard_from_committee_index(state, index, state.slot)
+        shard_transition = get_shard_transition_of_committee(spec, state, index)
+        attestation_block.body.shard_transitions[shard] = shard_transition
+    else:
+        shard_transition = None
+
+    attestation = get_valid_attestation(
+        spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True
+    )
 
     # Add to state via block transition
     pre_current_attestations_len = len(state.current_epoch_attestations)
-    attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
     attestation_block.body.attestations.append(attestation)
-    if spec.fork == PHASE1:
-        fill_block_shard_transitions_by_attestations(spec, state, attestation_block)
     signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
 
     assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
@@ -730,7 +762,7 @@ def create_signed_exit(index):
 # exceeding the minimal-config randao mixes memory size.
 # Applies to all voluntary-exit sanity block tests.
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_voluntary_exit(spec, state):
     validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
@@ -758,7 +790,7 @@ def test_voluntary_exit(spec, state):
     assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_double_validator_exit_same_block(spec, state):
     validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
@@ -779,7 +811,7 @@ def test_double_validator_exit_same_block(spec, state):
     yield 'post', None
 
 
-@with_phases(['phase0'])
+@with_phases([PHASE0])
 @spec_state_test
 def test_multiple_different_validator_exits_same_block(spec, state):
     validator_indices = [
diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py
rename to tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py
diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py
similarity index 84%
rename from tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py
rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py
--- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py
@@ -1,41 +1,13 @@
 from eth2spec.test.context import with_all_phases, spec_state_test
 from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
 from eth2spec.test.helpers.block import build_empty_block_for_next_slot
+from eth2spec.test.helpers.fork_choice import add_attestation_to_store, add_block_to_store, get_anchor_root
 from eth2spec.test.helpers.state import (
     next_epoch,
     state_transition_and_sign_block,
 )
 
 
-def add_block_to_store(spec, store, signed_block):
-    pre_state = store.block_states[signed_block.message.parent_root]
-    block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
-
-    if store.time < block_time:
-        spec.on_tick(store, block_time)
-
-    spec.on_block(store, signed_block)
-
-
-def add_attestation_to_store(spec, store, attestation):
-    parent_block = store.blocks[attestation.data.beacon_block_root]
-    pre_state = store.block_states[spec.hash_tree_root(parent_block)]
-    block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
-    next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
-
-    if store.time < next_epoch_time:
-        spec.on_tick(store, next_epoch_time)
-
-    spec.on_attestation(store, attestation)
-
-
-def get_anchor_root(spec, state):
-    anchor_block_header = state.latest_block_header.copy()
-    if anchor_block_header.state_root == spec.Bytes32():
-        anchor_block_header.state_root = spec.hash_tree_root(state)
-    return spec.hash_tree_root(anchor_block_header)
-
-
 @with_all_phases
 @spec_state_test
 def test_genesis(spec, state):
@@ -182,7 +154,7 @@ def test_filtered_block_tree(spec, state):
     attestations = []
     for i in range(spec.SLOTS_PER_EPOCH):
         slot = rogue_block.slot + i
-        for index in range(spec.get_committee_count_at_slot(non_viable_state, slot)):
+        for index in range(spec.get_committee_count_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot))):
             attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True)
             attestations.append(attestation)
 
diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py
similarity index 79%
rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py
rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py
--- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py
@@ -16,20 +16,22 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
     indexed_attestation = spec.get_indexed_attestation(state, attestation)
     spec.on_attestation(store, attestation)
 
+    sample_index = indexed_attestation.attesting_indices[0]
     if spec.fork == PHASE0:
-        sample_index = indexed_attestation.attesting_indices[0]
+        latest_message = spec.LatestMessage(
+            epoch=attestation.data.target.epoch,
+            root=attestation.data.beacon_block_root,
+        )
     else:
-        attesting_indices = [
-            index for i, index in enumerate(indexed_attestation.committee)
-            if attestation.aggregation_bits[i]
-        ]
-        sample_index = attesting_indices[0]
-    assert (
-        store.latest_messages[sample_index] ==
-        spec.LatestMessage(
+        latest_message = spec.LatestMessage(
             epoch=attestation.data.target.epoch,
             root=attestation.data.beacon_block_root,
+            shard=attestation.data.shard,
+            shard_root=attestation.data.shard_head_root,
         )
+
+    assert (
+        store.latest_messages[sample_index] == latest_message
     )
 
 
@@ -156,9 +158,9 @@ def test_on_attestation_inconsistent_target_and_head(spec, state):
 
 @with_all_phases
 @spec_state_test
-def test_on_attestation_target_not_in_store(spec, state):
+def test_on_attestation_target_block_not_in_store(spec, state):
     store = spec.get_forkchoice_store(state)
-    time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
+    time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
     spec.on_tick(store, time)
 
     # move to immediately before next epoch to make block new target
@@ -176,11 +178,63 @@ def test_on_attestation_target_not_in_store(spec, state):
     run_on_attestation(spec, state, store, attestation, False)
 
 
+@with_all_phases
+@spec_state_test
+def test_on_attestation_target_checkpoint_not_in_store(spec, state):
+    store = spec.get_forkchoice_store(state)
+    time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
+    spec.on_tick(store, time)
+
+    # move to immediately before next epoch to make block new target
+    next_epoch = spec.get_current_epoch(state) + 1
+    transition_to(spec, state, spec.compute_start_slot_at_epoch(next_epoch) - 1)
+
+    target_block = build_empty_block_for_next_slot(spec, state)
+    signed_target_block = state_transition_and_sign_block(spec, state, target_block)
+
+    # add target block to store
+    spec.on_block(store, signed_target_block)
+
+    # target checkpoint state is not yet in store
+
+    attestation = get_valid_attestation(spec, state, slot=target_block.slot, signed=True)
+    assert attestation.data.target.root == target_block.hash_tree_root()
+
+    run_on_attestation(spec, state, store, attestation)
+
+
+@with_all_phases
+@spec_state_test
+def test_on_attestation_target_checkpoint_not_in_store_diff_slot(spec, state):
+    store = spec.get_forkchoice_store(state)
+    time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
+    spec.on_tick(store, time)
+
+    # move to two slots before next epoch to make target block one before an empty slot
+    next_epoch = spec.get_current_epoch(state) + 1
+    transition_to(spec, state, spec.compute_start_slot_at_epoch(next_epoch) - 2)
+
+    target_block = build_empty_block_for_next_slot(spec, state)
+    signed_target_block = state_transition_and_sign_block(spec, state, target_block)
+
+    # add target block to store
+    spec.on_block(store, signed_target_block)
+
+    # target checkpoint state is not yet in store
+
+    attestation_slot = target_block.slot + 1
+    transition_to(spec, state, attestation_slot)
+    attestation = get_valid_attestation(spec, state, slot=attestation_slot, signed=True)
+    assert attestation.data.target.root == target_block.hash_tree_root()
+
+    run_on_attestation(spec, state, store, attestation)
+
+
 @with_all_phases
 @spec_state_test
 def test_on_attestation_beacon_block_not_in_store(spec, state):
     store = spec.get_forkchoice_store(state)
-    time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
+    time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
     spec.on_tick(store, time)
 
     # move to immediately before next epoch to make block new target
diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py
rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py
diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py
rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py
diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py
similarity index 87%
rename from tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py
rename to tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py
--- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py
+++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py
@@ -1,5 +1,9 @@
-from eth2spec.test.context import spec_state_test, always_bls, with_all_phases
-from eth2spec.test.helpers.attestations import build_attestation_data
+from eth2spec.test.context import (
+    spec_state_test,
+    always_bls, with_phases, with_all_phases,
+    PHASE0,
+)
+from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation
 from eth2spec.test.helpers.block import build_empty_block
 from eth2spec.test.helpers.deposits import prepare_state_and_deposit
 from eth2spec.test.helpers.keys import privkeys, pubkeys
@@ -22,7 +26,7 @@ def run_get_committee_assignment(spec, state, epoch, validator_index, valid=True
         committee, committee_index, slot = assignment
         assert spec.compute_epoch_at_slot(slot) == epoch
         assert committee == spec.get_beacon_committee(state, slot, committee_index)
-        assert committee_index < spec.get_committee_count_at_slot(state, slot)
+        assert committee_index < spec.get_committee_count_per_slot(state, epoch)
         assert validator_index in committee
         assert valid
     except AssertionError:
@@ -36,9 +40,9 @@ def run_is_candidate_block(spec, eth1_block, period_start, success=True):
 
 
 def get_min_new_period_epochs(spec):
-    return int(
-        spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2  # to seconds
-        / spec.SECONDS_PER_SLOT / spec.SLOTS_PER_EPOCH
+    return (
+        (spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2)  # to seconds
+        // spec.SECONDS_PER_SLOT // spec.SLOTS_PER_EPOCH
     )
 
 
@@ -314,21 +318,35 @@ def test_get_block_signature(spec, state):
     )
 
 
+@with_all_phases
+@spec_state_test
+def test_compute_fork_digest(spec, state):
+    actual_fork_digest = spec.compute_fork_digest(state.fork.current_version, state.genesis_validators_root)
+
+    expected_fork_data_root = spec.hash_tree_root(
+        spec.ForkData(current_version=state.fork.current_version,
+                      genesis_validators_root=state.genesis_validators_root))
+    expected_fork_digest = spec.ForkDigest(expected_fork_data_root[:4])
+
+    assert actual_fork_digest == expected_fork_digest
+
+
 # Attesting
 
 
 @with_all_phases
 @spec_state_test
 @always_bls
-def test_get_attestation_signature(spec, state):
+def test_get_attestation_signature_phase0(spec, state):
     privkey = privkeys[0]
     pubkey = pubkeys[0]
-    attestation_data = spec.AttestationData(slot=10)
-    domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
+    attestation = get_valid_attestation(spec, state, signed=False)
+    domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
+
     run_get_signature_test(
         spec=spec,
         state=state,
-        obj=attestation_data,
+        obj=attestation.data,
         domain=domain,
         get_signature_fn=spec.get_attestation_signature,
         privkey=privkey,
@@ -336,6 +354,21 @@ def test_get_attestation_signature(spec, state):
     )
 
 
+@with_all_phases
+@spec_state_test
+def test_compute_subnet_for_attestation(spec, state):
+    for committee_idx in range(spec.MAX_COMMITTEES_PER_SLOT):
+        for slot in range(state.slot, state.slot + spec.SLOTS_PER_EPOCH):
+            committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot))
+            actual_subnet_id = spec.compute_subnet_for_attestation(committees_per_slot, slot, committee_idx)
+
+            slots_since_epoch_start = slot % spec.SLOTS_PER_EPOCH
+            committees_since_epoch_start = committees_per_slot * slots_since_epoch_start
+            expected_subnet_id = (committees_since_epoch_start + committee_idx) % spec.ATTESTATION_SUBNET_COUNT
+
+            assert actual_subnet_id == expected_subnet_id
+
+
 # Attestation aggregation
 
 
@@ -363,7 +396,7 @@ def test_get_slot_signature(spec, state):
 @always_bls
 def test_is_aggregator(spec, state):
     # TODO: we can test the probabilistic result against `TARGET_AGGREGATORS_PER_COMMITTEE`
-    #  if we have more validators and larger committeee size
+    #  if we have more validators and larger committee size
     slot = state.slot
     committee_index = 0
     has_aggregator = False
@@ -377,7 +410,7 @@ def test_is_aggregator(spec, state):
     assert has_aggregator
 
 
-@with_all_phases
+@with_phases([PHASE0])
 @spec_state_test
 @always_bls
 def test_get_aggregate_signature(spec, state):
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase1/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/__init__.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py
rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/__init__.py
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py
similarity index 61%
rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py
rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py
--- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py
+++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py
@@ -1,4 +1,5 @@
 from eth2spec.test.context import (
+    PHASE0,
     with_all_phases_except,
     spec_state_test,
     always_bls,
@@ -11,7 +12,7 @@
 )
 
 
-@with_all_phases_except(['phase0'])
+@with_all_phases_except([PHASE0])
 @spec_state_test
 @always_bls
 def test_on_time_success(spec, state):
@@ -22,25 +23,12 @@ def test_on_time_success(spec, state):
     yield from run_attestation_processing(spec, state, attestation)
 
 
-@with_all_phases_except(['phase0'])
+@with_all_phases_except([PHASE0])
 @spec_state_test
 @always_bls
-def test_on_time_empty_custody_bits_blocks(spec, state):
+def test_late_success(spec, state):
     attestation = get_valid_late_attestation(spec, state, signed=True)
 
-    assert not any(attestation.custody_bits_blocks)
-
-    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
-
-    yield from run_attestation_processing(spec, state, attestation, False)
-
-
-@with_all_phases_except(['phase0'])
-@spec_state_test
-@always_bls
-def test_late_with_custody_bits_blocks(spec, state):
-    attestation = get_valid_on_time_attestation(spec, state, signed=True)
-
     transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1)
 
-    yield from run_attestation_processing(spec, state, attestation, False)
+    yield from run_attestation_processing(spec, state, attestation)
diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py
@@ -0,0 +1,306 @@
+from eth2spec.test.helpers.custody import (
+    get_valid_chunk_challenge,
+    get_valid_custody_chunk_response,
+    get_sample_shard_transition,
+)
+from eth2spec.test.helpers.attestations import (
+    get_valid_on_time_attestation,
+)
+from eth2spec.test.helpers.state import transition_to
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+    expect_assertion_error,
+)
+from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
+
+
+def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True):
+    """
+    Run ``process_chunk_challenge``, yielding:
+      - pre-state ('pre')
+      - CustodyBitChallenge ('custody_chunk_challenge')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    yield 'pre', state
+    yield 'custody_chunk_challenge', custody_chunk_challenge
+
+    if not valid:
+        expect_assertion_error(lambda: spec.process_chunk_challenge(state, custody_chunk_challenge))
+        yield 'post', None
+        return
+
+    spec.process_chunk_challenge(state, custody_chunk_challenge)
+
+    assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].responder_index == \
+        custody_chunk_challenge.responder_index
+    assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].chunk_index == \
+        custody_chunk_challenge.chunk_index
+
+    yield 'post', state
+
+
+def run_custody_chunk_response_processing(spec, state, custody_response, valid=True):
+    """
+    Run ``process_chunk_challenge_response``, yielding:
+      - pre-state ('pre')
+      - CustodyResponse ('custody_response')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    yield 'pre', state
+    yield 'custody_response', custody_response
+
+    if not valid:
+        expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
+        yield 'post', None
+        return
+
+    spec.process_chunk_challenge_response(state, custody_response)
+
+    assert state.custody_chunk_challenge_records[custody_response.challenge_index] == spec.CustodyChunkChallengeRecord()
+
+    yield 'post', state
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_challenge_appended(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD)
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_challenge_empty_element_replaced(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD)
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    state.custody_chunk_challenge_records.append(spec.CustodyChunkChallengeRecord())
+
+    yield from run_chunk_challenge_processing(spec, state, challenge)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_duplicate_challenge(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD)
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_second_challenge(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD)
+
+    challenge0 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=0)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge0)
+
+    challenge1 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=1)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge1)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_multiple_epochs_custody(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_many_epochs_custody(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_off_chain_attestation(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    yield from run_chunk_challenge_processing(spec, state, challenge)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_response(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    chunk_challenge_index = state.custody_chunk_challenge_index - 1
+
+    custody_response = get_valid_custody_chunk_response(
+        spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
+
+    yield from run_custody_chunk_response_processing(spec, state, custody_response)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_response_multiple_epochs(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    chunk_challenge_index = state.custody_chunk_challenge_index - 1
+
+    custody_response = get_valid_custody_chunk_response(
+        spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
+
+    yield from run_custody_chunk_response_processing(spec, state, custody_response)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_response_many_epochs(spec, state):
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
+
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    chunk_challenge_index = state.custody_chunk_challenge_index - 1
+
+    custody_response = get_valid_custody_chunk_response(
+        spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
+
+    yield from run_custody_chunk_response_processing(spec, state, custody_response)
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py
similarity index 69%
rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py
rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py
--- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py
+++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py
@@ -28,30 +28,14 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru
 
     pre_next_custody_secret_to_reveal = \
         state.validators[revealer_index].next_custody_secret_to_reveal
-    pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
 
     spec.process_custody_key_reveal(state, custody_key_reveal)
 
     post_next_custody_secret_to_reveal = \
         state.validators[revealer_index].next_custody_secret_to_reveal
-    post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
 
     assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1
 
-    if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period(
-        pre_next_custody_secret_to_reveal,
-        revealer_index
-    ) + spec.EPOCHS_PER_CUSTODY_PERIOD:
-        assert post_reveal_lateness > 0
-        if pre_reveal_lateness == 0:
-            assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period(
-                pre_next_custody_secret_to_reveal,
-                revealer_index
-            ) - spec.EPOCHS_PER_CUSTODY_PERIOD
-    else:
-        if pre_reveal_lateness > 0:
-            assert post_reveal_lateness < pre_reveal_lateness
-
     yield 'post', state
 
 
@@ -103,17 +87,3 @@ def test_double_reveal(spec, state):
     _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
 
     yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
-
-
-@with_all_phases_except([PHASE0])
-@spec_state_test
-@always_bls
-def test_max_decrement(spec, state):
-    state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
-    custody_key_reveal = get_valid_custody_key_reveal(spec, state)
-
-    _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
-
-    custody_key_reveal2 = get_valid_custody_key_reveal(spec, state)
-
-    yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2)
diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py
@@ -0,0 +1,138 @@
+from eth2spec.test.helpers.custody import (
+    get_valid_custody_slashing,
+    get_custody_secret,
+    get_custody_slashable_shard_transition,
+)
+from eth2spec.test.helpers.attestations import (
+    get_valid_on_time_attestation,
+)
+from eth2spec.utils.ssz.ssz_typing import ByteList
+from eth2spec.test.helpers.state import get_balance, transition_to
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+    expect_assertion_error,
+)
+from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
+
+
+def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True):
+    """
+    Run ``process_bit_challenge``, yielding:
+      - pre-state ('pre')
+      - CustodySlashing ('custody_slashing')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    yield 'pre', state
+    yield 'custody_slashing', custody_slashing
+
+    if not valid:
+        expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing))
+        yield 'post', None
+        return
+
+    if correct:
+        pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index)
+    else:
+        pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index)
+
+    spec.process_custody_slashing(state, custody_slashing)
+
+    if correct:
+        slashed_validator = state.validators[custody_slashing.message.malefactor_index]
+        assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance
+    else:
+        slashed_validator = state.validators[custody_slashing.message.whistleblower_index]
+        assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance
+
+    assert slashed_validator.slashed
+    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
+    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    yield 'post', state
+
+
+def run_standard_custody_slashing_test(spec,
+                                       state,
+                                       shard_lateness=None,
+                                       shard=None,
+                                       validator_index=None,
+                                       block_lengths=None,
+                                       slashing_message_data=None,
+                                       correct=True,
+                                       valid=True):
+    if shard_lateness is None:
+        shard_lateness = spec.SLOTS_PER_EPOCH
+    transition_to(spec, state, state.slot + shard_lateness)
+
+    if shard is None:
+        shard = 0
+    if validator_index is None:
+        validator_index = spec.get_beacon_committee(state, state.slot, shard)[0]
+
+    offset_slots = spec.get_offset_slots(state, shard)
+    if block_lengths is None:
+        block_lengths = [2**15 // 3] * len(offset_slots)
+
+    custody_secret = get_custody_secret(spec, state, validator_index)
+    shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(
+        spec,
+        state.slot,
+        block_lengths,
+        custody_secret,
+        slashable=correct,
+    )
+
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition,
+                                          custody_secret, slashable_test_vector)
+
+    if slashing_message_data is not None:
+        slashing.message.data = slashing_message_data
+
+    yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_slashing(spec, state):
+    yield from run_standard_custody_slashing_test(spec, state)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_incorrect_custody_slashing(spec, state):
+    yield from run_standard_custody_slashing_test(spec, state, correct=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_multiple_epochs_custody(spec, state):
+    yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_many_epochs_custody(spec, state):
+    yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_invalid_custody_slashing(spec, state):
+    yield from run_standard_custody_slashing_test(
+        spec,
+        state,
+        slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](),
+        valid=False,
+    )
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_early_derived_secret_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py
similarity index 100%
rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_early_derived_secret_reveal.py
rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py
diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py
@@ -0,0 +1,208 @@
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.helpers.attestations import (
+    get_valid_attestation,
+    get_valid_on_time_attestation,
+    run_attestation_processing,
+)
+from eth2spec.test.helpers.shard_transitions import (
+    run_shard_transitions_processing,
+    is_full_crosslink,
+)
+from eth2spec.test.helpers.shard_block import (
+    build_shard_block,
+    get_shard_transitions,
+    get_sample_shard_block_body,
+    get_committee_index_of_shard,
+)
+from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot
+
+
+def get_initial_env(spec, state, target_len_offset_slot):
+    state = transition_to_valid_shard_slot(spec, state)
+    committee_index = spec.CommitteeIndex(0)
+    target_shard_slot = state.slot + target_len_offset_slot - 1
+    shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot)
+    assert state.shard_states[shard].slot == state.slot - 1
+    return state, shard, target_shard_slot
+
+
+def get_attestations_and_shard_transitions(spec, state, shard_block_dict):
+    shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
+    attestations = [
+        get_valid_on_time_attestation(
+            spec, state,
+            index=get_committee_index_of_shard(spec, state, state.slot, shard),
+            shard_transition=shard_transition,
+            signed=True,
+        )
+        for shard, shard_transition in enumerate(shard_transitions)
+        if shard_transition != spec.ShardTransition()
+    ]
+    return attestations, shard_transitions
+
+
+def run_successful_crosslink_tests(spec, state, target_len_offset_slot):
+    state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot)
+    init_slot = state.slot
+
+    # Create SignedShardBlock at init_slot
+    shard_block = build_shard_block(
+        spec, state, shard,
+        slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
+    )
+
+    # Transition state to target shard slot
+    transition_to(spec, state, target_shard_slot)
+
+    # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
+    shard_block_dict = {shard: [shard_block]}
+    attestations, shard_transitions = get_attestations_and_shard_transitions(spec, state, shard_block_dict)
+
+    next_slot(spec, state)
+
+    for attestation in attestations:
+        _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    _, winning_roots = spec.get_shard_winning_roots(state, attestations)
+    assert len(winning_roots) == 1
+    shard_transition = shard_transitions[shard]
+    assert winning_roots[0] == shard_transition.hash_tree_root()
+
+    pre_gasprice = state.shard_states[shard].gasprice
+    pre_shard_states = state.shard_states.copy()
+    yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations)
+
+    for index, shard_state in enumerate(state.shard_states):
+        if index == shard:
+            assert shard_state != pre_shard_states[index]
+            assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
+            assert shard_state.latest_block_root == shard_block.message.hash_tree_root()
+            if target_len_offset_slot == 1:
+                assert shard_state.gasprice > pre_gasprice
+        else:
+            assert shard_state == pre_shard_states[index]
+
+    for pending_attestation in state.current_epoch_attestations:
+        assert bool(pending_attestation.crosslink_success) is True
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_basic_crosslinks(spec, state):
+    if not is_full_crosslink(spec, state):
+        # Skip this test
+        return
+
+    yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_multiple_offset_slots(spec, state):
+    if not is_full_crosslink(spec, state):
+        # Skip this test
+        return
+
+    yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_no_winning_root(spec, state):
+    if not is_full_crosslink(spec, state):
+        # Skip this test
+        return
+
+    state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
+    init_slot = state.slot
+
+    # Create SignedShardBlock at init_slot
+    shard_block = build_shard_block(
+        spec, state, shard,
+        slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
+    )
+
+    # Transition state to target shard slot
+    transition_to(spec, state, target_shard_slot)
+
+    # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
+    shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
+    shard_transition = shard_transitions[shard]
+    committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
+    attestation = get_valid_attestation(
+        spec, state,
+        index=committee_index,
+        shard_transition=shard_transition,
+        # Decrease attested participants to 1/3 committee
+        filter_participant_set=lambda committee: set(list(committee)[:len(committee) // 3]),
+        signed=True,
+        on_time=True,
+    )
+
+    next_slot(spec, state)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    _, winning_roots = spec.get_shard_winning_roots(state, [attestation])
+    assert len(winning_roots) == 0
+
+    # No winning root, shard_transitions[shard] is empty
+    shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
+    pre_shard_states = state.shard_states.copy()
+    yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation])
+
+    for pending_attestation in state.current_epoch_attestations:
+        assert bool(pending_attestation.crosslink_success) is False
+
+    assert state.shard_states == pre_shard_states
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_wrong_shard_transition_root(spec, state):
+    if not is_full_crosslink(spec, state):
+        # Skip this test
+        return
+
+    state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
+    init_slot = state.slot
+
+    # Create SignedShardBlock at init_slot
+    shard_block = build_shard_block(
+        spec, state, shard,
+        slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
+    )
+
+    # Transition state to target shard slot
+    transition_to(spec, state, target_shard_slot)
+
+    # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
+    shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
+    shard_transition = shard_transitions[shard]
+    wrong_shard_transition = shard_transition.copy()
+    wrong_shard_transition.shard_states[shard].gasprice = shard_transition.shard_states[shard].gasprice + 1
+    committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
+    attestation = get_valid_attestation(
+        spec, state,
+        index=committee_index,
+        shard_transition=wrong_shard_transition,
+        signed=True,
+        on_time=True,
+    )
+    attestations = [attestation]
+
+    next_slot(spec, state)
+
+    run_attestation_processing(spec, state, attestation)
+
+    # Check if winning root != shard_transition.hash_tree_root()
+    _, winning_roots = spec.get_shard_winning_roots(state, attestations)
+    assert len(winning_roots) == 1
+    shard_transition = shard_transitions[shard]
+    assert winning_roots[0] != shard_transition.hash_tree_root()
+
+    yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=False)
diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py
new file mode 100644
diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py
@@ -0,0 +1,58 @@
+from eth2spec.test.helpers.custody import (
+    get_valid_chunk_challenge,
+    get_sample_shard_transition,
+)
+from eth2spec.test.helpers.attestations import (
+    get_valid_on_time_attestation,
+)
+from eth2spec.test.helpers.state import transition_to
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
+
+from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import (
+    run_chunk_challenge_processing,
+)
+
+
+def run_process_challenge_deadlines(spec, state):
+    yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines')
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_slashed_after_chunk_challenge(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    validator_index = spec.get_beacon_committee(
+        state,
+        attestation.data.slot,
+        attestation.data.index
+    )[0]
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    assert state.validators[validator_index].slashed == 0
+
+    transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH)
+
+    state.validators[validator_index].slashed = 0
+
+    yield from run_process_challenge_deadlines(spec, state)
+
+    assert state.validators[validator_index].slashed == 1
diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py
@@ -0,0 +1,169 @@
+from eth2spec.test.context import (
+    PHASE0,
+)
+from eth2spec.test.helpers.custody import (
+    get_valid_chunk_challenge,
+    get_valid_custody_chunk_response,
+    get_valid_custody_key_reveal,
+    get_sample_shard_transition
+)
+from eth2spec.test.helpers.attestations import (
+    get_valid_on_time_attestation,
+)
+from eth2spec.test.helpers.state import next_epoch_via_block, transition_to
+from eth2spec.test.context import (
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
+
+from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import (
+    run_chunk_challenge_processing,
+    run_custody_chunk_response_processing,
+)
+from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
+
+
+def run_process_custody_final_updates(spec, state):
+    yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates')
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_withdrawal_delay(spec, state):
+    spec.initiate_validator_exit(state, 0)
+    assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    yield from run_process_custody_final_updates(spec, state)
+
+    assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
+    spec.initiate_validator_exit(state, 0)
+    assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    next_epoch_via_block(spec, state)
+
+    assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+    while spec.get_current_epoch(state) < state.validators[0].exit_epoch:
+        next_epoch_via_block(spec, state)
+
+    while (state.validators[0].next_custody_secret_to_reveal
+           <= spec.get_custody_period_for_validator(0, state.validators[0].exit_epoch - 1)):
+        custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0)
+        _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
+
+    yield from run_process_custody_final_updates(spec, state)
+
+    assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    validator_index = spec.get_beacon_committee(
+        state,
+        attestation.data.slot,
+        attestation.data.index
+    )[0]
+
+    spec.initiate_validator_exit(state, validator_index)
+    assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
+
+    assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+    while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
+        next_epoch_via_block(spec, state)
+
+    while (state.validators[validator_index].next_custody_secret_to_reveal
+           <= spec.get_custody_period_for_validator(
+               validator_index,
+               state.validators[validator_index].exit_epoch - 1)):
+        custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
+        _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
+
+    next_epoch_via_block(spec, state)
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    yield from run_process_custody_final_updates(spec, state)
+
+    assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state):
+    transition_to(spec, state, state.slot + 1)
+    shard = 0
+    offset_slots = spec.get_offset_slots(state, shard)
+    shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
+    attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
+                                                shard_transition=shard_transition)
+
+    transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
+
+    _, _, _ = run_attestation_processing(spec, state, attestation)
+
+    validator_index = spec.get_beacon_committee(
+        state,
+        attestation.data.slot,
+        attestation.data.index
+    )[0]
+
+    spec.initiate_validator_exit(state, validator_index)
+    assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    next_epoch_via_block(spec, state)
+
+    assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+    while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
+        next_epoch_via_block(spec, state)
+
+    while (state.validators[validator_index].next_custody_secret_to_reveal
+           <= spec.get_custody_period_for_validator(
+               validator_index,
+               state.validators[validator_index].exit_epoch - 1)):
+        custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
+        _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
+
+    next_epoch_via_block(spec, state)
+
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
+
+    _, _, _ = run_chunk_challenge_processing(spec, state, challenge)
+
+    next_epoch_via_block(spec, state)
+
+    assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
+
+    chunk_challenge_index = state.custody_chunk_challenge_index - 1
+    custody_response = get_valid_custody_chunk_response(
+        spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
+
+    _, _, _ = run_custody_chunk_response_processing(spec, state, custody_response)
+
+    yield from run_process_custody_final_updates(spec, state)
+
+    assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py
@@ -0,0 +1,51 @@
+from eth2spec.test.helpers.custody import (
+    get_valid_custody_key_reveal,
+)
+from eth2spec.test.helpers.state import transition_to
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
+from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
+
+
+def run_process_challenge_deadlines(spec, state):
+    yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines')
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_slashed_after_reveal_deadline(spec, state):
+    assert state.validators[0].slashed == 0
+    transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH)
+
+    # Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers)
+    custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1)
+    _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
+
+    transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
+
+    state.validators[0].slashed = 0
+
+    yield from run_process_challenge_deadlines(spec, state)
+
+    assert state.validators[0].slashed == 1
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_validator_not_slashed_after_reveal(spec, state):
+    transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
+    custody_key_reveal = get_valid_custody_key_reveal(spec, state)
+
+    _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
+
+    assert state.validators[0].slashed == 0
+
+    transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
+
+    yield from run_process_challenge_deadlines(spec, state)
+
+    assert state.validators[0].slashed == 0
diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/__init__.py
new file mode 100644
diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py
@@ -0,0 +1,242 @@
+from typing import Dict, Sequence
+
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
+from eth2spec.test.helpers.block import build_empty_block
+from eth2spec.test.helpers.custody import (
+    get_custody_secret,
+    get_custody_slashable_test_vector,
+    get_valid_chunk_challenge,
+    get_valid_custody_chunk_response,
+    get_valid_custody_key_reveal,
+    get_valid_custody_slashing,
+    get_valid_early_derived_secret_reveal,
+)
+from eth2spec.test.helpers.shard_block import (
+    build_shard_block,
+    get_committee_index_of_shard,
+    get_sample_shard_block_body,
+    get_shard_transitions,
+)
+from eth2spec.test.helpers.shard_transitions import is_full_crosslink
+from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to
+
+
+def run_beacon_block(spec, state, block, valid=True):
+    yield 'pre', state.copy()
+
+    if not valid:
+        signed_beacon_block = state_transition_and_sign_block(spec, state, block, expect_fail=True)
+        yield 'block', signed_beacon_block
+        yield 'post', None
+        return
+
+    signed_beacon_block = state_transition_and_sign_block(spec, state, block)
+    yield 'block', signed_beacon_block
+    yield 'post', state
+
+
+#
+# Beacon block with non-empty shard transitions
+#
+
+
+def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True):
+    transition_to(spec, state, state.slot + target_len_offset_slot)
+
+    body = get_sample_shard_block_body(spec, is_max=True)
+    shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
+    shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
+
+    shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
+    attestations = [
+        get_valid_on_time_attestation(
+            spec,
+            state,
+            index=committee_index,
+            shard_transition=shard_transitions[shard],
+            signed=True,
+        )
+        for shard in shard_block_dict.keys()
+    ]
+
+    beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
+    beacon_block.body.attestations = attestations
+    beacon_block.body.shard_transitions = shard_transitions
+
+    pre_gasprice = state.shard_states[shard].gasprice
+    pre_shard_states = state.shard_states.copy()
+    yield 'pre', state.copy()
+
+    if not valid:
+        state_transition_and_sign_block(spec, state, beacon_block, expect_fail=True)
+        yield 'block', beacon_block
+        yield 'post', None
+        return
+
+    signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block)
+    yield 'block', signed_beacon_block
+    yield 'post', state
+
+    for shard in range(spec.get_active_shard_count(state)):
+        post_shard_state = state.shard_states[shard]
+        if shard in shard_block_dict:
+            # Shard state has been changed to state_transition result
+            assert post_shard_state == shard_transitions[shard].shard_states[
+                len(shard_transitions[shard].shard_states) - 1
+            ]
+            assert post_shard_state.slot == state.slot - 1
+            if len((shard_block_dict[shard])) == 0:
+                # `latest_block_root` is the same
+                assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root
+            if target_len_offset_slot == 1 and len(shard_block_dict[shard]) > 0:
+                assert post_shard_state.gasprice > pre_gasprice
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_process_beacon_block_with_normal_shard_transition(spec, state):
+    # NOTE: this test is only for full crosslink (minimal config), not for mainnet
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    state = transition_to_valid_shard_slot(spec, state)
+
+    target_len_offset_slot = 1
+    committee_index = spec.CommitteeIndex(0)
+    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
+    assert state.shard_states[shard].slot == state.slot - 1
+
+    yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_process_beacon_block_with_empty_proposal_transition(spec, state):
+    # NOTE: this test is only for full crosslink (minimal config), not for mainnet
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    state = transition_to_valid_shard_slot(spec, state)
+
+    target_len_offset_slot = 1
+    committee_index = spec.CommitteeIndex(0)
+    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
+    assert state.shard_states[shard].slot == state.slot - 1
+
+    yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
+
+
+#
+# Beacon block with custody operations
+#
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
+    # NOTE: this test is only for full crosslink (minimal config), not for mainnet
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    state = transition_to_valid_shard_slot(spec, state)
+
+    # build shard block
+    shard = 0
+    committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
+    body = get_sample_shard_block_body(spec)
+    shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
+    shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
+    shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
+    attestation = get_valid_on_time_attestation(
+        spec, state, index=committee_index,
+        shard_transition=shard_transitions[shard], signed=True,
+    )
+
+    block = build_empty_block(spec, state, slot=state.slot + 1)
+    block.body.attestations = [attestation]
+    block.body.shard_transitions = shard_transitions
+
+    # CustodyChunkChallenge operation
+    challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transitions[shard])
+    block.body.chunk_challenges = [challenge]
+    # CustodyChunkResponse operation
+    chunk_challenge_index = state.custody_chunk_challenge_index
+    custody_response = get_valid_custody_chunk_response(
+        spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=body)
+    block.body.chunk_challenge_responses = [custody_response]
+
+    yield from run_beacon_block(spec, state, block)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_key_reveal(spec, state):
+    state = transition_to_valid_shard_slot(spec, state)
+    transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
+
+    block = build_empty_block(spec, state, slot=state.slot + 1)
+    custody_key_reveal = get_valid_custody_key_reveal(spec, state)
+    block.body.custody_key_reveals = [custody_key_reveal]
+
+    yield from run_beacon_block(spec, state, block)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_early_derived_secret_reveal(spec, state):
+    state = transition_to_valid_shard_slot(spec, state)
+    block = build_empty_block(spec, state, slot=state.slot + 1)
+    early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state)
+    block.body.early_derived_secret_reveals = [early_derived_secret_reveal]
+
+    yield from run_beacon_block(spec, state, block)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_custody_slashing(spec, state):
+    # NOTE: this test is only for full crosslink (minimal config), not for mainnet
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    state = transition_to_valid_shard_slot(spec, state)
+
+    # Build shard block
+    shard = 0
+    committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
+    # Create slashable shard block body
+    validator_index = spec.get_beacon_committee(state, state.slot, committee_index)[0]
+    custody_secret = get_custody_secret(spec, state, validator_index)
+    slashable_body = get_custody_slashable_test_vector(spec, custody_secret, length=100, slashable=True)
+    shard_block = build_shard_block(spec, state, shard, body=slashable_body, slot=state.slot, signed=True)
+    shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
+    shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
+
+    attestation = get_valid_on_time_attestation(
+        spec, state, index=committee_index,
+        shard_transition=shard_transitions[shard], signed=True,
+    )
+    block = build_empty_block(spec, state, slot=state.slot + 1)
+    block.body.attestations = [attestation]
+    block.body.shard_transitions = shard_transitions
+
+    _, _, _ = run_beacon_block(spec, state, block)
+
+    transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
+
+    block = build_empty_block(spec, state, slot=state.slot + 1)
+    custody_slashing = get_valid_custody_slashing(
+        spec, state, attestation, shard_transitions[shard], custody_secret, slashable_body
+    )
+    block.body.custody_slashings = [custody_slashing]
+
+    yield from run_beacon_block(spec, state, block)
diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py
@@ -0,0 +1,271 @@
+from eth2spec.test.context import (
+    PHASE0,
+    always_bls,
+    expect_assertion_error,
+    spec_state_test,
+    with_all_phases_except,
+)
+from eth2spec.test.helpers.shard_block import (
+    build_shard_block,
+    sign_shard_block,
+)
+from eth2spec.test.helpers.shard_transitions import is_full_crosslink
+from eth2spec.test.helpers.state import next_slot, transition_to_valid_shard_slot, transition_to
+
+
+def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, valid=True):
+    pre_shard_state = shard_state.copy()
+
+    yield 'pre', pre_shard_state
+    yield 'signed_shard_block', signed_shard_block
+    yield 'beacon_parent_state', beacon_parent_state
+
+    if not valid:
+        expect_assertion_error(
+            lambda: spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
+        )
+        yield 'post', None
+        return
+
+    spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
+    yield 'post', shard_state
+
+    # Verify `process_shard_block`
+    block = signed_shard_block.message
+
+    assert shard_state.slot == block.slot
+
+    shard_block_length = len(block.body)
+    assert shard_state.gasprice == spec.compute_updated_gasprice(pre_shard_state.gasprice, shard_block_length)
+    if shard_block_length != 0:
+        shard_state.latest_block_root == block.hash_tree_root()
+    else:
+        shard_state.latest_block_root == pre_shard_state.latest_block_root
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_valid_shard_block(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
+
+
+#
+# verify_shard_block_message
+#
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_invalid_shard_parent_root(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+    signed_shard_block.message.shard_parent_root = b'\x12' * 32
+    sign_shard_block(spec, beacon_state, shard, signed_shard_block)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_invalid_beacon_parent_root(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+    signed_shard_block.message.beacon_parent_root = b'\x12' * 32
+    sign_shard_block(spec, beacon_state, shard, signed_shard_block)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_invalid_slot(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+    signed_shard_block.message.slot = beacon_state.slot + 1
+    proposer_index = spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard)
+    sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_invalid_proposer_index(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+    active_validator_indices = spec.get_active_validator_indices(beacon_state, spec.get_current_epoch(beacon_state))
+    proposer_index = (
+        (spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + 1)
+        % len(active_validator_indices)
+    )
+    signed_shard_block.message.proposer_index = proposer_index
+    sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_out_of_bound_offset(spec, state):
+    # TODO: Handle this edge case properly
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    slot = (
+        beacon_state.shard_states[shard].slot
+        + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1]
+        + 1  # out-of-bound
+    )
+    transition_to(spec, beacon_state, slot)
+
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_invalid_offset(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    # 4 is not in `SHARD_BLOCK_OFFSETS`
+    shard = 0
+    slot = beacon_state.shard_states[shard].slot + 4
+    assert slot not in spec.SHARD_BLOCK_OFFSETS
+    transition_to(spec, beacon_state, slot)
+
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_empty_block_body(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, body=b'', signed=True)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+#
+# verify_shard_block_signature
+#
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_invalid_signature(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
+
+
+#
+# Other cases
+#
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_max_offset(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    slot = beacon_state.shard_states[shard].slot + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1]
+    transition_to(spec, beacon_state, slot)
+
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@always_bls
+def test_pending_shard_parent_block(spec, state):
+    if not is_full_crosslink(spec, state):
+        # skip
+        return
+
+    # Block N
+    beacon_state = transition_to_valid_shard_slot(spec, state)
+    shard = 0
+    shard_state = beacon_state.shard_states[shard]
+    signed_shard_block_1 = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
+    _, _, _, _ = run_shard_blocks(spec, shard_state, signed_shard_block_1, beacon_state)
+
+    # Block N+1
+    next_slot(spec, beacon_state)
+    signed_shard_block_2 = build_shard_block(
+        spec, beacon_state, shard,
+        slot=beacon_state.slot, shard_parent_state=shard_state, signed=True
+    )
+
+    assert signed_shard_block_2.message.shard_parent_root == shard_state.latest_block_root
+    assert signed_shard_block_2.message.slot == signed_shard_block_1.message.slot + 1
+    yield from run_shard_blocks(spec, shard_state, signed_shard_block_2, beacon_state)
diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/__init__.py
new file mode 100644
diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py
@@ -0,0 +1,129 @@
+from eth2spec.utils.ssz.ssz_impl import hash_tree_root
+
+from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls
+from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
+from eth2spec.test.helpers.shard_block import (
+    build_shard_block,
+    get_shard_transitions,
+    get_committee_index_of_shard,
+)
+from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root
+from eth2spec.test.helpers.state import state_transition_and_sign_block
+from eth2spec.test.helpers.block import build_empty_block
+
+
+def run_on_shard_block(spec, store, shard_store, signed_block, valid=True):
+    if not valid:
+        try:
+            spec.on_shard_block(store, shard_store, signed_block)
+        except AssertionError:
+            return
+        else:
+            assert False
+
+    spec.on_shard_block(store, shard_store, signed_block)
+    assert shard_store.signed_blocks[hash_tree_root(signed_block.message)] == signed_block
+
+
+def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer):
+    shard = shard_store.shard
+    body = b'\x56' * 4
+    shard_head_root = spec.get_shard_head(store, shard_store)
+    shard_parent_state = shard_store.block_states[shard_head_root]
+    assert shard_parent_state.slot != beacon_parent_state.slot
+    shard_block = build_shard_block(
+        spec, beacon_parent_state, shard,
+        shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
+    )
+    shard_blocks_buffer.append(shard_block)
+    run_on_shard_block(spec, store, shard_store, shard_block)
+    assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root()
+
+
+def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer):
+    pending_shard_blocks = spec.get_pending_shard_blocks(store, shard_store)
+    assert pending_shard_blocks == shard_blocks_buffer
+
+
+def is_in_offset_sets(spec, beacon_head_state, shard):
+    offset_slots = spec.compute_offset_slots(
+        beacon_head_state.shard_states[shard].slot, beacon_head_state.slot + 1
+    )
+    return beacon_head_state.slot in offset_slots
+
+
+def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer):
+    store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
+
+    shard = shard_store.shard
+    committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
+    has_shard_committee = committee_index is not None  # has committee of `shard` at this slot
+
+    beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
+
+    # If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block
+    if has_shard_committee and len(shard_blocks_buffer) > 0:
+        # Sanity check `get_pending_shard_blocks` function
+        check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer)
+        # Use temporary next state to get ShardTransition of shard block
+        shard_transitions = get_shard_transitions(
+            spec,
+            state,
+            shard_block_dict={shard: shard_blocks_buffer},
+        )
+        shard_transition = shard_transitions[shard]
+        attestation = get_valid_on_time_attestation(
+            spec,
+            state,
+            index=committee_index,
+            shard_transition=shard_transition,
+            signed=False,
+        )
+        assert attestation.data.shard == shard
+        beacon_block.body.attestations = [attestation]
+        beacon_block.body.shard_transitions = shard_transitions
+
+        # Clear buffer
+        shard_blocks_buffer.clear()
+
+    signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block)  # transition!
+    add_block_to_store(spec, store, signed_beacon_block)
+    assert spec.get_head(store) == beacon_block.hash_tree_root()
+
+    # On shard block at transitioned `state.slot`
+    if is_in_offset_sets(spec, state, shard):
+        # The created shard block would be appended to `shard_blocks_buffer`
+        apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer)
+
+    return has_shard_committee
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+@never_bls  # Set to never_bls for testing `check_pending_shard_blocks`
+def test_basic(spec, state):
+    spec.PHASE_1_GENESIS_SLOT = 0  # NOTE: mock genesis slot here
+    state = spec.upgrade_to_phase1(state)
+    shard = spec.Shard(1)
+
+    # Initialization
+    store = spec.get_forkchoice_store(state)
+    anchor_root = get_anchor_root(spec, state)
+    assert spec.get_head(store) == anchor_root
+
+    shard_store = spec.get_forkchoice_shard_store(state, shard)
+    shard_head_root = spec.get_shard_head(store, shard_store)
+    assert shard_head_root == state.shard_states[shard].latest_block_root
+    assert shard_store.block_states[shard_head_root].slot == 1
+    assert shard_store.block_states[shard_head_root] == state.shard_states[shard]
+
+    # For mainnet config, it's possible that only one committee of `shard` per epoch.
+    # we set this counter to test more rounds.
+    shard_committee_counter = 2
+    shard_blocks_buffer = []
+    while shard_committee_counter > 0:
+        has_shard_committee = apply_shard_and_beacon(
+            spec, state, store, shard_store, shard_blocks_buffer
+        )
+        if has_shard_committee:
+            shard_committee_counter -= 1
diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py
@@ -0,0 +1,76 @@
+from eth2spec.test.context import (
+    PHASE0,
+    with_all_phases_except,
+    spec_state_test,
+)
+from eth2spec.test.helpers.state import next_epoch
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_get_committee_count_delta(spec, state):
+    assert spec.get_committee_count_delta(state, 0, 0) == 0
+    assert spec.get_committee_count_per_slot(state, 0) != 0
+    assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_per_slot(state, 0)
+    assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_per_slot(state, 0)
+    assert spec.get_committee_count_delta(state, 0, 2) == spec.get_committee_count_per_slot(state, 0) * 2
+    assert spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) == (
+        spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH
+    )
+    assert spec.get_committee_count_delta(state, 0, 2 * spec.SLOTS_PER_EPOCH) == (
+        spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH
+        + spec.get_committee_count_per_slot(state, 1) * spec.SLOTS_PER_EPOCH
+    )
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_get_start_shard_current_epoch_start(spec, state):
+    assert state.current_epoch_start_shard == 0
+    next_epoch(spec, state)
+    active_shard_count = spec.get_active_shard_count(state)
+    assert state.current_epoch_start_shard == (
+        spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) % active_shard_count
+    )
+    current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
+
+    slot = current_epoch_start_slot
+    start_shard = spec.get_start_shard(state, slot)
+    assert start_shard == state.current_epoch_start_shard
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_get_start_shard_next_slot(spec, state):
+    next_epoch(spec, state)
+    active_shard_count = spec.get_active_shard_count(state)
+    current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
+
+    slot = current_epoch_start_slot + 1
+    start_shard = spec.get_start_shard(state, slot)
+
+    current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
+    expected_start_shard = (
+        state.current_epoch_start_shard
+        + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot)
+    ) % active_shard_count
+    assert start_shard == expected_start_shard
+
+
+@with_all_phases_except([PHASE0])
+@spec_state_test
+def test_get_start_shard_previous_slot(spec, state):
+    next_epoch(spec, state)
+    active_shard_count = spec.get_active_shard_count(state)
+    current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
+
+    slot = current_epoch_start_slot - 1
+    start_shard = spec.get_start_shard(state, slot)
+
+    current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
+    expected_start_shard = (
+        state.current_epoch_start_shard
+        + spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH * active_shard_count
+        - spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
+    ) % active_shard_count
+    assert start_shard == expected_start_shard
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py
deleted file mode 100644
--- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from eth2spec.test.context import (
-    PHASE0,
-    with_all_phases_except,
-    spec_state_test,
-    always_bls,
-)
-from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing
-from eth2spec.test.helpers.shard_block import (
-    build_attestation_with_shard_transition,
-    build_shard_block,
-    build_shard_transitions_till_slot,
-)
-from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
-
-
-def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True):
-    state = transition_to_valid_shard_slot(spec, state)
-    # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1`
-    slot_x = state.slot
-    committee_index = spec.CommitteeIndex(0)
-    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
-    assert state.shard_states[shard].slot == slot_x - 1
-
-    # Create SignedShardBlock
-    body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
-    shard_block = build_shard_block(spec, state, shard, body=body, signed=True)
-    shard_blocks = [shard_block]
-    # Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot`
-    shard_transitions = build_shard_transitions_till_slot(
-        spec,
-        state,
-        shard_blocks={shard: shard_blocks},
-        on_time_slot=state.slot + target_len_offset_slot,
-    )
-    shard_transition = shard_transitions[shard]
-    # Create an attestation that would be included at beacon block `state.slot + target_len_offset_slot`
-    attestation = build_attestation_with_shard_transition(
-        spec,
-        state,
-        index=committee_index,
-        on_time_slot=state.slot + target_len_offset_slot,
-        shard_transition=shard_transition,
-    )
-    pre_gasprice = state.shard_states[shard].gasprice
-
-    transition_to(spec, state, state.slot + target_len_offset_slot)
-    pre_shard_state = state.shard_states[shard]
-
-    yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid)
-
-    if valid:
-        # After state transition,
-        assert state.slot == slot_x + target_len_offset_slot
-        shard_state = state.shard_states[shard]
-        assert shard_state != pre_shard_state
-        assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
-
-        if target_len_offset_slot == 1:
-            assert shard_state.gasprice > pre_gasprice
-
-
-@with_all_phases_except([PHASE0])
-@spec_state_test
-@always_bls
-def test_basic_crosslinks(spec, state):
-    yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True)
-
-
-@with_all_phases_except([PHASE0])
-@spec_state_test
-@always_bls
-def test_multiple_offset_slots(spec, state):
-    yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=3, valid=True)
diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py
deleted file mode 100644
--- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from typing import Dict, Sequence
-
-from eth2spec.test.context import (
-    PHASE0,
-    with_all_phases_except,
-    spec_state_test,
-    always_bls,
-)
-from eth2spec.test.helpers.block import build_empty_block
-from eth2spec.test.helpers.shard_block import (
-    build_attestation_with_shard_transition,
-    build_shard_block,
-    build_shard_transitions_till_slot,
-)
-from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot
-
-
-def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index, valid=True):
-    shard_transitions = build_shard_transitions_till_slot(
-        spec, state, shard_blocks, on_time_slot=state.slot + target_len_offset_slot
-    )
-    attestations = [
-        build_attestation_with_shard_transition(
-            spec,
-            state,
-            on_time_slot=state.slot + target_len_offset_slot,
-            index=committee_index,
-            shard_transition=shard_transitions[shard],
-        )
-        for shard in shard_blocks.keys()
-    ]
-
-    # Propose beacon block at slot `x + 1`
-    beacon_block = build_empty_block(spec, state, slot=state.slot + target_len_offset_slot)
-    beacon_block.body.attestations = attestations
-    beacon_block.body.shard_transitions = shard_transitions
-
-    pre_shard_states = state.shard_states.copy()
-    yield 'pre', state.copy()
-    yield 'block', beacon_block
-    state_transition_and_sign_block(spec, state, beacon_block)
-    if valid:
-        yield 'post', state
-    else:
-        yield 'post', None
-        return
-
-    for shard in range(spec.get_active_shard_count(state)):
-        post_shard_state = state.shard_states[shard]
-        if shard in shard_blocks:
-            # Shard state has been changed to state_transition result
-            assert post_shard_state == shard_transitions[shard].shard_states[
-                len(shard_transitions[shard].shard_states) - 1
-            ]
-            assert beacon_block.slot == shard_transitions[shard].shard_states[0].slot + target_len_offset_slot
-            assert post_shard_state.slot == state.slot - 1
-            if len(shard_blocks[shard]) == 0:
-                # `latest_block_root` is the same
-                assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root
-
-
-@with_all_phases_except([PHASE0])
-@spec_state_test
-@always_bls
-def test_process_beacon_block_with_normal_shard_transition(spec, state):
-    state = transition_to_valid_shard_slot(spec, state)
-
-    target_len_offset_slot = 1
-    committee_index = spec.CommitteeIndex(0)
-    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
-    assert state.shard_states[shard].slot == state.slot - 1
-
-    pre_gasprice = state.shard_states[shard].gasprice
-
-    # Create SignedShardBlock at slot `shard_state.slot + 1`
-    body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
-    shard_block = build_shard_block(spec, state, shard, body=body, signed=True)
-    shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
-
-    yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index)
-
-    shard_state = state.shard_states[shard]
-
-    if target_len_offset_slot == 1 and len(shard_blocks) > 0:
-        assert shard_state.gasprice > pre_gasprice
-
-
-@with_all_phases_except([PHASE0])
-@spec_state_test
-@always_bls
-def test_process_beacon_block_with_empty_proposal_transition(spec, state):
-    state = transition_to_valid_shard_slot(spec, state)
-
-    target_len_offset_slot = 1
-    committee_index = spec.CommitteeIndex(0)
-    shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
-    assert state.shard_states[shard].slot == state.slot - 1
-
-    # No new shard block
-    shard_blocks = {}
-
-    pre_gasprice = state.shard_states[shard].gasprice
-
-    yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index)
-
-    if target_len_offset_slot == 1 and len(shard_blocks) > 0:
-        assert state.shard_states[shard].gasprice > pre_gasprice
diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py
--- a/tests/core/pyspec/eth2spec/utils/bls.py
+++ b/tests/core/pyspec/eth2spec/utils/bls.py
@@ -14,6 +14,22 @@
 STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE)
 
 
+def use_milagro():
+    """
+    Shortcut to use Milagro as BLS library
+    """
+    global bls
+    bls = milagro_bls
+
+
+def use_py_ecc():
+    """
+    Shortcut to use Py-ecc as BLS library
+    """
+    global bls
+    bls = py_ecc_bls
+
+
 def only_with_bls(alt_return=None):
     """
     Decorator factory to make a function only run when BLS is active. Otherwise return the default.
diff --git a/tests/core/pyspec/eth2spec/utils/hash_function.py b/tests/core/pyspec/eth2spec/utils/hash_function.py
--- a/tests/core/pyspec/eth2spec/utils/hash_function.py
+++ b/tests/core/pyspec/eth2spec/utils/hash_function.py
@@ -1,17 +1,9 @@
 from hashlib import sha256
-from typing import Dict, Union
+from remerkleable.byte_arrays import Bytes32
+from typing import Union
 
 ZERO_BYTES32 = b'\x00' * 32
 
 
-def _hash(x: Union[bytes, bytearray, memoryview]) -> bytes:
-    return sha256(x).digest()
-
-
-hash_cache: Dict[bytes, bytes] = {}
-
-
-def hash(x: bytes) -> bytes:
-    if x in hash_cache:
-        return hash_cache[x]
-    return _hash(x)
+def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32:
+    return Bytes32(sha256(x).digest())
diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py
--- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py
+++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py
@@ -1,3 +1,6 @@
+from typing import TypeVar
+
+from remerkleable.basic import uint
 from remerkleable.core import View
 from remerkleable.byte_arrays import Bytes32
 
@@ -8,3 +11,15 @@ def serialize(obj: View) -> bytes:
 
 def hash_tree_root(obj: View) -> Bytes32:
     return Bytes32(obj.get_backing().merkle_root())
+
+
+def uint_to_bytes(n: uint) -> bytes:
+    return serialize(n)
+
+
+V = TypeVar('V', bound=View)
+
+
+# Helper method for typing copies, and avoiding a example_input.copy() method call, instead of copy(example_input)
+def copy(obj: V) -> V:
+    return obj.copy()
diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py
--- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py
+++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py
@@ -5,4 +5,4 @@
 from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256
 from remerkleable.bitfields import Bitvector, Bitlist
 from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList
-from remerkleable.core import BasicView, View, TypeDef
+from remerkleable.core import BasicView, View
diff --git a/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py
new file mode 100644
--- /dev/null
+++ b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py
@@ -0,0 +1,47 @@
+import pytest
+
+
+# Note: these functions are extract from merkle-proofs.md (deprecated),
+# the tests are temporary to show correctness while the document is still there.
+
+def get_power_of_two_ceil(x: int) -> int:
+    if x <= 1:
+        return 1
+    elif x == 2:
+        return 2
+    else:
+        return 2 * get_power_of_two_ceil((x + 1) // 2)
+
+
+def get_power_of_two_floor(x: int) -> int:
+    if x <= 1:
+        return 1
+    if x == 2:
+        return x
+    else:
+        return 2 * get_power_of_two_floor(x // 2)
+
+
+power_of_two_ceil_cases = [
+    (0, 1), (1, 1), (2, 2), (3, 4), (4, 4), (5, 8), (6, 8), (7, 8), (8, 8), (9, 16),
+]
+
+power_of_two_floor_cases = [
+    (0, 1), (1, 1), (2, 2), (3, 2), (4, 4), (5, 4), (6, 4), (7, 4), (8, 8), (9, 8),
+]
+
+
+@pytest.mark.parametrize(
+    'value,expected',
+    power_of_two_ceil_cases,
+)
+def test_get_power_of_two_ceil(value, expected):
+    assert get_power_of_two_ceil(value) == expected
+
+
+@pytest.mark.parametrize(
+    'value,expected',
+    power_of_two_floor_cases,
+)
+def test_get_power_of_two_floor(value, expected):
+    assert get_power_of_two_floor(value) == expected
diff --git a/tests/formats/finality/README.md b/tests/formats/finality/README.md
new file mode 100644
--- /dev/null
+++ b/tests/formats/finality/README.md
@@ -0,0 +1,43 @@
+# Finality tests
+
+The aim of the tests for the finality rules.
+
+- `finality`: transitions triggered by one or more blocks.
+
+## Test case format
+
+### `meta.yaml`
+
+```yaml
+description: string    -- Optional. Description of test case, purely for debugging purposes.
+bls_setting: int       -- see general test-format spec.
+blocks_count: int      -- the number of blocks processed in this test.
+```
+
+### `pre.yaml`
+
+A YAML-encoded `BeaconState`, the state before running the block transitions.
+
+Also available as `pre.ssz`.
+
+
+### `blocks_<index>.yaml`
+
+A series of files, with `<index>` in range `[0, blocks_count)`. Blocks need to be processed in order,
+ following the main transition function (i.e. process slot and epoch transitions in between blocks as normal)
+
+Each file is a YAML-encoded `SignedBeaconBlock`.
+
+Each block is also available as `blocks_<index>.ssz`
+
+### `post.yaml`
+
+A YAML-encoded `BeaconState`, the state after applying the block transitions.
+
+Also available as `post.ssz`.
+
+
+## Condition
+
+The resulting state should match the expected `post` state, or if the `post` state is left blank,
+ the handler should reject the series of blocks as invalid.
diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py
--- a/tests/generators/bls/main.py
+++ b/tests/generators/bls/main.py
@@ -149,7 +149,9 @@ def case03_aggregate():
     else:
         raise Exception("Should have been INVALID")
 
-    yield f'aggregate_na_pubkeys', {
+    # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID.
+    # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8
+    yield f'aggregate_na_signatures', {
         'input': [],
         'output': None,
     }
@@ -319,6 +321,7 @@ def cases_fn() -> Iterable[gen_typing.TestCase]:
 
 
 if __name__ == "__main__":
+    bls.use_py_ecc()  # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct.
     gen_runner.run_generator("bls", [
         create_provider('sign', case01_sign),
         create_provider('verify', case02_verify),
diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py
--- a/tests/generators/epoch_processing/main.py
+++ b/tests/generators/epoch_processing/main.py
@@ -2,7 +2,7 @@
 
 from eth2spec.phase0 import spec as spec_phase0
 from eth2spec.phase1 import spec as spec_phase1
-from eth2spec.test.phase_0.epoch_processing import (
+from eth2spec.test.phase0.epoch_processing import (
     test_process_final_updates,
     test_process_justification_and_finalization,
     test_process_registry_updates,
@@ -14,6 +14,7 @@
 from importlib import reload
 from eth2spec.config import config_util
 from eth2spec.test.context import PHASE0
+from eth2spec.utils import bls
 
 
 def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
@@ -22,6 +23,7 @@ def prepare_fn(configs_path: str) -> str:
         config_util.prepare_config(configs_path, config_name)
         reload(spec_phase0)
         reload(spec_phase1)
+        bls.use_milagro()
         return config_name
 
     def cases_fn() -> Iterable[gen_typing.TestCase]:
diff --git a/tests/generators/finality/README.md b/tests/generators/finality/README.md
new file mode 100644
--- /dev/null
+++ b/tests/generators/finality/README.md
@@ -0,0 +1,5 @@
+# Finality tests
+
+Finality tests cover regular state-transitions in a common block-list format to test finality rules.
+
+Information on the format of the tests can be found in the [finality test formats documentation](../../formats/finality/README.md).
diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py
new file mode 100644
--- /dev/null
+++ b/tests/generators/finality/main.py
@@ -0,0 +1,39 @@
+from typing import Iterable
+from importlib import reload
+
+from gen_base import gen_runner, gen_typing
+from gen_from_tests.gen import generate_from_tests
+
+from eth2spec.test.context import PHASE0
+from eth2spec.test.phase0.finality import test_finality
+from eth2spec.config import config_util
+from eth2spec.phase0 import spec as spec_phase0
+from eth2spec.phase1 import spec as spec_phase1
+from eth2spec.utils import bls
+
+
+def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
+
+    def prepare_fn(configs_path: str) -> str:
+        config_util.prepare_config(configs_path, config_name)
+        reload(spec_phase0)
+        reload(spec_phase1)
+        bls.use_milagro()
+        return config_name
+
+    def cases_fn() -> Iterable[gen_typing.TestCase]:
+        return generate_from_tests(
+            runner_name='finality',
+            handler_name=handler_name,
+            src=tests_src,
+            fork_name=PHASE0,
+        )
+
+    return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
+
+
+if __name__ == "__main__":
+    gen_runner.run_generator("finality", [
+        create_provider('finality', test_finality, 'minimal'),
+        create_provider('finality', test_finality, 'mainnet'),
+    ])
diff --git a/tests/generators/finality/requirements.txt b/tests/generators/finality/requirements.txt
new file mode 100644
--- /dev/null
+++ b/tests/generators/finality/requirements.txt
@@ -0,0 +1,2 @@
+../../core/gen_helpers
+../../../
\ No newline at end of file
diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py
--- a/tests/generators/genesis/main.py
+++ b/tests/generators/genesis/main.py
@@ -1,13 +1,14 @@
 from typing import Iterable
 
 from eth2spec.test.context import PHASE0
-from eth2spec.test.genesis import test_initialization, test_validity
+from eth2spec.test.phase0.genesis import test_initialization, test_validity
 
 from gen_base import gen_runner, gen_typing
 from gen_from_tests.gen import generate_from_tests
 from eth2spec.phase0 import spec as spec
 from importlib import reload
 from eth2spec.config import config_util
+from eth2spec.utils import bls
 
 
 def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
@@ -15,6 +16,7 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
     def prepare_fn(configs_path: str) -> str:
         config_util.prepare_config(configs_path, config_name)
         reload(spec)
+        bls.use_milagro()
         return config_name
 
     def cases_fn() -> Iterable[gen_typing.TestCase]:
diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py
--- a/tests/generators/operations/main.py
+++ b/tests/generators/operations/main.py
@@ -1,6 +1,6 @@
 from typing import Iterable
 
-from eth2spec.test.phase_0.block_processing import (
+from eth2spec.test.phase0.block_processing import (
     test_process_attestation,
     test_process_attester_slashing,
     test_process_block_header,
@@ -16,6 +16,7 @@
 from eth2spec.phase0 import spec as spec_phase0
 from eth2spec.phase1 import spec as spec_phase1
 from eth2spec.test.context import PHASE0
+from eth2spec.utils import bls
 
 
 def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
@@ -24,6 +25,7 @@ def prepare_fn(configs_path: str) -> str:
         config_util.prepare_config(configs_path, config_name)
         reload(spec_phase0)
         reload(spec_phase1)
+        bls.use_milagro()
         return config_name
 
     def cases_fn() -> Iterable[gen_typing.TestCase]:
diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py
--- a/tests/generators/rewards/main.py
+++ b/tests/generators/rewards/main.py
@@ -2,7 +2,7 @@
 
 from eth2spec.phase0 import spec as spec_phase0
 from eth2spec.phase1 import spec as spec_phase1
-from eth2spec.test.phase_0.rewards import (
+from eth2spec.test.phase0.rewards import (
     test_basic,
     test_leak,
     test_random,
@@ -12,6 +12,7 @@
 from importlib import reload
 from eth2spec.config import config_util
 from eth2spec.test.context import PHASE0
+from eth2spec.utils import bls
 
 
 def create_provider(tests_src, config_name: str) -> gen_typing.TestProvider:
@@ -20,6 +21,7 @@ def prepare_fn(configs_path: str) -> str:
         config_util.prepare_config(configs_path, config_name)
         reload(spec_phase0)
         reload(spec_phase1)
+        bls.use_milagro()
         return config_name
 
     def cases_fn() -> Iterable[gen_typing.TestCase]:
diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py
--- a/tests/generators/sanity/main.py
+++ b/tests/generators/sanity/main.py
@@ -5,10 +5,11 @@
 from gen_from_tests.gen import generate_from_tests
 
 from eth2spec.test.context import PHASE0
-from eth2spec.test.phase_0.sanity import test_blocks, test_slots
+from eth2spec.test.phase0.sanity import test_blocks, test_slots
 from eth2spec.config import config_util
 from eth2spec.phase0 import spec as spec_phase0
 from eth2spec.phase1 import spec as spec_phase1
+from eth2spec.utils import bls
 
 
 def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
@@ -17,6 +18,7 @@ def prepare_fn(configs_path: str) -> str:
         config_util.prepare_config(configs_path, config_name)
         reload(spec_phase0)
         reload(spec_phase1)
+        bls.use_milagro()
         return config_name
 
     def cases_fn() -> Iterable[gen_typing.TestCase]: