Repository: ethereum/consensus-specs

Issue:
BLS and testing
Decided I wanted to get this out to explain the current state of testing, and **collect feedback** (implementers please comment) on what you need from testing, and your feelings about BLS usage in tests.

# BLS and testing

The two pain-points to get a pretty (and large) set of test-vectors out for clients are:
- BLS Signature creation
- BLS Signature verification

And side-issue, but easily resolved:
*efficient creation of a genesis state*:
When BLS functionality is implemented in test-code (creation of signed deposits, and verification).
Solution would be to either cache it, or create it directly, without going through the spec functions (current temporary solution on experiment branch).

## Status

Talking about the status on  [`spectest-deco` PR 1052](https://github.com/ethereum/eth2.0-specs/pull/1052) here, based on the  `v06x` branch, where we are developing 0.6 improvements. (to be merged back into dev later)

### The testing pipeline currently looks like:

- py-spec, calls BLS stub
- test-helpers, don't create self-signed objects with valid signatures
- py-test code, unified with test-vector-creation (see [PR 1052](https://github.com/ethereum/eth2.0-specs/pull/1052))
- py-test runner to run spec-tests, purely for assertions
- test-generator running the spec-tests, passing `generator_mode=true` to each of them, making them output a test-vector.

### Pytests status:

- move from `tests/` to `eth2spec/test`, i.e. part of package
    - removed use of `pytest`
    - annotated with `@spec_test` or similar (see PR 1052)
- as part of test-generation effort, yay for shared effort:
    - expanded in block-operation testing: [coverage checklist here](https://github.com/ethereum/eth2.0-specs/issues/927)
    - slightly faster, less deep-copies
- stuck on BLS stub (no sig creation/verification)

### Test-generation status:

- BLS, SSZ-generic, SSZ-static, shuffling test generators still all in place and up to date (`v06x` branch)
- `operations` test-gen uses test-package ability to output test-vectors for each test-case
    - but no valid signatures
    - lack of a definition how to handle this signature problem as a test-consumer
        - there are no signature-related testcases
        - turning BLS off would effectively let you check conformance, but it's hacky, and not remotely a good practice to have even an option for...
    - it's approx. ~140MB worth (iirc) of yaml encoded state-transitions, covering many edge-cases. Worth to get in the hands of implementers quick.
- `sanity` tests updated and can be cleanly used for test-generation, but requires more work to define the format of the test-vectors, as they is more variety.
- `epoch` processing tests also updated, also can be used, not as complete as block-processing, lower priority.

## Possible ways forward:

- Simple but hacky: "turn BLS off for testing"
- No "BLS off", BLS ON on client side, but only partially on spec side. Rely on signature verification not being hit before anything else during testing
    - valid test cases generated with valid signatures
    - invalid test cases marked: does it error because of BLS? And runners should check the reason for aborting processing: if it doesn't match, the test should fail. Now these pytests don't need full BLS update work, and can be released somewhat quicker
- "BLS on", more work (~1 week)
    - slower on test-generation, but we get the best kind of test-vectors: correct, BLS verification ON.
    - blocker: what if a test case fails because of a signature error (test setup not creating the sig correctly), instead of a real assertion case. Spec will look correct, passes tests, but things are not right. We need to mark Sig-verification errors distinctly, so we can catch these problems when we turn BLS on in the pyspec. How: instead of `assert verify_...`, just `verify_...`, and make it raise a special `BLSVerificationError` (or something like that)
    - We likely still want to mark tests as "signature related" or not, so implementers can catch it easily if their code is not aborting properly before signature verification, to assure invalid inputs are not costly.

A work-in-progress introduction of actual full BLS usage in the pytests is started here: [`tests-with-sigs` branch](https://github.com/ethereum/eth2.0-specs/tree/tests-with-sigs)

Suggestions welcome.







Discussion:

Resolving patch:
diff --git a/scripts/phase0/build_spec.py b/scripts/phase0/build_spec.py
--- a/scripts/phase0/build_spec.py
+++ b/scripts/phase0/build_spec.py
@@ -13,17 +13,9 @@ def build_phase0_spec(sourcefile, outfile):
     NewType,
     Tuple,
 )
-from eth2spec.utils.minimal_ssz import (
-    SSZType,
-    hash_tree_root,
-    signing_root,
-)
-from eth2spec.utils.bls_stub import (
-    bls_aggregate_pubkeys,
-    bls_verify,
-    bls_verify_multiple,
-)
+from eth2spec.utils.minimal_ssz import *
 from eth2spec.utils.hash_function import hash
+from eth2spec.utils.bls import *
 
 
 # stub, will get overwritten by real var


Test patch:
diff --git a/specs/test_formats/README.md b/specs/test_formats/README.md
--- a/specs/test_formats/README.md
+++ b/specs/test_formats/README.md
@@ -186,6 +186,18 @@ To prevent parsing of hundreds of different YAML files to test a specific test t
 ...                           <--- more test types
 ```
 
+## Common test-case properties
+
+Some test-case formats share some common key-value pair patterns, and these are documented here:
+
+```
+bls_setting: int     -- optional, can have 3 different values:
+                            0: (default, applies if key-value pair is absent). Free to choose either BLS ON or OFF.
+                                 Tests are generated with valid BLS data in this case,
+                                 but there is no change of outcome when running the test if BLS is ON or OFF.
+                            1: known as "BLS required" - if the test validity is strictly dependent on BLS being ON
+                            2: known as "BLS ignored"  - if the test validity is strictly dependent on BLS being OFF
+```
 
 ## Note for implementers
 
diff --git a/specs/test_formats/epoch_processing/README.md b/specs/test_formats/epoch_processing/README.md
new file mode 100644
--- /dev/null
+++ b/specs/test_formats/epoch_processing/README.md
@@ -0,0 +1,29 @@
+# Epoch processing tests
+
+The different epoch sub-transitions are tested individually with test handlers.
+The format is similar to block-processing state-transition tests.
+There is no "change" factor however, the transitions are pure functions with just the pre-state as input.
+Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.)
+
+## Test case format
+
+```yaml
+description: string    -- description of test case, purely for debugging purposes
+bls_setting: int       -- see general test-format spec.
+pre: BeaconState       -- state before running the sub-transition
+post: BeaconState      -- state after applying the epoch sub-transition.
+```
+
+## Condition
+
+A handler of the `epoch_processing` test-runner should process these cases, 
+ calling the corresponding processing implementation.
+
+Sub-transitions:
+
+| *`sub-transition-name`* | *`processing call`*               |
+|-------------------------|-----------------------------------|
+| `crosslinks`            | `process_crosslinks(state)`       |
+| `registry_updates`      | `process_registry_updates(state)` |
+
+The resulting state should match the expected `post` state.
diff --git a/specs/test_formats/operations/README.md b/specs/test_formats/operations/README.md
--- a/specs/test_formats/operations/README.md
+++ b/specs/test_formats/operations/README.md
@@ -2,9 +2,34 @@
 
 The different kinds of operations ("transactions") are tested individually with test handlers.
 
-The tested operation kinds are:
-- [`deposits`](./deposits.md)
-- More tests are work-in-progress.
+## Test case format
 
+```yaml
+description: string                    -- description of test case, purely for debugging purposes
+bls_setting: int                       -- see general test-format spec.
+pre: BeaconState                       -- state before applying the operation
+<operation-name>: <operation-object>   -- the YAML encoded operation, e.g. a "ProposerSlashing", or "Deposit".
+post: BeaconState                      -- state after applying the operation. No value if operation processing is aborted.
+```
 
+## Condition
 
+A handler of the `operations` test-runner should process these cases, 
+ calling the corresponding processing implementation.
+
+Operations:
+
+| *`operation-name`*      | *`operation-object`* | *`input name`*       | *`processing call`*                                    |
+|-------------------------|----------------------|----------------------|--------------------------------------------------------|
+| `attestation`           | `Attestation`        | `attestation`        | `process_attestation(state, attestation)`                  |
+| `attester_slashing`     | `AttesterSlashing`   | `attester_slashing`  | `process_attester_slashing(state, attester_slashing)`            |
+| `block_header`          | `Block`              | `block`              | `process_block_header(state, block)`                   |
+| `deposit`               | `Deposit`            | `deposit`            | `process_deposit(state, deposit)`                      |
+| `proposer_slashing`     | `ProposerSlashing`   | `proposer_slashing`  | `process_proposer_slashing(state, proposer_slashing)`  |
+| `transfer`              | `Transfer`           | `transfer`           | `process_transfer(state, transfer)`                    |
+| `voluntary_exit`        | `VoluntaryExit`      | `voluntary_exit`     | `process_voluntary_exit(state, voluntary_exit)`        |
+
+Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here. 
+
+The resulting state should match the expected `post` state, or if the `post` state is left blank,
+ the handler should reject the input operation as invalid.
diff --git a/specs/test_formats/operations/deposits.md b/specs/test_formats/operations/deposits.md
deleted file mode 100644
--- a/specs/test_formats/operations/deposits.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Test format: Deposit operations
-
-A deposit is a form of an operation (or "transaction"), modifying the state.
-
-## Test case format
-
-```yaml
-description: string  -- description of test case, purely for debugging purposes
-pre: BeaconState     -- state before applying the deposit
-deposit: Deposit     -- the deposit
-post: BeaconState    -- state after applying the deposit. No value if deposit processing is aborted.
-```
-
-## Condition
-
-A `deposits` handler of the `operations` should process these cases, 
- calling the implementation of the `process_deposit(state, deposit)` functionality described in the spec.
-The resulting state should match the expected `post` state, or if the `post` state is left blank, the handler should reject the inputs as invalid.
diff --git a/specs/test_formats/sanity/README.md b/specs/test_formats/sanity/README.md
new file mode 100644
--- /dev/null
+++ b/specs/test_formats/sanity/README.md
@@ -0,0 +1,7 @@
+# Sanity tests
+
+The aim of the sanity tests is to set a base-line on what really needs to pass, i.e. the essentials.
+
+There are two handlers, documented individually:
+- [`slots`](./slots.md): transitions of one or more slots (and epoch transitions within)
+- [`blocks`](./blocks.md): transitions triggered by one or more blocks
diff --git a/specs/test_formats/sanity/blocks.md b/specs/test_formats/sanity/blocks.md
new file mode 100644
--- /dev/null
+++ b/specs/test_formats/sanity/blocks.md
@@ -0,0 +1,18 @@
+# Sanity blocks testing
+
+Sanity tests to cover a series of one or more blocks being processed, aiming to cover common changes.
+
+## Test case format
+
+```yaml
+description: string    -- description of test case, purely for debugging purposes
+bls_setting: int       -- see general test-format spec.
+pre: BeaconState       -- state before running through the transitions triggered by the blocks.
+blocks: [BeaconBlock]  -- blocks to process, in given order, following the main transition function (i.e. process slot and epoch transitions in between blocks as normal)
+post: BeaconState      -- state after applying all the transitions triggered by the blocks.
+```
+
+## 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/specs/test_formats/sanity/slots.md b/specs/test_formats/sanity/slots.md
new file mode 100644
--- /dev/null
+++ b/specs/test_formats/sanity/slots.md
@@ -0,0 +1,23 @@
+# Sanity slots testing
+
+Sanity tests to cover a series of one or more empty-slot transitions being processed, aiming to cover common changes.
+
+## Test case format
+
+```yaml
+description: string    -- description of test case, purely for debugging purposes
+bls_setting: int       -- see general test-format spec.
+pre: BeaconState       -- state before running through the transitions.
+slots: N               -- amount of slots to process, N being a positive numer.
+post: BeaconState      -- state after applying all the transitions.
+```
+
+The transition with pure time, no blocks, is known as `state_transition_to(state, slot)` in the spec.
+This runs state-caching (pure slot transition) and epoch processing (every E slots).
+
+To process the data, call `state_transition_to(pre, pre.slot + N)`. And see if `pre` mutated into the equivalent of `post`.
+
+
+## Condition
+
+The resulting state should match the expected `post` state.
diff --git a/specs/test_formats/ssz_static/core.md b/specs/test_formats/ssz_static/core.md
--- a/specs/test_formats/ssz_static/core.md
+++ b/specs/test_formats/ssz_static/core.md
@@ -9,11 +9,11 @@ This test-format ensures these direct serializations are covered.
 ## Test case format
 
 ```yaml
-type_name: string  -- string, object name, formatted as in spec. E.g. "BeaconBlock"
-value: dynamic     -- the YAML-encoded value, of the type specified by type_name.
-serialized: bytes  -- string, SSZ-serialized data, hex encoded, with prefix 0x
-root: bytes32      -- string, hash-tree-root of the value, hex encoded, with prefix 0x
-signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. Optional, present if type contains ``signature`` field
+SomeObjectName:        -- key, object name, formatted as in spec. E.g. "BeaconBlock".
+    value: dynamic     -- the YAML-encoded value, of the type specified by type_name.
+    serialized: bytes  -- string, SSZ-serialized data, hex encoded, with prefix 0x
+    root: bytes32      -- string, hash-tree-root of the value, hex encoded, with prefix 0x
+    signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. Optional, present if type contains ``signature`` field
 ```
 
 ## Condition
diff --git a/test_generators/README.md b/test_generators/README.md
--- a/test_generators/README.md
+++ b/test_generators/README.md
@@ -58,7 +58,7 @@ It's recommended to extend the base-generator.
 
 Create a `requirements.txt` in the root of your generator directory:
 ```
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
 ../../test_libs/config_helpers
 ../../test_libs/pyspec
diff --git a/test_generators/bls/requirements.txt b/test_generators/bls/requirements.txt
--- a/test_generators/bls/requirements.txt
+++ b/test_generators/bls/requirements.txt
@@ -1,3 +1,3 @@
 py-ecc==1.7.0
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
diff --git a/test_generators/epoch_processing/README.md b/test_generators/epoch_processing/README.md
new file mode 100644
--- /dev/null
+++ b/test_generators/epoch_processing/README.md
@@ -0,0 +1,11 @@
+# Epoch processing
+
+Epoch processing covers the sub-transitions during an epoch change.
+
+An epoch-processing test-runner can consume these sub-transition test-suites,
+ and handle different kinds of epoch sub-transitions by processing the cases using the specified test handler.
+
+Information on the format of the tests can be found in the [epoch-processing test formats documentation](../../specs/test_formats/epoch_processing/README.md).
+
+ 
+
diff --git a/test_generators/epoch_processing/main.py b/test_generators/epoch_processing/main.py
new file mode 100644
--- /dev/null
+++ b/test_generators/epoch_processing/main.py
@@ -0,0 +1,38 @@
+from typing import Callable, Iterable
+
+from eth2spec.phase0 import spec
+from eth2spec.test.epoch_processing import (
+    test_process_crosslinks,
+    test_process_registry_updates
+)
+from gen_base import gen_runner, gen_suite, gen_typing
+from gen_from_tests.gen import generate_from_tests
+from preset_loader import loader
+
+
+def create_suite(transition_name: str, config_name: str, get_cases: Callable[[], Iterable[gen_typing.TestCase]]) \
+        -> Callable[[str], gen_typing.TestSuiteOutput]:
+    def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
+        presets = loader.load_presets(configs_path, config_name)
+        spec.apply_constants_preset(presets)
+
+        return ("%s_%s" % (transition_name, config_name), transition_name, gen_suite.render_suite(
+            title="%s epoch processing" % transition_name,
+            summary="Test suite for %s type epoch processing" % transition_name,
+            forks_timeline="testing",
+            forks=["phase0"],
+            config=config_name,
+            runner="epoch_processing",
+            handler=transition_name,
+            test_cases=get_cases()))
+
+    return suite_definition
+
+
+if __name__ == "__main__":
+    gen_runner.run_generator("epoch_processing", [
+        create_suite('crosslinks', 'minimal', lambda: generate_from_tests(test_process_crosslinks)),
+        create_suite('crosslinks', 'mainnet', lambda: generate_from_tests(test_process_crosslinks)),
+        create_suite('registry_updates', 'minimal', lambda: generate_from_tests(test_process_registry_updates)),
+        create_suite('registry_updates', 'mainnet', lambda: generate_from_tests(test_process_registry_updates)),
+    ])
diff --git a/test_generators/epoch_processing/requirements.txt b/test_generators/epoch_processing/requirements.txt
new file mode 100644
--- /dev/null
+++ b/test_generators/epoch_processing/requirements.txt
@@ -0,0 +1,4 @@
+eth-utils==1.6.0
+../../test_libs/gen_helpers
+../../test_libs/config_helpers
+../../test_libs/pyspec
\ No newline at end of file
diff --git a/test_generators/operations/README.md b/test_generators/operations/README.md
--- a/test_generators/operations/README.md
+++ b/test_generators/operations/README.md
@@ -3,7 +3,6 @@
 Operations (or "transactions" in previous spec iterations),
  are atomic changes to the state, introduced by embedding in blocks.
 
-This generator provides a series of test suites, divided into handler, for each operation type.
 An operation test-runner can consume these operation test-suites,
  and handle different kinds of operations by processing the cases using the specified test handler.
 
diff --git a/test_generators/operations/deposits.py b/test_generators/operations/deposits.py
deleted file mode 100644
--- a/test_generators/operations/deposits.py
+++ /dev/null
@@ -1,180 +0,0 @@
-from eth2spec.phase0 import spec
-from eth_utils import (
-    to_dict, to_tuple
-)
-from gen_base import gen_suite, gen_typing
-from preset_loader import loader
-from eth2spec.debug.encode import encode
-from eth2spec.utils.minimal_ssz import signing_root
-from eth2spec.utils.merkle_minimal import get_merkle_root, calc_merkle_tree_from_leaves, get_merkle_proof
-
-from typing import List, Tuple
-
-import genesis
-import keys
-from py_ecc import bls
-
-
-def build_deposit_data(state,
-                       pubkey: spec.BLSPubkey,
-                       withdrawal_cred: spec.Bytes32,
-                       privkey: int,
-                       amount: int):
-    deposit_data = spec.DepositData(
-        pubkey=pubkey,
-        withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + withdrawal_cred[1:],
-        amount=amount,
-    )
-    deposit_data.proof_of_possession = bls.sign(
-        message_hash=signing_root(deposit_data),
-        privkey=privkey,
-        domain=spec.get_domain(
-            state,
-            spec.get_current_epoch(state),
-            spec.DOMAIN_DEPOSIT,
-        )
-    )
-    return deposit_data
-
-
-def build_deposit(state,
-                  deposit_data_leaves: List[spec.Bytes32],
-                  pubkey: spec.BLSPubkey,
-                  withdrawal_cred: spec.Bytes32,
-                  privkey: int,
-                  amount: int) -> spec.Deposit:
-
-    deposit_data = build_deposit_data(state, pubkey, withdrawal_cred, privkey, amount)
-
-    item = deposit_data.hash_tree_root()
-    index = len(deposit_data_leaves)
-    deposit_data_leaves.append(item)
-    tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
-    proof = list(get_merkle_proof(tree, item_index=index))
-
-    deposit = spec.Deposit(
-        proof=list(proof),
-        index=index,
-        data=deposit_data,
-    )
-    assert spec.verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, get_merkle_root(tuple(deposit_data_leaves)))
-
-    return deposit
-
-
-def build_deposit_for_index(initial_validator_count: int, index: int) -> Tuple[spec.Deposit, spec.BeaconState]:
-    genesis_deposits = genesis.create_deposits(
-        keys.pubkeys[:initial_validator_count],
-        keys.withdrawal_creds[:initial_validator_count]
-    )
-    state = genesis.create_genesis_state(genesis_deposits)
-
-    deposit_data_leaves = [dep.data.hash_tree_root() for dep in genesis_deposits]
-
-    deposit = build_deposit(
-        state,
-        deposit_data_leaves,
-        keys.pubkeys[index],
-        keys.withdrawal_creds[index],
-        keys.privkeys[index],
-        spec.MAX_EFFECTIVE_BALANCE,
-    )
-
-    state.latest_eth1_data.deposit_root = get_merkle_root(tuple(deposit_data_leaves))
-    state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
-
-    return deposit, state
-
-
-@to_dict
-def valid_deposit():
-    new_dep, state = build_deposit_for_index(10, 10)
-    yield 'description', 'valid deposit to add new validator'
-    yield 'pre', encode(state, spec.BeaconState)
-    yield 'deposit', encode(new_dep, spec.Deposit)
-    spec.process_deposit(state, new_dep)
-    yield 'post', encode(state, spec.BeaconState)
-
-
-@to_dict
-def valid_topup():
-    new_dep, state = build_deposit_for_index(10, 3)
-    yield 'description', 'valid deposit to top-up existing validator'
-    yield 'pre', encode(state, spec.BeaconState)
-    yield 'deposit', encode(new_dep, spec.Deposit)
-    spec.process_deposit(state, new_dep)
-    yield 'post', encode(state, spec.BeaconState)
-
-
-@to_dict
-def invalid_deposit_index():
-    new_dep, state = build_deposit_for_index(10, 10)
-    # Mess up deposit index, 1 too small
-    state.deposit_index = 9
-
-    yield 'description', 'invalid deposit index'
-    yield 'pre', encode(state, spec.BeaconState)
-    yield 'deposit', encode(new_dep, spec.Deposit)
-    try:
-        spec.process_deposit(state, new_dep)
-    except AssertionError:
-        # expected
-        yield 'post', None
-        return
-    raise Exception('invalid_deposit_index has unexpectedly allowed deposit')
-
-
-@to_dict
-def invalid_deposit_proof():
-    new_dep, state = build_deposit_for_index(10, 10)
-    # Make deposit proof invalid (at bottom of proof)
-    new_dep.proof[-1] = spec.ZERO_HASH
-
-    yield 'description', 'invalid deposit proof'
-    yield 'pre', encode(state, spec.BeaconState)
-    yield 'deposit', encode(new_dep, spec.Deposit)
-    try:
-        spec.process_deposit(state, new_dep)
-    except AssertionError:
-        # expected
-        yield 'post', None
-        return
-    raise Exception('invalid_deposit_index has unexpectedly allowed deposit')
-
-
-@to_tuple
-def deposit_cases():
-    yield valid_deposit()
-    yield valid_topup()
-    yield invalid_deposit_index()
-    yield invalid_deposit_proof()
-
-
-def mini_deposits_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
-    presets = loader.load_presets(configs_path, 'minimal')
-    spec.apply_constants_preset(presets)
-
-    return ("deposit_minimal", "deposits", gen_suite.render_suite(
-        title="deposit operation",
-        summary="Test suite for deposit type operation processing",
-        forks_timeline="testing",
-        forks=["phase0"],
-        config="minimal",
-        runner="operations",
-        handler="deposits",
-        test_cases=deposit_cases()))
-
-
-def full_deposits_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
-    presets = loader.load_presets(configs_path, 'mainnet')
-    spec.apply_constants_preset(presets)
-
-    return ("deposit_full", "deposits", gen_suite.render_suite(
-        title="deposit operation",
-        summary="Test suite for deposit type operation processing",
-        forks_timeline="mainnet",
-        forks=["phase0"],
-        config="mainnet",
-        runner="operations",
-        handler="deposits",
-        test_cases=deposit_cases()))
diff --git a/test_generators/operations/genesis.py b/test_generators/operations/genesis.py
deleted file mode 100644
--- a/test_generators/operations/genesis.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from eth2spec.phase0 import spec
-from eth2spec.utils.merkle_minimal import get_merkle_root, calc_merkle_tree_from_leaves, get_merkle_proof
-from typing import List
-
-
-def create_genesis_state(deposits: List[spec.Deposit]) -> spec.BeaconState:
-    deposit_root = get_merkle_root((tuple([(dep.data.hash_tree_root()) for dep in deposits])))
-
-    return spec.get_genesis_beacon_state(
-        deposits,
-        genesis_time=0,
-        genesis_eth1_data=spec.Eth1Data(
-            deposit_root=deposit_root,
-            deposit_count=len(deposits),
-            block_hash=spec.ZERO_HASH,
-        ),
-    )
-
-
-def create_deposits(pubkeys: List[spec.BLSPubkey], withdrawal_cred: List[spec.Bytes32]) -> List[spec.Deposit]:
-
-    # Mock proof of possession
-    proof_of_possession = b'\x33' * 96
-
-    deposit_data = [
-        spec.DepositData(
-            pubkey=pubkeys[i],
-            withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + withdrawal_cred[i][1:],
-            amount=spec.MAX_EFFECTIVE_BALANCE,
-            proof_of_possession=proof_of_possession,
-        ) for i in range(len(pubkeys))
-    ]
-
-    # Fill tree with existing deposits
-    deposit_data_leaves = [data.hash_tree_root() for data in deposit_data]
-    tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
-
-    return [
-        spec.Deposit(
-            proof=list(get_merkle_proof(tree, item_index=i)),
-            index=i,
-            data=deposit_data[i]
-        ) for i in range(len(deposit_data))
-    ]
diff --git a/test_generators/operations/keys.py b/test_generators/operations/keys.py
deleted file mode 100644
--- a/test_generators/operations/keys.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from py_ecc import bls
-from eth2spec.phase0.spec import hash
-
-privkeys = list(range(1, 101))
-pubkeys = [bls.privtopub(k) for k in privkeys]
-# Insecure, but easier to follow
-withdrawal_creds = [hash(bls.privtopub(k)) for k in privkeys]
diff --git a/test_generators/operations/main.py b/test_generators/operations/main.py
--- a/test_generators/operations/main.py
+++ b/test_generators/operations/main.py
@@ -1,9 +1,53 @@
-from gen_base import gen_runner
+from typing import Callable, Iterable
+
+from eth2spec.test.block_processing import (
+    test_process_attestation,
+    test_process_attester_slashing,
+    test_process_block_header,
+    test_process_deposit,
+    test_process_proposer_slashing,
+    test_process_transfer,
+    test_process_voluntary_exit
+)
+
+from gen_base import gen_runner, gen_suite, gen_typing
+from gen_from_tests.gen import generate_from_tests
+from preset_loader import loader
+from eth2spec.phase0 import spec
+
+
+def create_suite(operation_name: str, config_name: str, get_cases: Callable[[], Iterable[gen_typing.TestCase]]) \
+        -> Callable[[str], gen_typing.TestSuiteOutput]:
+    def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
+        presets = loader.load_presets(configs_path, config_name)
+        spec.apply_constants_preset(presets)
+
+        return ("%s_%s" % (operation_name, config_name), operation_name, gen_suite.render_suite(
+            title="%s operation" % operation_name,
+            summary="Test suite for %s type operation processing" % operation_name,
+            forks_timeline="testing",
+            forks=["phase0"],
+            config=config_name,
+            runner="operations",
+            handler=operation_name,
+            test_cases=get_cases()))
+    return suite_definition
 
-from deposits import mini_deposits_suite, full_deposits_suite
 
 if __name__ == "__main__":
     gen_runner.run_generator("operations", [
-        mini_deposits_suite,
-        full_deposits_suite
+        create_suite('attestation',       'minimal', lambda: generate_from_tests(test_process_attestation)),
+        create_suite('attestation',       'mainnet', lambda: generate_from_tests(test_process_attestation)),
+        create_suite('attester_slashing', 'minimal', lambda: generate_from_tests(test_process_attester_slashing)),
+        create_suite('attester_slashing', 'mainnet', lambda: generate_from_tests(test_process_attester_slashing)),
+        create_suite('block_header',      'minimal', lambda: generate_from_tests(test_process_block_header)),
+        create_suite('block_header',      'mainnet', lambda: generate_from_tests(test_process_block_header)),
+        create_suite('deposit',          'minimal', lambda: generate_from_tests(test_process_deposit)),
+        create_suite('deposit',          'mainnet', lambda: generate_from_tests(test_process_deposit)),
+        create_suite('proposer_slashing', 'minimal', lambda: generate_from_tests(test_process_proposer_slashing)),
+        create_suite('proposer_slashing', 'mainnet', lambda: generate_from_tests(test_process_proposer_slashing)),
+        create_suite('transfer',          'minimal', lambda: generate_from_tests(test_process_transfer)),
+        create_suite('transfer',          'mainnet', lambda: generate_from_tests(test_process_transfer)),
+        create_suite('voluntary_exit',    'minimal', lambda: generate_from_tests(test_process_voluntary_exit)),
+        create_suite('voluntary_exit',    'mainnet', lambda: generate_from_tests(test_process_voluntary_exit)),
     ])
diff --git a/test_generators/operations/requirements.txt b/test_generators/operations/requirements.txt
--- a/test_generators/operations/requirements.txt
+++ b/test_generators/operations/requirements.txt
@@ -1,5 +1,4 @@
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
 ../../test_libs/config_helpers
-../../test_libs/pyspec
-py_ecc
\ No newline at end of file
+../../test_libs/pyspec
\ No newline at end of file
diff --git a/test_generators/sanity/README.md b/test_generators/sanity/README.md
new file mode 100644
--- /dev/null
+++ b/test_generators/sanity/README.md
@@ -0,0 +1,8 @@
+# Sanity tests
+
+Sanity tests cover regular state-transitions in a common block-list format, to ensure the basics work.
+
+Information on the format of the tests can be found in the [sanity test formats documentation](../../specs/test_formats/sanity/README.md).
+
+ 
+
diff --git a/test_generators/sanity/main.py b/test_generators/sanity/main.py
new file mode 100644
--- /dev/null
+++ b/test_generators/sanity/main.py
@@ -0,0 +1,35 @@
+from typing import Callable, Iterable
+
+from eth2spec.test.sanity import test_blocks, test_slots
+
+from gen_base import gen_runner, gen_suite, gen_typing
+from gen_from_tests.gen import generate_from_tests
+from preset_loader import loader
+from eth2spec.phase0 import spec
+
+
+def create_suite(handler_name: str, config_name: str, get_cases: Callable[[], Iterable[gen_typing.TestCase]]) \
+        -> Callable[[str], gen_typing.TestSuiteOutput]:
+    def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
+        presets = loader.load_presets(configs_path, config_name)
+        spec.apply_constants_preset(presets)
+
+        return ("%sanity_s_%s" % (handler_name, config_name), handler_name, gen_suite.render_suite(
+            title="sanity testing",
+            summary="Sanity test suite, %s type, generated from pytests" % handler_name,
+            forks_timeline="testing",
+            forks=["phase0"],
+            config=config_name,
+            runner="sanity",
+            handler=handler_name,
+            test_cases=get_cases()))
+    return suite_definition
+
+
+if __name__ == "__main__":
+    gen_runner.run_generator("sanity", [
+        create_suite('blocks', 'minimal', lambda: generate_from_tests(test_blocks)),
+        create_suite('blocks', 'mainnet', lambda: generate_from_tests(test_blocks)),
+        create_suite('slots', 'minimal', lambda: generate_from_tests(test_slots)),
+        create_suite('slots', 'mainnet', lambda: generate_from_tests(test_slots)),
+    ])
diff --git a/test_generators/sanity/requirements.txt b/test_generators/sanity/requirements.txt
new file mode 100644
--- /dev/null
+++ b/test_generators/sanity/requirements.txt
@@ -0,0 +1,4 @@
+eth-utils==1.6.0
+../../test_libs/gen_helpers
+../../test_libs/config_helpers
+../../test_libs/pyspec
\ No newline at end of file
diff --git a/test_generators/shuffling/main.py b/test_generators/shuffling/main.py
--- a/test_generators/shuffling/main.py
+++ b/test_generators/shuffling/main.py
@@ -10,7 +10,7 @@
 def shuffling_case(seed: spec.Bytes32, count: int):
     yield 'seed', '0x' + seed.hex()
     yield 'count', count
-    yield 'shuffled', [spec.get_permuted_index(i, count, seed) for i in range(count)]
+    yield 'shuffled', [spec.get_shuffled_index(i, count, seed) for i in range(count)]
 
 
 @to_tuple
diff --git a/test_generators/shuffling/requirements.txt b/test_generators/shuffling/requirements.txt
--- a/test_generators/shuffling/requirements.txt
+++ b/test_generators/shuffling/requirements.txt
@@ -1,4 +1,4 @@
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
 ../../test_libs/config_helpers
 ../../test_libs/pyspec
\ No newline at end of file
diff --git a/test_generators/ssz_generic/requirements.txt b/test_generators/ssz_generic/requirements.txt
--- a/test_generators/ssz_generic/requirements.txt
+++ b/test_generators/ssz_generic/requirements.txt
@@ -1,4 +1,4 @@
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
 ../../test_libs/config_helpers
 ssz==0.1.0a2
diff --git a/test_generators/ssz_static/main.py b/test_generators/ssz_static/main.py
--- a/test_generators/ssz_static/main.py
+++ b/test_generators/ssz_static/main.py
@@ -18,10 +18,7 @@
 
 
 @to_dict
-def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool):
-    typ = spec.get_ssz_type_by_name(name)
-    value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
-    yield "type_name", name
+def create_test_case_contents(value, typ):
     yield "value", encode.encode(value, typ)
     yield "serialized", '0x' + serialize(value).hex()
     yield "root", '0x' + hash_tree_root(value).hex()
@@ -29,6 +26,13 @@ def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMod
         yield "signing_root", '0x' + signing_root(value).hex()
 
 
+@to_dict
+def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool):
+    typ = spec.get_ssz_type_by_name(name)
+    value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
+    yield name, create_test_case_contents(value, typ)
+
+
 @to_tuple
 def ssz_static_cases(rng: Random, mode: random_value.RandomizationMode, chaos: bool, count: int):
     for type_name in spec.ssz_types:
diff --git a/test_generators/ssz_static/requirements.txt b/test_generators/ssz_static/requirements.txt
--- a/test_generators/ssz_static/requirements.txt
+++ b/test_generators/ssz_static/requirements.txt
@@ -1,4 +1,4 @@
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
 ../../test_libs/config_helpers
 ../../test_libs/pyspec
\ No newline at end of file
diff --git a/test_libs/config_helpers/requirements.txt b/test_libs/config_helpers/requirements.txt
--- a/test_libs/config_helpers/requirements.txt
+++ b/test_libs/config_helpers/requirements.txt
@@ -1 +1 @@
-ruamel.yaml==0.15.87
+ruamel.yaml==0.15.96
diff --git a/test_libs/config_helpers/setup.py b/test_libs/config_helpers/setup.py
--- a/test_libs/config_helpers/setup.py
+++ b/test_libs/config_helpers/setup.py
@@ -4,6 +4,6 @@
     name='config_helpers',
     packages=['preset_loader'],
     install_requires=[
-        "ruamel.yaml==0.15.87"
+        "ruamel.yaml==0.15.96"
     ]
 )
diff --git a/test_libs/pyspec/tests/__init__.py b/test_libs/gen_helpers/gen_from_tests/__init__.py
similarity index 100%
rename from test_libs/pyspec/tests/__init__.py
rename to test_libs/gen_helpers/gen_from_tests/__init__.py
diff --git a/test_libs/gen_helpers/gen_from_tests/gen.py b/test_libs/gen_helpers/gen_from_tests/gen.py
new file mode 100644
--- /dev/null
+++ b/test_libs/gen_helpers/gen_from_tests/gen.py
@@ -0,0 +1,25 @@
+from inspect import getmembers, isfunction
+
+def generate_from_tests(src, bls_active=True):
+    """
+    Generate a list of test cases by running tests from the given src in generator-mode.
+    :param src: to retrieve tests from (discovered using inspect.getmembers)
+    :param bls_active: optional, to override BLS switch preference. Defaults to True.
+    :return: the list of test cases.
+    """
+    fn_names = [
+        name for (name, _) in getmembers(src, isfunction)
+        if name.startswith('test_')
+    ]
+    out = []
+    print("generating test vectors from tests source: %s" % src.__name__)
+    for name in fn_names:
+        tfn = getattr(src, name)
+        try:
+            test_case = tfn(generator_mode=True, bls_active=bls_active)
+            # If no test case data is returned, the test is ignored.
+            if test_case is not None:
+                out.append(test_case)
+        except AssertionError:
+            print("ERROR: failed to generate vector from test: %s (src: %s)" % (name, src.__name__))
+    return out
diff --git a/test_libs/gen_helpers/requirements.txt b/test_libs/gen_helpers/requirements.txt
--- a/test_libs/gen_helpers/requirements.txt
+++ b/test_libs/gen_helpers/requirements.txt
@@ -1,2 +1,2 @@
-ruamel.yaml==0.15.87
-eth-utils==1.4.1
+ruamel.yaml==0.15.96
+eth-utils==1.6.0
diff --git a/test_libs/gen_helpers/setup.py b/test_libs/gen_helpers/setup.py
--- a/test_libs/gen_helpers/setup.py
+++ b/test_libs/gen_helpers/setup.py
@@ -2,9 +2,9 @@
 
 setup(
     name='gen_helpers',
-    packages=['gen_base'],
+    packages=['gen_base', 'gen_from_tests'],
     install_requires=[
-        "ruamel.yaml==0.15.87",
-        "eth-utils==1.4.1"
+        "ruamel.yaml==0.15.96",
+        "eth-utils==1.6.0"
     ]
 )
diff --git a/test_libs/pyspec/README.md b/test_libs/pyspec/README.md
--- a/test_libs/pyspec/README.md
+++ b/test_libs/pyspec/README.md
@@ -46,8 +46,9 @@ The `-B` flag may be helpful to force-overwrite the `pyspec` output after you ma
 
 Run the tests:
 ```
-pytest --config=minimal
+pytest --config=minimal eth2spec
 ```
+Note the package-name, this is to locate the tests.
 
 
 ## Contributing
diff --git a/test_libs/pyspec/eth2spec/test/__init__.py b/test_libs/pyspec/eth2spec/test/__init__.py
new file mode 100644
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/__init__.py b/test_libs/pyspec/eth2spec/test/block_processing/__init__.py
new file mode 100644
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py
@@ -0,0 +1,301 @@
+from copy import deepcopy
+
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    get_current_epoch,
+    process_attestation,
+    process_slots,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.attestations import (
+    get_valid_attestation,
+    sign_attestation,
+)
+from eth2spec.test.helpers.state import (
+    next_epoch,
+    next_slot,
+)
+from eth2spec.test.helpers.block import apply_empty_block
+
+
+def run_attestation_processing(state, attestation, valid=True):
+    """
+    Run ``process_attestation``, yielding:
+      - pre-state ('pre')
+      - attestation ('attestation')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    # yield pre-state
+    yield 'pre', state
+
+    yield 'attestation', attestation
+
+    # If the attestation is invalid, processing is aborted, and there is no post-state.
+    if not valid:
+        expect_assertion_error(lambda: process_attestation(state, attestation))
+        yield 'post', None
+        return
+
+    current_epoch_count = len(state.current_epoch_attestations)
+    previous_epoch_count = len(state.previous_epoch_attestations)
+
+    # process attestation
+    process_attestation(state, attestation)
+
+    # Make sure the attestation has been processed
+    if attestation.data.target_epoch == get_current_epoch(state):
+        assert len(state.current_epoch_attestations) == current_epoch_count + 1
+    else:
+        assert len(state.previous_epoch_attestations) == previous_epoch_count + 1
+
+    # yield post-state
+    yield 'post', state
+
+
+@spec_state_test
+def test_success(state):
+    attestation = get_valid_attestation(state, signed=True)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    yield from run_attestation_processing(state, attestation)
+
+
+@spec_state_test
+def test_success_previous_epoch(state):
+    attestation = get_valid_attestation(state, signed=True)
+    next_epoch(state)
+    apply_empty_block(state)
+
+    yield from run_attestation_processing(state, attestation)
+
+
+@spec_state_test
+def test_success_since_max_epochs_per_crosslink(state):
+    for _ in range(spec.MAX_EPOCHS_PER_CROSSLINK + 2):
+        next_epoch(state)
+    apply_empty_block(state)
+
+    attestation = get_valid_attestation(state, signed=True)
+    data = attestation.data
+    # test logic sanity check: make sure the attestation only includes MAX_EPOCHS_PER_CROSSLINK epochs
+    assert data.crosslink.end_epoch - data.crosslink.start_epoch == spec.MAX_EPOCHS_PER_CROSSLINK
+
+    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
+        next_slot(state)
+    apply_empty_block(state)
+
+    yield from run_attestation_processing(state, attestation)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_attestation_signature(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_before_inclusion_delay(state):
+    attestation = get_valid_attestation(state, signed=True)
+    # do not increment slot to allow for inclusion delay
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_after_epoch_slots(state):
+    attestation = get_valid_attestation(state, signed=True)
+    # increment past latest inclusion slot
+    process_slots(state, state.slot + spec.SLOTS_PER_EPOCH + 1)
+    apply_empty_block(state)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_old_source_epoch(state):
+    state.slot = spec.SLOTS_PER_EPOCH * 5
+    state.finalized_epoch = 2
+    state.previous_justified_epoch = 3
+    state.current_justified_epoch = 4
+    attestation = get_valid_attestation(state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
+
+    # test logic sanity check: make sure the attestation is pointing to oldest known source epoch
+    assert attestation.data.source_epoch == state.previous_justified_epoch
+
+    # Now go beyond that, it will be invalid
+    attestation.data.source_epoch -= 1
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_wrong_shard(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.data.crosslink.shard += 1
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_new_source_epoch(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.data.source_epoch += 1
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_source_root_is_target_root(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.data.source_root = attestation.data.target_root
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_invalid_current_source_root(state):
+    state.slot = spec.SLOTS_PER_EPOCH * 5
+    state.finalized_epoch = 2
+
+    state.previous_justified_epoch = 3
+    state.previous_justified_root = b'\x01' * 32
+
+    state.current_justified_epoch = 4
+    state.current_justified_root = b'\xff' * 32
+
+    attestation = get_valid_attestation(state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    # Test logic sanity checks:
+    assert state.current_justified_root != state.previous_justified_root
+    assert attestation.data.source_root == state.previous_justified_root
+
+    # Make attestation source root invalid: should be previous justified, not current one
+    attestation.data.source_root = state.current_justified_root
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_bad_source_root(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.data.source_root = b'\x42' * 32
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_non_zero_crosslink_data_root(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.data.crosslink.data_root = b'\x42' * 32
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_bad_parent_crosslink(state):
+    next_epoch(state)
+    apply_empty_block(state)
+
+    attestation = get_valid_attestation(state, signed=True)
+    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
+        next_slot(state)
+    apply_empty_block(state)
+
+    attestation.data.crosslink.parent_root = b'\x27' * 32
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_bad_crosslink_start_epoch(state):
+    next_epoch(state)
+    apply_empty_block(state)
+
+    attestation = get_valid_attestation(state, signed=True)
+    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
+        next_slot(state)
+    apply_empty_block(state)
+
+    attestation.data.crosslink.start_epoch += 1
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_bad_crosslink_end_epoch(state):
+    next_epoch(state)
+    apply_empty_block(state)
+
+    attestation = get_valid_attestation(state, signed=True)
+    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
+        next_slot(state)
+    apply_empty_block(state)
+
+    attestation.data.crosslink.end_epoch += 1
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_inconsistent_bitfields(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield) + b'\x00'
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_non_empty_custody_bitfield(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield)
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation, False)
+
+
+@spec_state_test
+def test_empty_aggregation_bitfield(state):
+    attestation = get_valid_attestation(state)
+    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+
+    attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield)
+
+    sign_attestation(state, attestation)
+
+    yield from run_attestation_processing(state, attestation)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attester_slashing.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attester_slashing.py
@@ -0,0 +1,149 @@
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    get_beacon_proposer_index,
+    process_attester_slashing,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.attestations import sign_indexed_attestation
+from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
+from eth2spec.test.helpers.block import apply_empty_block
+from eth2spec.test.helpers.state import (
+    get_balance,
+    next_epoch,
+)
+
+
+def run_attester_slashing_processing(state, attester_slashing, valid=True):
+    """
+    Run ``process_attester_slashing``, yielding:
+      - pre-state ('pre')
+      - attester_slashing ('attester_slashing')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+
+    yield 'pre', state
+    yield 'attester_slashing', attester_slashing
+
+    if not valid:
+        expect_assertion_error(lambda: process_attester_slashing(state, attester_slashing))
+        yield 'post', None
+        return
+
+    slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
+    pre_slashed_balance = get_balance(state, slashed_index)
+
+    proposer_index = get_beacon_proposer_index(state)
+    pre_proposer_balance = get_balance(state, proposer_index)
+
+    # Process slashing
+    process_attester_slashing(state, attester_slashing)
+
+    slashed_validator = state.validator_registry[slashed_index]
+
+    # Check slashing
+    assert slashed_validator.slashed
+    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
+    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    if slashed_index != proposer_index:
+        # lost whistleblower reward
+        assert get_balance(state, slashed_index) < pre_slashed_balance
+        # gained whistleblower reward
+        assert get_balance(state, proposer_index) > pre_proposer_balance
+    else:
+        # gained rewards for all slashings, which may include others. And only lost that of themselves.
+        # Netto at least 0, if more people where slashed, a balance increase.
+        assert get_balance(state, slashed_index) >= pre_slashed_balance
+
+    yield 'post', state
+
+
+@spec_state_test
+def test_success_double(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=True)
+
+    yield from run_attester_slashing_processing(state, attester_slashing)
+
+
+@spec_state_test
+def test_success_surround(state):
+    next_epoch(state)
+    apply_empty_block(state)
+
+    state.current_justified_epoch += 1
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=True)
+
+    # set attestion1 to surround attestation 2
+    attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1
+    attester_slashing.attestation_1.data.target_epoch = attester_slashing.attestation_2.data.target_epoch + 1
+
+    sign_indexed_attestation(state, attester_slashing.attestation_1)
+
+    yield from run_attester_slashing_processing(state, attester_slashing)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_1(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=True)
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_2(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=False)
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_1_and_2(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=False)
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@spec_state_test
+def test_same_data(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=True)
+
+    attester_slashing.attestation_1.data = attester_slashing.attestation_2.data
+    sign_indexed_attestation(state, attester_slashing.attestation_1)
+
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@spec_state_test
+def test_no_double_or_surround(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=True)
+
+    attester_slashing.attestation_1.data.target_epoch += 1
+    sign_indexed_attestation(state, attester_slashing.attestation_1)
+
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@spec_state_test
+def test_participants_already_slashed(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=True)
+
+    # set all indices to slashed
+    attestation_1 = attester_slashing.attestation_1
+    validator_indices = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
+    for index in validator_indices:
+        state.validator_registry[index].slashed = True
+
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
+
+
+@spec_state_test
+def test_custody_bit_0_and_1(state):
+    attester_slashing = get_valid_attester_slashing(state, signed_1=False, signed_2=True)
+
+    attester_slashing.attestation_1.custody_bit_1_indices = (
+        attester_slashing.attestation_1.custody_bit_0_indices
+    )
+    sign_indexed_attestation(state, attester_slashing.attestation_1)
+
+    yield from run_attester_slashing_processing(state, attester_slashing, False)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_block_header.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_block_header.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_block_header.py
@@ -0,0 +1,85 @@
+from copy import deepcopy
+
+from eth2spec.phase0.spec import (
+    get_beacon_proposer_index,
+    process_slots,
+    process_block_header,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.block import (
+    build_empty_block_for_next_slot,
+    sign_block
+)
+from eth2spec.test.helpers.state import next_slot
+
+
+def prepare_state_for_header_processing(state):
+    process_slots(state, state.slot + 1)
+
+
+def run_block_header_processing(state, block, valid=True):
+    """
+    Run ``process_block_header``, yielding:
+      - pre-state ('pre')
+      - block ('block')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    prepare_state_for_header_processing(state)
+
+    yield 'pre', state
+    yield 'block', block
+
+    if not valid:
+        expect_assertion_error(lambda: process_block_header(state, block))
+        yield 'post', None
+        return
+
+    process_block_header(state, block)
+    yield 'post', state
+
+
+@spec_state_test
+def test_success_block_header(state):
+    block = build_empty_block_for_next_slot(state, signed=True)
+    yield from run_block_header_processing(state, block)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_block_header(state):
+    block = build_empty_block_for_next_slot(state)
+    yield from run_block_header_processing(state, block, valid=False)
+
+
+@spec_state_test
+def test_invalid_slot_block_header(state):
+    block = build_empty_block_for_next_slot(state)
+    block.slot = state.slot + 2  # invalid slot
+    sign_block(state, block)
+
+    yield from run_block_header_processing(state, block, valid=False)
+
+
+@spec_state_test
+def test_invalid_parent_root(state):
+    block = build_empty_block_for_next_slot(state)
+    block.parent_root = b'\12' * 32  # invalid prev root
+    sign_block(state, block)
+
+    yield from run_block_header_processing(state, block, valid=False)
+
+
+@spec_state_test
+def test_proposer_slashed(state):
+    # use stub state to get proposer index of next slot
+    stub_state = deepcopy(state)
+    next_slot(stub_state)
+    proposer_index = get_beacon_proposer_index(stub_state)
+
+    # set proposer to slashed
+    state.validator_registry[proposer_index].slashed = True
+
+    block = build_empty_block_for_next_slot(state, signed=True)
+
+    yield from run_block_header_processing(state, block, valid=False)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_deposit.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_deposit.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_deposit.py
@@ -0,0 +1,124 @@
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import process_deposit
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.deposits import prepare_state_and_deposit, sign_deposit_data
+from eth2spec.test.helpers.state import get_balance
+from eth2spec.test.helpers.keys import privkeys
+
+
+def run_deposit_processing(state, deposit, validator_index, valid=True, effective=True):
+    """
+    Run ``process_deposit``, yielding:
+      - pre-state ('pre')
+      - deposit ('deposit')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    pre_validator_count = len(state.validator_registry)
+    pre_balance = 0
+    if validator_index < pre_validator_count:
+        pre_balance = get_balance(state, validator_index)
+    else:
+        # if it is a new validator, it should be right at the end of the current registry.
+        assert validator_index == pre_validator_count
+
+    yield 'pre', state
+    yield 'deposit', deposit
+
+    if not valid:
+        expect_assertion_error(lambda: process_deposit(state, deposit))
+        yield 'post', None
+        return
+
+    process_deposit(state, deposit)
+
+    yield 'post', state
+
+    if not effective:
+        assert len(state.validator_registry) == pre_validator_count
+        assert len(state.balances) == pre_validator_count
+        if validator_index < pre_validator_count:
+            assert get_balance(state, validator_index) == pre_balance
+    else:
+        if validator_index < pre_validator_count:
+            # top-up
+            assert len(state.validator_registry) == pre_validator_count
+            assert len(state.balances) == pre_validator_count
+        else:
+            # new validator
+            assert len(state.validator_registry) == pre_validator_count + 1
+            assert len(state.balances) == pre_validator_count + 1
+        assert get_balance(state, validator_index) == pre_balance + deposit.data.amount
+
+    assert state.deposit_index == state.latest_eth1_data.deposit_count
+
+
+@spec_state_test
+def test_new_deposit(state):
+    # fresh deposit = next validator index = validator appended to registry
+    validator_index = len(state.validator_registry)
+    amount = spec.MAX_EFFECTIVE_BALANCE
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=True)
+
+    yield from run_deposit_processing(state, deposit, validator_index)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_new_deposit(state):
+    # fresh deposit = next validator index = validator appended to registry
+    validator_index = len(state.validator_registry)
+    amount = spec.MAX_EFFECTIVE_BALANCE
+    deposit = prepare_state_and_deposit(state, validator_index, amount)
+    yield from run_deposit_processing(state, deposit, validator_index, valid=True, effective=False)
+
+
+@spec_state_test
+def test_success_top_up(state):
+    validator_index = 0
+    amount = spec.MAX_EFFECTIVE_BALANCE // 4
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=True)
+
+    yield from run_deposit_processing(state, deposit, validator_index)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_top_up(state):
+    validator_index = 0
+    amount = spec.MAX_EFFECTIVE_BALANCE // 4
+    deposit = prepare_state_and_deposit(state, validator_index, amount)
+
+    # invalid signatures, in top-ups, are allowed!
+    yield from run_deposit_processing(state, deposit, validator_index, valid=True, effective=True)
+
+
+@spec_state_test
+def test_wrong_index(state):
+    validator_index = len(state.validator_registry)
+    amount = spec.MAX_EFFECTIVE_BALANCE
+    deposit = prepare_state_and_deposit(state, validator_index, amount)
+
+    # mess up deposit_index
+    deposit.index = state.deposit_index + 1
+
+    sign_deposit_data(state, deposit.data, privkeys[validator_index])
+
+    yield from run_deposit_processing(state, deposit, validator_index, valid=False)
+
+
+# TODO: test invalid signature
+
+
+@spec_state_test
+def test_bad_merkle_proof(state):
+    validator_index = len(state.validator_registry)
+    amount = spec.MAX_EFFECTIVE_BALANCE
+    deposit = prepare_state_and_deposit(state, validator_index, amount)
+
+    # mess up merkle branch
+    deposit.proof[-1] = spec.ZERO_HASH
+
+    sign_deposit_data(state, deposit.data, privkeys[validator_index])
+
+    yield from run_deposit_processing(state, deposit, validator_index, valid=False)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_proposer_slashing.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_proposer_slashing.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_proposer_slashing.py
@@ -0,0 +1,137 @@
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    get_current_epoch,
+    process_proposer_slashing,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.block_header import sign_block_header
+from eth2spec.test.helpers.keys import privkeys
+from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
+from eth2spec.test.helpers.state import get_balance
+
+
+def run_proposer_slashing_processing(state, proposer_slashing, valid=True):
+    """
+    Run ``process_proposer_slashing``, yielding:
+      - pre-state ('pre')
+      - proposer_slashing ('proposer_slashing')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+
+    yield 'pre', state
+    yield 'proposer_slashing', proposer_slashing
+
+    if not valid:
+        expect_assertion_error(lambda: process_proposer_slashing(state, proposer_slashing))
+        yield 'post', None
+        return
+
+    pre_proposer_balance = get_balance(state, proposer_slashing.proposer_index)
+
+    process_proposer_slashing(state, proposer_slashing)
+    yield 'post', state
+
+    # check if slashed
+    slashed_validator = state.validator_registry[proposer_slashing.proposer_index]
+    assert slashed_validator.slashed
+    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
+    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+
+    # lost whistleblower reward
+    assert (
+        get_balance(state, proposer_slashing.proposer_index) <
+        pre_proposer_balance
+    )
+
+
+@spec_state_test
+def test_success(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_1(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=False, signed_2=True)
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_2(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=False)
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_sig_1_and_2(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=False, signed_2=False)
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_invalid_proposer_index(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+    # Index just too high (by 1)
+    proposer_slashing.proposer_index = len(state.validator_registry)
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_epochs_are_different(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=False)
+
+    # set slots to be in different epochs
+    proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH
+    sign_block_header(state, proposer_slashing.header_2, privkeys[proposer_slashing.proposer_index])
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_headers_are_same(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=False)
+
+    # set headers to be the same
+    proposer_slashing.header_2 = proposer_slashing.header_1
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_proposer_is_not_activated(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+
+    # set proposer to be not active yet
+    state.validator_registry[proposer_slashing.proposer_index].activation_epoch = get_current_epoch(state) + 1
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_proposer_is_slashed(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+
+    # set proposer to slashed
+    state.validator_registry[proposer_slashing.proposer_index].slashed = True
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
+
+
+@spec_state_test
+def test_proposer_is_withdrawn(state):
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+
+    # move 1 epoch into future, to allow for past withdrawable epoch
+    state.slot += spec.SLOTS_PER_EPOCH
+    # set proposer withdrawable_epoch in past
+    current_epoch = get_current_epoch(state)
+    proposer_index = proposer_slashing.proposer_index
+    state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1
+
+    yield from run_proposer_slashing_processing(state, proposer_slashing, False)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_transfer.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_transfer.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_transfer.py
@@ -0,0 +1,172 @@
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    get_active_validator_indices,
+    get_beacon_proposer_index,
+    get_current_epoch,
+    process_transfer,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.state import next_epoch
+from eth2spec.test.helpers.block import apply_empty_block
+from eth2spec.test.helpers.transfers import get_valid_transfer
+
+
+def run_transfer_processing(state, transfer, valid=True):
+    """
+    Run ``process_transfer``, yielding:
+      - pre-state ('pre')
+      - transfer ('transfer')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+
+    proposer_index = get_beacon_proposer_index(state)
+    pre_transfer_sender_balance = state.balances[transfer.sender]
+    pre_transfer_recipient_balance = state.balances[transfer.recipient]
+    pre_transfer_proposer_balance = state.balances[proposer_index]
+
+    yield 'pre', state
+    yield 'transfer', transfer
+
+    if not valid:
+        expect_assertion_error(lambda: process_transfer(state, transfer))
+        yield 'post', None
+        return
+
+    process_transfer(state, transfer)
+    yield 'post', state
+
+    sender_balance = state.balances[transfer.sender]
+    recipient_balance = state.balances[transfer.recipient]
+    assert sender_balance == pre_transfer_sender_balance - transfer.amount - transfer.fee
+    assert recipient_balance == pre_transfer_recipient_balance + transfer.amount
+    assert state.balances[proposer_index] == pre_transfer_proposer_balance + transfer.fee
+
+
+@spec_state_test
+def test_success_non_activated(state):
+    transfer = get_valid_transfer(state, signed=True)
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer)
+
+
+@spec_state_test
+def test_success_withdrawable(state):
+    next_epoch(state)
+    apply_empty_block(state)
+
+    transfer = get_valid_transfer(state, signed=True)
+
+    # withdrawable_epoch in past so can transfer
+    state.validator_registry[transfer.sender].withdrawable_epoch = get_current_epoch(state) - 1
+
+    yield from run_transfer_processing(state, transfer)
+
+
+@spec_state_test
+def test_success_active_above_max_effective(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + 1
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=1, fee=0, signed=True)
+
+    yield from run_transfer_processing(state, transfer)
+
+
+@spec_state_test
+def test_success_active_above_max_effective_fee(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + 1
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=0, fee=1, signed=True)
+
+    yield from run_transfer_processing(state, transfer)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_signature(state):
+    transfer = get_valid_transfer(state)
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
+    
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_active_but_transfer_past_effective_balance(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    amount = spec.MAX_EFFECTIVE_BALANCE // 32
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0, signed=True)
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_incorrect_slot(state):
+    transfer = get_valid_transfer(state, slot=state.slot + 1, signed=True)
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_insufficient_balance_for_fee(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=0, fee=1, signed=True)
+
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_insufficient_balance(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=1, fee=0, signed=True)
+
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_no_dust_sender(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    balance = state.balances[sender_index]
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=balance - spec.MIN_DEPOSIT_AMOUNT + 1, fee=0, signed=True)
+
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_no_dust_recipient(state):
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + 1
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=1, fee=0, signed=True)
+    state.balances[transfer.recipient] = 0
+
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
+
+
+@spec_state_test
+def test_invalid_pubkey(state):
+    transfer = get_valid_transfer(state, signed=True)
+    state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH
+
+    # un-activate so validator can transfer
+    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield from run_transfer_processing(state, transfer, False)
diff --git a/test_libs/pyspec/eth2spec/test/block_processing/test_process_voluntary_exit.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_voluntary_exit.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_voluntary_exit.py
@@ -0,0 +1,225 @@
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    get_active_validator_indices,
+    get_churn_limit,
+    get_current_epoch,
+    process_voluntary_exit,
+)
+from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
+from eth2spec.test.helpers.keys import pubkey_to_privkey
+from eth2spec.test.helpers.voluntary_exits import build_voluntary_exit, sign_voluntary_exit
+
+
+def run_voluntary_exit_processing(state, voluntary_exit, valid=True):
+    """
+    Run ``process_voluntary_exit``, yielding:
+      - pre-state ('pre')
+      - voluntary_exit ('voluntary_exit')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    validator_index = voluntary_exit.validator_index
+
+    yield 'pre', state
+    yield 'voluntary_exit', voluntary_exit
+
+    if not valid:
+        expect_assertion_error(lambda: process_voluntary_exit(state, voluntary_exit))
+        yield 'post', None
+        return
+
+    pre_exit_epoch = state.validator_registry[validator_index].exit_epoch
+
+    process_voluntary_exit(state, voluntary_exit)
+
+    yield 'post', state
+
+    assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH
+    assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
+
+
+@spec_state_test
+def test_success(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    voluntary_exit = build_voluntary_exit(state, current_epoch, validator_index, privkey, signed=True)
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit)
+
+
+@always_bls
+@spec_state_test
+def test_invalid_signature(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    voluntary_exit = build_voluntary_exit(state, current_epoch, validator_index, privkey)
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
+
+
+@spec_state_test
+def test_success_exit_queue(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+
+    # exit `MAX_EXITS_PER_EPOCH`
+    initial_indices = get_active_validator_indices(state, current_epoch)[:get_churn_limit(state)]
+
+    # Prepare a bunch of exits, based on the current state
+    exit_queue = []
+    for index in initial_indices:
+        privkey = pubkey_to_privkey[state.validator_registry[index].pubkey]
+        exit_queue.append(build_voluntary_exit(
+            state,
+            current_epoch,
+            index,
+            privkey,
+            signed=True,
+        ))
+
+    # Now run all the exits
+    for voluntary_exit in exit_queue:
+        # the function yields data, but we are just interested in running it here, ignore yields.
+        for _ in run_voluntary_exit_processing(state, voluntary_exit):
+            continue
+
+    # exit an additional validator
+    validator_index = get_active_validator_indices(state, current_epoch)[-1]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=True,
+    )
+
+    # This is the interesting part of the test: on a pre-state with a full exit queue,
+    #  when processing an additional exit, it results in an exit in a later epoch
+    yield from run_voluntary_exit_processing(state, voluntary_exit)
+
+    assert (
+        state.validator_registry[validator_index].exit_epoch ==
+        state.validator_registry[initial_indices[0]].exit_epoch + 1
+    )
+
+
+@spec_state_test
+def test_validator_exit_in_future(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=False,
+    )
+    voluntary_exit.epoch += 1
+    sign_voluntary_exit(state, voluntary_exit, privkey)
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
+
+
+@spec_state_test
+def test_validator_invalid_validator_index(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=False,
+    )
+    voluntary_exit.validator_index = len(state.validator_registry)
+    sign_voluntary_exit(state, voluntary_exit, privkey)
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
+
+
+@spec_state_test
+def test_validator_not_active(state):
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
+
+    # build and test voluntary exit
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=True,
+    )
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
+
+
+@spec_state_test
+def test_validator_already_exited(state):
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    # but validator already has exited
+    state.validator_registry[validator_index].exit_epoch = current_epoch + 2
+
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=True,
+    )
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
+
+
+@spec_state_test
+def test_validator_not_active_long_enough(state):
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[0]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+
+    voluntary_exit = build_voluntary_exit(
+        state,
+        current_epoch,
+        validator_index,
+        privkey,
+        signed=True,
+    )
+
+    assert (
+        current_epoch - state.validator_registry[validator_index].activation_epoch <
+        spec.PERSISTENT_COMMITTEE_PERIOD
+    )
+
+    yield from run_voluntary_exit_processing(state, voluntary_exit, False)
diff --git a/test_libs/pyspec/eth2spec/test/conftest.py b/test_libs/pyspec/eth2spec/test/conftest.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/conftest.py
@@ -0,0 +1,36 @@
+from eth2spec.phase0 import spec
+
+# We import pytest only when it's present, i.e. when we are running tests.
+# The test-cases themselves can be generated without installing pytest.
+
+def module_exists(module_name):
+    try:
+        __import__(module_name)
+    except ImportError:
+        return False
+    else:
+        return True
+
+
+def fixture(*args, **kwargs):
+    if module_exists("pytest"):
+        import pytest
+        return pytest.fixture(*args, **kwargs)
+    else:
+        def ignore():
+            pass
+        return ignore
+
+
+def pytest_addoption(parser):
+    parser.addoption(
+        "--config", action="store", default="minimal", help="config: make the pyspec use the specified configuration"
+    )
+
+
+@fixture(autouse=True)
+def config(request):
+    config_name = request.config.getoption("--config")
+    from preset_loader import loader
+    presets = loader.load_presets('../../configs/', config_name)
+    spec.apply_constants_preset(presets)
diff --git a/test_libs/pyspec/eth2spec/test/context.py b/test_libs/pyspec/eth2spec/test/context.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/context.py
@@ -0,0 +1,82 @@
+from eth2spec.phase0 import spec
+from eth2spec.utils import bls
+
+from .helpers.genesis import create_genesis_state
+
+from .utils import spectest, with_args, with_tags
+
+# Provides a genesis state as first argument to the function decorated with this
+with_state = with_args(lambda: [create_genesis_state(spec.SLOTS_PER_EPOCH * 8)])
+
+
+# BLS is turned off by default *for performance purposes during TESTING*.
+# The runner of the test can indicate the preferred setting (test generators prefer BLS to be ON).
+# - Some tests are marked as BLS-requiring, and ignore this setting.
+#    (tests that express differences caused by BLS, e.g. invalid signatures being rejected)
+# - Some other tests are marked as BLS-ignoring, and ignore this setting.
+#    (tests that are heavily performance impacted / require unsigned state transitions)
+# - Most tests respect the BLS setting.
+DEFAULT_BLS_ACTIVE = False
+
+
+# shorthand for decorating @with_state @spectest()
+def spec_state_test(fn):
+    return with_state(bls_switch(spectest()(fn)))
+
+
+def expect_assertion_error(fn):
+    bad = False
+    try:
+        fn()
+        bad = True
+    except AssertionError:
+        pass
+    except IndexError:
+        # Index errors are special; the spec is not explicit on bound checking, an IndexError is like a failed assert.
+        pass
+    if bad:
+        raise AssertionError('expected an assertion error, but got none.')
+
+
+# Tags a test to be ignoring BLS for it to pass.
+bls_ignored = with_tags({'bls_setting': 2})
+
+
+def never_bls(fn):
+    """
+    Decorator to apply on ``bls_switch`` decorator to force BLS de-activation. Useful to mark tests as BLS-ignorant.
+    """
+    def entry(*args, **kw):
+        # override bls setting
+        kw['bls_active'] = False
+        return fn(*args, **kw)
+    return bls_ignored(entry)
+
+
+# Tags a test to be requiring BLS for it to pass.
+bls_required = with_tags({'bls_setting': 1})
+
+
+def always_bls(fn):
+    """
+    Decorator to apply on ``bls_switch`` decorator to force BLS activation. Useful to mark tests as BLS-dependent.
+    """
+    def entry(*args, **kw):
+        # override bls setting
+        kw['bls_active'] = True
+        return fn(*args, **kw)
+    return bls_required(entry)
+
+
+def bls_switch(fn):
+    """
+    Decorator to make a function execute with BLS ON, or BLS off.
+    Based on an optional bool argument ``bls_active``, passed to the function at runtime.
+    """
+    def entry(*args, **kw):
+        old_state = bls.bls_active
+        bls.bls_active = kw.pop('bls_active', DEFAULT_BLS_ACTIVE)
+        out = fn(*args, **kw)
+        bls.bls_active = old_state
+        return out
+    return entry
diff --git a/test_libs/pyspec/eth2spec/test/epoch_processing/__init__.py b/test_libs/pyspec/eth2spec/test/epoch_processing/__init__.py
new file mode 100644
diff --git a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
similarity index 62%
rename from test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py
rename to test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
--- a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py
+++ b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
@@ -1,5 +1,4 @@
 from copy import deepcopy
-import pytest
 
 import eth2spec.phase0.spec as spec
 
@@ -9,106 +8,123 @@
     process_crosslinks,
     state_transition,
 )
-from tests.helpers import (
+from eth2spec.test.context import spec_state_test
+from eth2spec.test.helpers.state import (
+    next_epoch,
+    next_slot
+)
+from eth2spec.test.helpers.block import apply_empty_block, sign_block
+from eth2spec.test.helpers.attestations import (
     add_attestation_to_state,
     build_empty_block_for_next_slot,
     fill_aggregate_attestation,
     get_crosslink_committee,
     get_valid_attestation,
-    next_epoch,
-    next_slot,
-    set_bitfield_bit,
+    sign_attestation,
 )
 
 
-# mark entire file as 'crosslinks'
-pytestmark = pytest.mark.crosslinks
-
-
 def run_process_crosslinks(state, valid=True):
+    """
+    Run ``process_crosslinks``, yielding:
+      - pre-state ('pre')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
     # transition state to slot before state transition
     slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
     block = build_empty_block_for_next_slot(state)
     block.slot = slot
+    sign_block(state, block)
     state_transition(state, block)
 
     # cache state before epoch transition
     process_slot(state)
 
-    post_state = deepcopy(state)
-    process_crosslinks(post_state)
-
-    return state, post_state
+    yield 'pre', state
+    process_crosslinks(state)
+    yield 'post', state
 
 
+@spec_state_test
 def test_no_attestations(state):
-    pre_state, post_state = run_process_crosslinks(state)
+    yield from run_process_crosslinks(state)
 
     for shard in range(spec.SHARD_COUNT):
-        assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
-
-    return pre_state, post_state
+        assert state.previous_crosslinks[shard] == state.current_crosslinks[shard]
 
 
+@spec_state_test
 def test_single_crosslink_update_from_current_epoch(state):
     next_epoch(state)
 
-    attestation = get_valid_attestation(state)
+    attestation = get_valid_attestation(state, signed=True)
 
     fill_aggregate_attestation(state, attestation)
     add_attestation_to_state(state, attestation, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
 
     assert len(state.current_epoch_attestations) == 1
 
-    pre_state, post_state = run_process_crosslinks(state)
-
     shard = attestation.data.crosslink.shard
-    assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
-    assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
+    pre_crosslink = deepcopy(state.current_crosslinks[shard])
 
-    return pre_state, post_state
+    yield from run_process_crosslinks(state)
 
+    assert state.previous_crosslinks[shard] != state.current_crosslinks[shard]
+    assert pre_crosslink != state.current_crosslinks[shard]
 
+
+@spec_state_test
 def test_single_crosslink_update_from_previous_epoch(state):
     next_epoch(state)
 
-    attestation = get_valid_attestation(state)
+    attestation = get_valid_attestation(state, signed=True)
 
     fill_aggregate_attestation(state, attestation)
     add_attestation_to_state(state, attestation, state.slot + spec.SLOTS_PER_EPOCH)
 
     assert len(state.previous_epoch_attestations) == 1
 
-    pre_state, post_state = run_process_crosslinks(state)
+    shard = attestation.data.crosslink.shard
+    pre_crosslink = deepcopy(state.current_crosslinks[shard])
+
     crosslink_deltas = get_crosslink_deltas(state)
 
-    shard = attestation.data.crosslink.shard
-    assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
-    assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
+    yield from run_process_crosslinks(state)
+
+    assert state.previous_crosslinks[shard] != state.current_crosslinks[shard]
+    assert pre_crosslink != state.current_crosslinks[shard]
+
     # ensure rewarded
     for index in get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.crosslink.shard):
         assert crosslink_deltas[0][index] > 0
         assert crosslink_deltas[1][index] == 0
 
-    return pre_state, post_state
-
 
+@spec_state_test
 def test_double_late_crosslink(state):
+    if spec.get_epoch_committee_count(state, spec.get_current_epoch(state)) < spec.SHARD_COUNT:
+        print("warning: ignoring test, test-assumptions are incompatible with configuration")
+        return
+
     next_epoch(state)
     state.slot += 4
 
-    attestation_1 = get_valid_attestation(state)
+    attestation_1 = get_valid_attestation(state, signed=True)
     fill_aggregate_attestation(state, attestation_1)
 
-    # add attestation_1 in the next epoch
+    # add attestation_1 to next epoch
     next_epoch(state)
     add_attestation_to_state(state, attestation_1, state.slot + 1)
 
     for slot in range(spec.SLOTS_PER_EPOCH):
         attestation_2 = get_valid_attestation(state)
         if attestation_2.data.crosslink.shard == attestation_1.data.crosslink.shard:
+            sign_attestation(state, attestation_2)
             break
         next_slot(state)
+    apply_empty_block(state)
+
     fill_aggregate_attestation(state, attestation_2)
 
     # add attestation_2 in the next epoch after attestation_1 has
@@ -119,16 +135,15 @@ def test_double_late_crosslink(state):
     assert len(state.previous_epoch_attestations) == 1
     assert len(state.current_epoch_attestations) == 0
 
-    pre_state, post_state = run_process_crosslinks(state)
     crosslink_deltas = get_crosslink_deltas(state)
 
+    yield from run_process_crosslinks(state)
+
     shard = attestation_2.data.crosslink.shard
 
     # ensure that the current crosslinks were not updated by the second attestation
-    assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
+    assert state.previous_crosslinks[shard] == state.current_crosslinks[shard]
     # ensure no reward, only penalties for the failed crosslink
     for index in get_crosslink_committee(state, attestation_2.data.target_epoch, attestation_2.data.crosslink.shard):
         assert crosslink_deltas[0][index] == 0
         assert crosslink_deltas[1][index] > 0
-
-    return pre_state, post_state
diff --git a/test_libs/pyspec/tests/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
similarity index 54%
rename from test_libs/pyspec/tests/epoch_processing/test_process_registry_updates.py
rename to test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
--- a/test_libs/pyspec/tests/epoch_processing/test_process_registry_updates.py
+++ b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
@@ -1,21 +1,44 @@
-from copy import deepcopy
-
-import pytest
-
 import eth2spec.phase0.spec as spec
 
 from eth2spec.phase0.spec import (
     get_current_epoch,
     is_active_validator,
+    process_registry_updates
 )
-from tests.helpers import (
-    next_epoch,
-)
-
-# mark entire file as 'state'
-pytestmark = pytest.mark.state
-
-
+from eth2spec.phase0.spec import state_transition
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
+from eth2spec.test.helpers.state import next_epoch
+from eth2spec.test.context import spec_state_test
+
+
+def run_process_registry_updates(state, valid=True):
+    """
+    Run ``process_crosslinks``, yielding:
+      - pre-state ('pre')
+      - post-state ('post').
+    If ``valid == False``, run expecting ``AssertionError``
+    """
+    # transition state to slot before state transition
+    slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
+    block = build_empty_block_for_next_slot(state)
+    block.slot = slot
+    sign_block(state, block)
+    state_transition(state, block)
+
+    # cache state before epoch transition
+    spec.process_slot(state)
+
+    # process components of epoch transition before registry update
+    spec.process_justification_and_finalization(state)
+    spec.process_crosslinks(state)
+    spec.process_rewards_and_penalties(state)
+
+    yield 'pre', state
+    process_registry_updates(state)
+    yield 'post', state
+
+
+@spec_state_test
 def test_activation(state):
     index = 0
     assert is_active_validator(state.validator_registry[index], get_current_epoch(state))
@@ -26,12 +49,10 @@ def test_activation(state):
     state.validator_registry[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
     assert not is_active_validator(state.validator_registry[index], get_current_epoch(state))
 
-    pre_state = deepcopy(state)
-
-    blocks = []
     for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
-        block = next_epoch(state)
-        blocks.append(block)
+        next_epoch(state)
+
+    yield from run_process_registry_updates(state)
 
     assert state.validator_registry[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
     assert state.validator_registry[index].activation_epoch != spec.FAR_FUTURE_EPOCH
@@ -40,9 +61,8 @@ def test_activation(state):
         get_current_epoch(state),
     )
 
-    return pre_state, blocks, state
-
 
+@spec_state_test
 def test_ejection(state):
     index = 0
     assert is_active_validator(state.validator_registry[index], get_current_epoch(state))
@@ -51,17 +71,13 @@ def test_ejection(state):
     # Mock an ejection
     state.validator_registry[index].effective_balance = spec.EJECTION_BALANCE
 
-    pre_state = deepcopy(state)
-
-    blocks = []
     for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
-        block = next_epoch(state)
-        blocks.append(block)
+        next_epoch(state)
+
+    yield from run_process_registry_updates(state)
 
     assert state.validator_registry[index].exit_epoch != spec.FAR_FUTURE_EPOCH
     assert not is_active_validator(
         state.validator_registry[index],
         get_current_epoch(state),
     )
-
-    return pre_state, blocks, state
diff --git a/test_libs/pyspec/eth2spec/test/helpers/__init__.py b/test_libs/pyspec/eth2spec/test/helpers/__init__.py
new file mode 100644
diff --git a/test_libs/pyspec/eth2spec/test/helpers/attestations.py b/test_libs/pyspec/eth2spec/test/helpers/attestations.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/attestations.py
@@ -0,0 +1,161 @@
+from typing import List
+
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    Attestation,
+    AttestationData,
+    AttestationDataAndCustodyBit,
+    Crosslink,
+    get_epoch_start_slot, get_block_root, get_current_epoch, get_previous_epoch, slot_to_epoch,
+    get_crosslink_committee, get_domain, IndexedAttestation, get_attesting_indices, BeaconState, get_block_root_at_slot,
+    get_epoch_start_shard, get_epoch_committee_count,
+    state_transition, process_slots,
+)
+from eth2spec.test.helpers.bitfields import set_bitfield_bit
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
+from eth2spec.test.helpers.keys import privkeys
+from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
+from eth2spec.utils.minimal_ssz import hash_tree_root
+
+
+def build_attestation_data(state, slot, shard):
+    assert state.slot >= slot
+
+    if slot == state.slot:
+        block_root = build_empty_block_for_next_slot(state).parent_root
+    else:
+        block_root = get_block_root_at_slot(state, slot)
+
+    current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
+    if slot < current_epoch_start_slot:
+        epoch_boundary_root = get_block_root(state, get_previous_epoch(state))
+    elif slot == current_epoch_start_slot:
+        epoch_boundary_root = block_root
+    else:
+        epoch_boundary_root = get_block_root(state, get_current_epoch(state))
+
+    if slot < current_epoch_start_slot:
+        justified_epoch = state.previous_justified_epoch
+        justified_block_root = state.previous_justified_root
+    else:
+        justified_epoch = state.current_justified_epoch
+        justified_block_root = state.current_justified_root
+
+    if slot_to_epoch(slot) == get_current_epoch(state):
+        parent_crosslink = state.current_crosslinks[shard]
+    else:
+        parent_crosslink = state.previous_crosslinks[shard]
+
+    return AttestationData(
+        beacon_block_root=block_root,
+        source_epoch=justified_epoch,
+        source_root=justified_block_root,
+        target_epoch=slot_to_epoch(slot),
+        target_root=epoch_boundary_root,
+        crosslink=Crosslink(
+            shard=shard,
+            start_epoch=parent_crosslink.end_epoch,
+            end_epoch=min(slot_to_epoch(slot), parent_crosslink.end_epoch + spec.MAX_EPOCHS_PER_CROSSLINK),
+            data_root=spec.ZERO_HASH,
+            parent_root=hash_tree_root(parent_crosslink),
+        ),
+    )
+
+
+def get_valid_attestation(state, slot=None, signed=False):
+    if slot is None:
+        slot = state.slot
+
+    epoch = slot_to_epoch(slot)
+    epoch_start_shard = get_epoch_start_shard(state, epoch)
+    committees_per_slot = get_epoch_committee_count(state, epoch) // spec.SLOTS_PER_EPOCH
+    shard = (epoch_start_shard + committees_per_slot * (slot % spec.SLOTS_PER_EPOCH)) % spec.SHARD_COUNT
+
+    attestation_data = build_attestation_data(state, slot, shard)
+
+    crosslink_committee = get_crosslink_committee(
+        state,
+        attestation_data.target_epoch,
+        attestation_data.crosslink.shard
+    )
+
+    committee_size = len(crosslink_committee)
+    bitfield_length = (committee_size + 7) // 8
+    aggregation_bitfield = b'\x00' * bitfield_length
+    custody_bitfield = b'\x00' * bitfield_length
+    attestation = Attestation(
+        aggregation_bitfield=aggregation_bitfield,
+        data=attestation_data,
+        custody_bitfield=custody_bitfield,
+    )
+    fill_aggregate_attestation(state, attestation)
+    if signed:
+        sign_attestation(state, attestation)
+    return attestation
+
+
+def sign_aggregate_attestation(state: BeaconState, data: AttestationData, participants: List[int]):
+    signatures = []
+    for validator_index in participants:
+        privkey = privkeys[validator_index]
+        signatures.append(
+            get_attestation_signature(
+                state,
+                data,
+                privkey
+            )
+        )
+
+    return bls_aggregate_signatures(signatures)
+
+
+def sign_indexed_attestation(state, indexed_attestation: IndexedAttestation):
+    participants = indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_indices
+    indexed_attestation.signature = sign_aggregate_attestation(state, indexed_attestation.data, participants)
+
+
+def sign_attestation(state, attestation: Attestation):
+    participants = get_attesting_indices(
+        state,
+        attestation.data,
+        attestation.aggregation_bitfield,
+    )
+
+    attestation.signature = sign_aggregate_attestation(state, attestation.data, participants)
+
+
+def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0):
+    message_hash = AttestationDataAndCustodyBit(
+        data=attestation_data,
+        custody_bit=custody_bit,
+    ).hash_tree_root()
+
+    return bls_sign(
+        message_hash=message_hash,
+        privkey=privkey,
+        domain=get_domain(
+            state=state,
+            domain_type=spec.DOMAIN_ATTESTATION,
+            message_epoch=attestation_data.target_epoch,
+        )
+    )
+
+
+def fill_aggregate_attestation(state, attestation):
+    crosslink_committee = get_crosslink_committee(
+        state,
+        attestation.data.target_epoch,
+        attestation.data.crosslink.shard,
+    )
+    for i in range(len(crosslink_committee)):
+        attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
+
+
+def add_attestation_to_state(state, attestation, slot):
+    block = build_empty_block_for_next_slot(state)
+    block.slot = slot
+    block.body.attestations.append(attestation)
+    process_slots(state, block.slot)
+    sign_block(state, block)
+    state_transition(state, block)
diff --git a/test_libs/pyspec/eth2spec/test/helpers/attester_slashings.py b/test_libs/pyspec/eth2spec/test/helpers/attester_slashings.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/attester_slashings.py
@@ -0,0 +1,19 @@
+from copy import deepcopy
+
+from eth2spec.phase0.spec import AttesterSlashing, convert_to_indexed
+from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation
+
+
+def get_valid_attester_slashing(state, signed_1=False, signed_2=False):
+    attestation_1 = get_valid_attestation(state, signed=signed_1)
+
+    attestation_2 = deepcopy(attestation_1)
+    attestation_2.data.target_root = b'\x01' * 32
+
+    if signed_2:
+        sign_attestation(state, attestation_2)
+
+    return AttesterSlashing(
+        attestation_1=convert_to_indexed(state, attestation_1),
+        attestation_2=convert_to_indexed(state, attestation_2),
+    )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/bitfields.py b/test_libs/pyspec/eth2spec/test/helpers/bitfields.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/bitfields.py
@@ -0,0 +1,11 @@
+def set_bitfield_bit(bitfield, i):
+    """
+    Set the bit in ``bitfield`` at position ``i`` to ``1``.
+    """
+    byte_index = i // 8
+    bit_index = i % 8
+    return (
+            bitfield[:byte_index] +
+            bytes([bitfield[byte_index] | (1 << bit_index)]) +
+            bitfield[byte_index + 1:]
+    )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/block.py b/test_libs/pyspec/eth2spec/test/helpers/block.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/block.py
@@ -0,0 +1,80 @@
+from copy import deepcopy
+
+from eth2spec.phase0 import spec
+from eth2spec.phase0.spec import (
+    BeaconBlock,
+    get_beacon_proposer_index, slot_to_epoch, get_domain,
+    process_slots, state_transition,
+)
+from eth2spec.test.helpers.keys import privkeys
+from eth2spec.utils.bls import bls_sign, only_with_bls
+from eth2spec.utils.minimal_ssz import signing_root, hash_tree_root
+
+
+# Fully ignore the function if BLS is off, beacon-proposer index calculation is slow.
+@only_with_bls()
+def sign_block(state, block, proposer_index=None):
+    assert state.slot <= block.slot
+
+    if proposer_index is None:
+        if block.slot == state.slot:
+            proposer_index = get_beacon_proposer_index(state)
+        else:
+            if slot_to_epoch(state.slot) + 1 > slot_to_epoch(block.slot):
+                print("warning: block slot far away, and no proposer index manually given."
+                      " Signing block is slow due to transition for proposer index calculation.")
+            # use stub state to get proposer index of future slot
+            stub_state = deepcopy(state)
+            process_slots(stub_state, block.slot)
+            proposer_index = get_beacon_proposer_index(stub_state)
+
+    privkey = privkeys[proposer_index]
+
+    block.body.randao_reveal = bls_sign(
+        privkey=privkey,
+        message_hash=hash_tree_root(slot_to_epoch(block.slot)),
+        domain=get_domain(
+            state,
+            message_epoch=slot_to_epoch(block.slot),
+            domain_type=spec.DOMAIN_RANDAO,
+        )
+    )
+    block.signature = bls_sign(
+        message_hash=signing_root(block),
+        privkey=privkey,
+        domain=get_domain(
+            state,
+            spec.DOMAIN_BEACON_PROPOSER,
+            slot_to_epoch(block.slot)))
+
+
+def apply_empty_block(state):
+    """
+    Transition via an empty block (on current slot, assuming no block has been applied yet).
+    :return: the empty block that triggered the transition.
+    """
+    block = build_empty_block(state, signed=True)
+    state_transition(state, block)
+    return block
+
+
+def build_empty_block(state, slot=None, signed=False):
+    if slot is None:
+        slot = state.slot
+    empty_block = BeaconBlock()
+    empty_block.slot = slot
+    empty_block.body.eth1_data.deposit_count = state.deposit_index
+    previous_block_header = deepcopy(state.latest_block_header)
+    if previous_block_header.state_root == spec.ZERO_HASH:
+        previous_block_header.state_root = state.hash_tree_root()
+    empty_block.parent_root = signing_root(previous_block_header)
+
+    if signed:
+        sign_block(state, empty_block)
+
+    return empty_block
+
+
+def build_empty_block_for_next_slot(state, signed=False):
+    return build_empty_block(state, state.slot + 1, signed=signed)
+
diff --git a/test_libs/pyspec/eth2spec/test/helpers/block_header.py b/test_libs/pyspec/eth2spec/test/helpers/block_header.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/block_header.py
@@ -0,0 +1,18 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import get_domain
+from eth2spec.utils.bls import bls_sign
+from eth2spec.utils.minimal_ssz import signing_root
+
+
+def sign_block_header(state, header, privkey):
+    domain = get_domain(
+        state=state,
+        domain_type=spec.DOMAIN_BEACON_PROPOSER,
+    )
+    header.signature = bls_sign(
+        message_hash=signing_root(header),
+        privkey=privkey,
+        domain=domain,
+    )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/deposits.py b/test_libs/pyspec/eth2spec/test/helpers/deposits.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/deposits.py
@@ -0,0 +1,81 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import get_domain, DepositData, verify_merkle_branch, Deposit, ZERO_HASH
+from eth2spec.test.helpers.keys import pubkeys, privkeys
+from eth2spec.utils.bls import bls_sign
+from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_root, get_merkle_proof
+from eth2spec.utils.minimal_ssz import signing_root
+
+
+def build_deposit_data(state, pubkey, privkey, amount, signed=False):
+    deposit_data = DepositData(
+        pubkey=pubkey,
+        # insecurely use pubkey as withdrawal key as well
+        withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(pubkey)[1:],
+        amount=amount,
+    )
+    if signed:
+        sign_deposit_data(state, deposit_data, privkey)
+    return deposit_data
+
+
+def sign_deposit_data(state, deposit_data, privkey):
+    signature = bls_sign(
+        message_hash=signing_root(deposit_data),
+        privkey=privkey,
+        domain=get_domain(
+            state,
+            spec.DOMAIN_DEPOSIT,
+        )
+    )
+    deposit_data.signature = signature
+
+
+def build_deposit(state,
+                  deposit_data_leaves,
+                  pubkey,
+                  privkey,
+                  amount,
+                  signed):
+    deposit_data = build_deposit_data(state, pubkey, privkey, amount, signed)
+
+    item = deposit_data.hash_tree_root()
+    index = len(deposit_data_leaves)
+    deposit_data_leaves.append(item)
+    tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
+    root = get_merkle_root((tuple(deposit_data_leaves)))
+    proof = list(get_merkle_proof(tree, item_index=index))
+    assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
+
+    deposit = Deposit(
+        proof=list(proof),
+        index=index,
+        data=deposit_data,
+    )
+
+    return deposit, root, deposit_data_leaves
+
+
+def prepare_state_and_deposit(state, validator_index, amount, signed=False):
+    """
+    Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount.
+    """
+    pre_validator_count = len(state.validator_registry)
+    # fill previous deposits with zero-hash
+    deposit_data_leaves = [ZERO_HASH] * pre_validator_count
+
+    pubkey = pubkeys[validator_index]
+    privkey = privkeys[validator_index]
+    deposit, root, deposit_data_leaves = build_deposit(
+        state,
+        deposit_data_leaves,
+        pubkey,
+        privkey,
+        amount,
+        signed
+    )
+
+    state.latest_eth1_data.deposit_root = root
+    state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
+    return deposit
diff --git a/test_libs/pyspec/eth2spec/test/helpers/genesis.py b/test_libs/pyspec/eth2spec/test/helpers/genesis.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/genesis.py
@@ -0,0 +1,51 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import Eth1Data, ZERO_HASH, get_active_validator_indices
+from eth2spec.test.helpers.keys import pubkeys
+from eth2spec.utils.minimal_ssz import hash_tree_root
+
+
+def build_mock_validator(i: int, balance: int):
+    pubkey = pubkeys[i]
+    # insecurely use pubkey as withdrawal key as well
+    withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(pubkey)[1:]
+    return spec.Validator(
+        pubkey=pubkeys[i],
+        withdrawal_credentials=withdrawal_credentials,
+        activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH,
+        activation_epoch=spec.FAR_FUTURE_EPOCH,
+        exit_epoch=spec.FAR_FUTURE_EPOCH,
+        withdrawable_epoch=spec.FAR_FUTURE_EPOCH,
+        effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE)
+    )
+
+
+def create_genesis_state(num_validators):
+    deposit_root = b'\x42' * 32
+
+    state = spec.BeaconState(
+        genesis_time=0,
+        deposit_index=num_validators,
+        latest_eth1_data=Eth1Data(
+            deposit_root=deposit_root,
+            deposit_count=num_validators,
+            block_hash=ZERO_HASH,
+        ))
+
+    # We "hack" in the initial validators,
+    #  as it is much faster than creating and processing genesis deposits for every single test case.
+    state.balances = [spec.MAX_EFFECTIVE_BALANCE] * num_validators
+    state.validator_registry = [build_mock_validator(i, state.balances[i]) for i in range(num_validators)]
+
+    # Process genesis activations
+    for validator in state.validator_registry:
+        if validator.effective_balance >= spec.MAX_EFFECTIVE_BALANCE:
+            validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
+            validator.activation_epoch = spec.GENESIS_EPOCH
+
+    genesis_active_index_root = hash_tree_root(get_active_validator_indices(state, spec.GENESIS_EPOCH))
+    for index in range(spec.LATEST_ACTIVE_INDEX_ROOTS_LENGTH):
+        state.latest_active_index_roots[index] = genesis_active_index_root
+
+    return state
diff --git a/test_libs/pyspec/eth2spec/test/helpers/keys.py b/test_libs/pyspec/eth2spec/test/helpers/keys.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/keys.py
@@ -0,0 +1,6 @@
+from py_ecc import bls
+from eth2spec.phase0 import spec
+
+privkeys = [i + 1 for i in range(spec.SLOTS_PER_EPOCH * 16)]
+pubkeys = [bls.privtopub(privkey) for privkey in privkeys]
+pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
diff --git a/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py b/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/proposer_slashings.py
@@ -0,0 +1,35 @@
+from copy import deepcopy
+
+from eth2spec.phase0.spec import (
+    get_current_epoch, get_active_validator_indices, BeaconBlockHeader, ProposerSlashing
+)
+from eth2spec.test.helpers.block_header import sign_block_header
+from eth2spec.test.helpers.keys import pubkey_to_privkey
+
+
+def get_valid_proposer_slashing(state, signed_1=False, signed_2=False):
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[-1]
+    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
+    slot = state.slot
+
+    header_1 = BeaconBlockHeader(
+        slot=slot,
+        parent_root=b'\x33' * 32,
+        state_root=b'\x44' * 32,
+        block_body_root=b'\x55' * 32,
+    )
+    header_2 = deepcopy(header_1)
+    header_2.parent_root = b'\x99' * 32
+    header_2.slot = slot + 1
+
+    if signed_1:
+        sign_block_header(state, header_1, privkey)
+    if signed_2:
+        sign_block_header(state, header_2, privkey)
+
+    return ProposerSlashing(
+        proposer_index=validator_index,
+        header_1=header_1,
+        header_2=header_2,
+    )
diff --git a/test_libs/pyspec/eth2spec/test/helpers/state.py b/test_libs/pyspec/eth2spec/test/helpers/state.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/state.py
@@ -0,0 +1,31 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import process_slots
+
+
+def get_balance(state, index):
+    return state.balances[index]
+
+
+def next_slot(state):
+    """
+    Transition to the next slot.
+    """
+    process_slots(state, state.slot + 1)
+
+
+def next_epoch(state):
+    """
+    Transition to the start slot of the next epoch
+    """
+    slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH)
+    process_slots(state, slot)
+
+
+def get_state_root(state, slot) -> bytes:
+    """
+    Return the state root at a recent ``slot``.
+    """
+    assert slot < state.slot <= slot + spec.SLOTS_PER_HISTORICAL_ROOT
+    return state.latest_state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT]
diff --git a/test_libs/pyspec/eth2spec/test/helpers/transfers.py b/test_libs/pyspec/eth2spec/test/helpers/transfers.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/transfers.py
@@ -0,0 +1,55 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import get_current_epoch, get_active_validator_indices, Transfer, get_domain
+from eth2spec.test.helpers.keys import pubkeys, privkeys
+from eth2spec.test.helpers.state import get_balance
+from eth2spec.utils.bls import bls_sign
+from eth2spec.utils.minimal_ssz import signing_root
+
+
+def get_valid_transfer(state, slot=None, sender_index=None, amount=None, fee=None, signed=False):
+    if slot is None:
+        slot = state.slot
+    current_epoch = get_current_epoch(state)
+    if sender_index is None:
+        sender_index = get_active_validator_indices(state, current_epoch)[-1]
+    recipient_index = get_active_validator_indices(state, current_epoch)[0]
+    transfer_pubkey = pubkeys[-1]
+    transfer_privkey = privkeys[-1]
+
+    if fee is None:
+        fee = get_balance(state, sender_index) // 32
+    if amount is None:
+        amount = get_balance(state, sender_index) - fee
+
+    transfer = Transfer(
+        sender=sender_index,
+        recipient=recipient_index,
+        amount=amount,
+        fee=fee,
+        slot=slot,
+        pubkey=transfer_pubkey,
+    )
+    if signed:
+        sign_transfer(state, transfer, transfer_privkey)
+
+    # ensure withdrawal_credentials reproducible
+    state.validator_registry[transfer.sender].withdrawal_credentials = (
+            spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(transfer.pubkey)[1:]
+    )
+
+    return transfer
+
+
+def sign_transfer(state, transfer, privkey):
+    transfer.signature = bls_sign(
+        message_hash=signing_root(transfer),
+        privkey=privkey,
+        domain=get_domain(
+            state=state,
+            domain_type=spec.DOMAIN_TRANSFER,
+            message_epoch=get_current_epoch(state),
+        )
+    )
+    return transfer
diff --git a/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py b/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/helpers/voluntary_exits.py
@@ -0,0 +1,28 @@
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import VoluntaryExit, get_domain
+from eth2spec.utils.bls import bls_sign
+from eth2spec.utils.minimal_ssz import signing_root
+
+
+def build_voluntary_exit(state, epoch, validator_index, privkey, signed=False):
+    voluntary_exit = VoluntaryExit(
+        epoch=epoch,
+        validator_index=validator_index,
+    )
+    if signed:
+        sign_voluntary_exit(state, voluntary_exit, privkey)
+    return voluntary_exit
+
+
+def sign_voluntary_exit(state, voluntary_exit, privkey):
+    voluntary_exit.signature = bls_sign(
+        message_hash=signing_root(voluntary_exit),
+        privkey=privkey,
+        domain=get_domain(
+            state=state,
+            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
+            message_epoch=voluntary_exit.epoch,
+        )
+    )
diff --git a/test_libs/pyspec/eth2spec/test/sanity/__init__.py b/test_libs/pyspec/eth2spec/test/sanity/__init__.py
new file mode 100644
diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/sanity/test_blocks.py
@@ -0,0 +1,406 @@
+from copy import deepcopy
+
+import eth2spec.phase0.spec as spec
+from eth2spec.utils.bls import bls_sign
+
+from eth2spec.utils.minimal_ssz import signing_root
+from eth2spec.phase0.spec import (
+    # SSZ
+    VoluntaryExit,
+    # functions
+    get_active_validator_indices,
+    get_beacon_proposer_index,
+    get_block_root_at_slot,
+    get_current_epoch,
+    get_domain,
+    state_transition,
+)
+from eth2spec.test.helpers.state import get_balance
+from eth2spec.test.helpers.transfers import get_valid_transfer
+from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
+from eth2spec.test.helpers.keys import privkeys, pubkeys
+from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
+from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
+from eth2spec.test.helpers.attestations import get_valid_attestation
+from eth2spec.test.helpers.deposits import prepare_state_and_deposit
+
+from eth2spec.test.context import spec_state_test, never_bls
+
+
+@never_bls
+@spec_state_test
+def test_empty_block_transition(state):
+    pre_slot = state.slot
+    pre_eth1_votes = len(state.eth1_data_votes)
+
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state, signed=True)
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    assert len(state.eth1_data_votes) == pre_eth1_votes + 1
+    assert get_block_root_at_slot(state, pre_slot) == block.parent_root
+
+
+@never_bls
+@spec_state_test
+def test_skipped_slots(state):
+    pre_slot = state.slot
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state)
+    block.slot += 3
+    sign_block(state, block)
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    assert state.slot == block.slot
+    for slot in range(pre_slot, state.slot):
+        assert get_block_root_at_slot(state, slot) == block.parent_root
+
+
+@spec_state_test
+def test_empty_epoch_transition(state):
+    pre_slot = state.slot
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state)
+    block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, block)
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    assert state.slot == block.slot
+    for slot in range(pre_slot, state.slot):
+        assert get_block_root_at_slot(state, slot) == block.parent_root
+
+
+# @spec_state_test
+# def test_empty_epoch_transition_not_finalizing(state):
+#     # copy for later balance lookups.
+#     pre_state = deepcopy(state)
+#     yield 'pre', state
+#
+#     block = build_empty_block_for_next_slot(state)
+#     block.slot += spec.SLOTS_PER_EPOCH * 5
+#     sign_block(state, block, proposer_index=0)
+#     yield 'blocks', [block], [spec.BeaconBlock]
+#
+#     state_transition(state, block)
+#     yield 'post', state
+#
+#     assert state.slot == block.slot
+#     assert state.finalized_epoch < get_current_epoch(state) - 4
+#     for index in range(len(state.validator_registry)):
+#         assert get_balance(state, index) < get_balance(pre_state, index)
+
+
+@spec_state_test
+def test_proposer_slashing(state):
+    # copy for later balance lookups.
+    pre_state = deepcopy(state)
+    proposer_slashing = get_valid_proposer_slashing(state, signed_1=True, signed_2=True)
+    validator_index = proposer_slashing.proposer_index
+
+    assert not state.validator_registry[validator_index].slashed
+
+    yield 'pre', state
+
+    #
+    # Add to state via block transition
+    #
+    block = build_empty_block_for_next_slot(state)
+    block.body.proposer_slashings.append(proposer_slashing)
+    sign_block(state, block)
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    # check if slashed
+    slashed_validator = state.validator_registry[validator_index]
+    assert slashed_validator.slashed
+    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
+    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+    # lost whistleblower reward
+    assert get_balance(state, validator_index) < get_balance(pre_state, validator_index)
+
+
+@spec_state_test
+def test_attester_slashing(state):
+    # copy for later balance lookups.
+    pre_state = deepcopy(state)
+
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=True)
+    validator_index = (attester_slashing.attestation_1.custody_bit_0_indices +
+                       attester_slashing.attestation_1.custody_bit_1_indices)[0]
+
+    assert not state.validator_registry[validator_index].slashed
+
+    yield 'pre', state
+
+    #
+    # Add to state via block transition
+    #
+    block = build_empty_block_for_next_slot(state)
+    block.body.attester_slashings.append(attester_slashing)
+    sign_block(state, block)
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    slashed_validator = state.validator_registry[validator_index]
+    assert slashed_validator.slashed
+    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
+    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
+    # lost whistleblower reward
+    assert get_balance(state, validator_index) < get_balance(pre_state, validator_index)
+
+    proposer_index = get_beacon_proposer_index(state)
+    # gained whistleblower reward
+    assert (
+        get_balance(state, proposer_index) >
+        get_balance(pre_state, proposer_index)
+    )
+
+
+# TODO update functions below to be like above, i.e. with @spec_state_test and yielding data to put into the test vector
+
+@spec_state_test
+def test_deposit_in_block(state):
+    initial_registry_len = len(state.validator_registry)
+    initial_balances_len = len(state.balances)
+
+    validator_index = len(state.validator_registry)
+    amount = spec.MAX_EFFECTIVE_BALANCE
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=True)
+
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state)
+    block.body.deposits.append(deposit)
+    sign_block(state, block)
+
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    assert len(state.validator_registry) == initial_registry_len + 1
+    assert len(state.balances) == initial_balances_len + 1
+    assert get_balance(state, validator_index) == spec.MAX_EFFECTIVE_BALANCE
+    assert state.validator_registry[validator_index].pubkey == pubkeys[validator_index]
+
+
+@spec_state_test
+def test_deposit_top_up(state):
+    validator_index = 0
+    amount = spec.MAX_EFFECTIVE_BALANCE // 4
+    deposit = prepare_state_and_deposit(state, validator_index, amount)
+
+    initial_registry_len = len(state.validator_registry)
+    initial_balances_len = len(state.balances)
+    validator_pre_balance = get_balance(state, validator_index)
+
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state)
+    block.body.deposits.append(deposit)
+    sign_block(state, block)
+
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    assert len(state.validator_registry) == initial_registry_len
+    assert len(state.balances) == initial_balances_len
+    assert get_balance(state, validator_index) == validator_pre_balance + amount
+
+
+@spec_state_test
+def test_attestation(state):
+    state.slot = spec.SLOTS_PER_EPOCH
+
+    yield 'pre', state
+
+    attestation = get_valid_attestation(state, signed=True)
+
+    # Add to state via block transition
+    pre_current_attestations_len = len(state.current_epoch_attestations)
+    attestation_block = build_empty_block_for_next_slot(state)
+    attestation_block.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
+    attestation_block.body.attestations.append(attestation)
+    sign_block(state, attestation_block)
+    state_transition(state, attestation_block)
+
+    assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
+
+    # Epoch transition should move to previous_epoch_attestations
+    pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations)
+
+    epoch_block = build_empty_block_for_next_slot(state)
+    epoch_block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, epoch_block)
+    state_transition(state, epoch_block)
+
+    yield 'blocks', [attestation_block, epoch_block], [spec.BeaconBlock]
+    yield 'post', state
+
+    assert len(state.current_epoch_attestations) == 0
+    assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
+
+
+@spec_state_test
+def test_voluntary_exit(state):
+    validator_index = get_active_validator_indices(
+        state,
+        get_current_epoch(state)
+    )[-1]
+
+    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
+    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
+
+    yield 'pre', state
+
+    voluntary_exit = VoluntaryExit(
+        epoch=get_current_epoch(state),
+        validator_index=validator_index,
+    )
+    voluntary_exit.signature = bls_sign(
+        message_hash=signing_root(voluntary_exit),
+        privkey=privkeys[validator_index],
+        domain=get_domain(
+            state=state,
+            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
+        )
+    )
+
+    # Add to state via block transition
+    initiate_exit_block = build_empty_block_for_next_slot(state)
+    initiate_exit_block.body.voluntary_exits.append(voluntary_exit)
+    sign_block(state, initiate_exit_block)
+    state_transition(state, initiate_exit_block)
+
+    assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
+
+    # Process within epoch transition
+    exit_block = build_empty_block_for_next_slot(state)
+    exit_block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, exit_block)
+    state_transition(state, exit_block)
+
+    yield 'blocks', [initiate_exit_block, exit_block], [spec.BeaconBlock]
+    yield 'post', state
+
+    assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
+
+
+@spec_state_test
+def test_transfer(state):
+    # overwrite default 0 to test
+    spec.MAX_TRANSFERS = 1
+
+    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
+    amount = get_balance(state, sender_index)
+
+    transfer = get_valid_transfer(state, state.slot + 1, sender_index, amount, signed=True)
+    recipient_index = transfer.recipient
+    pre_transfer_recipient_balance = get_balance(state, recipient_index)
+
+    # un-activate so validator can transfer
+    state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
+
+    yield 'pre', state
+
+    # Add to state via block transition
+    block = build_empty_block_for_next_slot(state)
+    block.body.transfers.append(transfer)
+    sign_block(state, block)
+
+    yield 'blocks', [block], [spec.BeaconBlock]
+
+    state_transition(state, block)
+    yield 'post', state
+
+    sender_balance = get_balance(state, sender_index)
+    recipient_balance = get_balance(state, recipient_index)
+    assert sender_balance == 0
+    assert recipient_balance == pre_transfer_recipient_balance + amount
+
+
+@spec_state_test
+def test_balance_driven_status_transitions(state):
+    current_epoch = get_current_epoch(state)
+    validator_index = get_active_validator_indices(state, current_epoch)[-1]
+
+    assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
+
+    # set validator balance to below ejection threshold
+    state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE
+
+    yield 'pre', state
+
+    # trigger epoch transition
+    block = build_empty_block_for_next_slot(state)
+    block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, block)
+    state_transition(state, block)
+
+    yield 'blocks', [block], [spec.BeaconBlock]
+    yield 'post', state
+
+    assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
+
+
+@spec_state_test
+def test_historical_batch(state):
+    state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1
+    pre_historical_roots_len = len(state.historical_roots)
+
+    yield 'pre', state
+
+    block = build_empty_block_for_next_slot(state, signed=True)
+    state_transition(state, block)
+
+    yield 'blocks', [block], [spec.BeaconBlock]
+    yield 'post', state
+
+    assert state.slot == block.slot
+    assert get_current_epoch(state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0
+    assert len(state.historical_roots) == pre_historical_roots_len + 1
+
+
+# @spec_state_test
+# def test_eth1_data_votes(state):
+#     yield 'pre', state
+#
+#     expected_votes = 0
+#     assert len(state.eth1_data_votes) == expected_votes
+#
+#     blocks = []
+#     for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1):
+#         block = build_empty_block_for_next_slot(state)
+#         state_transition(state, block)
+#         expected_votes += 1
+#         assert len(state.eth1_data_votes) == expected_votes
+#         blocks.append(block)
+#
+#     block = build_empty_block_for_next_slot(state)
+#     blocks.append(block)
+#
+#     state_transition(state, block)
+#
+#     yield 'blocks', [block], [spec.BeaconBlock]
+#     yield 'post', state
+#
+#     assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
+#     assert len(state.eth1_data_votes) == 1
diff --git a/test_libs/pyspec/eth2spec/test/sanity/test_slots.py b/test_libs/pyspec/eth2spec/test/sanity/test_slots.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/sanity/test_slots.py
@@ -0,0 +1,58 @@
+import eth2spec.phase0.spec as spec
+
+from eth2spec.phase0.spec import process_slots
+from eth2spec.test.helpers.state import get_state_root
+from eth2spec.test.context import spec_state_test
+
+
+@spec_state_test
+def test_slots_1(state):
+    pre_slot = state.slot
+    pre_root = state.hash_tree_root()
+    yield 'pre', state
+
+    slots = 1
+    yield 'slots', slots
+    process_slots(state, state.slot + slots)
+
+    yield 'post', state
+    assert state.slot == pre_slot + 1
+    assert get_state_root(state, pre_slot) == pre_root
+
+
+@spec_state_test
+def test_slots_2(state):
+    yield 'pre', state
+    slots = 2
+    yield 'slots', slots
+    process_slots(state, state.slot + slots)
+    yield 'post', state
+
+
+@spec_state_test
+def test_empty_epoch(state):
+    yield 'pre', state
+    slots = spec.SLOTS_PER_EPOCH
+    yield 'slots', slots
+    process_slots(state, state.slot + slots)
+    yield 'post', state
+
+
+@spec_state_test
+def test_double_empty_epoch(state):
+    yield 'pre', state
+    slots = spec.SLOTS_PER_EPOCH * 2
+    yield 'slots', slots
+    process_slots(state, state.slot + slots)
+    yield 'post', state
+
+
+@spec_state_test
+def test_over_epoch_boundary(state):
+    process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH // 2))
+    yield 'pre', state
+    slots = spec.SLOTS_PER_EPOCH
+    yield 'slots', slots
+    process_slots(state, state.slot + slots)
+    yield 'post', state
+
diff --git a/test_libs/pyspec/tests/test_finality.py b/test_libs/pyspec/eth2spec/test/test_finality.py
similarity index 55%
rename from test_libs/pyspec/tests/test_finality.py
rename to test_libs/pyspec/eth2spec/test/test_finality.py
--- a/test_libs/pyspec/tests/test_finality.py
+++ b/test_libs/pyspec/eth2spec/test/test_finality.py
@@ -1,20 +1,14 @@
 from copy import deepcopy
 
-import pytest
-
 import eth2spec.phase0.spec as spec
-
-from .helpers import (
-    build_empty_block_for_next_slot,
-    fill_aggregate_attestation,
+from eth2spec.phase0.spec import (
     get_current_epoch,
     get_epoch_start_slot,
-    get_valid_attestation,
-    next_epoch,
 )
-
-# mark entire file as 'state'
-pytestmark = pytest.mark.state
+from .context import spec_state_test, never_bls
+from .helpers.state import next_epoch
+from .helpers.block import build_empty_block_for_next_slot, apply_empty_block
+from .helpers.attestations import get_valid_attestation
 
 
 def check_finality(state,
@@ -55,13 +49,11 @@ def next_epoch_with_attestations(state,
             slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
             if slot_to_attest >= get_epoch_start_slot(get_current_epoch(post_state)):
                 cur_attestation = get_valid_attestation(post_state, slot_to_attest)
-                fill_aggregate_attestation(post_state, cur_attestation)
                 block.body.attestations.append(cur_attestation)
 
         if fill_prev_epoch:
             slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
             prev_attestation = get_valid_attestation(post_state, slot_to_attest)
-            fill_aggregate_attestation(post_state, prev_attestation)
             block.body.attestations.append(prev_attestation)
 
         spec.state_transition(post_state, block)
@@ -70,126 +62,140 @@ def next_epoch_with_attestations(state,
     return state, blocks, post_state
 
 
+@never_bls
+@spec_state_test
 def test_finality_rule_4(state):
-    test_state = deepcopy(state)
+    yield 'pre', state
 
     blocks = []
     for epoch in range(4):
-        prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
+        prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
         blocks += new_blocks
 
         # justification/finalization skipped at GENESIS_EPOCH
         if epoch == 0:
-            check_finality(test_state, prev_state, False, False, False)
+            check_finality(state, prev_state, False, False, False)
         # justification/finalization skipped at GENESIS_EPOCH + 1
         elif epoch == 1:
-            check_finality(test_state, prev_state, False, False, False)
+            check_finality(state, prev_state, False, False, False)
         elif epoch == 2:
-            check_finality(test_state, prev_state, True, False, False)
+            check_finality(state, prev_state, True, False, False)
         elif epoch >= 3:
             # rule 4 of finality
-            check_finality(test_state, prev_state, True, True, True)
-            assert test_state.finalized_epoch == prev_state.current_justified_epoch
-            assert test_state.finalized_root == prev_state.current_justified_root
+            check_finality(state, prev_state, True, True, True)
+            assert state.finalized_epoch == prev_state.current_justified_epoch
+            assert state.finalized_root == prev_state.current_justified_root
 
-    return state, blocks, test_state
+    yield 'blocks', blocks, [spec.BeaconBlock]
+    yield 'post', state
 
 
+@never_bls
+@spec_state_test
 def test_finality_rule_1(state):
     # get past first two epochs that finality does not run on
     next_epoch(state)
+    apply_empty_block(state)
     next_epoch(state)
+    apply_empty_block(state)
 
-    pre_state = deepcopy(state)
-    test_state = deepcopy(state)
+    yield 'pre', state
 
     blocks = []
     for epoch in range(3):
-        prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
+        prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
         blocks += new_blocks
 
         if epoch == 0:
-            check_finality(test_state, prev_state, True, False, False)
+            check_finality(state, prev_state, True, False, False)
         elif epoch == 1:
-            check_finality(test_state, prev_state, True, True, False)
+            check_finality(state, prev_state, True, True, False)
         elif epoch == 2:
             # finalized by rule 1
-            check_finality(test_state, prev_state, True, True, True)
-            assert test_state.finalized_epoch == prev_state.previous_justified_epoch
-            assert test_state.finalized_root == prev_state.previous_justified_root
+            check_finality(state, prev_state, True, True, True)
+            assert state.finalized_epoch == prev_state.previous_justified_epoch
+            assert state.finalized_root == prev_state.previous_justified_root
 
-    return pre_state, blocks, test_state
+    yield 'blocks', blocks, [spec.BeaconBlock]
+    yield 'post', state
 
 
+@never_bls
+@spec_state_test
 def test_finality_rule_2(state):
     # get past first two epochs that finality does not run on
     next_epoch(state)
+    apply_empty_block(state)
     next_epoch(state)
+    apply_empty_block(state)
 
-    pre_state = deepcopy(state)
-    test_state = deepcopy(state)
+    yield 'pre', state
 
     blocks = []
     for epoch in range(3):
         if epoch == 0:
-            prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
-            check_finality(test_state, prev_state, True, False, False)
+            prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
+            check_finality(state, prev_state, True, False, False)
         elif epoch == 1:
-            prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
-            check_finality(test_state, prev_state, False, True, False)
+            prev_state, new_blocks, state = next_epoch_with_attestations(state, False, False)
+            check_finality(state, prev_state, False, True, False)
         elif epoch == 2:
-            prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
+            prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
             # finalized by rule 2
-            check_finality(test_state, prev_state, True, False, True)
-            assert test_state.finalized_epoch == prev_state.previous_justified_epoch
-            assert test_state.finalized_root == prev_state.previous_justified_root
+            check_finality(state, prev_state, True, False, True)
+            assert state.finalized_epoch == prev_state.previous_justified_epoch
+            assert state.finalized_root == prev_state.previous_justified_root
 
         blocks += new_blocks
 
-    return pre_state, blocks, test_state
+    yield 'blocks', blocks, [spec.BeaconBlock]
+    yield 'post', state
 
 
+@never_bls
+@spec_state_test
 def test_finality_rule_3(state):
     """
     Test scenario described here
     https://github.com/ethereum/eth2.0-specs/issues/611#issuecomment-463612892
     """
-
     # get past first two epochs that finality does not run on
     next_epoch(state)
+    apply_empty_block(state)
     next_epoch(state)
+    apply_empty_block(state)
 
-    pre_state = deepcopy(state)
-    test_state = deepcopy(state)
+    yield 'pre', state
 
     blocks = []
-    prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
+    prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
     blocks += new_blocks
-    check_finality(test_state, prev_state, True, False, False)
+    check_finality(state, prev_state, True, False, False)
 
     # In epoch N, JE is set to N, prev JE is set to N-1
-    prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
+    prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
     blocks += new_blocks
-    check_finality(test_state, prev_state, True, True, True)
+    check_finality(state, prev_state, True, True, True)
 
     # In epoch N+1, JE is N, prev JE is N-1, and not enough messages get in to do anything
-    prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
+    prev_state, new_blocks, state = next_epoch_with_attestations(state, False, False)
     blocks += new_blocks
-    check_finality(test_state, prev_state, False, True, False)
+    check_finality(state, prev_state, False, True, False)
 
     # In epoch N+2, JE is N, prev JE is N, and enough messages from the previous epoch get in to justify N+1.
     # N+1 now becomes the JE. Not enough messages from epoch N+2 itself get in to justify N+2
-    prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
+    prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
     blocks += new_blocks
     # rule 2
-    check_finality(test_state, prev_state, True, False, True)
+    check_finality(state, prev_state, True, False, True)
 
     # In epoch N+3, LJE is N+1, prev LJE is N, and enough messages get in to justify epochs N+2 and N+3.
-    prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, True)
+    prev_state, new_blocks, state = next_epoch_with_attestations(state, True, True)
     blocks += new_blocks
     # rule 3
-    check_finality(test_state, prev_state, True, True, True)
-    assert test_state.finalized_epoch == prev_state.current_justified_epoch
-    assert test_state.finalized_root == prev_state.current_justified_root
+    check_finality(state, prev_state, True, True, True)
+    assert state.finalized_epoch == prev_state.current_justified_epoch
+    assert state.finalized_root == prev_state.current_justified_root
 
-    return pre_state, blocks, test_state
+    yield 'blocks', blocks, [spec.BeaconBlock]
+    yield 'post', state
diff --git a/test_libs/pyspec/eth2spec/test/utils.py b/test_libs/pyspec/eth2spec/test/utils.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/test/utils.py
@@ -0,0 +1,80 @@
+from typing import Dict, Any, Callable, Iterable
+from eth2spec.debug.encode import encode
+
+
+def spectest(description: str = None):
+    def runner(fn):
+        # this wraps the function, to hide that the function actually is yielding data, instead of returning once.
+        def entry(*args, **kw):
+            # check generator mode, may be None/else.
+            # "pop" removes it, so it is not passed to the inner function.
+            if kw.pop('generator_mode', False) is True:
+                out = {}
+                if description is None:
+                    # fall back on function name for test description
+                    name = fn.__name__
+                    if name.startswith('test_'):
+                        name = name[5:]
+                    out['description'] = name
+                else:
+                    # description can be explicit
+                    out['description'] = description
+                has_contents = False
+                # put all generated data into a dict.
+                for data in fn(*args, **kw):
+                    has_contents = True
+                    # If there is a type argument, encode it as that type.
+                    if len(data) == 3:
+                        (key, value, typ) = data
+                        out[key] = encode(value, typ)
+                    else:
+                        # Otherwise, try to infer the type, but keep it as-is if it's not a SSZ container.
+                        (key, value) = data
+                        if hasattr(value.__class__, 'fields'):
+                            out[key] = encode(value, value.__class__)
+                        else:
+                            out[key] = value
+                if has_contents:
+                    return out
+                else:
+                    return None
+            else:
+                # just complete the function, ignore all yielded data, we are not using it
+                for _ in fn(*args, **kw):
+                    continue
+                return None
+        return entry
+    return runner
+
+
+def with_tags(tags: Dict[str, Any]):
+    """
+    Decorator factory, adds tags (key, value) pairs to the output of the function.
+    Useful to build test-vector annotations with.
+    This decorator is applied after the ``spectest`` decorator is applied.
+    :param tags: dict of tags
+    :return: Decorator.
+    """
+    def runner(fn):
+        def entry(*args, **kw):
+            fn_out = fn(*args, **kw)
+            # do not add tags if the function is not returning a dict at all (i.e. not in generator mode)
+            if fn_out is None:
+                return None
+            return {**tags, **fn_out}
+        return entry
+    return runner
+
+
+def with_args(create_args: Callable[[], Iterable[Any]]):
+    """
+    Decorator factory, adds given extra arguments to the decorated function.
+    :param create_args: function to create arguments with.
+    :return: Decorator.
+    """
+    def runner(fn):
+        # this wraps the function, to hide that the function actually yielding data.
+        def entry(*args, **kw):
+            return fn(*(list(create_args()) + list(args)), **kw)
+        return entry
+    return runner
diff --git a/test_libs/pyspec/eth2spec/utils/bls.py b/test_libs/pyspec/eth2spec/utils/bls.py
new file mode 100644
--- /dev/null
+++ b/test_libs/pyspec/eth2spec/utils/bls.py
@@ -0,0 +1,46 @@
+from py_ecc import bls
+
+# Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing.
+bls_active = True
+
+STUB_SIGNATURE = b'\x11' * 96
+STUB_PUBKEY = b'\x22' * 48
+
+
+def only_with_bls(alt_return=None):
+    """
+    Decorator factory to make a function only run when BLS is active. Otherwise return the default.
+    """
+    def runner(fn):
+        def entry(*args, **kw):
+            if bls_active:
+                return fn(*args, **kw)
+            else:
+                return alt_return
+        return entry
+    return runner
+
+
+@only_with_bls(alt_return=True)
+def bls_verify(pubkey, message_hash, signature, domain):
+    return bls.verify(message_hash=message_hash, pubkey=pubkey, signature=signature, domain=domain)
+
+
+@only_with_bls(alt_return=True)
+def bls_verify_multiple(pubkeys, message_hashes, signature, domain):
+    return bls.verify_multiple(pubkeys, message_hashes, signature, domain)
+
+
+@only_with_bls(alt_return=STUB_PUBKEY)
+def bls_aggregate_pubkeys(pubkeys):
+    return bls.aggregate_pubkeys(pubkeys)
+
+
+@only_with_bls(alt_return=STUB_SIGNATURE)
+def bls_aggregate_signatures(signatures):
+    return bls.aggregate_signatures(signatures)
+
+
+@only_with_bls(alt_return=STUB_SIGNATURE)
+def bls_sign(message_hash, privkey, domain):
+    return bls.sign(message_hash=message_hash, privkey=privkey, domain=domain)
diff --git a/test_libs/pyspec/eth2spec/utils/bls_stub.py b/test_libs/pyspec/eth2spec/utils/bls_stub.py
deleted file mode 100644
--- a/test_libs/pyspec/eth2spec/utils/bls_stub.py
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-def bls_verify(pubkey, message_hash, signature, domain):
-    return True
-
-
-def bls_verify_multiple(pubkeys, message_hashes, signature, domain):
-    return True
-
-
-def bls_aggregate_pubkeys(pubkeys):
-    return b'\x42' * 48
diff --git a/test_libs/pyspec/eth2spec/utils/hash_function.py b/test_libs/pyspec/eth2spec/utils/hash_function.py
--- a/test_libs/pyspec/eth2spec/utils/hash_function.py
+++ b/test_libs/pyspec/eth2spec/utils/hash_function.py
@@ -1,6 +1,4 @@
 from hashlib import sha256
-# from eth_utils import keccak
 
 
 def hash(x): return sha256(x).digest()
-# def hash(x):  return keccak(x)
diff --git a/test_libs/pyspec/tests/block_processing/test_process_attestation.py b/test_libs/pyspec/tests/block_processing/test_process_attestation.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_attestation.py
+++ /dev/null
@@ -1,195 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.spec import (
-    get_current_epoch,
-    process_attestation,
-    slot_to_epoch,
-    state_transition,
-)
-from tests.helpers import (
-    build_empty_block_for_next_slot,
-    get_valid_attestation,
-    next_epoch,
-    next_slot,
-)
-
-
-# mark entire file as 'attestations'
-pytestmark = pytest.mark.attestations
-
-
-def run_attestation_processing(state, attestation, valid=True):
-    """
-    Run ``process_attestation`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_attestation(post_state, attestation)
-        return state, None
-
-    process_attestation(post_state, attestation)
-
-    current_epoch = get_current_epoch(state)
-    if attestation.data.target_epoch == current_epoch:
-        assert len(post_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1
-    else:
-        assert len(post_state.previous_epoch_attestations) == len(state.previous_epoch_attestations) + 1
-
-    return state, post_state
-
-
-def test_success(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    pre_state, post_state = run_attestation_processing(state, attestation)
-
-    return pre_state, attestation, post_state
-
-
-def test_success_prevous_epoch(state):
-    attestation = get_valid_attestation(state)
-    block = build_empty_block_for_next_slot(state)
-    block.slot = state.slot + spec.SLOTS_PER_EPOCH
-    state_transition(state, block)
-
-    pre_state, post_state = run_attestation_processing(state, attestation)
-
-    return pre_state, attestation, post_state
-
-
-def test_success_since_max_epochs_per_crosslink(state):
-    for _ in range(spec.MAX_EPOCHS_PER_CROSSLINK + 2):
-        next_epoch(state)
-
-    attestation = get_valid_attestation(state)
-    data = attestation.data
-    assert data.crosslink.end_epoch - data.crosslink.start_epoch == spec.MAX_EPOCHS_PER_CROSSLINK
-
-    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
-        next_slot(state)
-
-    pre_state, post_state = run_attestation_processing(state, attestation)
-
-    return pre_state, attestation, post_state
-
-
-def test_before_inclusion_delay(state):
-    attestation = get_valid_attestation(state)
-    # do not increment slot to allow for inclusion delay
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_after_epoch_slots(state):
-    attestation = get_valid_attestation(state)
-    block = build_empty_block_for_next_slot(state)
-    # increment past latest inclusion slot
-    block.slot = state.slot + spec.SLOTS_PER_EPOCH + 1
-    state_transition(state, block)
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_bad_source_epoch(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    attestation.data.source_epoch += 10
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_bad_source_root(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    attestation.data.source_root = b'\x42' * 32
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_non_zero_crosslink_data_root(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    attestation.data.crosslink.data_root = b'\x42' * 32
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_bad_previous_crosslink(state):
-    next_epoch(state)
-    attestation = get_valid_attestation(state)
-    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
-        next_slot(state)
-
-    attestation.data.crosslink.parent_root = b'\x27' * 32
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_bad_crosslink_start_epoch(state):
-    next_epoch(state)
-    attestation = get_valid_attestation(state)
-    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
-        next_slot(state)
-
-    attestation.data.crosslink.start_epoch += 1
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_bad_crosslink_end_epoch(state):
-    next_epoch(state)
-    attestation = get_valid_attestation(state)
-    for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
-        next_slot(state)
-
-    attestation.data.crosslink.end_epoch += 1
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_non_empty_custody_bitfield(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield)
-
-    pre_state, post_state = run_attestation_processing(state, attestation, False)
-
-    return pre_state, attestation, post_state
-
-
-def test_empty_aggregation_bitfield(state):
-    attestation = get_valid_attestation(state)
-    state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-
-    attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield)
-
-    pre_state, post_state = run_attestation_processing(state, attestation)
-
-    return pre_state, attestation, post_state
diff --git a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-from eth2spec.phase0.spec import (
-    get_beacon_proposer_index,
-    process_attester_slashing,
-)
-from tests.helpers import (
-    get_balance,
-    get_valid_attester_slashing,
-    next_epoch,
-)
-
-# mark entire file as 'attester_slashing'
-pytestmark = pytest.mark.attester_slashings
-
-
-def run_attester_slashing_processing(state, attester_slashing, valid=True):
-    """
-    Run ``process_attester_slashing`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_attester_slashing(post_state, attester_slashing)
-        return state, None
-
-    process_attester_slashing(post_state, attester_slashing)
-
-    slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
-    slashed_validator = post_state.validator_registry[slashed_index]
-    assert slashed_validator.slashed
-    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
-    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
-    # lost whistleblower reward
-    assert (
-        get_balance(post_state, slashed_index) <
-        get_balance(state, slashed_index)
-    )
-    proposer_index = get_beacon_proposer_index(state)
-    # gained whistleblower reward
-    assert (
-        get_balance(post_state, proposer_index) >
-        get_balance(state, proposer_index)
-    )
-
-    return state, post_state
-
-
-def test_success_double(state):
-    attester_slashing = get_valid_attester_slashing(state)
-
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing)
-
-    return pre_state, attester_slashing, post_state
-
-
-def test_success_surround(state):
-    next_epoch(state)
-    state.current_justified_epoch += 1
-    attester_slashing = get_valid_attester_slashing(state)
-
-    # set attestion1 to surround attestation 2
-    attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1
-    attester_slashing.attestation_1.data.target_epoch = attester_slashing.attestation_2.data.target_epoch + 1
-
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing)
-
-    return pre_state, attester_slashing, post_state
-
-
-def test_same_data(state):
-    attester_slashing = get_valid_attester_slashing(state)
-
-    attester_slashing.attestation_1.data = attester_slashing.attestation_2.data
-
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
-
-    return pre_state, attester_slashing, post_state
-
-
-def test_no_double_or_surround(state):
-    attester_slashing = get_valid_attester_slashing(state)
-
-    attester_slashing.attestation_1.data.target_epoch += 1
-
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
-
-    return pre_state, attester_slashing, post_state
-
-
-def test_participants_already_slashed(state):
-    attester_slashing = get_valid_attester_slashing(state)
-
-    # set all indices to slashed
-    attestation_1 = attester_slashing.attestation_1
-    validator_indices = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
-    for index in validator_indices:
-        state.validator_registry[index].slashed = True
-
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
-
-    return pre_state, attester_slashing, post_state
-
-
-def test_custody_bit_0_and_1(state):
-    attester_slashing = get_valid_attester_slashing(state)
-
-    attester_slashing.attestation_1.custody_bit_1_indices = (
-        attester_slashing.attestation_1.custody_bit_0_indices
-    )
-    pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
-
-    return pre_state, attester_slashing, post_state
diff --git a/test_libs/pyspec/tests/block_processing/test_process_block_header.py b/test_libs/pyspec/tests/block_processing/test_process_block_header.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_block_header.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from copy import deepcopy
-import pytest
-
-
-from eth2spec.phase0.spec import (
-    get_beacon_proposer_index,
-    process_slot,
-    process_block_header,
-)
-from tests.helpers import (
-    advance_slot,
-    build_empty_block_for_next_slot,
-    next_slot,
-)
-
-# mark entire file as 'header'
-pytestmark = pytest.mark.header
-
-
-def prepare_state_for_header_processing(state):
-    process_slot(state)
-    advance_slot(state)
-
-
-def run_block_header_processing(state, block, valid=True):
-    """
-    Run ``process_block_header`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    prepare_state_for_header_processing(state)
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_block_header(post_state, block)
-        return state, None
-
-    process_block_header(post_state, block)
-    return state, post_state
-
-
-def test_success(state):
-    block = build_empty_block_for_next_slot(state)
-    pre_state, post_state = run_block_header_processing(state, block)
-    return state, block, post_state
-
-
-def test_invalid_slot(state):
-    block = build_empty_block_for_next_slot(state)
-    block.slot = state.slot + 2  # invalid slot
-
-    pre_state, post_state = run_block_header_processing(state, block, valid=False)
-    return pre_state, block, None
-
-
-def test_invalid_parent_block_root(state):
-    block = build_empty_block_for_next_slot(state)
-    block.parent_root = b'\12' * 32  # invalid prev root
-
-    pre_state, post_state = run_block_header_processing(state, block, valid=False)
-    return pre_state, block, None
-
-
-def test_proposer_slashed(state):
-    # use stub state to get proposer index of next slot
-    stub_state = deepcopy(state)
-    next_slot(stub_state)
-    proposer_index = get_beacon_proposer_index(stub_state)
-
-    # set proposer to slashed
-    state.validator_registry[proposer_index].slashed = True
-
-    block = build_empty_block_for_next_slot(state)
-
-    pre_state, post_state = run_block_header_processing(state, block, valid=False)
-    return pre_state, block, None
diff --git a/test_libs/pyspec/tests/block_processing/test_process_deposit.py b/test_libs/pyspec/tests/block_processing/test_process_deposit.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_deposit.py
+++ /dev/null
@@ -1,141 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.spec import (
-    ZERO_HASH,
-    process_deposit,
-)
-from tests.helpers import (
-    get_balance,
-    build_deposit,
-    privkeys,
-    pubkeys,
-)
-
-
-# mark entire file as 'deposits'
-pytestmark = pytest.mark.deposits
-
-
-def test_success(state):
-    pre_state = deepcopy(state)
-    # fill previous deposits with zero-hash
-    deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    index = len(deposit_data_leaves)
-    pubkey = pubkeys[index]
-    privkey = privkeys[index]
-    deposit, root, deposit_data_leaves = build_deposit(
-        pre_state,
-        deposit_data_leaves,
-        pubkey,
-        privkey,
-        spec.MAX_EFFECTIVE_BALANCE,
-    )
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
-
-    post_state = deepcopy(pre_state)
-
-    process_deposit(post_state, deposit)
-
-    assert len(post_state.validator_registry) == len(state.validator_registry) + 1
-    assert len(post_state.balances) == len(state.balances) + 1
-    assert post_state.validator_registry[index].pubkey == pubkeys[index]
-    assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE
-    assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
-
-    return pre_state, deposit, post_state
-
-
-def test_success_top_up(state):
-    pre_state = deepcopy(state)
-    deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    validator_index = 0
-    amount = spec.MAX_EFFECTIVE_BALANCE // 4
-    pubkey = pubkeys[validator_index]
-    privkey = privkeys[validator_index]
-    deposit, root, deposit_data_leaves = build_deposit(
-        pre_state,
-        deposit_data_leaves,
-        pubkey,
-        privkey,
-        amount,
-    )
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
-    pre_balance = get_balance(pre_state, validator_index)
-
-    post_state = deepcopy(pre_state)
-
-    process_deposit(post_state, deposit)
-
-    assert len(post_state.validator_registry) == len(state.validator_registry)
-    assert len(post_state.balances) == len(state.balances)
-    assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
-    assert get_balance(post_state, validator_index) == pre_balance + amount
-
-    return pre_state, deposit, post_state
-
-
-def test_wrong_index(state):
-    pre_state = deepcopy(state)
-    deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    index = len(deposit_data_leaves)
-    pubkey = pubkeys[index]
-    privkey = privkeys[index]
-    deposit, root, deposit_data_leaves = build_deposit(
-        pre_state,
-        deposit_data_leaves,
-        pubkey,
-        privkey,
-        spec.MAX_EFFECTIVE_BALANCE,
-    )
-
-    # mess up deposit_index
-    deposit.index = pre_state.deposit_index + 1
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
-
-    post_state = deepcopy(pre_state)
-
-    with pytest.raises(AssertionError):
-        process_deposit(post_state, deposit)
-
-    return pre_state, deposit, None
-
-
-def test_bad_merkle_proof(state):
-    pre_state = deepcopy(state)
-    deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    index = len(deposit_data_leaves)
-    pubkey = pubkeys[index]
-    privkey = privkeys[index]
-    deposit, root, deposit_data_leaves = build_deposit(
-        pre_state,
-        deposit_data_leaves,
-        pubkey,
-        privkey,
-        spec.MAX_EFFECTIVE_BALANCE,
-    )
-
-    # mess up merkle branch
-    deposit.proof[-1] = spec.ZERO_HASH
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
-
-    post_state = deepcopy(pre_state)
-
-    with pytest.raises(AssertionError):
-        process_deposit(post_state, deposit)
-
-    return pre_state, deposit, None
diff --git a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-from eth2spec.phase0.spec import (
-    get_current_epoch,
-    process_proposer_slashing,
-)
-from tests.helpers import (
-    get_balance,
-    get_valid_proposer_slashing,
-)
-
-# mark entire file as 'proposer_slashings'
-pytestmark = pytest.mark.proposer_slashings
-
-
-def run_proposer_slashing_processing(state, proposer_slashing, valid=True):
-    """
-    Run ``process_proposer_slashing`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_proposer_slashing(post_state, proposer_slashing)
-        return state, None
-
-    process_proposer_slashing(post_state, proposer_slashing)
-
-    slashed_validator = post_state.validator_registry[proposer_slashing.proposer_index]
-    assert slashed_validator.slashed
-    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
-    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
-    # lost whistleblower reward
-    assert (
-        get_balance(post_state, proposer_slashing.proposer_index) <
-        get_balance(state, proposer_slashing.proposer_index)
-    )
-
-    return state, post_state
-
-
-def test_success(state):
-    proposer_slashing = get_valid_proposer_slashing(state)
-
-    pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing)
-
-    return pre_state, proposer_slashing, post_state
-
-
-def test_epochs_are_different(state):
-    proposer_slashing = get_valid_proposer_slashing(state)
-
-    # set slots to be in different epochs
-    proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH
-
-    pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
-
-    return pre_state, proposer_slashing, post_state
-
-
-def test_headers_are_same(state):
-    proposer_slashing = get_valid_proposer_slashing(state)
-
-    # set headers to be the same
-    proposer_slashing.header_2 = proposer_slashing.header_1
-
-    pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
-
-    return pre_state, proposer_slashing, post_state
-
-
-def test_proposer_is_slashed(state):
-    proposer_slashing = get_valid_proposer_slashing(state)
-
-    # set proposer to slashed
-    state.validator_registry[proposer_slashing.proposer_index].slashed = True
-
-    pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
-
-    return pre_state, proposer_slashing, post_state
-
-
-def test_proposer_is_withdrawn(state):
-    proposer_slashing = get_valid_proposer_slashing(state)
-
-    # set proposer withdrawable_epoch in past
-    current_epoch = get_current_epoch(state)
-    proposer_index = proposer_slashing.proposer_index
-    state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1
-
-    pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
-
-    return pre_state, proposer_slashing, post_state
diff --git a/test_libs/pyspec/tests/block_processing/test_process_transfer.py b/test_libs/pyspec/tests/block_processing/test_process_transfer.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_process_transfer.py
+++ /dev/null
@@ -1,141 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.spec import (
-    get_active_validator_indices,
-    get_beacon_proposer_index,
-    get_current_epoch,
-    process_transfer,
-)
-from tests.helpers import (
-    get_valid_transfer,
-    next_epoch,
-)
-
-
-# mark entire file as 'transfers'
-pytestmark = pytest.mark.transfers
-
-
-def run_transfer_processing(state, transfer, valid=True):
-    """
-    Run ``process_transfer`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_transfer(post_state, transfer)
-        return state, None
-
-
-    process_transfer(post_state, transfer)
-
-    proposer_index = get_beacon_proposer_index(state)
-    pre_transfer_sender_balance = state.balances[transfer.sender]
-    pre_transfer_recipient_balance = state.balances[transfer.recipient]
-    pre_transfer_proposer_balance = state.balances[proposer_index]
-    sender_balance = post_state.balances[transfer.sender]
-    recipient_balance = post_state.balances[transfer.recipient]
-    assert sender_balance == pre_transfer_sender_balance - transfer.amount - transfer.fee
-    assert recipient_balance == pre_transfer_recipient_balance + transfer.amount
-    assert post_state.balances[proposer_index] == pre_transfer_proposer_balance + transfer.fee
-
-    return state, post_state
-
-
-def test_success_non_activated(state):
-    transfer = get_valid_transfer(state)
-    # un-activate so validator can transfer
-    state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
-
-    pre_state, post_state = run_transfer_processing(state, transfer)
-
-    return pre_state, transfer, post_state
-
-
-def test_success_withdrawable(state):
-    next_epoch(state)
-
-    transfer = get_valid_transfer(state)
-
-    # withdrawable_epoch in past so can transfer
-    state.validator_registry[transfer.sender].withdrawable_epoch = get_current_epoch(state) - 1
-
-    pre_state, post_state = run_transfer_processing(state, transfer)
-
-    return pre_state, transfer, post_state
-
-
-def test_success_active_above_max_effective(state):
-    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
-    amount = spec.MAX_EFFECTIVE_BALANCE // 32
-    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + amount
-    transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0)
-
-    pre_state, post_state = run_transfer_processing(state, transfer)
-
-    return pre_state, transfer, post_state
-
-
-def test_active_but_transfer_past_effective_balance(state):
-    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
-    amount = spec.MAX_EFFECTIVE_BALANCE // 32
-    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
-    transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0)
-
-    pre_state, post_state = run_transfer_processing(state, transfer, False)
-
-    return pre_state, transfer, post_state
-
-
-def test_incorrect_slot(state):
-    transfer = get_valid_transfer(state, slot=state.slot+1)
-    # un-activate so validator can transfer
-    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
-
-    pre_state, post_state = run_transfer_processing(state, transfer, False)
-
-    return pre_state, transfer, post_state
-
-
-def test_insufficient_balance(state):
-    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
-    amount = spec.MAX_EFFECTIVE_BALANCE
-    state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
-    transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount + 1, fee=0)
-
-    # un-activate so validator can transfer
-    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
-
-    pre_state, post_state = run_transfer_processing(state, transfer, False)
-
-    return pre_state, transfer, post_state
-
-
-def test_no_dust(state):
-    sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1]
-    balance = state.balances[sender_index]
-    transfer = get_valid_transfer(state, sender_index=sender_index, amount=balance - spec.MIN_DEPOSIT_AMOUNT + 1, fee=0)
-
-    # un-activate so validator can transfer
-    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
-
-    pre_state, post_state = run_transfer_processing(state, transfer, False)
-
-    return pre_state, transfer, post_state
-
-
-def test_invalid_pubkey(state):
-    transfer = get_valid_transfer(state)
-    state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH
-
-    # un-activate so validator can transfer
-    state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
-
-    pre_state, post_state = run_transfer_processing(state, transfer, False)
-
-    return pre_state, transfer, post_state
diff --git a/test_libs/pyspec/tests/block_processing/test_voluntary_exit.py b/test_libs/pyspec/tests/block_processing/test_voluntary_exit.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/block_processing/test_voluntary_exit.py
+++ /dev/null
@@ -1,163 +0,0 @@
-from copy import deepcopy
-import pytest
-
-import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.spec import (
-    get_active_validator_indices,
-    get_churn_limit,
-    get_current_epoch,
-    process_voluntary_exit,
-)
-from tests.helpers import (
-    build_voluntary_exit,
-    pubkey_to_privkey,
-)
-
-
-# mark entire file as 'voluntary_exits'
-pytestmark = pytest.mark.voluntary_exits
-
-
-def run_voluntary_exit_processing(state, voluntary_exit, valid=True):
-    """
-    Run ``process_voluntary_exit`` returning the pre and post state.
-    If ``valid == False``, run expecting ``AssertionError``
-    """
-    post_state = deepcopy(state)
-
-    if not valid:
-        with pytest.raises(AssertionError):
-            process_voluntary_exit(post_state, voluntary_exit)
-        return state, None
-
-    process_voluntary_exit(post_state, voluntary_exit)
-
-    validator_index = voluntary_exit.validator_index
-    assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
-    assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
-
-    return state, post_state
-
-
-def test_success(state):
-    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
-    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
-
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[0]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-
-    voluntary_exit = build_voluntary_exit(
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-    )
-
-    pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit)
-    return pre_state, voluntary_exit, post_state
-
-
-def test_success_exit_queue(state):
-    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
-    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
-
-    current_epoch = get_current_epoch(state)
-
-    # exit `MAX_EXITS_PER_EPOCH`
-    initial_indices = get_active_validator_indices(state, current_epoch)[:get_churn_limit(state)]
-    post_state = state
-    for index in initial_indices:
-        privkey = pubkey_to_privkey[state.validator_registry[index].pubkey]
-        voluntary_exit = build_voluntary_exit(
-            state,
-            current_epoch,
-            index,
-            privkey,
-        )
-
-        pre_state, post_state = run_voluntary_exit_processing(post_state, voluntary_exit)
-
-    # exit an additional validator
-    validator_index = get_active_validator_indices(state, current_epoch)[-1]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-    voluntary_exit = build_voluntary_exit(
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-    )
-
-    pre_state, post_state = run_voluntary_exit_processing(post_state, voluntary_exit)
-
-    assert (
-        post_state.validator_registry[validator_index].exit_epoch ==
-        post_state.validator_registry[initial_indices[0]].exit_epoch + 1
-    )
-
-    return pre_state, voluntary_exit, post_state
-
-
-def test_validator_not_active(state):
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[0]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-
-    state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
-
-    #
-    # build and test voluntary exit
-    #
-    voluntary_exit = build_voluntary_exit(
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-    )
-
-    pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False)
-    return pre_state, voluntary_exit, post_state
-
-
-def test_validator_already_exited(state):
-    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
-    state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
-
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[0]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-
-    # but validator already has exited
-    state.validator_registry[validator_index].exit_epoch = current_epoch + 2
-
-    voluntary_exit = build_voluntary_exit(
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-    )
-
-    pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False)
-    return pre_state, voluntary_exit, post_state
-
-
-def test_validator_not_active_long_enough(state):
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[0]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-
-    voluntary_exit = build_voluntary_exit(
-        state,
-        current_epoch,
-        validator_index,
-        privkey,
-    )
-
-    assert (
-        current_epoch - state.validator_registry[validator_index].activation_epoch <
-        spec.PERSISTENT_COMMITTEE_PERIOD
-    )
-
-    pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False)
-    return pre_state, voluntary_exit, post_state
diff --git a/test_libs/pyspec/tests/conftest.py b/test_libs/pyspec/tests/conftest.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/conftest.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import pytest
-
-from eth2spec.phase0 import spec
-from preset_loader import loader
-
-from .helpers import (
-    create_genesis_state,
-)
-
-
-def pytest_addoption(parser):
-    parser.addoption(
-        "--config", action="store", default="minimal", help="config: make the pyspec use the specified configuration"
-    )
-
-
-@pytest.fixture(autouse=True)
-def config(request):
-    config_name = request.config.getoption("--config")
-    presets = loader.load_presets('../../configs/', config_name)
-    spec.apply_constants_preset(presets)
-
-
-@pytest.fixture
-def num_validators(config):
-    return spec.SLOTS_PER_EPOCH * 8
-
-
-@pytest.fixture
-def deposit_data_leaves():
-    return list()
-
-
-@pytest.fixture
-def state(num_validators, deposit_data_leaves):
-    return create_genesis_state(num_validators, deposit_data_leaves)
diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/helpers.py
+++ /dev/null
@@ -1,429 +0,0 @@
-from copy import deepcopy
-
-from py_ecc import bls
-
-import eth2spec.phase0.spec as spec
-from eth2spec.utils.minimal_ssz import signing_root
-from eth2spec.phase0.spec import (
-    # constants
-    ZERO_HASH,
-    MAX_EPOCHS_PER_CROSSLINK,
-    # SSZ
-    Attestation,
-    AttestationData,
-    AttestationDataAndCustodyBit,
-    AttesterSlashing,
-    BeaconBlock,
-    BeaconBlockHeader,
-    Crosslink,
-    Deposit,
-    DepositData,
-    Eth1Data,
-    ProposerSlashing,
-    Transfer,
-    VoluntaryExit,
-    # functions
-    convert_to_indexed,
-    bls_domain,
-    get_active_validator_indices,
-    get_attesting_indices,
-    get_block_root,
-    get_block_root_at_slot,
-    get_crosslink_committee,
-    get_current_epoch,
-    get_domain,
-    get_epoch_start_slot,
-    get_genesis_beacon_state,
-    get_previous_epoch,
-    get_shard_delta,
-    hash_tree_root,
-    slot_to_epoch,
-    state_transition,
-    verify_merkle_branch,
-    hash,
-)
-from eth2spec.utils.merkle_minimal import (
-    calc_merkle_tree_from_leaves,
-    get_merkle_proof,
-    get_merkle_root,
-)
-
-
-privkeys = [i + 1 for i in range(1024)]
-pubkeys = [bls.privtopub(privkey) for privkey in privkeys]
-pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
-
-
-def advance_slot(state) -> None:
-    state.slot += 1
-
-
-def get_balance(state, index):
-    return state.balances[index]
-
-
-def set_bitfield_bit(bitfield, i):
-    """
-    Set the bit in ``bitfield`` at position ``i`` to ``1``.
-    """
-    byte_index = i // 8
-    bit_index = i % 8
-    return (
-        bitfield[:byte_index] +
-        bytes([bitfield[byte_index] | (1 << bit_index)]) +
-        bitfield[byte_index+1:]
-    )
-
-
-def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None):
-    if not deposit_data_leaves:
-        deposit_data_leaves = []
-    signature = b'\x33' * 96
-
-    deposit_data_list = []
-    for i in range(num_validators):
-        pubkey = pubkeys[i]
-        deposit_data = DepositData(
-            pubkey=pubkey,
-            # insecurely use pubkey as withdrawal key as well
-            withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
-            amount=spec.MAX_EFFECTIVE_BALANCE,
-            signature=signature,
-        )
-        item = deposit_data.hash_tree_root()
-        deposit_data_leaves.append(item)
-        tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
-        root = get_merkle_root((tuple(deposit_data_leaves)))
-        proof = list(get_merkle_proof(tree, item_index=i))
-        assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, i, root)
-        deposit_data_list.append(deposit_data)
-
-    genesis_validator_deposits = []
-    for i in range(num_validators):
-        genesis_validator_deposits.append(Deposit(
-            proof=list(get_merkle_proof(tree, item_index=i)),
-            index=i,
-            data=deposit_data_list[i]
-        ))
-    return genesis_validator_deposits, root
-
-
-def create_genesis_state(num_validators, deposit_data_leaves=None):
-    initial_deposits, deposit_root = create_mock_genesis_validator_deposits(
-        num_validators,
-        deposit_data_leaves,
-    )
-    return get_genesis_beacon_state(
-        initial_deposits,
-        genesis_time=0,
-        genesis_eth1_data=Eth1Data(
-            deposit_root=deposit_root,
-            deposit_count=len(initial_deposits),
-            block_hash=spec.ZERO_HASH,
-        ),
-    )
-
-
-def build_empty_block_for_next_slot(state):
-    empty_block = BeaconBlock()
-    empty_block.slot = state.slot + 1
-    empty_block.body.eth1_data.deposit_count = state.deposit_index
-    previous_block_header = deepcopy(state.latest_block_header)
-    if previous_block_header.state_root == spec.ZERO_HASH:
-        previous_block_header.state_root = state.hash_tree_root()
-    empty_block.parent_root = signing_root(previous_block_header)
-    return empty_block
-
-
-def build_deposit_data(state, pubkey, privkey, amount):
-    deposit_data = DepositData(
-        pubkey=pubkey,
-        # insecurely use pubkey as withdrawal key as well
-        withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
-        amount=amount,
-    )
-    signature = bls.sign(
-        message_hash=signing_root(deposit_data),
-        privkey=privkey,
-        domain=bls_domain(spec.DOMAIN_DEPOSIT),
-    )
-    deposit_data.signature = signature
-    return deposit_data
-
-
-def build_attestation_data(state, slot, shard):
-    assert state.slot >= slot
-
-    if slot == state.slot:
-        block_root = build_empty_block_for_next_slot(state).parent_root
-    else:
-        block_root = get_block_root_at_slot(state, slot)
-
-    current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
-    if slot < current_epoch_start_slot:
-        epoch_boundary_root = get_block_root(state, get_previous_epoch(state))
-    elif slot == current_epoch_start_slot:
-        epoch_boundary_root = block_root
-    else:
-        epoch_boundary_root = get_block_root(state, get_current_epoch(state))
-
-    if slot < current_epoch_start_slot:
-        justified_epoch = state.previous_justified_epoch
-        justified_block_root = state.previous_justified_root
-    else:
-        justified_epoch = state.current_justified_epoch
-        justified_block_root = state.current_justified_root
-
-    crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks
-    parent_crosslink = crosslinks[shard]
-    return AttestationData(
-        beacon_block_root=block_root,
-        source_epoch=justified_epoch,
-        source_root=justified_block_root,
-        target_epoch=slot_to_epoch(slot),
-        target_root=epoch_boundary_root,
-        crosslink=Crosslink(
-            shard=shard,
-            start_epoch=parent_crosslink.end_epoch,
-            end_epoch=min(slot_to_epoch(slot), parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK),
-            data_root=spec.ZERO_HASH,
-            parent_root=hash_tree_root(parent_crosslink),
-        ),
-    )
-
-
-def build_voluntary_exit(state, epoch, validator_index, privkey):
-    voluntary_exit = VoluntaryExit(
-        epoch=epoch,
-        validator_index=validator_index,
-    )
-    voluntary_exit.signature = bls.sign(
-        message_hash=signing_root(voluntary_exit),
-        privkey=privkey,
-        domain=get_domain(
-            state=state,
-            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
-            message_epoch=epoch,
-        )
-    )
-
-    return voluntary_exit
-
-
-def build_deposit(state,
-                  deposit_data_leaves,
-                  pubkey,
-                  privkey,
-                  amount):
-    deposit_data = build_deposit_data(state, pubkey, privkey, amount)
-
-    item = deposit_data.hash_tree_root()
-    index = len(deposit_data_leaves)
-    deposit_data_leaves.append(item)
-    tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
-    root = get_merkle_root((tuple(deposit_data_leaves)))
-    proof = list(get_merkle_proof(tree, item_index=index))
-    assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
-
-    deposit = Deposit(
-        proof=list(proof),
-        index=index,
-        data=deposit_data,
-    )
-
-    return deposit, root, deposit_data_leaves
-
-
-def get_valid_proposer_slashing(state):
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[-1]
-    privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
-    slot = state.slot
-
-    header_1 = BeaconBlockHeader(
-        slot=slot,
-        parent_root=ZERO_HASH,
-        state_root=ZERO_HASH,
-        body_root=ZERO_HASH,
-    )
-    header_2 = deepcopy(header_1)
-    header_2.parent_root = b'\x02' * 32
-    header_2.slot = slot + 1
-
-    domain = get_domain(
-        state=state,
-        domain_type=spec.DOMAIN_BEACON_PROPOSER,
-    )
-    header_1.signature = bls.sign(
-        message_hash=signing_root(header_1),
-        privkey=privkey,
-        domain=domain,
-    )
-    header_2.signature = bls.sign(
-        message_hash=signing_root(header_2),
-        privkey=privkey,
-        domain=domain,
-    )
-
-    return ProposerSlashing(
-        proposer_index=validator_index,
-        header_1=header_1,
-        header_2=header_2,
-    )
-
-
-def get_valid_attester_slashing(state):
-    attestation_1 = get_valid_attestation(state)
-    attestation_2 = deepcopy(attestation_1)
-    attestation_2.data.target_root = b'\x01' * 32
-
-    return AttesterSlashing(
-        attestation_1=convert_to_indexed(state, attestation_1),
-        attestation_2=convert_to_indexed(state, attestation_2),
-    )
-
-
-def get_valid_attestation(state, slot=None):
-    if slot is None:
-        slot = state.slot
-
-    if slot_to_epoch(slot) == get_current_epoch(state):
-        shard = (state.latest_start_shard + slot) % spec.SLOTS_PER_EPOCH
-    else:
-        previous_shard_delta = get_shard_delta(state, get_previous_epoch(state))
-        shard = (state.latest_start_shard - previous_shard_delta + slot) % spec.SHARD_COUNT
-
-    attestation_data = build_attestation_data(state, slot, shard)
-
-    crosslink_committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.crosslink.shard)
-
-    committee_size = len(crosslink_committee)
-    bitfield_length = (committee_size + 7) // 8
-    aggregation_bitfield = b'\xC0' + b'\x00' * (bitfield_length - 1)
-    custody_bitfield = b'\x00' * bitfield_length
-    attestation = Attestation(
-        aggregation_bitfield=aggregation_bitfield,
-        data=attestation_data,
-        custody_bitfield=custody_bitfield,
-    )
-    participants = get_attesting_indices(
-        state,
-        attestation.data,
-        attestation.aggregation_bitfield,
-    )
-    assert len(participants) == 2
-
-    signatures = []
-    for validator_index in participants:
-        privkey = privkeys[validator_index]
-        signatures.append(
-            get_attestation_signature(
-                state,
-                attestation.data,
-                privkey
-            )
-        )
-
-    attestation.aggregation_signature = bls.aggregate_signatures(signatures)
-    return attestation
-
-
-def get_valid_transfer(state, slot=None, sender_index=None, amount=None, fee=None):
-    if slot is None:
-        slot = state.slot
-    current_epoch = get_current_epoch(state)
-    if sender_index is None:
-        sender_index = get_active_validator_indices(state, current_epoch)[-1]
-    recipient_index = get_active_validator_indices(state, current_epoch)[0]
-    transfer_pubkey = pubkeys[-1]
-    transfer_privkey = privkeys[-1]
-
-    if fee is None:
-        fee = get_balance(state, sender_index) // 32
-    if amount is None:
-        amount = get_balance(state, sender_index) - fee
-
-    transfer = Transfer(
-        sender=sender_index,
-        recipient=recipient_index,
-        amount=amount,
-        fee=fee,
-        slot=slot,
-        pubkey=transfer_pubkey,
-        signature=ZERO_HASH,
-    )
-    transfer.signature = bls.sign(
-        message_hash=signing_root(transfer),
-        privkey=transfer_privkey,
-        domain=get_domain(
-            state=state,
-            domain_type=spec.DOMAIN_TRANSFER,
-            message_epoch=get_current_epoch(state),
-        )
-    )
-
-    # ensure withdrawal_credentials reproducable
-    state.validator_registry[transfer.sender].withdrawal_credentials = (
-        spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(transfer.pubkey)[1:]
-    )
-
-    return transfer
-
-
-def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0):
-    message_hash = AttestationDataAndCustodyBit(
-        data=attestation_data,
-        custody_bit=custody_bit,
-    ).hash_tree_root()
-
-    return bls.sign(
-        message_hash=message_hash,
-        privkey=privkey,
-        domain=get_domain(
-            state=state,
-            domain_type=spec.DOMAIN_ATTESTATION,
-            message_epoch=attestation_data.target_epoch,
-        )
-    )
-
-
-def fill_aggregate_attestation(state, attestation):
-    crosslink_committee = get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.crosslink.shard)
-    for i in range(len(crosslink_committee)):
-        attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
-
-
-def add_attestation_to_state(state, attestation, slot):
-    block = build_empty_block_for_next_slot(state)
-    block.slot = slot
-    block.body.attestations.append(attestation)
-    state_transition(state, block)
-
-
-def next_slot(state):
-    """
-    Transition to the next slot via an empty block.
-    Return the empty block that triggered the transition.
-    """
-    block = build_empty_block_for_next_slot(state)
-    state_transition(state, block)
-    return block
-
-
-def next_epoch(state):
-    """
-    Transition to the start slot of the next epoch via an empty block.
-    Return the empty block that triggered the transition.
-    """
-    block = build_empty_block_for_next_slot(state)
-    block.slot += spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH)
-    state_transition(state, block)
-    return block
-
-
-def get_state_root(state, slot) -> bytes:
-    """
-    Return the state root at a recent ``slot``.
-    """
-    assert slot < state.slot <= slot + spec.SLOTS_PER_HISTORICAL_ROOT
-    return state.latest_state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT]
diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py
deleted file mode 100644
--- a/test_libs/pyspec/tests/test_sanity.py
+++ /dev/null
@@ -1,436 +0,0 @@
-from copy import deepcopy
-
-import pytest
-
-from py_ecc import bls
-import eth2spec.phase0.spec as spec
-
-from eth2spec.utils.minimal_ssz import signing_root
-from eth2spec.phase0.spec import (
-    # constants
-    ZERO_HASH,
-    SLOTS_PER_HISTORICAL_ROOT,
-    # SSZ
-    Deposit,
-    Transfer,
-    VoluntaryExit,
-    # functions
-    get_active_validator_indices,
-    get_beacon_proposer_index,
-    get_block_root_at_slot,
-    get_current_epoch,
-    get_domain,
-    process_slot,
-    verify_merkle_branch,
-    state_transition,
-    hash,
-)
-from eth2spec.utils.merkle_minimal import (
-    calc_merkle_tree_from_leaves,
-    get_merkle_proof,
-    get_merkle_root,
-)
-from .helpers import (
-    advance_slot,
-    get_balance,
-    build_deposit_data,
-    build_empty_block_for_next_slot,
-    fill_aggregate_attestation,
-    get_state_root,
-    get_valid_attestation,
-    get_valid_attester_slashing,
-    get_valid_proposer_slashing,
-    next_slot,
-    privkeys,
-    pubkeys,
-)
-
-
-# mark entire file as 'sanity'
-pytestmark = pytest.mark.sanity
-
-
-def test_slot_transition(state):
-    test_state = deepcopy(state)
-    process_slot(test_state)
-    advance_slot(test_state)
-    assert test_state.slot == state.slot + 1
-    assert get_state_root(test_state, state.slot) == state.hash_tree_root()
-    return test_state
-
-
-def test_empty_block_transition(state):
-    test_state = deepcopy(state)
-
-    block = build_empty_block_for_next_slot(test_state)
-    state_transition(test_state, block)
-
-    assert len(test_state.eth1_data_votes) == len(state.eth1_data_votes) + 1
-    assert get_block_root_at_slot(test_state, state.slot) == block.parent_root
-
-    return state, [block], test_state
-
-
-def test_skipped_slots(state):
-    test_state = deepcopy(state)
-    block = build_empty_block_for_next_slot(test_state)
-    block.slot += 3
-
-    state_transition(test_state, block)
-
-    assert test_state.slot == block.slot
-    for slot in range(state.slot, test_state.slot):
-        assert get_block_root_at_slot(test_state, slot) == block.parent_root
-
-    return state, [block], test_state
-
-
-def test_empty_epoch_transition(state):
-    test_state = deepcopy(state)
-    block = build_empty_block_for_next_slot(test_state)
-    block.slot += spec.SLOTS_PER_EPOCH
-
-    state_transition(test_state, block)
-
-    assert test_state.slot == block.slot
-    for slot in range(state.slot, test_state.slot):
-        assert get_block_root_at_slot(test_state, slot) == block.parent_root
-
-    return state, [block], test_state
-
-
-def test_empty_epoch_transition_not_finalizing(state):
-    test_state = deepcopy(state)
-    block = build_empty_block_for_next_slot(test_state)
-    block.slot += spec.SLOTS_PER_EPOCH * 5
-
-    state_transition(test_state, block)
-
-    assert test_state.slot == block.slot
-    assert test_state.finalized_epoch < get_current_epoch(test_state) - 4
-    for index in range(len(test_state.validator_registry)):
-        assert get_balance(test_state, index) < get_balance(state, index)
-
-    return state, [block], test_state
-
-
-def test_proposer_slashing(state):
-    test_state = deepcopy(state)
-    proposer_slashing = get_valid_proposer_slashing(state)
-    validator_index = proposer_slashing.proposer_index
-
-    #
-    # Add to state via block transition
-    #
-    block = build_empty_block_for_next_slot(test_state)
-    block.body.proposer_slashings.append(proposer_slashing)
-    state_transition(test_state, block)
-
-    assert not state.validator_registry[validator_index].slashed
-
-    slashed_validator = test_state.validator_registry[validator_index]
-    assert slashed_validator.slashed
-    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
-    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
-    # lost whistleblower reward
-    assert get_balance(test_state, validator_index) < get_balance(state, validator_index)
-
-    return state, [block], test_state
-
-
-def test_attester_slashing(state):
-    test_state = deepcopy(state)
-    attester_slashing = get_valid_attester_slashing(state)
-    validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
-
-    #
-    # Add to state via block transition
-    #
-    block = build_empty_block_for_next_slot(test_state)
-    block.body.attester_slashings.append(attester_slashing)
-    state_transition(test_state, block)
-
-    assert not state.validator_registry[validator_index].slashed
-
-    slashed_validator = test_state.validator_registry[validator_index]
-    assert slashed_validator.slashed
-    assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
-    assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
-    # lost whistleblower reward
-    assert get_balance(test_state, validator_index) < get_balance(state, validator_index)
-
-    proposer_index = get_beacon_proposer_index(test_state)
-    # gained whistleblower reward
-    assert (
-        get_balance(test_state, proposer_index) >
-        get_balance(state, proposer_index)
-    )
-
-    return state, [block], test_state
-
-
-def test_deposit_in_block(state):
-    pre_state = deepcopy(state)
-    test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    index = len(test_deposit_data_leaves)
-    pubkey = pubkeys[index]
-    privkey = privkeys[index]
-    deposit_data = build_deposit_data(pre_state, pubkey, privkey, spec.MAX_EFFECTIVE_BALANCE)
-
-    item = deposit_data.hash_tree_root()
-    test_deposit_data_leaves.append(item)
-    tree = calc_merkle_tree_from_leaves(tuple(test_deposit_data_leaves))
-    root = get_merkle_root((tuple(test_deposit_data_leaves)))
-    proof = list(get_merkle_proof(tree, item_index=index))
-    assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
-
-    deposit = Deposit(
-        proof=list(proof),
-        index=index,
-        data=deposit_data,
-    )
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(test_deposit_data_leaves)
-    post_state = deepcopy(pre_state)
-    block = build_empty_block_for_next_slot(post_state)
-    block.body.deposits.append(deposit)
-
-    state_transition(post_state, block)
-    assert len(post_state.validator_registry) == len(state.validator_registry) + 1
-    assert len(post_state.balances) == len(state.balances) + 1
-    assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE
-    assert post_state.validator_registry[index].pubkey == pubkeys[index]
-
-    return pre_state, [block], post_state
-
-
-def test_deposit_top_up(state):
-    pre_state = deepcopy(state)
-    test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
-
-    validator_index = 0
-    amount = spec.MAX_EFFECTIVE_BALANCE // 4
-    pubkey = pubkeys[validator_index]
-    privkey = privkeys[validator_index]
-    deposit_data = build_deposit_data(pre_state, pubkey, privkey, amount)
-
-    merkle_index = len(test_deposit_data_leaves)
-    item = deposit_data.hash_tree_root()
-    test_deposit_data_leaves.append(item)
-    tree = calc_merkle_tree_from_leaves(tuple(test_deposit_data_leaves))
-    root = get_merkle_root((tuple(test_deposit_data_leaves)))
-    proof = list(get_merkle_proof(tree, item_index=merkle_index))
-    assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, merkle_index, root)
-
-    deposit = Deposit(
-        proof=list(proof),
-        index=merkle_index,
-        data=deposit_data,
-    )
-
-    pre_state.latest_eth1_data.deposit_root = root
-    pre_state.latest_eth1_data.deposit_count = len(test_deposit_data_leaves)
-    block = build_empty_block_for_next_slot(pre_state)
-    block.body.deposits.append(deposit)
-
-    pre_balance = get_balance(pre_state, validator_index)
-    post_state = deepcopy(pre_state)
-    state_transition(post_state, block)
-    assert len(post_state.validator_registry) == len(pre_state.validator_registry)
-    assert len(post_state.balances) == len(pre_state.balances)
-    assert get_balance(post_state, validator_index) == pre_balance + amount
-
-    return pre_state, [block], post_state
-
-
-def test_attestation(state):
-    state.slot = spec.SLOTS_PER_EPOCH
-    test_state = deepcopy(state)
-    attestation = get_valid_attestation(state)
-
-    #
-    # Add to state via block transition
-    #
-    attestation_block = build_empty_block_for_next_slot(test_state)
-    attestation_block.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
-    attestation_block.body.attestations.append(attestation)
-    state_transition(test_state, attestation_block)
-
-    assert len(test_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1
-
-
-    #
-    # Epoch transition should move to previous_epoch_attestations
-    #
-    pre_current_epoch_attestations = deepcopy(test_state.current_epoch_attestations)
-
-    epoch_block = build_empty_block_for_next_slot(test_state)
-    epoch_block.slot += spec.SLOTS_PER_EPOCH
-    state_transition(test_state, epoch_block)
-
-    assert len(test_state.current_epoch_attestations) == 0
-    assert test_state.previous_epoch_attestations == pre_current_epoch_attestations
-
-    return state, [attestation_block, epoch_block], test_state
-
-
-def test_voluntary_exit(state):
-    pre_state = deepcopy(state)
-    validator_index = get_active_validator_indices(
-        pre_state,
-        get_current_epoch(pre_state)
-    )[-1]
-
-    # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
-    pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
-
-    post_state = deepcopy(pre_state)
-
-    voluntary_exit = VoluntaryExit(
-        epoch=get_current_epoch(pre_state),
-        validator_index=validator_index,
-    )
-    voluntary_exit.signature = bls.sign(
-        message_hash=signing_root(voluntary_exit),
-        privkey=privkeys[validator_index],
-        domain=get_domain(
-            state=pre_state,
-            domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
-        )
-    )
-
-    #
-    # Add to state via block transition
-    #
-    initiate_exit_block = build_empty_block_for_next_slot(post_state)
-    initiate_exit_block.body.voluntary_exits.append(voluntary_exit)
-    state_transition(post_state, initiate_exit_block)
-
-    assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
-
-    #
-    # Process within epoch transition
-    #
-    exit_block = build_empty_block_for_next_slot(post_state)
-    exit_block.slot += spec.SLOTS_PER_EPOCH
-    state_transition(post_state, exit_block)
-
-    assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
-
-    return pre_state, [initiate_exit_block, exit_block], post_state
-
-
-def test_transfer(state):
-    # overwrite default 0 to test
-    spec.MAX_TRANSFERS = 1
-
-    pre_state = deepcopy(state)
-    current_epoch = get_current_epoch(pre_state)
-    sender_index = get_active_validator_indices(pre_state, current_epoch)[-1]
-    recipient_index = get_active_validator_indices(pre_state, current_epoch)[0]
-    transfer_pubkey = pubkeys[-1]
-    transfer_privkey = privkeys[-1]
-    amount = get_balance(pre_state, sender_index)
-    pre_transfer_recipient_balance = get_balance(pre_state, recipient_index)
-    transfer = Transfer(
-        sender=sender_index,
-        recipient=recipient_index,
-        amount=amount,
-        fee=0,
-        slot=pre_state.slot + 1,
-        pubkey=transfer_pubkey,
-    )
-    transfer.signature = bls.sign(
-        message_hash=signing_root(transfer),
-        privkey=transfer_privkey,
-        domain=get_domain(
-            state=pre_state,
-            domain_type=spec.DOMAIN_TRANSFER,
-        )
-    )
-
-    # ensure withdrawal_credentials reproducable
-    pre_state.validator_registry[sender_index].withdrawal_credentials = (
-        spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer_pubkey)[1:]
-    )
-    # un-activate so validator can transfer
-    pre_state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
-
-    post_state = deepcopy(pre_state)
-    #
-    # Add to state via block transition
-    #
-    block = build_empty_block_for_next_slot(post_state)
-    block.body.transfers.append(transfer)
-    state_transition(post_state, block)
-
-    sender_balance = get_balance(post_state, sender_index)
-    recipient_balance = get_balance(post_state, recipient_index)
-    assert sender_balance == 0
-    assert recipient_balance == pre_transfer_recipient_balance + amount
-
-    return pre_state, [block], post_state
-
-
-def test_balance_driven_status_transitions(state):
-    current_epoch = get_current_epoch(state)
-    validator_index = get_active_validator_indices(state, current_epoch)[-1]
-
-    assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
-
-    # set validator balance to below ejection threshold
-    state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE
-
-    post_state = deepcopy(state)
-    #
-    # trigger epoch transition
-    #
-    block = build_empty_block_for_next_slot(post_state)
-    block.slot += spec.SLOTS_PER_EPOCH
-    state_transition(post_state, block)
-
-    assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
-
-    return state, [block], post_state
-
-
-def test_historical_batch(state):
-    state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1
-
-    post_state = deepcopy(state)
-
-    block = build_empty_block_for_next_slot(post_state)
-
-    state_transition(post_state, block)
-
-    assert post_state.slot == block.slot
-    assert get_current_epoch(post_state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0
-    assert len(post_state.historical_roots) == len(state.historical_roots) + 1
-
-    return state, [block], post_state
-
-
-def test_eth1_data_votes(state):
-    post_state = deepcopy(state)
-
-    expected_votes = 0
-    assert len(state.eth1_data_votes) == expected_votes
-
-    blocks = []
-    for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1):
-        block = build_empty_block_for_next_slot(post_state)
-        state_transition(post_state, block)
-        expected_votes += 1
-        assert len(post_state.eth1_data_votes) == expected_votes
-        blocks.append(block)
-
-    block = build_empty_block_for_next_slot(post_state)
-    state_transition(post_state, block)
-    blocks.append(block)
-
-    assert post_state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
-    assert len(post_state.eth1_data_votes) == 1
-
-    return state, blocks, post_state