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,7 +13,7 @@ def build_phase0_spec(sourcefile, outfile):
     Tuple,
 )
 from eth2spec.utils.minimal_ssz import *
-from eth2spec.utils.bls_stub import *
+from eth2spec.utils.bls import *
 
 """)
     for i in (1, 2, 3, 4, 8, 32, 48, 96):


Test patch:
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.6.0
-eth-utils==1.4.1
+eth-utils==1.6.0
 ../../test_libs/gen_helpers
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,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/operations/suite_creator.py b/test_generators/operations/suite_creator.py
--- a/test_generators/operations/suite_creator.py
+++ b/test_generators/operations/suite_creator.py
@@ -15,7 +15,7 @@ def generate_from_tests(pkg):
     for name in fn_names:
         tfn = getattr(pkg, name)
         try:
-            out.append(tfn(generator_mode=True))
+            out.append(tfn(generator_mode=True, bls_active=True))
         except AssertionError:
             print("ERROR: failed to generate vector from test: %s (pkg: %s)" % (name, pkg.__name__))
     return out
@@ -34,6 +34,6 @@ def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
             forks=["phase0"],
             config=config_name,
             runner="operations",
-            handler=config_name,
+            handler=operation_name,
             test_cases=get_cases()))
     return suite_definition
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/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/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
@@ -4,7 +4,7 @@
     name='gen_helpers',
     packages=['gen_base'],
     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/eth2spec/test/block_processing/test_process_attestation.py b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py
--- a/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_attestation.py
@@ -1,22 +1,23 @@
 from copy import deepcopy
 
 import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.state_transition import (
-    state_transition,
-)
 from eth2spec.phase0.spec import (
     get_current_epoch,
     process_attestation
 )
-from eth2spec.test.helpers import (
-    build_empty_block_for_next_slot,
+from eth2spec.phase0.state_transition import (
+    state_transition_to,
+)
+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.context import spec_state_test, expect_assertion_error
+from eth2spec.test.helpers.block import apply_empty_block
 
 
 def run_attestation_processing(state, attestation, valid=True):
@@ -56,7 +57,7 @@ def run_attestation_processing(state, attestation, valid=True):
 
 @spec_state_test
 def test_success(state):
-    attestation = get_valid_attestation(state)
+    attestation = get_valid_attestation(state, signed=True)
     state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
 
     yield from run_attestation_processing(state, attestation)
@@ -64,17 +65,25 @@ def test_success(state):
 
 @spec_state_test
 def test_success_previous_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)
+    attestation = get_valid_attestation(state, signed=True)
+    next_epoch(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, signed=False)
+    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)
+    attestation = get_valid_attestation(state, signed=True)
     # do not increment slot to allow for inclusion delay
 
     yield from run_attestation_processing(state, attestation, False)
@@ -82,11 +91,10 @@ def test_before_inclusion_delay(state):
 
 @spec_state_test
 def test_after_epoch_slots(state):
-    attestation = get_valid_attestation(state)
-    block = build_empty_block_for_next_slot(state)
+    attestation = get_valid_attestation(state, signed=True)
     # increment past latest inclusion slot
-    block.slot = state.slot + spec.SLOTS_PER_EPOCH + 1
-    state_transition(state, block)
+    state_transition_to(state, state.slot + spec.SLOTS_PER_EPOCH + 1)
+    apply_empty_block(state)
 
     yield from run_attestation_processing(state, attestation, False)
 
@@ -97,7 +105,7 @@ def test_old_source_epoch(state):
     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)
+    attestation = get_valid_attestation(state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1, signed=False)
 
     # test logic sanity check: make sure the attestation is pointing to oldest known source epoch
     assert attestation.data.source_epoch == state.previous_justified_epoch
@@ -105,36 +113,44 @@ def test_old_source_epoch(state):
     # 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)
+    attestation = get_valid_attestation(state, signed=False)
     state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
 
     attestation.data.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)
+    attestation = get_valid_attestation(state, signed=False)
     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)
+    attestation = get_valid_attestation(state, signed=False)
     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)
 
 
@@ -149,7 +165,7 @@ def test_invalid_current_source_root(state):
     state.current_justified_epoch = 4
     state.current_justified_root = b'\xff' * 32
 
-    attestation = get_valid_attestation(state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
+    attestation = get_valid_attestation(state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1, signed=False)
     state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
 
     # Test logic sanity checks:
@@ -159,56 +175,81 @@ def test_invalid_current_source_root(state):
     # 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)
+    attestation = get_valid_attestation(state, signed=False)
     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)
+    attestation = get_valid_attestation(state, signed=False)
     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_previous_crosslink(state):
     next_epoch(state)
-    attestation = get_valid_attestation(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)
 
     state.current_crosslinks[attestation.data.shard].epoch += 10
 
     yield from run_attestation_processing(state, attestation, False)
 
 
+@spec_state_test
+def test_inconsistent_bitfields(state):
+    attestation = get_valid_attestation(state, signed=False)
+    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)
+    attestation = get_valid_attestation(state, signed=False)
     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)
+    attestation = get_valid_attestation(state, signed=False)
     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, False)
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
--- 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
@@ -3,14 +3,15 @@
     get_beacon_proposer_index,
     process_attester_slashing,
 )
-from eth2spec.test.helpers import (
+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,
-    get_valid_attester_slashing,
     next_epoch,
 )
 
-from eth2spec.test.context import spec_state_test, expect_assertion_error
-
 
 def run_attester_slashing_processing(state, attester_slashing, valid=True):
     """
@@ -45,24 +46,22 @@ def run_attester_slashing_processing(state, attester_slashing, valid=True):
     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, slashed_index) <
-        pre_slashed_balance
-    )
-
-    # gained whistleblower reward
-    assert (
-        get_balance(state, proposer_index) >
-        pre_proposer_balance
-    )
+    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)
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=True)
 
     yield from run_attester_slashing_processing(state, attester_slashing)
 
@@ -70,37 +69,64 @@ def test_success_double(state):
 @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)
+    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)
+    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)
+    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)
+    attester_slashing = get_valid_attester_slashing(state, signed_1=True, signed_2=True)
 
     # set all indices to slashed
     attestation_1 = attester_slashing.attestation_1
@@ -113,10 +139,11 @@ def test_participants_already_slashed(state):
 
 @spec_state_test
 def test_custody_bit_0_and_1(state):
-    attester_slashing = get_valid_attester_slashing(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
--- 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
@@ -6,12 +6,12 @@
     advance_slot,
     process_block_header,
 )
-from eth2spec.test.helpers import (
+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,
-    next_slot,
+    sign_block
 )
-
-from eth2spec.test.context import spec_state_test, expect_assertion_error
+from eth2spec.test.helpers.state import next_slot
 
 
 def prepare_state_for_header_processing(state):
@@ -42,23 +42,32 @@ def run_block_header_processing(state, block, valid=True):
 
 
 @spec_state_test
-def test_success(state):
-    block = build_empty_block_for_next_slot(state)
+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, signed=False)
+    yield from run_block_header_processing(state, block, valid=False)
+
+
 @spec_state_test
-def test_invalid_slot(state):
-    block = build_empty_block_for_next_slot(state)
+def test_invalid_slot_block_header(state):
+    block = build_empty_block_for_next_slot(state, signed=False)
     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_previous_block_root(state):
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.previous_block_root = b'\12' * 32  # invalid prev root
+    sign_block(state, block)
 
     yield from run_block_header_processing(state, block, valid=False)
 
@@ -73,6 +82,6 @@ def test_proposer_slashed(state):
     # set proposer to slashed
     state.validator_registry[proposer_index].slashed = True
 
-    block = build_empty_block_for_next_slot(state)
+    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
--- a/test_libs/pyspec/eth2spec/test/block_processing/test_process_deposit.py
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_deposit.py
@@ -1,21 +1,12 @@
 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
 
-from eth2spec.phase0.spec import (
-    ZERO_HASH,
-    process_deposit,
-)
-from eth2spec.test.helpers import (
-    get_balance,
-    build_deposit,
-    prepare_state_and_deposit,
-    privkeys,
-    pubkeys,
-)
 
-from eth2spec.test.context import spec_state_test, expect_assertion_error
-
-
-def run_deposit_processing(state, deposit, validator_index, valid=True):
+def run_deposit_processing(state, deposit, validator_index, valid=True, effective=True):
     """
     Run ``process_deposit``, yielding:
       - pre-state ('pre')
@@ -43,47 +34,76 @@ def run_deposit_processing(state, deposit, validator_index, valid=True):
 
     yield 'post', state
 
-    if validator_index < pre_validator_count:
-        # top-up
+    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:
-        # new validator
-        assert len(state.validator_registry) == pre_validator_count + 1
-        assert len(state.balances) == pre_validator_count + 1
+        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
-    assert get_balance(state, validator_index) == pre_balance + deposit.data.amount
 
 
 @spec_state_test
-def test_success(state):
+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)
+    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, signed=False)
+    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)
+    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, signed=False)
+
+    # 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)
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=False)
 
     # 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)
 
 
@@ -94,9 +114,11 @@ def test_wrong_index(state):
 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)
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=False)
 
     # 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
--- 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
@@ -3,12 +3,11 @@
     get_current_epoch,
     process_proposer_slashing,
 )
-from eth2spec.test.helpers import (
-    get_balance,
-    get_valid_proposer_slashing,
-)
-
-from eth2spec.test.context import spec_state_test, expect_assertion_error
+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):
@@ -48,14 +47,35 @@ def run_proposer_slashing_processing(state, proposer_slashing, valid=True):
 
 @spec_state_test
 def test_success(state):
-    proposer_slashing = get_valid_proposer_slashing(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)
+    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)
 
@@ -64,17 +84,18 @@ def test_invalid_proposer_index(state):
 
 @spec_state_test
 def test_epochs_are_different(state):
-    proposer_slashing = get_valid_proposer_slashing(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)
+    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
@@ -84,7 +105,7 @@ def test_headers_are_same(state):
 
 @spec_state_test
 def test_proposer_is_not_activated(state):
-    proposer_slashing = get_valid_proposer_slashing(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
@@ -94,7 +115,7 @@ def test_proposer_is_not_activated(state):
 
 @spec_state_test
 def test_proposer_is_slashed(state):
-    proposer_slashing = get_valid_proposer_slashing(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
@@ -104,8 +125,10 @@ def test_proposer_is_slashed(state):
 
 @spec_state_test
 def test_proposer_is_withdrawn(state):
-    proposer_slashing = get_valid_proposer_slashing(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
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
--- a/test_libs/pyspec/eth2spec/test/block_processing/test_process_transfer.py
+++ b/test_libs/pyspec/eth2spec/test/block_processing/test_process_transfer.py
@@ -1,17 +1,14 @@
 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.helpers import (
-    get_valid_transfer,
-    next_epoch,
-)
-
-from eth2spec.test.context import spec_state_test, expect_assertion_error
+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):
@@ -48,7 +45,7 @@ def run_transfer_processing(state, transfer, valid=True):
 
 @spec_state_test
 def test_success_non_activated(state):
-    transfer = get_valid_transfer(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
 
@@ -58,8 +55,9 @@ def test_success_non_activated(state):
 @spec_state_test
 def test_success_withdrawable(state):
     next_epoch(state)
+    apply_empty_block(state)
 
-    transfer = get_valid_transfer(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
@@ -71,7 +69,7 @@ def test_success_withdrawable(state):
 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)
+    transfer = get_valid_transfer(state, sender_index=sender_index, amount=1, fee=0, signed=True)
 
     yield from run_transfer_processing(state, transfer)
 
@@ -80,24 +78,34 @@ def test_success_active_above_max_effective(state):
 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)
+    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, signed=False)
+    # 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)
+    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)
+    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
 
@@ -108,7 +116,7 @@ def test_incorrect_slot(state):
 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)
+    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
@@ -120,7 +128,7 @@ def test_insufficient_balance_for_fee(state):
 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)
+    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
@@ -132,7 +140,7 @@ def test_insufficient_balance(state):
 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)
+    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
@@ -144,7 +152,7 @@ def test_no_dust_sender(state):
 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)
+    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
@@ -155,7 +163,7 @@ def test_no_dust_recipient(state):
 
 @spec_state_test
 def test_invalid_pubkey(state):
-    transfer = get_valid_transfer(state)
+    transfer = get_valid_transfer(state, signed=True)
     state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH
 
     # un-activate so validator can transfer
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
--- 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
@@ -1,17 +1,13 @@
 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.helpers import (
-    build_voluntary_exit,
-    pubkey_to_privkey,
-)
-
-from eth2spec.test.context import spec_state_test, expect_assertion_error
+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):
@@ -51,16 +47,26 @@ def test_success(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,
-    )
+    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, signed=False)
+
+    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
@@ -80,6 +86,7 @@ def test_success_exit_queue(state):
             current_epoch,
             index,
             privkey,
+            signed=True,
         ))
 
     # Now run all the exits
@@ -96,6 +103,7 @@ def test_success_exit_queue(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,
@@ -122,8 +130,10 @@ def test_validator_exit_in_future(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)
 
@@ -142,8 +152,10 @@ def test_validator_invalid_validator_index(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)
 
@@ -162,6 +174,7 @@ def test_validator_not_active(state):
         current_epoch,
         validator_index,
         privkey,
+        signed=True,
     )
 
     yield from run_voluntary_exit_processing(state, voluntary_exit, False)
@@ -184,6 +197,7 @@ def test_validator_already_exited(state):
         current_epoch,
         validator_index,
         privkey,
+        signed=True,
     )
 
     yield from run_voluntary_exit_processing(state, voluntary_exit, False)
@@ -200,6 +214,7 @@ def test_validator_not_active_long_enough(state):
         current_epoch,
         validator_index,
         privkey,
+        signed=True,
     )
 
     assert (
diff --git a/test_libs/pyspec/eth2spec/test/context.py b/test_libs/pyspec/eth2spec/test/context.py
--- a/test_libs/pyspec/eth2spec/test/context.py
+++ b/test_libs/pyspec/eth2spec/test/context.py
@@ -1,16 +1,27 @@
 from eth2spec.phase0 import spec
+from eth2spec.utils import bls
 
-from .helpers import create_genesis_state
+from .helpers.genesis import create_genesis_state
 
-from .utils import spectest, with_args
+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, list())])
+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(spectest()(fn))
+    return with_state(bls_switch(spectest()(fn)))
 
 
 def expect_assertion_error(fn):
@@ -25,3 +36,47 @@ def expect_assertion_error(fn):
         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_ignored': True})
+
+
+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_required': True})
+
+
+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/test_process_crosslinks.py b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
--- a/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
+++ b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_crosslinks.py
@@ -1,25 +1,27 @@
 from copy import deepcopy
 
 import eth2spec.phase0.spec as spec
-
-from eth2spec.phase0.state_transition import (
-    state_transition,
-)
 from eth2spec.phase0.spec import (
     cache_state,
     get_crosslink_deltas,
     process_crosslinks,
 )
-from eth2spec.test.helpers import (
+from eth2spec.phase0.state_transition import (
+    state_transition,
+)
+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,
 )
-from eth2spec.test.context import spec_state_test
 
 
 def run_process_crosslinks(state, valid=True):
@@ -31,8 +33,9 @@ def run_process_crosslinks(state, valid=True):
     """
     # 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 = build_empty_block_for_next_slot(state, signed=False)
     block.slot = slot
+    sign_block(state, block)
     state_transition(state, block)
 
     # cache state before epoch transition
@@ -55,7 +58,7 @@ def test_no_attestations(state):
 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)
@@ -75,7 +78,7 @@ def test_single_crosslink_update_from_current_epoch(state):
 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)
@@ -103,7 +106,7 @@ def test_double_late_crosslink(state):
     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
@@ -111,10 +114,12 @@ def test_double_late_crosslink(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)
+        attestation_2 = get_valid_attestation(state, signed=True)
         if attestation_2.data.shard == attestation_1.data.shard:
             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
@@ -126,7 +131,7 @@ def test_double_late_crosslink(state):
     assert len(state.current_epoch_attestations) == 0
 
     crosslink_deltas = get_crosslink_deltas(state)
-    
+
     yield from run_process_crosslinks(state)
 
     shard = attestation_2.data.shard
diff --git a/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
--- a/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
+++ b/test_libs/pyspec/eth2spec/test/epoch_processing/test_process_registry_updates.py
@@ -4,10 +4,7 @@
     get_current_epoch,
     is_active_validator,
 )
-from eth2spec.test.helpers import (
-    next_epoch,
-)
-
+from eth2spec.test.helpers.state import next_epoch
 from eth2spec.test.context import spec_state_test
 
 
diff --git a/test_libs/pyspec/eth2spec/test/helpers.py b/test_libs/pyspec/eth2spec/test/helpers.py
deleted file mode 100644
--- a/test_libs/pyspec/eth2spec/test/helpers.py
+++ /dev/null
@@ -1,445 +0,0 @@
-from copy import deepcopy
-
-from py_ecc import bls
-
-from eth2spec.phase0.state_transition import (
-    state_transition,
-)
-import eth2spec.phase0.spec as spec
-from eth2spec.utils.minimal_ssz import signing_root
-from eth2spec.phase0.spec import (
-    # constants
-    ZERO_HASH,
-    # SSZ
-    Attestation,
-    AttestationData,
-    AttestationDataAndCustodyBit,
-    AttesterSlashing,
-    BeaconBlock,
-    BeaconBlockHeader,
-    Deposit,
-    DepositData,
-    Eth1Data,
-    ProposerSlashing,
-    Transfer,
-    VoluntaryExit,
-    # functions
-    convert_to_indexed,
-    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,
-    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 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.previous_block_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=get_domain(
-            state,
-            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).previous_block_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
-    return AttestationData(
-        shard=shard,
-        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_data_root=spec.ZERO_HASH,
-        previous_crosslink_root=hash_tree_root(crosslinks[shard]),
-    )
-
-
-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,
-        previous_block_root=ZERO_HASH,
-        state_root=ZERO_HASH,
-        block_body_root=ZERO_HASH,
-    )
-    header_2 = deepcopy(header_1)
-    header_2.previous_block_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.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.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]
-
-
-def prepare_state_and_deposit(state, validator_index, amount):
-    """
-    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,
-    )
-
-    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/__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,146 @@
+from typing import List
+
+# Access constants from spec pkg reference.
+import eth2spec.phase0.spec as spec
+from eth2spec.phase0.spec import (
+    Attestation,
+    AttestationData,
+    AttestationDataAndCustodyBit,
+    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)
+from eth2spec.phase0.state_transition import (
+    state_transition, state_transition_to
+)
+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).previous_block_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
+    return AttestationData(
+        shard=shard,
+        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_data_root=spec.ZERO_HASH,
+        previous_crosslink_root=hash_tree_root(crosslinks[shard]),
+    )
+
+
+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.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.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, signed=False)
+    block.slot = slot
+    block.body.attestations.append(attestation)
+    state_transition_to(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,77 @@
+from copy import deepcopy
+
+from eth2spec.phase0 import spec
+from eth2spec.phase0.spec import get_beacon_proposer_index, slot_to_epoch, get_domain, BeaconBlock
+from eth2spec.phase0.state_transition import state_transition, state_transition_to
+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)
+            state_transition_to(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.previous_block_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,
+        previous_block_root=b'\x33' * 32,
+        state_root=b'\x44' * 32,
+        block_body_root=b'\x55' * 32,
+    )
+    header_2 = deepcopy(header_1)
+    header_2.previous_block_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.state_transition import state_transition_to
+
+
+def get_balance(state, index):
+    return state.balances[index]
+
+
+def next_slot(state):
+    """
+    Transition to the next slot.
+    """
+    state_transition_to(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)
+    state_transition_to(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/test_finality.py b/test_libs/pyspec/eth2spec/test/test_finality.py
--- a/test_libs/pyspec/eth2spec/test/test_finality.py
+++ b/test_libs/pyspec/eth2spec/test/test_finality.py
@@ -1,21 +1,18 @@
 from copy import deepcopy
 
 import eth2spec.phase0.spec as spec
-
 from eth2spec.phase0.state_transition import (
     state_transition,
 )
-from .helpers import (
-    build_empty_block_for_next_slot,
-    fill_aggregate_attestation,
+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_current_epoch,
     get_epoch_start_slot,
     get_valid_attestation,
-    next_epoch,
 )
 
-from .context import spec_state_test
-
 
 def check_finality(state,
                    prev_state,
@@ -55,13 +52,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)
 
         state_transition(post_state, block)
@@ -70,6 +65,7 @@ def next_epoch_with_attestations(state,
     return state, blocks, post_state
 
 
+@never_bls
 @spec_state_test
 def test_finality_rule_4(state):
     yield 'pre', state
@@ -97,11 +93,14 @@ def test_finality_rule_4(state):
     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)
 
     yield 'pre', state
 
@@ -124,11 +123,14 @@ def test_finality_rule_1(state):
     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)
 
     yield 'pre', state
 
@@ -153,6 +155,7 @@ def test_finality_rule_2(state):
     yield 'post', state
 
 
+@never_bls
 @spec_state_test
 def test_finality_rule_3(state):
     """
@@ -161,7 +164,9 @@ def test_finality_rule_3(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)
 
     yield 'pre', state
 
diff --git a/test_libs/pyspec/eth2spec/test/test_sanity.py b/test_libs/pyspec/eth2spec/test/test_sanity.py
--- a/test_libs/pyspec/eth2spec/test/test_sanity.py
+++ b/test_libs/pyspec/eth2spec/test/test_sanity.py
@@ -1,7 +1,7 @@
 from copy import deepcopy
 
-from py_ecc import bls
 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 (
@@ -19,20 +19,22 @@
 from eth2spec.phase0.state_transition import (
     state_transition,
 )
-from .helpers import (
+from .helpers.state import (
     get_balance,
-    build_empty_block_for_next_slot,
-    get_state_root,
-    get_valid_attestation,
-    get_valid_attester_slashing,
-    get_valid_proposer_slashing,
-    get_valid_transfer,
-    prepare_state_and_deposit,
+    get_state_root
+)
+from .helpers.transfers import get_valid_transfer
+from .helpers.block import build_empty_block_for_next_slot, sign_block
+from .helpers.keys import (
     privkeys,
     pubkeys,
 )
+from .helpers.attester_slashings import get_valid_attester_slashing
+from .helpers.proposer_slashings import get_valid_proposer_slashing
+from .helpers.attestations import get_valid_attestation
+from .helpers.deposits import prepare_state_and_deposit
 
-from .context import spec_state_test
+from .context import spec_state_test, never_bls
 
 
 @spec_state_test
@@ -49,6 +51,7 @@ def test_slot_transition(state):
     assert get_state_root(state, pre_slot) == pre_root
 
 
+@never_bls
 @spec_state_test
 def test_empty_block_transition(state):
     pre_slot = state.slot
@@ -56,7 +59,7 @@ def test_empty_block_transition(state):
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=True)
     yield 'blocks', [block], [spec.BeaconBlock]
 
     state_transition(state, block)
@@ -66,13 +69,15 @@ def test_empty_block_transition(state):
     assert get_block_root_at_slot(state, pre_slot) == block.previous_block_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 = build_empty_block_for_next_slot(state, signed=False)
     block.slot += 3
+    sign_block(state, block)
     yield 'blocks', [block], [spec.BeaconBlock]
 
     state_transition(state, block)
@@ -88,8 +93,9 @@ def test_empty_epoch_transition(state):
     pre_slot = state.slot
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, block)
     yield 'blocks', [block], [spec.BeaconBlock]
 
     state_transition(state, block)
@@ -100,30 +106,31 @@ def test_empty_epoch_transition(state):
         assert get_block_root_at_slot(state, slot) == block.previous_block_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
-    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_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, signed=False)
+#     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)
+    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
@@ -133,8 +140,9 @@ def test_proposer_slashing(state):
     #
     # Add to state via block transition
     #
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.body.proposer_slashings.append(proposer_slashing)
+    sign_block(state, block)
     yield 'blocks', [block], [spec.BeaconBlock]
 
     state_transition(state, block)
@@ -154,8 +162,9 @@ def test_attester_slashing(state):
     # copy for later balance lookups.
     pre_state = deepcopy(state)
 
-    attester_slashing = get_valid_attester_slashing(state)
-    validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
+    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
 
@@ -164,8 +173,9 @@ def test_attester_slashing(state):
     #
     # Add to state via block transition
     #
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.body.attester_slashings.append(attester_slashing)
+    sign_block(state, block)
     yield 'blocks', [block], [spec.BeaconBlock]
 
     state_transition(state, block)
@@ -195,12 +205,13 @@ def test_deposit_in_block(state):
 
     validator_index = len(state.validator_registry)
     amount = spec.MAX_EFFECTIVE_BALANCE
-    deposit = prepare_state_and_deposit(state, validator_index, amount)
+    deposit = prepare_state_and_deposit(state, validator_index, amount, signed=True)
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.body.deposits.append(deposit)
+    sign_block(state, block)
 
     yield 'blocks', [block], [spec.BeaconBlock]
 
@@ -225,8 +236,9 @@ def test_deposit_top_up(state):
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.body.deposits.append(deposit)
+    sign_block(state, block)
 
     yield 'blocks', [block], [spec.BeaconBlock]
 
@@ -244,23 +256,24 @@ def test_attestation(state):
 
     yield 'pre', state
 
-    attestation = get_valid_attestation(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 = build_empty_block_for_next_slot(state, signed=False)
     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 = build_empty_block_for_next_slot(state, signed=False)
     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]
@@ -286,7 +299,7 @@ def test_voluntary_exit(state):
         epoch=get_current_epoch(state),
         validator_index=validator_index,
     )
-    voluntary_exit.signature = bls.sign(
+    voluntary_exit.signature = bls_sign(
         message_hash=signing_root(voluntary_exit),
         privkey=privkeys[validator_index],
         domain=get_domain(
@@ -296,15 +309,17 @@ def test_voluntary_exit(state):
     )
 
     # Add to state via block transition
-    initiate_exit_block = build_empty_block_for_next_slot(state)
+    initiate_exit_block = build_empty_block_for_next_slot(state, signed=False)
     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 = build_empty_block_for_next_slot(state, signed=False)
     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]
@@ -321,7 +336,7 @@ def test_transfer(state):
     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)
+    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)
 
@@ -331,8 +346,9 @@ def test_transfer(state):
     yield 'pre', state
 
     # Add to state via block transition
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.body.transfers.append(transfer)
+    sign_block(state, block)
 
     yield 'blocks', [block], [spec.BeaconBlock]
 
@@ -358,8 +374,9 @@ def test_balance_driven_status_transitions(state):
     yield 'pre', state
 
     # trigger epoch transition
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=False)
     block.slot += spec.SLOTS_PER_EPOCH
+    sign_block(state, block)
     state_transition(state, block)
 
     yield 'blocks', [block], [spec.BeaconBlock]
@@ -375,7 +392,7 @@ def test_historical_batch(state):
 
     yield 'pre', state
 
-    block = build_empty_block_for_next_slot(state)
+    block = build_empty_block_for_next_slot(state, signed=True)
     state_transition(state, block)
 
     yield 'blocks', [block], [spec.BeaconBlock]
@@ -386,28 +403,28 @@ def test_historical_batch(state):
     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
+# @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, signed=False)
+#         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, signed=False)
+#     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/utils.py b/test_libs/pyspec/eth2spec/test/utils.py
--- a/test_libs/pyspec/eth2spec/test/utils.py
+++ b/test_libs/pyspec/eth2spec/test/utils.py
@@ -1,3 +1,4 @@
+from typing import Dict, Any, Callable, Iterable
 from eth2spec.debug.encode import encode
 
 
@@ -40,10 +41,34 @@ def entry(*args, **kw):
     return runner
 
 
-def with_args(create_args):
+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 fn_out
+            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(*(create_args() + list(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' * 96