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