Merge branch 'dev' into ssz-impl-rework

This commit is contained in:
protolambda 2019-06-01 01:34:49 +02:00
commit e044305457
No known key found for this signature in database
GPG Key ID: EC89FDBB2B4C7623
86 changed files with 3714 additions and 2271 deletions

View File

@ -34,7 +34,7 @@ install_test:
cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements-testing.txt;
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)
cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml .

View File

@ -28,6 +28,7 @@ Core specifications for Eth 2.0 client validation can be found in [specs/core](s
* [General test format](specs/test_formats/README.md)
* [Merkle proof formats](specs/light_client/merkle_proofs.md)
* [Light client syncing protocol](specs/light_client/sync_protocol.md)
* [Beacon node API for validator](specs/validator/0_beacon-node-validator-api.md)
## Design goals

View File

@ -21,12 +21,12 @@ from eth2spec.utils.ssz.ssz_typing import (
uint8, uint16, uint32, uint64, uint128, uint256,
Container, Vector, BytesN
)
from eth2spec.utils.bls_stub import (
from eth2spec.utils.hash_function import hash
from eth2spec.utils.bls import (
bls_aggregate_pubkeys,
bls_verify,
bls_verify_multiple,
)
from eth2spec.utils.hash_function import hash
# Note: 'int' type defaults to being interpreted as a uint64 by SSZ implementation.
Slot = NewType('Slot', int) # uint64

View File

@ -17,7 +17,7 @@
- [Initial values](#initial-values)
- [Time parameters](#time-parameters)
- [State list lengths](#state-list-lengths)
- [Reward and penalty quotients](#reward-and-penalty-quotients)
- [Rewards and penalties](#rewards-and-penalties)
- [Max operations per block](#max-operations-per-block)
- [Signature domains](#signature-domains)
- [Data structures](#data-structures)
@ -103,7 +103,7 @@
- [Helper functions](#helper-functions-1)
- [Justification and finalization](#justification-and-finalization)
- [Crosslinks](#crosslinks)
- [Rewards and penalties](#rewards-and-penalties)
- [Rewards and penalties](#rewards-and-penalties-1)
- [Registry updates](#registry-updates)
- [Slashings](#slashings)
- [Final updates](#final-updates)
@ -220,17 +220,17 @@ These configurations are updated for releases, but may be out of sync during `de
| `LATEST_ACTIVE_INDEX_ROOTS_LENGTH` | `2**13` (= 8,192) | epochs | ~36 days |
| `LATEST_SLASHED_EXIT_LENGTH` | `2**13` (= 8,192) | epochs | ~36 days |
### Reward and penalty quotients
### Rewards and penalties
| Name | Value |
| - | - |
| `BASE_REWARD_QUOTIENT` | `2**5` (= 32) |
| `BASE_REWARD_FACTOR` | `2**5` (= 32) |
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) |
| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) |
* **The `BASE_REWARD_QUOTIENT` is NOT final. Once all other protocol details are finalized, it will be adjusted to target a theoretical maximum total issuance of `2**21` ETH per year if `2**27` ETH is validating (and therefore `2**20` per year if `2**25` ETH is validating, etc.)**
* **The `BASE_REWARD_FACTOR` is NOT final. Once all other protocol details are finalized, it will be adjusted to target a theoretical maximum total issuance of `2**21` ETH per year if `2**27` ETH is validating (and therefore `2**20` per year if `2**25` ETH is validating, etc.)**
* The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`.
### Max operations per block
@ -457,8 +457,6 @@ class Attestation(Container):
class Deposit(Container):
# Branch in the deposit tree
proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH]
# Index in the deposit tree
index: uint64
# Data
data: DepositData
```
@ -943,9 +941,9 @@ def bytes_to_int(data: bytes) -> int:
```python
def get_total_balance(state: BeaconState, indices: List[ValidatorIndex]) -> Gwei:
"""
Return the combined effective balance of an array of ``validators``.
Return the combined effective balance of the ``indices``. (1 Gwei minimum to avoid divisions by zero.)
"""
return sum([state.validator_registry[index].effective_balance for index in indices])
return max(sum([state.validator_registry[index].effective_balance for index in indices]), 1)
```
### `get_domain`
@ -1228,7 +1226,7 @@ def state_transition(state: BeaconState, block: BeaconBlock, validate_state_root
```python
def process_slots(state: BeaconState, slot: Slot) -> None:
assert state.slot < slot
assert state.slot <= slot
while state.slot < slot:
process_slot(state)
# Process epoch on the first slot of the next epoch
@ -1393,10 +1391,9 @@ def process_crosslinks(state: BeaconState) -> None:
```python
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
adjusted_quotient = integer_squareroot(get_total_active_balance(state)) // BASE_REWARD_QUOTIENT
if adjusted_quotient == 0:
return 0
return state.validator_registry[index].effective_balance // adjusted_quotient // BASE_REWARDS_PER_EPOCH
total_balance = get_total_active_balance(state)
effective_balance = state.validator_registry[index].effective_balance
return effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH
```
```python
@ -1511,10 +1508,9 @@ def process_registry_updates(state: BeaconState) -> None:
```python
def process_slashings(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
active_validator_indices = get_active_validator_indices(state, current_epoch)
total_balance = get_total_balance(state, active_validator_indices)
total_balance = get_total_active_balance(state)
# Compute `total_penalties`
# Compute slashed balances in the current epoch
total_at_start = state.latest_slashed_balances[(current_epoch + 1) % LATEST_SLASHED_EXIT_LENGTH]
total_at_end = state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH]
total_penalties = total_at_end - total_at_start
@ -1743,19 +1739,20 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
leaf=hash_tree_root(deposit.data),
proof=deposit.proof,
depth=DEPOSIT_CONTRACT_TREE_DEPTH,
index=deposit.index,
index=state.deposit_index,
root=state.latest_eth1_data.deposit_root,
)
# Deposits must be processed in order
assert deposit.index == state.deposit_index
state.deposit_index += 1
pubkey = deposit.data.pubkey
amount = deposit.data.amount
validator_pubkeys = [v.pubkey for v in state.validator_registry]
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`
if not bls_verify(
pubkey, signing_root(deposit.data), deposit.data.signature, bls_domain(DOMAIN_DEPOSIT)

View File

@ -35,7 +35,8 @@ Test formats:
- [`bls`](./bls/README.md)
- [`operations`](./operations/README.md)
- [`shuffling`](./shuffling/README.md)
- [`ssz`](./ssz/README.md)
- [`ssz_generic`](./ssz_generic/README.md)
- [`ssz_static`](./ssz_static/README.md)
- More formats are planned, see tracking issues for CI/testing
## Glossary
@ -186,6 +187,18 @@ To prevent parsing of hundreds of different YAML files to test a specific test t
... <--- more test types
```
## Common test-case properties
Some test-case formats share some common key-value pair patterns, and these are documented here:
```
bls_setting: int -- optional, can have 3 different values:
0: (default, applies if key-value pair is absent). Free to choose either BLS ON or OFF.
Tests are generated with valid BLS data in this case,
but there is no change of outcome when running the test if BLS is ON or OFF.
1: known as "BLS required" - if the test validity is strictly dependent on BLS being ON
2: known as "BLS ignored" - if the test validity is strictly dependent on BLS being OFF
```
## Note for implementers

View 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.

View File

@ -2,9 +2,34 @@
The different kinds of operations ("transactions") are tested individually with test handlers.
The tested operation kinds are:
- [`deposits`](./deposits.md)
- More tests are work-in-progress.
## Test case format
```yaml
description: string -- description of test case, purely for debugging purposes
bls_setting: int -- see general test-format spec.
pre: BeaconState -- state before applying the operation
<operation-name>: <operation-object> -- the YAML encoded operation, e.g. a "ProposerSlashing", or "Deposit".
post: BeaconState -- state after applying the operation. No value if operation processing is aborted.
```
## Condition
A handler of the `operations` test-runner should process these cases,
calling the corresponding processing implementation.
Operations:
| *`operation-name`* | *`operation-object`* | *`input name`* | *`processing call`* |
|-------------------------|----------------------|----------------------|--------------------------------------------------------|
| `attestation` | `Attestation` | `attestation` | `process_attestation(state, attestation)` |
| `attester_slashing` | `AttesterSlashing` | `attester_slashing` | `process_attester_slashing(state, attester_slashing)` |
| `block_header` | `Block` | `block` | `process_block_header(state, block)` |
| `deposit` | `Deposit` | `deposit` | `process_deposit(state, deposit)` |
| `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` |
| `transfer` | `Transfer` | `transfer` | `process_transfer(state, transfer)` |
| `voluntary_exit` | `VoluntaryExit` | `voluntary_exit` | `process_voluntary_exit(state, voluntary_exit)` |
Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here.
The resulting state should match the expected `post` state, or if the `post` state is left blank,
the handler should reject the input operation as invalid.

View File

@ -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.

View 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

View 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.

View 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.

View File

@ -9,11 +9,11 @@ This test-format ensures these direct serializations are covered.
## Test case format
```yaml
type_name: string -- string, object name, formatted as in spec. E.g. "BeaconBlock"
value: dynamic -- the YAML-encoded value, of the type specified by type_name.
serialized: bytes -- string, SSZ-serialized data, hex encoded, with prefix 0x
root: bytes32 -- string, hash-tree-root of the value, hex encoded, with prefix 0x
signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. Optional, present if type contains ``signature`` field
SomeObjectName: -- key, object name, formatted as in spec. E.g. "BeaconBlock".
value: dynamic -- the YAML-encoded value, of the type specified by type_name.
serialized: bytes -- string, SSZ-serialized data, hex encoded, with prefix 0x
root: bytes32 -- string, hash-tree-root of the value, hex encoded, with prefix 0x
signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. Optional, present if type contains ``signature`` field
```
## Condition

View File

@ -224,7 +224,7 @@ epoch_signature = bls_sign(
`block.eth1_data` is a mechanism used by block proposers vote on a recent Ethereum 1.0 block hash and an associated deposit root found in the Ethereum 1.0 deposit contract. When consensus is formed, `state.latest_eth1_data` is updated, and validator deposits up to this root can be processed. The deposit root can be calculated by calling the `get_deposit_root()` function of the deposit contract using the post-state of the block hash.
* Let `D` be the set of `Eth1DataVote` objects `vote` in `state.eth1_data_votes` where:
* Let `D` be the list of `Eth1DataVote` objects `vote` in `state.eth1_data_votes` where:
* `vote.eth1_data.block_hash` is the hash of an Eth 1.0 block that is (i) part of the canonical chain, (ii) >= `ETH1_FOLLOW_DISTANCE` blocks behind the head, and (iii) newer than `state.latest_eth1_data.block_hash`.
* `vote.eth1_data.deposit_count` is the deposit count of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
* `vote.eth1_data.deposit_root` is the deposit root of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
@ -233,7 +233,7 @@ epoch_signature = bls_sign(
* Let `deposit_root` and `deposit_count` be the deposit root and deposit count of the Eth 1.0 deposit contract in the post-state of the block referenced by `block_hash`
* Let `best_vote_data = Eth1Data(block_hash=block_hash, deposit_root=deposit_root, deposit_count=deposit_count)`.
* If `D` is nonempty:
* Let `best_vote_data` be the `eth1_data` of the member of `D` that has the highest `vote.vote_count`, breaking ties by favoring block hashes with higher associated block height.
* Let `best_vote_data` be the `eth1_data` member of `D` that has the highest vote count (`D.count(eth1_data)`), breaking ties by favoring block hashes with higher associated block height.
* Set `block.eth1_data = best_vote_data`.
##### Signature

View File

@ -0,0 +1,28 @@
# Ethereum 2.0 Phase 0 -- Beacon Node API for Validator
__NOTICE__: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- Honest Validator](0_beacon-chain-validator.md) that describes an API exposed by the beacon node, which enables the validator client to participate in the Ethereum 2.0 protocol.
## Outline
This document outlines a minimal application programming interface (API) which is exposed by a beacon node for use by a validator client implementation which aims to facilitate [_phase 0_](../../README.md#phase-0) of Ethereum 2.0.
The API is a REST interface, accessed via HTTP, designed for use as a local communications protocol between binaries. The only supported return data type is currently JSON.
### Background
The beacon node maintains the state of the beacon chain by communicating with other beacon nodes in the Ethereum Serenity network. Conceptually, it does not maintain keypairs that participate with the beacon chain.
The validator client is a conceptually separate entity which utilizes private keys to perform validator related tasks on the beacon chain, which we call validator "duties". These duties include the production of beacon blocks and signing of attestations.
Since it is recommended to separate these concerns in the client implementations, we must clearly define the communication between them.
The goal of this specification is to promote interoperability between beacon nodes and validator clients derived from different projects and to encourage innovation in validator client implementations, independently from beacon node development. For example, the validator client from Lighthouse could communicate with a running instance of the beacon node from Prysm, or a staking pool might create a decentrally managed validator client which utilises the same API.
This specification is derived from a proposal and discussion on Issues [#1011](https://github.com/ethereum/eth2.0-specs/issues/1011) and [#1012](https://github.com/ethereum/eth2.0-specs/issues/1012)
## Specification
The API specification has been written in [OpenAPI 3.0](https://swagger.io/docs/specification/about/) and is provided in the [beacon_node_oapi.yaml](beacon_node_oapi.yaml) file alongside this document.
For convenience, this specification has been uploaded to [SwaggerHub](https://swagger.io/tools/swaggerhub/) at the following URL:
[https://app.swaggerhub.com/apis/spble/beacon_node_api_for_validator](https://app.swaggerhub.com/apis/spble/beacon_node_api_for_validator)

View File

@ -0,0 +1,641 @@
openapi: "3.0.2"
info:
title: "Minimal Beacon Node API for Validator"
description: "A minimal API specification for the beacon node, which enables a validator to connect and perform its obligations on the Ethereum 2.0 phase 0 beacon chain."
version: "0.2.0"
license:
name: "Apache 2.0"
url: "https://www.apache.org/licenses/LICENSE-2.0.html"
tags:
- name: MinimalSet
description: The minimal set of endpoints to enable a working validator implementation.
- name: OptionalSet
description: Extra endpoints which are nice-to-haves.
paths:
/node/version:
get:
tags:
- MinimalSet
summary: "Get version string of the running beacon node."
description: "Requests that the beacon node identify information about its implementation in a format similar to a [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) field."
responses:
200:
description: Request successful
content:
application/json:
schema:
$ref: '#/components/schemas/version'
500:
$ref: '#/components/responses/InternalError'
/node/genesis_time:
get:
tags:
- MinimalSet
summary: "Get the genesis_time parameter from beacon node configuration."
description: "Requests the genesis_time parameter from the beacon node, which should be consistent across all beacon nodes that follow the same beacon chain."
responses:
200:
description: Request successful
content:
application/json:
schema:
$ref: '#/components/schemas/genesis_time'
500:
$ref: '#/components/responses/InternalError'
/node/syncing:
get:
tags:
- MinimalSet
summary: "Poll to see if the the beacon node is syncing."
description: "Requests the beacon node to describe if it's currently syncing or not, and if it is, what block it is up to. This is modelled after the Eth1.0 JSON-RPC eth_syncing call.."
responses:
200:
description: Request successful
content:
application/json:
schema:
type: object
properties:
is_syncing:
type: boolean
description: "A boolean of whether the node is currently syncing or not."
sync_status:
$ref: '#/components/schemas/SyncingStatus'
500:
$ref: '#/components/responses/InternalError'
/node/fork:
get:
tags:
- OptionalSet
summary: "Get fork information from running beacon node."
description: "Requests the beacon node to provide which fork version it is currently on."
responses:
200:
description: Request successful
content:
application/json:
schema:
type: object
properties:
fork:
$ref: '#/components/schemas/Fork'
chain_id:
type: integer
format: uint64
description: "Sometimes called the network id, this number discerns the active chain for the beacon node. Analogous to Eth1.0 JSON-RPC net_version."
500:
$ref: '#/components/responses/InternalError'
/validator/duties:
get:
tags:
- MinimalSet
summary: "Get validator duties for the requested validators."
description: "Requests the beacon node to provide a set of _duties_, which are actions that should be performed by validators, for a particular epoch. Duties should only need to be checked once per epoch, however a chain reorganization (of > MIN_SEED_LOOKAHEAD epochs) could occur, resulting in a change of duties. For full safety, this API call should be polled at every slot to ensure that chain reorganizations are recognized, and to ensure that the beacon node is properly synchronized."
parameters:
- name: validator_pubkeys
in: query
required: true
description: "An array of hex-encoded BLS public keys"
schema:
type: array
items:
$ref: '#/components/schemas/pubkey'
minItems: 1
- name: epoch
in: query
required: false
schema:
type: integer
responses:
200:
description: Success response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ValidatorDuty'
400:
$ref: '#/components/responses/InvalidRequest'
406:
description: "Duties cannot be provided for the requested epoch."
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/CurrentlySyncing'
/validator/block:
get:
tags:
- MinimalSet
summary: "Produce a new block, without signature."
description: "Requests a beacon node to produce a valid block, which can then be signed by a validator."
parameters:
- name: slot
in: query
required: true
description: "The slot for which the block should be proposed."
schema:
type: integer
format: uint64
- name: randao_reveal
in: query
required: true
description: "The validator's randao reveal value."
schema:
type: string
format: byte
responses:
200:
description: Success response
content:
application/json:
schema:
$ref: '#/components/schemas/BeaconBlock'
400:
$ref: '#/components/responses/InvalidRequest'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/CurrentlySyncing'
post:
tags:
- MinimalSet
summary: "Publish a signed block."
description: "Instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be included in the beacon chain. The beacon node is not required to validate the signed `BeaconBlock`, and a successful response (20X) only indicates that the broadcast has been successful. The beacon node is expected to integrate the new block into its state, and therefore validate the block internally, however blocks which fail the validation are still broadcast but a different status code is returned (202)"
parameters:
- name: beacon_block
in: query
required: true
description: "The `BeaconBlock` object, as sent from the beacon node originally, but now with the signature field completed."
schema:
$ref: '#/components/schemas/BeaconBlock'
responses:
200:
description: "The block was validated successfully and has been broadcast. It has also been integrated into the beacon node's database."
202:
description: "The block failed validation, but was successfully broadcast anyway. It was not integrated into the beacon node's database."
400:
$ref: '#/components/responses/InvalidRequest'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/CurrentlySyncing'
/validator/attestation:
get:
tags:
- MinimalSet
summary: "Produce an attestation, without signature."
description: "Requests that the beacon node produce an IndexedAttestation, with a blank signature field, which the validator will then sign."
parameters:
- name: validator_pubkey
in: query
required: true
description: "Uniquely identifying which validator this attestation is to be produced for."
schema:
$ref: '#/components/schemas/pubkey'
- name: poc_bit
in: query
required: true
description: "The proof-of-custody bit that is to be reported by the requesting validator. This bit will be inserted into the appropriate location in the returned `IndexedAttestation`."
schema:
type: integer
format: uint32
minimum: 0
maximum: 1
- name: slot
in: query
required: true
description: "The slot for which the attestation should be proposed."
schema:
type: integer
- name: shard
in: query
required: true
description: "The shard number for which the attestation is to be proposed."
schema:
type: integer
responses:
200:
description: Success response
content:
application/json:
schema:
$ref: '#/components/schemas/IndexedAttestation'
400:
$ref: '#/components/responses/InvalidRequest'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/CurrentlySyncing'
post:
tags:
- MinimalSet
summary: "Publish a signed attestation."
description: "Instructs the beacon node to broadcast a newly signed IndexedAttestation object to the intended shard subnet. The beacon node is not required to validate the signed IndexedAttestation, and a successful response (20X) only indicates that the broadcast has been successful. The beacon node is expected to integrate the new attestation into its state, and therefore validate the attestation internally, however attestations which fail the validation are still broadcast but a different status code is returned (202)"
parameters:
- name: attestation
in: query
required: true
description: "An `IndexedAttestation` structure, as originally provided by the beacon node, but now with the signature field completed."
schema:
$ref: '#/components/schemas/IndexedAttestation'
responses:
200:
description: "The attestation was validated successfully and has been broadcast. It has also been integrated into the beacon node's database."
202:
description: "The attestation failed validation, but was successfully broadcast anyway. It was not integrated into the beacon node's database."
400:
$ref: '#/components/responses/InvalidRequest'
500:
$ref: '#/components/responses/InternalError'
503:
$ref: '#/components/responses/CurrentlySyncing'
components:
schemas:
pubkey:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{96}$"
description: "The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._"
example: "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc"
version:
type: string
description: "A string which uniquely identifies the client implementation and its version; similar to [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3)."
example: "Lighthouse / v0.1.5 (Linux x86_64)"
genesis_time:
type: integer
format: uint64
description: "The genesis_time configured for the beacon node, which is the unix time at which the Eth2.0 chain began."
example: 1557716289
ValidatorDuty:
type: object
properties:
validator_pubkey:
$ref: '#/components/schemas/pubkey'
attestation_slot:
type: integer
format: uint64
description: "The slot at which the validator must attest."
attestation_shard:
type: integer
format: uint64
description: "The shard in which the validator must attest."
block_proposal_slot:
type: integer
format: uint64
nullable: true
description: "The slot in which a validator must propose a block, or `null` if block production is not required."
SyncingStatus:
type: object
nullable: true
properties:
starting_slot:
type: integer
format: uint64
description: "The slot at which syncing started (will only be reset after the sync reached its head)"
current_slot:
type: integer
format: uint64
description: "The most recent slot sync'd by the beacon node."
highest_slot:
type: integer
format: uint64
description: "Globally, the estimated most recent slot number, or current target slot number."
BeaconBlock:
description: "The [`BeaconBlock`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#beaconblock) object from the Eth2.0 spec."
allOf:
- $ref: '#/components/schemas/BeaconBlockCommon'
- type: object
properties:
body:
$ref: '#/components/schemas/BeaconBlockBody'
BeaconBlockHeader:
description: "The [`BeaconBlockHeader`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#beaconblockheader) object from the Eth2.0 spec."
allOf:
- $ref: '#/components/schemas/BeaconBlockCommon'
- type: object
properties:
body_root:
type: string
format: bytes
pattern: "^0x[a-fA-F0-9]{64}$"
description: "The tree hash merkle root of the `BeaconBlockBody` for the `BeaconBlock`"
BeaconBlockCommon:
# An abstract object to collect the common fields between the BeaconBlockHeader and the BeaconBlock objects
type: object
properties:
slot:
type: integer
format: uint64
description: "The slot to which this block corresponds."
parent_root:
type: string
format: bytes
pattern: "^0x[a-fA-F0-9]{64}$"
description: "The signing merkle root of the parent `BeaconBlock`."
state_root:
type: string
format: bytes
pattern: "^0x[a-fA-F0-9]{64}$"
description: "The tree hash merkle root of the `BeaconState` for the `BeaconBlock`."
signature:
type: string
format: bytes
pattern: "^0x[a-fA-F0-9]{192}$"
example: "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
description: "The BLS signature of the `BeaconBlock` made by the validator of the block."
BeaconBlockBody:
type: object
description: "The [`BeaconBlockBody`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#beaconblockbody) object from the Eth2.0 spec."
properties:
randao_reveal:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{192}$"
description: "The RanDAO reveal value provided by the validator."
eth1_data:
title: Eth1Data
type: object
description: "The [`Eth1Data`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#eth1data) object from the Eth2.0 spec."
properties:
deposit_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Root of the deposit tree."
deposit_count:
type: integer
format: uint64
description: "Total number of deposits."
block_hash:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Ethereum 1.x block hash."
graffiti:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
proposer_slashings:
type: array
items:
title: ProposerSlashings
type: object
description: "The [`ProposerSlashing`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposerslashing) object from the Eth2.0 spec."
properties:
proposer_index:
type: integer
format: uint64
description: "The index of the proposer to be slashed."
header_1:
$ref: '#/components/schemas/BeaconBlockHeader'
header_2:
$ref: '#/components/schemas/BeaconBlockHeader'
attester_slashings:
type: array
items:
title: AttesterSlashings
type: object
description: "The [`AttesterSlashing`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attesterslashing) object from the Eth2.0 spec."
properties:
attestation_1:
$ref: '#/components/schemas/IndexedAttestation'
attestation_2:
$ref: '#/components/schemas/IndexedAttestation'
attestations:
type: array
items:
title: Attestation
type: object
description: "The [`Attestation`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestation) object from the Eth2.0 spec."
properties:
aggregation_bitfield:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]+$"
description: "Attester aggregation bitfield."
custody_bitfield:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]+$"
description: "Custody bitfield."
signature:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{192}$"
description: "BLS aggregate signature."
data:
$ref: '#/components/schemas/AttestationData'
deposits:
type: array
items:
title: Deposit
type: object
description: "The [`Deposit`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#deposit) object from the Eth2.0 spec."
properties:
proof:
type: array
description: "Branch in the deposit tree."
items:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
minItems: 32
maxItems: 32
index:
type: integer
format: uint64
description: "Index in the deposit tree."
data:
title: DepositData
type: object
description: "The [`DepositData`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#depositdata) object from the Eth2.0 spec."
properties:
pubkey:
$ref: '#/components/schemas/pubkey'
withdrawal_credentials:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "The withdrawal credentials."
amount:
type: integer
format: uint64
description: "Amount in Gwei."
signature:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{192}$"
description: "Container self-signature."
voluntary_exits:
type: array
items:
title: VoluntaryExit
type: object
description: "The [`VoluntaryExit`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#voluntaryexit) object from the Eth2.0 spec."
properties:
epoch:
type: integer
format: uint64
description: "Minimum epoch for processing exit."
validator_index:
type: integer
format: uint64
description: "Index of the exiting validator."
signature:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{192}$"
description: "Validator signature."
transfers:
type: array
items:
title: Transfer
type: object
description: "The [`Transfer`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#transfer) object from the Eth2.0 spec."
properties:
sender:
type: integer
format: uint64
description: "Sender index."
recipient:
type: integer
format: uint64
description: "Recipient index."
amount:
type: integer
format: uint64
description: "Amount in Gwei."
fee:
type: integer
format: uint64
description: "Fee in Gwei for block producer."
slot:
type: integer
format: uint64
description: "Inclusion slot."
pubkey:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{96}$"
description: "Sender withdrawal public key."
signature:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{192}$"
description: "Sender signature."
Fork:
type: object
description: "The [`Fork`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#Fork) object from the Eth2.0 spec."
properties:
previous_version:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{8}$"
description: "Previous fork version."
current_version:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{8}$"
description: "Current fork version."
epoch:
type: integer
format: uint64
description: "Fork epoch number."
IndexedAttestation:
type: object
description: "The [`IndexedAttestation`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#indexedattestation) object from the Eth2.0 spec."
properties:
custody_bit_0_indices:
type: array
description: "Validator indices for 0 bits."
items:
type: integer
format: uint64
custody_bit_1_indices:
type: array
description: "Validator indices for 1 bits."
items:
type: integer
format: uint64
signature:
type: string
format: bytes
pattern: "^0x[a-fA-F0-9]{192}$"
example: "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
description: "The BLS signature of the `IndexedAttestation`, created by the validator of the attestation."
data:
$ref: '#/components/schemas/AttestationData'
AttestationData:
type: object
description: "The [`AttestationData`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestationdata) object from the Eth2.0 spec."
properties:
beacon_block_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "LMD GHOST vote."
source_epoch:
type: integer
format: uint64
description: "Source epoch from FFG vote."
source_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Source root from FFG vote."
target_epoch:
type: integer
format: uint64
description: "Target epoch from FFG vote."
target_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Target root from FFG vote."
crosslink:
title: CrossLink
type: object
description: "The [`Crosslink`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#crosslink) object from the Eth2.0 spec, contains data from epochs [`start_epoch`, `end_epoch`)."
properties:
shard:
type: integer
format: uint64
description: "The shard number."
start_epoch:
type: integer
format: uint64
description: "The first epoch which the crosslinking data references."
end_epoch:
type: integer
format: uint64
description: "The 'end' epoch referred to by the crosslinking data; no data in this Crosslink should refer to the `end_epoch` since it is not included in the crosslinking data interval."
parent_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Root of the previous crosslink."
data_root:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]{64}$"
description: "Root of the crosslinked shard data since the previous crosslink."
responses:
Success:
description: "Request successful."
InvalidRequest:
description: "Invalid request syntax."
InternalError:
description: "Beacon node internal error."
CurrentlySyncing:
description: "Beacon node is currently syncing, try again later."
NotFound:
description: "The requested API endpoint does not exist."

View File

@ -58,7 +58,7 @@ It's recommended to extend the base-generator.
Create a `requirements.txt` in the root of your generator directory:
```
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -1,3 +1,3 @@
py-ecc==1.7.0
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers

View 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).

View 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)),
])

View File

@ -0,0 +1,4 @@
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -3,7 +3,6 @@
Operations (or "transactions" in previous spec iterations),
are atomic changes to the state, introduced by embedding in blocks.
This generator provides a series of test suites, divided into handler, for each operation type.
An operation test-runner can consume these operation test-suites,
and handle different kinds of operations by processing the cases using the specified test handler.

View File

@ -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.ssz.ssz_impl 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()))

View File

@ -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))
]

View File

@ -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]

View File

@ -1,9 +1,53 @@
from gen_base import gen_runner
from typing import Callable, Iterable
from eth2spec.test.block_processing import (
test_process_attestation,
test_process_attester_slashing,
test_process_block_header,
test_process_deposit,
test_process_proposer_slashing,
test_process_transfer,
test_process_voluntary_exit
)
from gen_base import gen_runner, gen_suite, gen_typing
from gen_from_tests.gen import generate_from_tests
from preset_loader import loader
from eth2spec.phase0 import spec
def create_suite(operation_name: str, config_name: str, get_cases: Callable[[], Iterable[gen_typing.TestCase]]) \
-> Callable[[str], gen_typing.TestSuiteOutput]:
def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
presets = loader.load_presets(configs_path, config_name)
spec.apply_constants_preset(presets)
return ("%s_%s" % (operation_name, config_name), operation_name, gen_suite.render_suite(
title="%s operation" % operation_name,
summary="Test suite for %s type operation processing" % operation_name,
forks_timeline="testing",
forks=["phase0"],
config=config_name,
runner="operations",
handler=operation_name,
test_cases=get_cases()))
return suite_definition
from deposits import mini_deposits_suite, full_deposits_suite
if __name__ == "__main__":
gen_runner.run_generator("operations", [
mini_deposits_suite,
full_deposits_suite
create_suite('attestation', 'minimal', lambda: generate_from_tests(test_process_attestation)),
create_suite('attestation', 'mainnet', lambda: generate_from_tests(test_process_attestation)),
create_suite('attester_slashing', 'minimal', lambda: generate_from_tests(test_process_attester_slashing)),
create_suite('attester_slashing', 'mainnet', lambda: generate_from_tests(test_process_attester_slashing)),
create_suite('block_header', 'minimal', lambda: generate_from_tests(test_process_block_header)),
create_suite('block_header', 'mainnet', lambda: generate_from_tests(test_process_block_header)),
create_suite('deposit', 'minimal', lambda: generate_from_tests(test_process_deposit)),
create_suite('deposit', 'mainnet', lambda: generate_from_tests(test_process_deposit)),
create_suite('proposer_slashing', 'minimal', lambda: generate_from_tests(test_process_proposer_slashing)),
create_suite('proposer_slashing', 'mainnet', lambda: generate_from_tests(test_process_proposer_slashing)),
create_suite('transfer', 'minimal', lambda: generate_from_tests(test_process_transfer)),
create_suite('transfer', 'mainnet', lambda: generate_from_tests(test_process_transfer)),
create_suite('voluntary_exit', 'minimal', lambda: generate_from_tests(test_process_voluntary_exit)),
create_suite('voluntary_exit', 'mainnet', lambda: generate_from_tests(test_process_voluntary_exit)),
])

View File

@ -1,5 +1,4 @@
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec
py_ecc
../../test_libs/pyspec

View 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).

View 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)),
])

View File

@ -0,0 +1,4 @@
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -10,7 +10,7 @@ from preset_loader import loader
def shuffling_case(seed: spec.Bytes32, count: int):
yield 'seed', '0x' + seed.hex()
yield 'count', count
yield 'shuffled', [spec.get_permuted_index(i, count, seed) for i in range(count)]
yield 'shuffled', [spec.get_shuffled_index(i, count, seed) for i in range(count)]
@to_tuple

View File

@ -1,4 +1,4 @@
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -1,4 +1,4 @@
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
ssz==0.1.0a2

View File

@ -18,10 +18,7 @@ MAX_LIST_LENGTH = 10
@to_dict
def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool):
typ = spec.get_ssz_type_by_name(name)
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
yield "type_name", name
def create_test_case_contents(value, typ):
yield "value", encode.encode(value, typ)
yield "serialized", '0x' + serialize(value).hex()
yield "root", '0x' + hash_tree_root(value).hex()
@ -29,6 +26,13 @@ def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMod
yield "signing_root", '0x' + signing_root(value).hex()
@to_dict
def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool):
typ = spec.get_ssz_type_by_name(name)
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
yield name, create_test_case_contents(value, typ)
@to_tuple
def ssz_static_cases(rng: Random, mode: random_value.RandomizationMode, chaos: bool, count: int):
for type_name in spec.ssz_types:

View File

@ -1,4 +1,4 @@
eth-utils==1.4.1
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -1 +1 @@
ruamel.yaml==0.15.87
ruamel.yaml==0.15.96

View File

@ -4,6 +4,6 @@ setup(
name='config_helpers',
packages=['preset_loader'],
install_requires=[
"ruamel.yaml==0.15.87"
"ruamel.yaml==0.15.96"
]
)

View 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

View File

@ -1,2 +1,2 @@
ruamel.yaml==0.15.87
eth-utils==1.4.1
ruamel.yaml==0.15.96
eth-utils==1.6.0

View File

@ -2,9 +2,9 @@ from distutils.core import setup
setup(
name='gen_helpers',
packages=['gen_base'],
packages=['gen_base', 'gen_from_tests'],
install_requires=[
"ruamel.yaml==0.15.87",
"eth-utils==1.4.1"
"ruamel.yaml==0.15.96",
"eth-utils==1.6.0"
]
)

View File

@ -46,8 +46,9 @@ The `-B` flag may be helpful to force-overwrite the `pyspec` output after you ma
Run the tests:
```
pytest --config=minimal
pytest --config=minimal eth2spec
```
Note the package-name, this is to locate the tests.
## Contributing

View 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)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,181 @@
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 (
build_deposit,
prepare_state_and_deposit,
sign_deposit_data,
)
from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.keys import privkeys, pubkeys
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)
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_invalid_withdrawal_credentials_top_up(state):
validator_index = 0
amount = spec.MAX_EFFECTIVE_BALANCE // 4
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(b"junk")[1:]
deposit = prepare_state_and_deposit(
state,
validator_index,
amount,
withdrawal_credentials=withdrawal_credentials
)
# inconsistent withdrawal credentials, 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)
@spec_state_test
def test_wrong_deposit_for_deposit_count(state):
deposit_data_leaves = [spec.ZERO_HASH] * len(state.validator_registry)
# build root for deposit_1
index_1 = len(deposit_data_leaves)
pubkey_1 = pubkeys[index_1]
privkey_1 = privkeys[index_1]
deposit_1, root_1, deposit_data_leaves = build_deposit(
state,
deposit_data_leaves,
pubkey_1,
privkey_1,
spec.MAX_EFFECTIVE_BALANCE,
withdrawal_credentials=b'\x00'*32,
signed=True,
)
deposit_count_1 = len(deposit_data_leaves)
# build root for deposit_2
index_2 = len(deposit_data_leaves)
pubkey_2 = pubkeys[index_2]
privkey_2 = privkeys[index_2]
deposit_2, root_2, deposit_data_leaves = build_deposit(
state,
deposit_data_leaves,
pubkey_2,
privkey_2,
spec.MAX_EFFECTIVE_BALANCE,
withdrawal_credentials=b'\x00'*32,
signed=True,
)
# state has root for deposit_2 but is at deposit_count for deposit_1
state.latest_eth1_data.deposit_root = root_2
state.latest_eth1_data.deposit_count = deposit_count_1
yield from run_deposit_processing(state, deposit_2, index_2, 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)

View File

@ -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)

View File

@ -0,0 +1,178 @@
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)

View File

@ -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)

View File

@ -0,0 +1,37 @@
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)

View 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

View File

@ -1,5 +1,4 @@
from copy import deepcopy
import pytest
import eth2spec.phase0.spec as spec
@ -9,106 +8,123 @@ from eth2spec.phase0.spec import (
process_crosslinks,
state_transition,
)
from tests.helpers import (
from eth2spec.test.context import spec_state_test
from eth2spec.test.helpers.state import (
next_epoch,
next_slot
)
from eth2spec.test.helpers.block import apply_empty_block, sign_block
from eth2spec.test.helpers.attestations import (
add_attestation_to_state,
build_empty_block_for_next_slot,
fill_aggregate_attestation,
get_crosslink_committee,
get_valid_attestation,
next_epoch,
next_slot,
set_bitfield_bit,
sign_attestation,
)
# mark entire file as 'crosslinks'
pytestmark = pytest.mark.crosslinks
def run_process_crosslinks(state, valid=True):
"""
Run ``process_crosslinks``, yielding:
- pre-state ('pre')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# transition state to slot before state transition
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
block = build_empty_block_for_next_slot(state)
block.slot = slot
sign_block(state, block)
state_transition(state, block)
# cache state before epoch transition
process_slot(state)
post_state = deepcopy(state)
process_crosslinks(post_state)
return state, post_state
yield 'pre', state
process_crosslinks(state)
yield 'post', state
@spec_state_test
def test_no_attestations(state):
pre_state, post_state = run_process_crosslinks(state)
yield from run_process_crosslinks(state)
for shard in range(spec.SHARD_COUNT):
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
return pre_state, post_state
assert state.previous_crosslinks[shard] == state.current_crosslinks[shard]
@spec_state_test
def test_single_crosslink_update_from_current_epoch(state):
next_epoch(state)
attestation = get_valid_attestation(state)
attestation = get_valid_attestation(state, signed=True)
fill_aggregate_attestation(state, attestation)
add_attestation_to_state(state, attestation, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
assert len(state.current_epoch_attestations) == 1
pre_state, post_state = run_process_crosslinks(state)
shard = attestation.data.crosslink.shard
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
pre_crosslink = deepcopy(state.current_crosslinks[shard])
return pre_state, post_state
yield from run_process_crosslinks(state)
assert state.previous_crosslinks[shard] != state.current_crosslinks[shard]
assert pre_crosslink != state.current_crosslinks[shard]
@spec_state_test
def test_single_crosslink_update_from_previous_epoch(state):
next_epoch(state)
attestation = get_valid_attestation(state)
attestation = get_valid_attestation(state, signed=True)
fill_aggregate_attestation(state, attestation)
add_attestation_to_state(state, attestation, state.slot + spec.SLOTS_PER_EPOCH)
assert len(state.previous_epoch_attestations) == 1
pre_state, post_state = run_process_crosslinks(state)
shard = attestation.data.crosslink.shard
pre_crosslink = deepcopy(state.current_crosslinks[shard])
crosslink_deltas = get_crosslink_deltas(state)
shard = attestation.data.crosslink.shard
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
yield from run_process_crosslinks(state)
assert state.previous_crosslinks[shard] != state.current_crosslinks[shard]
assert pre_crosslink != state.current_crosslinks[shard]
# ensure rewarded
for index in get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.crosslink.shard):
assert crosslink_deltas[0][index] > 0
assert crosslink_deltas[1][index] == 0
return pre_state, post_state
@spec_state_test
def test_double_late_crosslink(state):
if spec.get_epoch_committee_count(state, spec.get_current_epoch(state)) < spec.SHARD_COUNT:
print("warning: ignoring test, test-assumptions are incompatible with configuration")
return
next_epoch(state)
state.slot += 4
attestation_1 = get_valid_attestation(state)
attestation_1 = get_valid_attestation(state, signed=True)
fill_aggregate_attestation(state, attestation_1)
# add attestation_1 in the next epoch
# add attestation_1 to next epoch
next_epoch(state)
add_attestation_to_state(state, attestation_1, state.slot + 1)
for slot in range(spec.SLOTS_PER_EPOCH):
attestation_2 = get_valid_attestation(state)
if attestation_2.data.crosslink.shard == attestation_1.data.crosslink.shard:
sign_attestation(state, attestation_2)
break
next_slot(state)
apply_empty_block(state)
fill_aggregate_attestation(state, attestation_2)
# add attestation_2 in the next epoch after attestation_1 has
@ -119,16 +135,15 @@ def test_double_late_crosslink(state):
assert len(state.previous_epoch_attestations) == 1
assert len(state.current_epoch_attestations) == 0
pre_state, post_state = run_process_crosslinks(state)
crosslink_deltas = get_crosslink_deltas(state)
yield from run_process_crosslinks(state)
shard = attestation_2.data.crosslink.shard
# ensure that the current crosslinks were not updated by the second attestation
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
assert state.previous_crosslinks[shard] == state.current_crosslinks[shard]
# ensure no reward, only penalties for the failed crosslink
for index in get_crosslink_committee(state, attestation_2.data.target_epoch, attestation_2.data.crosslink.shard):
assert crosslink_deltas[0][index] == 0
assert crosslink_deltas[1][index] > 0
return pre_state, post_state

View File

@ -1,21 +1,44 @@
from copy import deepcopy
import pytest
import eth2spec.phase0.spec as spec
from eth2spec.phase0.spec import (
get_current_epoch,
is_active_validator,
process_registry_updates
)
from tests.helpers import (
next_epoch,
)
# mark entire file as 'state'
pytestmark = pytest.mark.state
from eth2spec.phase0.spec import state_transition
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
from eth2spec.test.helpers.state import next_epoch
from eth2spec.test.context import spec_state_test
def run_process_registry_updates(state, valid=True):
"""
Run ``process_crosslinks``, yielding:
- pre-state ('pre')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# transition state to slot before state transition
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
block = build_empty_block_for_next_slot(state)
block.slot = slot
sign_block(state, block)
state_transition(state, block)
# cache state before epoch transition
spec.process_slot(state)
# process components of epoch transition before registry update
spec.process_justification_and_finalization(state)
spec.process_crosslinks(state)
spec.process_rewards_and_penalties(state)
yield 'pre', state
process_registry_updates(state)
yield 'post', state
@spec_state_test
def test_activation(state):
index = 0
assert is_active_validator(state.validator_registry[index], get_current_epoch(state))
@ -26,12 +49,10 @@ def test_activation(state):
state.validator_registry[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
assert not is_active_validator(state.validator_registry[index], get_current_epoch(state))
pre_state = deepcopy(state)
blocks = []
for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
block = next_epoch(state)
blocks.append(block)
next_epoch(state)
yield from run_process_registry_updates(state)
assert state.validator_registry[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert state.validator_registry[index].activation_epoch != spec.FAR_FUTURE_EPOCH
@ -40,9 +61,8 @@ def test_activation(state):
get_current_epoch(state),
)
return pre_state, blocks, state
@spec_state_test
def test_ejection(state):
index = 0
assert is_active_validator(state.validator_registry[index], get_current_epoch(state))
@ -51,17 +71,13 @@ def test_ejection(state):
# Mock an ejection
state.validator_registry[index].effective_balance = spec.EJECTION_BALANCE
pre_state = deepcopy(state)
blocks = []
for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
block = next_epoch(state)
blocks.append(block)
next_epoch(state)
yield from run_process_registry_updates(state)
assert state.validator_registry[index].exit_epoch != spec.FAR_FUTURE_EPOCH
assert not is_active_validator(
state.validator_registry[index],
get_current_epoch(state),
)
return pre_state, blocks, state

View 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)

View 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),
)

View 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:]
)

View File

@ -0,0 +1,79 @@
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)

View 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,
)

View File

@ -0,0 +1,87 @@
# 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, withdrawal_credentials, signed=False):
deposit_data = DepositData(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
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,
withdrawal_credentials,
signed):
deposit_data = build_deposit_data(state, pubkey, privkey, amount, withdrawal_credentials, 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, withdrawal_credentials=None, 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]
# insecurely use pubkey as withdrawal key if no credentials provided
if withdrawal_credentials is None:
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(pubkey)[1:]
deposit, root, deposit_data_leaves = build_deposit(
state,
deposit_data_leaves,
pubkey,
privkey,
amount,
withdrawal_credentials,
signed
)
state.latest_eth1_data.deposit_root = root
state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
return deposit

View 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

View 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)}

View 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,
)

View 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]

View 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

View 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,
)
)

View 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

View File

@ -0,0 +1,57 @@
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

View File

@ -1,20 +1,14 @@
from copy import deepcopy
import pytest
import eth2spec.phase0.spec as spec
from .helpers import (
build_empty_block_for_next_slot,
fill_aggregate_attestation,
from eth2spec.phase0.spec import (
get_current_epoch,
get_epoch_start_slot,
get_valid_attestation,
next_epoch,
)
# mark entire file as 'state'
pytestmark = pytest.mark.state
from .context import spec_state_test, never_bls
from .helpers.state import next_epoch
from .helpers.block import build_empty_block_for_next_slot, apply_empty_block
from .helpers.attestations import get_valid_attestation
def check_finality(state,
@ -55,13 +49,11 @@ def next_epoch_with_attestations(state,
slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= get_epoch_start_slot(get_current_epoch(post_state)):
cur_attestation = get_valid_attestation(post_state, slot_to_attest)
fill_aggregate_attestation(post_state, cur_attestation)
block.body.attestations.append(cur_attestation)
if fill_prev_epoch:
slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
prev_attestation = get_valid_attestation(post_state, slot_to_attest)
fill_aggregate_attestation(post_state, prev_attestation)
block.body.attestations.append(prev_attestation)
spec.state_transition(post_state, block)
@ -70,126 +62,140 @@ def next_epoch_with_attestations(state,
return state, blocks, post_state
@never_bls
@spec_state_test
def test_finality_rule_4(state):
test_state = deepcopy(state)
yield 'pre', state
blocks = []
for epoch in range(4):
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
blocks += new_blocks
# justification/finalization skipped at GENESIS_EPOCH
if epoch == 0:
check_finality(test_state, prev_state, False, False, False)
check_finality(state, prev_state, False, False, False)
# justification/finalization skipped at GENESIS_EPOCH + 1
elif epoch == 1:
check_finality(test_state, prev_state, False, False, False)
check_finality(state, prev_state, False, False, False)
elif epoch == 2:
check_finality(test_state, prev_state, True, False, False)
check_finality(state, prev_state, True, False, False)
elif epoch >= 3:
# rule 4 of finality
check_finality(test_state, prev_state, True, True, True)
assert test_state.finalized_epoch == prev_state.current_justified_epoch
assert test_state.finalized_root == prev_state.current_justified_root
check_finality(state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.current_justified_epoch
assert state.finalized_root == prev_state.current_justified_root
return state, blocks, test_state
yield 'blocks', blocks, [spec.BeaconBlock]
yield 'post', state
@never_bls
@spec_state_test
def test_finality_rule_1(state):
# get past first two epochs that finality does not run on
next_epoch(state)
apply_empty_block(state)
next_epoch(state)
apply_empty_block(state)
pre_state = deepcopy(state)
test_state = deepcopy(state)
yield 'pre', state
blocks = []
for epoch in range(3):
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
blocks += new_blocks
if epoch == 0:
check_finality(test_state, prev_state, True, False, False)
check_finality(state, prev_state, True, False, False)
elif epoch == 1:
check_finality(test_state, prev_state, True, True, False)
check_finality(state, prev_state, True, True, False)
elif epoch == 2:
# finalized by rule 1
check_finality(test_state, prev_state, True, True, True)
assert test_state.finalized_epoch == prev_state.previous_justified_epoch
assert test_state.finalized_root == prev_state.previous_justified_root
check_finality(state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.previous_justified_epoch
assert state.finalized_root == prev_state.previous_justified_root
return pre_state, blocks, test_state
yield 'blocks', blocks, [spec.BeaconBlock]
yield 'post', state
@never_bls
@spec_state_test
def test_finality_rule_2(state):
# get past first two epochs that finality does not run on
next_epoch(state)
apply_empty_block(state)
next_epoch(state)
apply_empty_block(state)
pre_state = deepcopy(state)
test_state = deepcopy(state)
yield 'pre', state
blocks = []
for epoch in range(3):
if epoch == 0:
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
check_finality(test_state, prev_state, True, False, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
check_finality(state, prev_state, True, False, False)
elif epoch == 1:
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
check_finality(test_state, prev_state, False, True, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, False, False)
check_finality(state, prev_state, False, True, False)
elif epoch == 2:
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
# finalized by rule 2
check_finality(test_state, prev_state, True, False, True)
assert test_state.finalized_epoch == prev_state.previous_justified_epoch
assert test_state.finalized_root == prev_state.previous_justified_root
check_finality(state, prev_state, True, False, True)
assert state.finalized_epoch == prev_state.previous_justified_epoch
assert state.finalized_root == prev_state.previous_justified_root
blocks += new_blocks
return pre_state, blocks, test_state
yield 'blocks', blocks, [spec.BeaconBlock]
yield 'post', state
@never_bls
@spec_state_test
def test_finality_rule_3(state):
"""
Test scenario described here
https://github.com/ethereum/eth2.0-specs/issues/611#issuecomment-463612892
"""
# get past first two epochs that finality does not run on
next_epoch(state)
apply_empty_block(state)
next_epoch(state)
apply_empty_block(state)
pre_state = deepcopy(state)
test_state = deepcopy(state)
yield 'pre', state
blocks = []
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
blocks += new_blocks
check_finality(test_state, prev_state, True, False, False)
check_finality(state, prev_state, True, False, False)
# In epoch N, JE is set to N, prev JE is set to N-1
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, True, False)
blocks += new_blocks
check_finality(test_state, prev_state, True, True, True)
check_finality(state, prev_state, True, True, True)
# In epoch N+1, JE is N, prev JE is N-1, and not enough messages get in to do anything
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
prev_state, new_blocks, state = next_epoch_with_attestations(state, False, False)
blocks += new_blocks
check_finality(test_state, prev_state, False, True, False)
check_finality(state, prev_state, False, True, False)
# In epoch N+2, JE is N, prev JE is N, and enough messages from the previous epoch get in to justify N+1.
# N+1 now becomes the JE. Not enough messages from epoch N+2 itself get in to justify N+2
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
prev_state, new_blocks, state = next_epoch_with_attestations(state, False, True)
blocks += new_blocks
# rule 2
check_finality(test_state, prev_state, True, False, True)
check_finality(state, prev_state, True, False, True)
# In epoch N+3, LJE is N+1, prev LJE is N, and enough messages get in to justify epochs N+2 and N+3.
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, True)
prev_state, new_blocks, state = next_epoch_with_attestations(state, True, True)
blocks += new_blocks
# rule 3
check_finality(test_state, prev_state, True, True, True)
assert test_state.finalized_epoch == prev_state.current_justified_epoch
assert test_state.finalized_root == prev_state.current_justified_root
check_finality(state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.current_justified_epoch
assert state.finalized_root == prev_state.current_justified_root
return pre_state, blocks, test_state
yield 'blocks', blocks, [spec.BeaconBlock]
yield 'post', state

View 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

View 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)

View File

@ -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

View File

@ -1,6 +1,4 @@
from hashlib import sha256
# from eth_utils import keccak
def hash(x): return sha256(x).digest()
# def hash(x): return keccak(x)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -1,429 +0,0 @@
from copy import deepcopy
from py_ecc import bls
import eth2spec.phase0.spec as spec
from eth2spec.utils.ssz.ssz_impl 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]

View File

@ -1,436 +0,0 @@
from copy import deepcopy
import pytest
from py_ecc import bls
import eth2spec.phase0.spec as spec
from eth2spec.utils.ssz.ssz_impl 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