mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-20 22:38:11 +00:00
Merge branch 'dev' into fix-deposit-forkv
This commit is contained in:
commit
7a9097ec23
@ -52,7 +52,7 @@ jobs:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- ~/specs-repo
|
||||
install_test:
|
||||
install_env:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
@ -60,13 +60,13 @@ jobs:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v1-pyspec
|
||||
venv_name: v1-pyspec-03
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- run:
|
||||
name: Install pyspec requirements
|
||||
command: make install_test
|
||||
command: make install_test && make install_lint
|
||||
- save_cached_venv:
|
||||
venv_name: v1-pyspec
|
||||
venv_name: v1-pyspec-03
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
venv_path: ./test_libs/pyspec/venv
|
||||
test:
|
||||
@ -77,21 +77,37 @@ jobs:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v1-pyspec
|
||||
venv_name: v1-pyspec-03
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- run:
|
||||
name: Run py-tests
|
||||
command: make citest
|
||||
- store_test_results:
|
||||
path: test_libs/pyspec/test-reports
|
||||
lint:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v1-pyspec-03
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}'
|
||||
- run:
|
||||
name: Run linter
|
||||
command: make lint
|
||||
workflows:
|
||||
version: 2.1
|
||||
test_spec:
|
||||
jobs:
|
||||
- checkout_specs
|
||||
- install_test:
|
||||
- install_env:
|
||||
requires:
|
||||
- checkout_specs
|
||||
- test:
|
||||
requires:
|
||||
- install_test
|
||||
- install_env
|
||||
- lint:
|
||||
requires:
|
||||
- test
|
||||
|
7
Makefile
7
Makefile
@ -39,6 +39,13 @@ test: $(PY_SPEC_ALL_TARGETS)
|
||||
citest: $(PY_SPEC_ALL_TARGETS)
|
||||
cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml .
|
||||
|
||||
install_lint:
|
||||
cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install flake8==3.5.0
|
||||
|
||||
lint: $(PY_SPEC_ALL_TARGETS)
|
||||
cd $(PY_SPEC_DIR); . venv/bin/activate; \
|
||||
flake8 --max-line-length=120 ./eth2spec;
|
||||
|
||||
# "make pyspec" to create the pyspec for all phases.
|
||||
pyspec: $(PY_SPEC_ALL_TARGETS)
|
||||
|
||||
|
@ -72,7 +72,7 @@ MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
|
||||
# 2**11 (= 2,048) epochs 9 days
|
||||
PERSISTENT_COMMITTEE_PERIOD: 2048
|
||||
# 2**6 (= 64) epochs ~7 hours
|
||||
MAX_CROSSLINK_EPOCHS: 64
|
||||
MAX_EPOCHS_PER_CROSSLINK: 64
|
||||
# 2**2 (= 4) epochs 25.6 minutes
|
||||
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
|
||||
|
||||
@ -124,4 +124,4 @@ DOMAIN_RANDAO: 1
|
||||
DOMAIN_ATTESTATION: 2
|
||||
DOMAIN_DEPOSIT: 3
|
||||
DOMAIN_VOLUNTARY_EXIT: 4
|
||||
DOMAIN_TRANSFER: 5
|
||||
DOMAIN_TRANSFER: 5
|
||||
|
@ -71,7 +71,7 @@ MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
|
||||
# 2**11 (= 2,048) epochs 9 days
|
||||
PERSISTENT_COMMITTEE_PERIOD: 2048
|
||||
# 2**6 (= 64) epochs ~7 hours
|
||||
MAX_CROSSLINK_EPOCHS: 64
|
||||
MAX_EPOCHS_PER_CROSSLINK: 64
|
||||
# 2**2 (= 4) epochs 25.6 minutes
|
||||
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
|
||||
|
||||
@ -123,4 +123,4 @@ DOMAIN_RANDAO: 1
|
||||
DOMAIN_ATTESTATION: 2
|
||||
DOMAIN_DEPOSIT: 3
|
||||
DOMAIN_VOLUNTARY_EXIT: 4
|
||||
DOMAIN_TRANSFER: 5
|
||||
DOMAIN_TRANSFER: 5
|
||||
|
@ -5,6 +5,7 @@ import function_puller
|
||||
def build_phase0_spec(sourcefile, outfile):
|
||||
code_lines = []
|
||||
code_lines.append("""
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@ -12,19 +13,22 @@ from typing import (
|
||||
NewType,
|
||||
Tuple,
|
||||
)
|
||||
from eth2spec.utils.minimal_ssz import *
|
||||
from eth2spec.utils.bls_stub import *
|
||||
from eth2spec.utils.minimal_ssz import (
|
||||
SSZType,
|
||||
hash_tree_root,
|
||||
signing_root,
|
||||
)
|
||||
from eth2spec.utils.bls_stub import (
|
||||
bls_aggregate_pubkeys,
|
||||
bls_verify,
|
||||
bls_verify_multiple,
|
||||
)
|
||||
from eth2spec.utils.hash_function import hash
|
||||
|
||||
""")
|
||||
for i in (1, 2, 3, 4, 8, 32, 48, 96):
|
||||
code_lines.append("def int_to_bytes%d(x): return x.to_bytes(%d, 'little')" % (i, i))
|
||||
|
||||
code_lines.append("""
|
||||
|
||||
# stub, will get overwritten by real var
|
||||
SLOTS_PER_EPOCH = 64
|
||||
|
||||
|
||||
Slot = NewType('Slot', int) # uint64
|
||||
Epoch = NewType('Epoch', int) # uint64
|
||||
Shard = NewType('Shard', int) # uint64
|
||||
@ -68,6 +72,7 @@ def hash(x):
|
||||
hash_cache[x] = ret
|
||||
return ret
|
||||
|
||||
|
||||
# Access to overwrite spec constants based on configuration
|
||||
def apply_constants_preset(preset: Dict[str, Any]):
|
||||
global_vars = globals()
|
||||
|
@ -55,15 +55,19 @@ def get_spec(file_name: str) -> List[str]:
|
||||
if eligible:
|
||||
code_lines.append(row[0] + ' = ' + (row[1].replace('**TBD**', '0x1234567890123456789012345678901234567890')))
|
||||
# Build type-def re-initialization
|
||||
code_lines.append('')
|
||||
code_lines.append('\n')
|
||||
code_lines.append('def init_SSZ_types():')
|
||||
code_lines.append(' global_vars = globals()')
|
||||
for ssz_type_name, ssz_type in type_defs:
|
||||
code_lines.append('')
|
||||
for type_line in ssz_type:
|
||||
code_lines.append(' ' + type_line)
|
||||
if len(type_line) > 0:
|
||||
code_lines.append(' ' + type_line)
|
||||
code_lines.append('\n')
|
||||
code_lines.append('ssz_types = [' + ', '.join([f'\'{ssz_type_name}\'' for (ssz_type_name, _) in type_defs]) + ']')
|
||||
code_lines.append('ssz_types = [\n')
|
||||
for (ssz_type_name, _) in type_defs:
|
||||
code_lines.append(f' {ssz_type_name},\n')
|
||||
code_lines.append(']')
|
||||
code_lines.append('\n')
|
||||
code_lines.append('def get_ssz_type_by_name(name: str) -> SSZType:')
|
||||
code_lines.append(' return globals()[name]')
|
||||
|
@ -1,6 +1,8 @@
|
||||
# BLS signature verification
|
||||
|
||||
**Warning: This document is pending academic review and should not yet be considered secure.**
|
||||
**Notice**: This document is a placeholder to facilitate the emergence of cross-client testnets. Substantive changes are postponed until [BLS standardisation](https://github.com/pairingwg/bls_standard) is finalized.
|
||||
|
||||
**Warning**: The constructions in this document should not be considered secure. In particular, the `hash_to_G2` function is known to be unsecure.
|
||||
|
||||
## Table of contents
|
||||
<!-- TOC -->
|
||||
|
@ -75,14 +75,14 @@
|
||||
- [`compute_committee`](#compute_committee)
|
||||
- [`get_crosslink_committee`](#get_crosslink_committee)
|
||||
- [`get_attesting_indices`](#get_attesting_indices)
|
||||
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
|
||||
- [`int_to_bytes`](#int_to_bytes)
|
||||
- [`bytes_to_int`](#bytes_to_int)
|
||||
- [`get_total_balance`](#get_total_balance)
|
||||
- [`get_domain`](#get_domain)
|
||||
- [`get_bitfield_bit`](#get_bitfield_bit)
|
||||
- [`verify_bitfield`](#verify_bitfield)
|
||||
- [`convert_to_indexed`](#convert_to_indexed)
|
||||
- [`verify_indexed_attestation`](#verify_indexed_attestation)
|
||||
- [`validate_indexed_attestation`](#validate_indexed_attestation)
|
||||
- [`is_slashable_attestation_data`](#is_slashable_attestation_data)
|
||||
- [`integer_squareroot`](#integer_squareroot)
|
||||
- [`get_delayed_activation_exit_epoch`](#get_delayed_activation_exit_epoch)
|
||||
@ -98,8 +98,7 @@
|
||||
- [Genesis state](#genesis-state)
|
||||
- [Genesis block](#genesis-block)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [State caching](#state-caching)
|
||||
- [Per-epoch processing](#per-epoch-processing)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [Helper functions](#helper-functions-1)
|
||||
- [Justification and finalization](#justification-and-finalization)
|
||||
- [Crosslinks](#crosslinks)
|
||||
@ -107,8 +106,7 @@
|
||||
- [Registry updates](#registry-updates)
|
||||
- [Slashings](#slashings)
|
||||
- [Final updates](#final-updates)
|
||||
- [Per-slot processing](#per-slot-processing)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Block processing](#block-processing)
|
||||
- [Block header](#block-header)
|
||||
- [RANDAO](#randao)
|
||||
- [Eth1 data](#eth1-data)
|
||||
@ -119,7 +117,6 @@
|
||||
- [Deposits](#deposits)
|
||||
- [Voluntary exits](#voluntary-exits)
|
||||
- [Transfers](#transfers)
|
||||
- [State root verification](#state-root-verification)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
@ -194,8 +191,8 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||
| `GENESIS_SLOT` | `0` |
|
||||
| `GENESIS_EPOCH` | `0` |
|
||||
| `FAR_FUTURE_EPOCH` | `2**64 - 1` |
|
||||
| `ZERO_HASH` | `int_to_bytes32(0)` |
|
||||
| `BLS_WITHDRAWAL_PREFIX_BYTE` | `int_to_bytes1(0)` |
|
||||
| `ZERO_HASH` | `b'\x00' * 32` |
|
||||
| `BLS_WITHDRAWAL_PREFIX` | `0` |
|
||||
|
||||
### Time parameters
|
||||
|
||||
@ -209,10 +206,10 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours |
|
||||
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
|
||||
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
|
||||
| `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours |
|
||||
| `MAX_EPOCHS_PER_CROSSLINK` | `2**6` (= 64) | epochs | ~7 hours |
|
||||
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes |
|
||||
|
||||
* `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH`
|
||||
* `MAX_EPOCHS_PER_CROSSLINK` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH`
|
||||
|
||||
### State list lengths
|
||||
|
||||
@ -282,12 +279,15 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
|
||||
```python
|
||||
{
|
||||
# Epoch number
|
||||
'epoch': 'uint64',
|
||||
# Shard number
|
||||
'shard': 'uint64',
|
||||
# Crosslinking data from epochs [start....end-1]
|
||||
'start_epoch': 'uint64',
|
||||
'end_epoch': 'uint64',
|
||||
# Root of the previous crosslink
|
||||
'previous_crosslink_root': 'bytes32',
|
||||
'parent_root': 'bytes32',
|
||||
# Root of the crosslinked shard data since the previous crosslink
|
||||
'crosslink_data_root': 'bytes32',
|
||||
'data_root': 'bytes32',
|
||||
}
|
||||
```
|
||||
|
||||
@ -318,9 +318,7 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
'target_root': 'bytes32',
|
||||
|
||||
# Crosslink vote
|
||||
'shard': 'uint64',
|
||||
'previous_crosslink_root': 'bytes32',
|
||||
'crosslink_data_root': 'bytes32',
|
||||
'crosslink': Crosslink,
|
||||
}
|
||||
```
|
||||
|
||||
@ -369,12 +367,13 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
```python
|
||||
{
|
||||
'slot': 'uint64',
|
||||
'previous_block_root': 'bytes32',
|
||||
'parent_root': 'bytes32',
|
||||
'state_root': 'bytes32',
|
||||
'block_body_root': 'bytes32',
|
||||
'body_root': 'bytes32',
|
||||
'signature': 'bytes96',
|
||||
}
|
||||
```
|
||||
|
||||
#### `Validator`
|
||||
|
||||
```python
|
||||
@ -536,7 +535,7 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
{
|
||||
# Header
|
||||
'slot': 'uint64',
|
||||
'previous_block_root': 'bytes32',
|
||||
'parent_root': 'bytes32',
|
||||
'state_root': 'bytes32',
|
||||
'body': BeaconBlockBody,
|
||||
'signature': 'bytes96',
|
||||
@ -579,8 +578,8 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||
'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||
'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH],
|
||||
'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], # Balances slashed at every withdrawal period
|
||||
'latest_block_header': BeaconBlockHeader, # `latest_block_header.state_root == ZERO_HASH` temporarily
|
||||
'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH],
|
||||
'latest_block_header': BeaconBlockHeader,
|
||||
'historical_roots': ['bytes32'],
|
||||
|
||||
# Ethereum 1.0 chain data
|
||||
@ -649,7 +648,7 @@ def get_previous_epoch(state: BeaconState) -> Epoch:
|
||||
Return the current epoch if it's genesis epoch.
|
||||
"""
|
||||
current_epoch = get_current_epoch(state)
|
||||
return (current_epoch - 1) if current_epoch > GENESIS_EPOCH else current_epoch
|
||||
return GENESIS_EPOCH if current_epoch == GENESIS_EPOCH else current_epoch - 1
|
||||
```
|
||||
|
||||
### `get_current_epoch`
|
||||
@ -767,7 +766,7 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard:
|
||||
```python
|
||||
def get_attestation_data_slot(state: BeaconState, data: AttestationData) -> Slot:
|
||||
committee_count = get_epoch_committee_count(state, data.target_epoch)
|
||||
offset = (data.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target_epoch)) % SHARD_COUNT
|
||||
offset = (data.crosslink.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target_epoch)) % SHARD_COUNT
|
||||
return get_epoch_start_slot(data.target_epoch) + offset // (committee_count // SLOTS_PER_EPOCH)
|
||||
```
|
||||
|
||||
@ -830,7 +829,7 @@ def generate_seed(state: BeaconState,
|
||||
return hash(
|
||||
get_randao_mix(state, epoch + LATEST_RANDAO_MIXES_LENGTH - MIN_SEED_LOOKAHEAD) +
|
||||
get_active_index_root(state, epoch) +
|
||||
int_to_bytes32(epoch)
|
||||
int_to_bytes(epoch, length=32)
|
||||
)
|
||||
```
|
||||
|
||||
@ -851,7 +850,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
||||
i = 0
|
||||
while True:
|
||||
candidate_index = first_committee[(epoch + i) % len(first_committee)]
|
||||
random_byte = hash(seed + int_to_bytes8(i // 32))[i % 32]
|
||||
random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32]
|
||||
effective_balance = state.validator_registry[candidate_index].effective_balance
|
||||
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
|
||||
return candidate_index
|
||||
@ -888,10 +887,10 @@ def get_shuffled_index(index: ValidatorIndex, index_count: int, seed: Bytes32) -
|
||||
# Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf)
|
||||
# See the 'generalized domain' algorithm on page 3
|
||||
for round in range(SHUFFLE_ROUND_COUNT):
|
||||
pivot = bytes_to_int(hash(seed + int_to_bytes1(round))[0:8]) % index_count
|
||||
flip = (pivot - index) % index_count
|
||||
pivot = bytes_to_int(hash(seed + int_to_bytes(round, length=1))[0:8]) % index_count
|
||||
flip = (pivot + index_count - index) % index_count
|
||||
position = max(index, flip)
|
||||
source = hash(seed + int_to_bytes1(round) + int_to_bytes4(position // 256))
|
||||
source = hash(seed + int_to_bytes(round, length=1) + int_to_bytes(position // 256, length=4))
|
||||
byte = source[(position % 256) // 8]
|
||||
bit = (byte >> (position % 8)) % 2
|
||||
index = flip if bit else index
|
||||
@ -929,14 +928,17 @@ def get_attesting_indices(state: BeaconState,
|
||||
"""
|
||||
Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``.
|
||||
"""
|
||||
committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.shard)
|
||||
committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.crosslink.shard)
|
||||
assert verify_bitfield(bitfield, len(committee))
|
||||
return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1])
|
||||
```
|
||||
|
||||
### `int_to_bytes1`, `int_to_bytes2`, ...
|
||||
### `int_to_bytes`
|
||||
|
||||
`int_to_bytes1(x): return x.to_bytes(1, 'little')`, `int_to_bytes2(x): return x.to_bytes(2, 'little')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32, 48, 96.
|
||||
```python
|
||||
def int_to_bytes(integer: int, length: int) -> bytes:
|
||||
return integer.to_bytes(length, 'little')
|
||||
```
|
||||
|
||||
### `bytes_to_int`
|
||||
|
||||
@ -967,7 +969,7 @@ def get_domain(state: BeaconState,
|
||||
epoch = get_current_epoch(state) if message_epoch is None else message_epoch
|
||||
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
|
||||
# fork version is on the big-endian side: when signing using only the type (e.g. deposits), the type can be passed directly.
|
||||
return bytes_to_int(int_to_bytes4(domain_type) + fork_version)
|
||||
return bytes_to_int(int_to_bytes(domain_type, length=4) + fork_version)
|
||||
```
|
||||
|
||||
### `get_bitfield_bit`
|
||||
@ -1017,35 +1019,29 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA
|
||||
)
|
||||
```
|
||||
|
||||
### `verify_indexed_attestation`
|
||||
### `validate_indexed_attestation`
|
||||
|
||||
```python
|
||||
def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
|
||||
def validate_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> None:
|
||||
"""
|
||||
Verify validity of ``indexed_attestation`` fields.
|
||||
Verify validity of ``indexed_attestation``.
|
||||
"""
|
||||
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||
bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
|
||||
# Ensure no duplicate indices across custody bits
|
||||
assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0
|
||||
|
||||
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
||||
return False
|
||||
|
||||
if not (1 <= len(custody_bit_0_indices) + len(custody_bit_1_indices) <= MAX_INDICES_PER_ATTESTATION):
|
||||
return False
|
||||
|
||||
if custody_bit_0_indices != sorted(custody_bit_0_indices):
|
||||
return False
|
||||
|
||||
if custody_bit_1_indices != sorted(custody_bit_1_indices):
|
||||
return False
|
||||
|
||||
return bls_verify_multiple(
|
||||
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
|
||||
assert len(bit_1_indices) == 0
|
||||
# Verify max number of indices
|
||||
assert len(bit_0_indices) + len(bit_1_indices) <= MAX_INDICES_PER_ATTESTATION
|
||||
# Verify index sets are disjoint
|
||||
assert len(set(bit_0_indices).intersection(bit_1_indices)) == 0
|
||||
# Verify indices are sorted
|
||||
assert bit_0_indices == sorted(bit_0_indices) and bit_1_indices == sorted(bit_1_indices)
|
||||
# Verify aggregate signature
|
||||
assert bls_verify_multiple(
|
||||
pubkeys=[
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_indices]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_indices]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in bit_0_indices]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in bit_1_indices]),
|
||||
],
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b0)),
|
||||
@ -1131,7 +1127,7 @@ def get_churn_limit(state: BeaconState) -> int:
|
||||
```python
|
||||
def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
"""
|
||||
Initiate the validator of the given ``index``.
|
||||
Initiate the exit of the validator of the given ``index``.
|
||||
"""
|
||||
# Return if validator already initiated exit
|
||||
validator = state.validator_registry[index]
|
||||
@ -1153,7 +1149,9 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
#### `slash_validator`
|
||||
|
||||
```python
|
||||
def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex=None) -> None:
|
||||
def slash_validator(state: BeaconState,
|
||||
slashed_index: ValidatorIndex,
|
||||
whistleblower_index: ValidatorIndex=None) -> None:
|
||||
"""
|
||||
Slash the validator with index ``slashed_index``.
|
||||
"""
|
||||
@ -1193,7 +1191,11 @@ Let `genesis_state = get_genesis_beacon_state(genesis_deposits, eth2genesis.gene
|
||||
|
||||
```python
|
||||
def get_genesis_beacon_state(deposits: List[Deposit], genesis_time: int, genesis_eth1_data: Eth1Data) -> BeaconState:
|
||||
state = BeaconState(genesis_time=genesis_time, latest_eth1_data=genesis_eth1_data)
|
||||
state = BeaconState(
|
||||
genesis_time=genesis_time,
|
||||
latest_eth1_data=genesis_eth1_data,
|
||||
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
|
||||
)
|
||||
|
||||
# Process genesis deposits
|
||||
for deposit in deposits:
|
||||
@ -1219,50 +1221,61 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
|
||||
|
||||
## Beacon chain state transition function
|
||||
|
||||
We now define the state transition function. At a high level, the state transition is made up of four parts:
|
||||
|
||||
1. State caching, which happens at the start of every slot.
|
||||
2. The per-epoch transitions, which happens at the start of the first slot of every epoch.
|
||||
3. The per-slot transitions, which happens at every slot.
|
||||
4. The per-block transitions, which happens at every block.
|
||||
|
||||
Transition section notes:
|
||||
* The state caching caches the state root of the previous slot and updates block and state roots records.
|
||||
* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization.
|
||||
* The per-slot transitions focus on the slot counter.
|
||||
* The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`.
|
||||
|
||||
Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid.
|
||||
|
||||
*Note*: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block.
|
||||
|
||||
### State caching
|
||||
|
||||
At every `slot > GENESIS_SLOT` run the following function:
|
||||
The post-state corresponding to a pre-state `state` and a block `block` is defined as `state_transition(state, block)`. State transitions that trigger an unhandled excpetion (e.g. a failed `assert` or an out-of-range list access) are considered invalid.
|
||||
|
||||
```python
|
||||
def cache_state(state: BeaconState) -> None:
|
||||
# Cache latest known state root (for previous slot)
|
||||
latest_state_root = hash_tree_root(state)
|
||||
state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = latest_state_root
|
||||
|
||||
# Store latest known state root (for previous slot) in latest_block_header if it is empty
|
||||
if state.latest_block_header.state_root == ZERO_HASH:
|
||||
state.latest_block_header.state_root = latest_state_root
|
||||
|
||||
# Cache latest known block root (for previous slot)
|
||||
latest_block_root = signing_root(state.latest_block_header)
|
||||
state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = latest_block_root
|
||||
def state_transition(state: BeaconState, block: BeaconBlock, validate_state_root: bool=False) -> BeaconState:
|
||||
# Process slots (including those with no blocks) since block
|
||||
process_slots(state, block.slot)
|
||||
# Process block
|
||||
process_block(state, block)
|
||||
# Validate state root (`validate_state_root == True` in production)
|
||||
if validate_state_root:
|
||||
assert block.state_root == hash_tree_root(state)
|
||||
# Return post-state
|
||||
return state
|
||||
```
|
||||
|
||||
### Per-epoch processing
|
||||
```python
|
||||
def process_slots(state: BeaconState, slot: Slot) -> None:
|
||||
assert state.slot < slot
|
||||
while state.slot < slot:
|
||||
process_slot(state)
|
||||
# Process epoch on the first slot of the next epoch
|
||||
if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
|
||||
process_epoch(state)
|
||||
state.slot += 1
|
||||
```
|
||||
|
||||
The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SLOTS_PER_EPOCH == 0`.
|
||||
```python
|
||||
def process_slot(state: BeaconState) -> None:
|
||||
# Cache state root
|
||||
previous_state_root = hash_tree_root(state)
|
||||
state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root
|
||||
|
||||
# Cache latest block header state root
|
||||
if state.latest_block_header.state_root == ZERO_HASH:
|
||||
state.latest_block_header.state_root = previous_state_root
|
||||
|
||||
# Cache block root
|
||||
previous_block_root = signing_root(state.latest_block_header)
|
||||
state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root
|
||||
```
|
||||
|
||||
### Epoch processing
|
||||
|
||||
```python
|
||||
def process_epoch(state: BeaconState) -> None:
|
||||
process_justification_and_finalization(state)
|
||||
process_crosslinks(state)
|
||||
process_rewards_and_penalties(state)
|
||||
process_registry_updates(state)
|
||||
process_slashings(state)
|
||||
process_final_updates(state)
|
||||
```
|
||||
|
||||
#### Helper functions
|
||||
|
||||
We define epoch transition helper functions:
|
||||
|
||||
```python
|
||||
def get_total_active_balance(state: BeaconState) -> Gwei:
|
||||
return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state)))
|
||||
@ -1291,7 +1304,8 @@ def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[Pen
|
||||
```
|
||||
|
||||
```python
|
||||
def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
||||
def get_unslashed_attesting_indices(state: BeaconState,
|
||||
attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
||||
output = set()
|
||||
for a in attestations:
|
||||
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield))
|
||||
@ -1304,39 +1318,24 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat
|
||||
```
|
||||
|
||||
```python
|
||||
def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink:
|
||||
return Crosslink(
|
||||
epoch=min(data.target_epoch, state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS),
|
||||
previous_crosslink_root=data.previous_crosslink_root,
|
||||
crosslink_data_root=data.crosslink_data_root,
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]:
|
||||
shard_attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.shard == shard]
|
||||
shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations]
|
||||
candidate_crosslinks = [
|
||||
c for c in shard_crosslinks
|
||||
if hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c))
|
||||
]
|
||||
if len(candidate_crosslinks) == 0:
|
||||
return Crosslink(), []
|
||||
|
||||
def get_attestations_for(crosslink: Crosslink) -> List[PendingAttestation]:
|
||||
return [a for a in shard_attestations if get_crosslink_from_attestation_data(state, a.data) == crosslink]
|
||||
# Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically)
|
||||
winning_crosslink = max(candidate_crosslinks, key=lambda crosslink: (
|
||||
get_attesting_balance(state, get_attestations_for(crosslink)), crosslink.crosslink_data_root
|
||||
def get_winning_crosslink_and_attesting_indices(state: BeaconState,
|
||||
epoch: Epoch,
|
||||
shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]:
|
||||
attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.crosslink.shard == shard]
|
||||
crosslinks = list(filter(
|
||||
lambda c: hash_tree_root(state.current_crosslinks[shard]) in (c.parent_root, hash_tree_root(c)),
|
||||
[a.data.crosslink for a in attestations]
|
||||
))
|
||||
|
||||
return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink))
|
||||
# Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically)
|
||||
winning_crosslink = max(crosslinks, key=lambda c: (
|
||||
get_attesting_balance(state, [a for a in attestations if a.data.crosslink == c]), c.data_root
|
||||
), default=Crosslink())
|
||||
winning_attestations = [a for a in attestations if a.data.crosslink == winning_crosslink]
|
||||
return winning_crosslink, get_unslashed_attesting_indices(state, winning_attestations)
|
||||
```
|
||||
|
||||
#### Justification and finalization
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_justification_and_finalization(state: BeaconState) -> None:
|
||||
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
|
||||
@ -1351,12 +1350,16 @@ def process_justification_and_finalization(state: BeaconState) -> None:
|
||||
state.previous_justified_epoch = state.current_justified_epoch
|
||||
state.previous_justified_root = state.current_justified_root
|
||||
state.justification_bitfield = (state.justification_bitfield << 1) % 2**64
|
||||
previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch))
|
||||
previous_epoch_matching_target_balance = get_attesting_balance(
|
||||
state, get_matching_target_attestations(state, previous_epoch)
|
||||
)
|
||||
if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_epoch = previous_epoch
|
||||
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
|
||||
state.justification_bitfield |= (1 << 1)
|
||||
current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch))
|
||||
current_epoch_matching_target_balance = get_attesting_balance(
|
||||
state, get_matching_target_attestations(state, current_epoch)
|
||||
)
|
||||
if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_epoch = current_epoch
|
||||
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
|
||||
@ -1365,27 +1368,25 @@ def process_justification_and_finalization(state: BeaconState) -> None:
|
||||
# Process finalizations
|
||||
bitfield = state.justification_bitfield
|
||||
# The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source
|
||||
if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch == current_epoch - 3:
|
||||
if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch + 3 == current_epoch:
|
||||
state.finalized_epoch = old_previous_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
# The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source
|
||||
if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch == current_epoch - 2:
|
||||
if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch + 2 == current_epoch:
|
||||
state.finalized_epoch = old_previous_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
# The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source
|
||||
if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch == current_epoch - 2:
|
||||
if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch + 2 == current_epoch:
|
||||
state.finalized_epoch = old_current_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
# The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source
|
||||
if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch == current_epoch - 1:
|
||||
if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch + 1 == current_epoch:
|
||||
state.finalized_epoch = old_current_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
```
|
||||
|
||||
#### Crosslinks
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_crosslinks(state: BeaconState) -> None:
|
||||
state.previous_crosslinks = [c for c in state.current_crosslinks]
|
||||
@ -1400,8 +1401,6 @@ def process_crosslinks(state: BeaconState) -> None:
|
||||
|
||||
#### Rewards and penalties
|
||||
|
||||
First, we define additional helpers:
|
||||
|
||||
```python
|
||||
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
||||
adjusted_quotient = integer_squareroot(get_total_active_balance(state)) // BASE_REWARD_QUOTIENT
|
||||
@ -1450,7 +1449,9 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||
for index in eligible_validator_indices:
|
||||
penalties[index] += BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)
|
||||
if index not in matching_target_attesting_indices:
|
||||
penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT
|
||||
penalties[index] += (
|
||||
state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT
|
||||
)
|
||||
|
||||
return rewards, penalties
|
||||
```
|
||||
@ -1475,8 +1476,6 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||
return rewards, penalties
|
||||
```
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_rewards_and_penalties(state: BeaconState) -> None:
|
||||
if get_current_epoch(state) == GENESIS_EPOCH:
|
||||
@ -1491,13 +1490,14 @@ def process_rewards_and_penalties(state: BeaconState) -> None:
|
||||
|
||||
#### Registry updates
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_registry_updates(state: BeaconState) -> None:
|
||||
# Process activation eligibility and ejections
|
||||
for index, validator in enumerate(state.validator_registry):
|
||||
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_EFFECTIVE_BALANCE:
|
||||
if (
|
||||
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and
|
||||
validator.effective_balance >= MAX_EFFECTIVE_BALANCE
|
||||
):
|
||||
validator.activation_eligibility_epoch = get_current_epoch(state)
|
||||
|
||||
if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE:
|
||||
@ -1518,8 +1518,6 @@ def process_registry_updates(state: BeaconState) -> None:
|
||||
|
||||
#### Slashings
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_slashings(state: BeaconState) -> None:
|
||||
current_epoch = get_current_epoch(state)
|
||||
@ -1542,8 +1540,6 @@ def process_slashings(state: BeaconState) -> None:
|
||||
|
||||
#### Final updates
|
||||
|
||||
Run the following function:
|
||||
|
||||
```python
|
||||
def process_final_updates(state: BeaconState) -> None:
|
||||
current_epoch = get_current_epoch(state)
|
||||
@ -1582,19 +1578,16 @@ def process_final_updates(state: BeaconState) -> None:
|
||||
state.current_epoch_attestations = []
|
||||
```
|
||||
|
||||
### Per-slot processing
|
||||
|
||||
At every `slot > GENESIS_SLOT` run the following function:
|
||||
### Block processing
|
||||
|
||||
```python
|
||||
def advance_slot(state: BeaconState) -> None:
|
||||
state.slot += 1
|
||||
def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_block_header(state, block)
|
||||
process_randao(state, block.body)
|
||||
process_eth1_data(state, block.body)
|
||||
process_operations(state, block.body)
|
||||
```
|
||||
|
||||
### Per-block processing
|
||||
|
||||
For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`.
|
||||
|
||||
#### Block header
|
||||
|
||||
```python
|
||||
@ -1602,12 +1595,12 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
||||
# Verify that the slots match
|
||||
assert block.slot == state.slot
|
||||
# Verify that the parent matches
|
||||
assert block.previous_block_root == signing_root(state.latest_block_header)
|
||||
assert block.parent_root == signing_root(state.latest_block_header)
|
||||
# Save current block as the new latest block
|
||||
state.latest_block_header = BeaconBlockHeader(
|
||||
slot=block.slot,
|
||||
previous_block_root=block.previous_block_root,
|
||||
block_body_root=hash_tree_root(block.body),
|
||||
parent_root=block.parent_root,
|
||||
body_root=hash_tree_root(block.body),
|
||||
)
|
||||
# Verify proposer is not slashed
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state)]
|
||||
@ -1619,39 +1612,57 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
||||
#### RANDAO
|
||||
|
||||
```python
|
||||
def process_randao(state: BeaconState, block: BeaconBlock) -> None:
|
||||
def process_randao(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state)]
|
||||
# Verify that the provided randao value is valid
|
||||
assert bls_verify(proposer.pubkey, hash_tree_root(get_current_epoch(state)), block.body.randao_reveal, get_domain(state, DOMAIN_RANDAO))
|
||||
assert bls_verify(
|
||||
proposer.pubkey,
|
||||
hash_tree_root(get_current_epoch(state)),
|
||||
body.randao_reveal,
|
||||
get_domain(state, DOMAIN_RANDAO),
|
||||
)
|
||||
# Mix it in
|
||||
state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = (
|
||||
xor(get_randao_mix(state, get_current_epoch(state)),
|
||||
hash(block.body.randao_reveal))
|
||||
hash(body.randao_reveal))
|
||||
)
|
||||
```
|
||||
|
||||
#### Eth1 data
|
||||
|
||||
```python
|
||||
def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None:
|
||||
state.eth1_data_votes.append(block.body.eth1_data)
|
||||
if state.eth1_data_votes.count(block.body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD:
|
||||
state.latest_eth1_data = block.body.eth1_data
|
||||
def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
state.eth1_data_votes.append(body.eth1_data)
|
||||
if state.eth1_data_votes.count(body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD:
|
||||
state.latest_eth1_data = body.eth1_data
|
||||
```
|
||||
|
||||
#### Operations
|
||||
|
||||
*Note*: All functions in this section mutate `state`.
|
||||
```python
|
||||
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
# Verify that outstanding deposits are processed up to the maximum number of deposits
|
||||
assert len(body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index)
|
||||
# Verify that there are no duplicate transfers
|
||||
assert len(body.transfers) == len(set(body.transfers))
|
||||
|
||||
for operations, max_operations, function in (
|
||||
(body.proposer_slashings, MAX_PROPOSER_SLASHINGS, process_proposer_slashing),
|
||||
(body.attester_slashings, MAX_ATTESTER_SLASHINGS, process_attester_slashing),
|
||||
(body.attestations, MAX_ATTESTATIONS, process_attestation),
|
||||
(body.deposits, MAX_DEPOSITS, process_deposit),
|
||||
(body.voluntary_exits, MAX_VOLUNTARY_EXITS, process_voluntary_exit),
|
||||
(body.transfers, MAX_TRANSFERS, process_transfer),
|
||||
):
|
||||
assert len(operations) <= max_operations
|
||||
for operation in operations:
|
||||
function(state, operation)
|
||||
```
|
||||
|
||||
##### Proposer slashings
|
||||
|
||||
Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`.
|
||||
|
||||
For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_proposer_slashing(state: BeaconState,
|
||||
proposer_slashing: ProposerSlashing) -> None:
|
||||
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
|
||||
"""
|
||||
Process ``ProposerSlashing`` operation.
|
||||
"""
|
||||
@ -1672,21 +1683,16 @@ def process_proposer_slashing(state: BeaconState,
|
||||
|
||||
##### Attester slashings
|
||||
|
||||
Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`.
|
||||
|
||||
For each `attester_slashing` in `block.body.attester_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attester_slashing(state: BeaconState,
|
||||
attester_slashing: AttesterSlashing) -> None:
|
||||
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
|
||||
"""
|
||||
Process ``AttesterSlashing`` operation.
|
||||
"""
|
||||
attestation_1 = attester_slashing.attestation_1
|
||||
attestation_2 = attester_slashing.attestation_2
|
||||
assert is_slashable_attestation_data(attestation_1.data, attestation_2.data)
|
||||
assert verify_indexed_attestation(state, attestation_1)
|
||||
assert verify_indexed_attestation(state, attestation_2)
|
||||
validate_indexed_attestation(state, attestation_1)
|
||||
validate_indexed_attestation(state, attestation_2)
|
||||
|
||||
slashed_any = False
|
||||
attesting_indices_1 = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
|
||||
@ -1700,10 +1706,6 @@ def process_attester_slashing(state: BeaconState,
|
||||
|
||||
##### Attestations
|
||||
|
||||
Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`.
|
||||
|
||||
For each `attestation` in `block.body.attestations`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
"""
|
||||
@ -1713,37 +1715,34 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
attestation_slot = get_attestation_data_slot(state, data)
|
||||
assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH
|
||||
|
||||
# Check target epoch, source epoch, source root, and source crosslink
|
||||
assert (data.target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in {
|
||||
(get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, hash_tree_root(state.current_crosslinks[data.shard])),
|
||||
(get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, hash_tree_root(state.previous_crosslinks[data.shard])),
|
||||
}
|
||||
|
||||
# Check crosslink data root
|
||||
assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1]
|
||||
|
||||
# Check signature and bitfields
|
||||
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||
|
||||
# Cache pending attestation
|
||||
pending_attestation = PendingAttestation(
|
||||
data=data,
|
||||
aggregation_bitfield=attestation.aggregation_bitfield,
|
||||
inclusion_delay=state.slot - attestation_slot,
|
||||
proposer_index=get_beacon_proposer_index(state),
|
||||
)
|
||||
|
||||
assert data.target_epoch in (get_previous_epoch(state), get_current_epoch(state))
|
||||
if data.target_epoch == get_current_epoch(state):
|
||||
ffg_data = (state.current_justified_epoch, state.current_justified_root, get_current_epoch(state))
|
||||
parent_crosslink = state.current_crosslinks[data.crosslink.shard]
|
||||
state.current_epoch_attestations.append(pending_attestation)
|
||||
else:
|
||||
ffg_data = (state.previous_justified_epoch, state.previous_justified_root, get_previous_epoch(state))
|
||||
parent_crosslink = state.previous_crosslinks[data.crosslink.shard]
|
||||
state.previous_epoch_attestations.append(pending_attestation)
|
||||
|
||||
# Check FFG data, crosslink data, and signature
|
||||
assert ffg_data == (data.source_epoch, data.source_root, data.target_epoch)
|
||||
assert data.crosslink.start_epoch == parent_crosslink.end_epoch
|
||||
assert data.crosslink.end_epoch == min(data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)
|
||||
assert data.crosslink.parent_root == hash_tree_root(parent_crosslink)
|
||||
assert data.crosslink.data_root == ZERO_HASH # [to be removed in phase 1]
|
||||
validate_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||
```
|
||||
|
||||
##### Deposits
|
||||
|
||||
Verify that `len(block.body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index)`.
|
||||
|
||||
For each `deposit` in `block.body.deposits`, run the following function:
|
||||
|
||||
```python
|
||||
def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
"""
|
||||
@ -1790,10 +1789,6 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
|
||||
##### Voluntary exits
|
||||
|
||||
Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`.
|
||||
|
||||
For each `exit` in `block.body.voluntary_exits`, run the following function:
|
||||
|
||||
```python
|
||||
def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None:
|
||||
"""
|
||||
@ -1817,10 +1812,6 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None:
|
||||
|
||||
##### Transfers
|
||||
|
||||
Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct.
|
||||
|
||||
For each `transfer` in `block.body.transfers`, run the following function:
|
||||
|
||||
```python
|
||||
def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
"""
|
||||
@ -1839,7 +1830,7 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
# Verify that the pubkey is valid
|
||||
assert (
|
||||
state.validator_registry[transfer.sender].withdrawal_credentials ==
|
||||
BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:]
|
||||
int_to_bytes(BLS_WITHDRAWAL_PREFIX, length=1) + hash(transfer.pubkey)[1:]
|
||||
)
|
||||
# Verify that the signature is valid
|
||||
assert bls_verify(transfer.pubkey, signing_root(transfer), transfer.signature, get_domain(state, DOMAIN_TRANSFER))
|
||||
@ -1851,12 +1842,3 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
assert not (0 < state.balances[transfer.sender] < MIN_DEPOSIT_AMOUNT)
|
||||
assert not (0 < state.balances[transfer.recipient] < MIN_DEPOSIT_AMOUNT)
|
||||
```
|
||||
|
||||
#### State root verification
|
||||
|
||||
Verify the block's `state_root` by running the following function:
|
||||
|
||||
```python
|
||||
def verify_block_state_root(state: BeaconState, block: BeaconBlock) -> None:
|
||||
assert block.state_root == hash_tree_root(state)
|
||||
```
|
||||
|
@ -72,7 +72,7 @@ Every Ethereum 1.0 deposit, of size at least `MIN_DEPOSIT_AMOUNT`, emits a `Depo
|
||||
|
||||
### `Eth2Genesis` log
|
||||
|
||||
When `CHAIN_START_FULL_DEPOSIT_THRESHOLD` of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined below) where:
|
||||
When `CHAIN_START_FULL_DEPOSIT_THRESHOLD` of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined [here](./0_beacon-chain.md#genesis-state)) where:
|
||||
|
||||
* `genesis_time` equals `time` in the `Eth2Genesis` log
|
||||
* `latest_eth1_data.deposit_root` equals `deposit_root` in the `Eth2Genesis` log
|
||||
|
@ -38,7 +38,7 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph
|
||||
|
||||
Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks and maintain a view of what is the current "canonical chain", terminating at the current "head". For a beacon block, `block`, to be processed by a node, the following conditions must be met:
|
||||
|
||||
* The parent block with root `block.previous_block_root` has been processed and accepted.
|
||||
* The parent block with root `block.parent_root` has been processed and accepted.
|
||||
* An Ethereum 1.0 block pointed to by the `state.latest_eth1_data.block_hash` has been processed and accepted.
|
||||
* The node's Unix time is greater than or equal to `state.genesis_time + block.slot * SECONDS_PER_SLOT`.
|
||||
|
||||
|
@ -35,19 +35,9 @@
|
||||
- [`empty`](#empty)
|
||||
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
|
||||
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
|
||||
- [`get_chunk_bits_root`](#get_chunk_bits_root)
|
||||
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
|
||||
- [`get_validators_custody_reveal_period`](#get_validators_custody_reveal_period)
|
||||
- [`get_chunk_bits_root`](#get_chunk_bits_root)
|
||||
- [`replace_empty_or_append`](#replace_empty_or_append)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Operations](#operations)
|
||||
- [Custody key reveals](#custody-key-reveals)
|
||||
- [Early derived secret reveals](#early-derived-secret-reveals)
|
||||
- [Chunk challenges](#chunk-challenges)
|
||||
- [Bit challenges](#bit-challenges)
|
||||
- [Custody responses](#custody-responses)
|
||||
- [Per-epoch processing](#per-epoch-processing)
|
||||
- [Handling of custody-related deadlines](#handling-of-custody-related-deadlines)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
@ -104,6 +94,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
||||
|
||||
### Reward and penalty quotients
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) |
|
||||
|
||||
### Signature domains
|
||||
@ -146,8 +138,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
||||
'challenge_index': 'uint64',
|
||||
'challenger_index': ValidatorIndex,
|
||||
'responder_index': ValidatorIndex,
|
||||
'deadline': Epoch,
|
||||
'crosslink_data_root': Hash,
|
||||
'inclusion_epoch': Epoch,
|
||||
'data_root': Hash,
|
||||
'depth': 'uint64',
|
||||
'chunk_index': 'uint64',
|
||||
}
|
||||
@ -160,8 +152,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
||||
'challenge_index': 'uint64',
|
||||
'challenger_index': ValidatorIndex,
|
||||
'responder_index': ValidatorIndex,
|
||||
'deadline': Epoch,
|
||||
'crosslink_data_root': Hash,
|
||||
'inclusion_epoch': Epoch,
|
||||
'data_root': Hash,
|
||||
'chunk_count': 'uint64',
|
||||
'chunk_bits_merkle_root': Hash,
|
||||
'responder_key': BLSSignature,
|
||||
@ -262,12 +254,10 @@ The `empty` function accepts and SSZ type as input and returns an object of that
|
||||
### `get_crosslink_chunk_count`
|
||||
|
||||
```python
|
||||
def get_custody_chunk_count(attestation: Attestation) -> int:
|
||||
crosslink_start_epoch = attestation.data.latest_crosslink.epoch
|
||||
crosslink_end_epoch = slot_to_epoch(attestation.data.slot)
|
||||
crosslink_crosslink_length = min(MAX_CROSSLINK_EPOCHS, end_epoch - start_epoch)
|
||||
def get_custody_chunk_count(crosslink: Crosslink) -> int:
|
||||
crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch)
|
||||
chunks_per_epoch = 2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK
|
||||
return crosslink_crosslink_length * chunks_per_epoch
|
||||
return crosslink_length * chunks_per_epoch
|
||||
```
|
||||
|
||||
### `get_custody_chunk_bit`
|
||||
@ -426,10 +416,10 @@ def process_early_derived_secret_reveal(state: BeaconState,
|
||||
# round key
|
||||
slash_validator(state, reveal.revealed_index, reveal.masker_index)
|
||||
else:
|
||||
# Only a small penalty proportional to proposer slot reward for RANDAO reveal
|
||||
# Only a small penalty proportional to proposer slot reward for RANDAO reveal
|
||||
# that does not interfere with the custody period
|
||||
# The penalty is proportional to the max proposer reward
|
||||
|
||||
# The penalty is proportional to the max proposer reward
|
||||
|
||||
# Calculate penalty
|
||||
max_proposer_slot_reward = (
|
||||
get_base_reward(state, reveal.revealed_index) *
|
||||
@ -448,7 +438,7 @@ def process_early_derived_secret_reveal(state: BeaconState,
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
decrease_balance(state, reveal.revealed_index, penalty)
|
||||
|
||||
# Mark this derived secret as exposed so validator cannot be punished repeatedly
|
||||
# Mark this derived secret as exposed so validator cannot be punished repeatedly
|
||||
state.exposed_derived_secrets[reveal.epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS].append(reveal.revealed_index)
|
||||
|
||||
```
|
||||
@ -474,19 +464,19 @@ def process_chunk_challenge(state: BeaconState,
|
||||
# Verify the challenge is not a duplicate
|
||||
for record in state.custody_chunk_challenge_records:
|
||||
assert (
|
||||
record.crosslink_data_root != challenge.attestation.data.crosslink_data_root or
|
||||
record.data_root != challenge.attestation.data.crosslink.data_root or
|
||||
record.chunk_index != challenge.chunk_index
|
||||
)
|
||||
# Verify depth
|
||||
depth = math.log2(next_power_of_two(get_custody_chunk_count(challenge.attestation)))
|
||||
depth = math.log2(next_power_of_two(get_custody_chunk_count(challenge.attestation.data.crosslink)))
|
||||
assert challenge.chunk_index < 2**depth
|
||||
# Add new chunk challenge record
|
||||
new_record = CustodyChunkChallengeRecord(
|
||||
challenge_index=state.custody_challenge_index,
|
||||
challenger_index=get_beacon_proposer_index(state),
|
||||
responder_index=challenge.responder_index
|
||||
deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE,
|
||||
crosslink_data_root=challenge.attestation.data.crosslink_data_root,
|
||||
inclusion_epoch=get_current_epoch(state),
|
||||
data_root=challenge.attestation.data.crosslink.data_root,
|
||||
depth=depth,
|
||||
chunk_index=challenge.chunk_index,
|
||||
)
|
||||
@ -528,10 +518,9 @@ def process_bit_challenge(state: BeaconState,
|
||||
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||
assert challenge.responder_index in attesters
|
||||
|
||||
# A validator can be the challenger or responder for at most one challenge at a time
|
||||
# A validator can be the challenger for at most one challenge at a time
|
||||
for record in state.custody_bit_challenge_records:
|
||||
assert record.challenger_index != challenge.challenger_index
|
||||
assert record.responder_index != challenge.responder_index
|
||||
|
||||
# Verify the responder is a valid custody key
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(
|
||||
@ -553,7 +542,7 @@ def process_bit_challenge(state: BeaconState,
|
||||
)
|
||||
|
||||
# Verify the chunk count
|
||||
chunk_count = get_custody_chunk_count(challenge.attestation)
|
||||
chunk_count = get_custody_chunk_count(challenge.attestation.data.crosslink)
|
||||
assert verify_bitfield(challenge.chunk_bits, chunk_count)
|
||||
# Verify the first bit of the hash of the chunk bits does not equal the custody bit
|
||||
custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(responder_index))
|
||||
@ -563,8 +552,8 @@ def process_bit_challenge(state: BeaconState,
|
||||
challenge_index=state.custody_challenge_index,
|
||||
challenger_index=challenge.challenger_index,
|
||||
responder_index=challenge.responder_index,
|
||||
deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE,
|
||||
crosslink_data_root=challenge.attestation.data.crosslink_data_root,
|
||||
inclusion_epoch=get_current_epoch(state),
|
||||
data_root=challenge.attestation.data.crosslink.data_root,
|
||||
chunk_count=chunk_count,
|
||||
chunk_bits_merkle_root=merkle_root(pad_to_power_of_2((challenge.chunk_bits))),
|
||||
responder_key=challenge.responder_key,
|
||||
@ -604,13 +593,15 @@ def process_chunk_challenge_response(state: BeaconState,
|
||||
assert response.chunk_index == challenge.chunk_index
|
||||
# Verify bit challenge data is null
|
||||
assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == ZERO_HASH
|
||||
# Verify minimum delay
|
||||
assert get_current_epoch(state) >= challenge.inclusion_epoch + ACTIVATION_EXIT_DELAY
|
||||
# Verify the chunk matches the crosslink data root
|
||||
assert verify_merkle_branch(
|
||||
leaf=hash_tree_root(response.chunk),
|
||||
branch=response.data_branch,
|
||||
depth=challenge.depth,
|
||||
index=response.chunk_index,
|
||||
root=challenge.crosslink_data_root,
|
||||
root=challenge.data_root,
|
||||
)
|
||||
# Clear the challenge
|
||||
records = state.custody_chunk_challenge_records
|
||||
@ -626,13 +617,16 @@ def process_bit_challenge_response(state: BeaconState,
|
||||
challenge: CustodyBitChallengeRecord) -> None:
|
||||
# Verify chunk index
|
||||
assert response.chunk_index < challenge.chunk_count
|
||||
# Verify responder has not been slashed
|
||||
responder = state.validator_registry[challenge.responder_index]
|
||||
assert not responder.slashed
|
||||
# Verify the chunk matches the crosslink data root
|
||||
assert verify_merkle_branch(
|
||||
leaf=hash_tree_root(response.chunk),
|
||||
branch=response.data_branch,
|
||||
depth=math.log2(next_power_of_two(challenge.chunk_count)),
|
||||
index=response.chunk_index,
|
||||
root=challenge.crosslink_data_root,
|
||||
root=challenge.data_root,
|
||||
)
|
||||
# Verify the chunk bit leaf matches the challenge data
|
||||
assert verify_merkle_branch(
|
||||
@ -671,13 +665,13 @@ Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadl
|
||||
```python
|
||||
def process_challenge_deadlines(state: BeaconState) -> None:
|
||||
for challenge in state.custody_chunk_challenge_records:
|
||||
if get_current_epoch(state) > challenge.deadline:
|
||||
if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
|
||||
slash_validator(state, challenge.responder_index, challenge.challenger_index)
|
||||
records = state.custody_chunk_challenge_records
|
||||
records[records.index(challenge)] = CustodyChunkChallengeRecord()
|
||||
|
||||
for challenge in state.custody_bit_challenge_records:
|
||||
if get_current_epoch(state) > challenge.deadline:
|
||||
if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
|
||||
slash_validator(state, challenge.responder_index, challenge.challenger_index)
|
||||
records = state.custody_bit_challenge_records
|
||||
records[records.index(challenge)] = CustodyBitChallengeRecord()
|
||||
@ -688,6 +682,15 @@ Append this to `process_final_updates(state)`:
|
||||
```python
|
||||
# Clean up exposed RANDAO key reveals
|
||||
state.exposed_derived_secrets[current_epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = []
|
||||
# Reset withdrawable epochs if challenge records are empty
|
||||
records = state.custody_chunk_challenge_records + state.bit_challenge_records
|
||||
validator_indices_in_records = set(
|
||||
[record.challenger_index for record in records] + [record.responder_index for record in records]
|
||||
)
|
||||
for index, validator in enumerate(state.validator_registry):
|
||||
if index not in validator_indices_in_records:
|
||||
if validator.exit_epoch != FAR_FUTURE_EPOCH and validator.withdrawable_epoch == FAR_FUTURE_EPOCH:
|
||||
validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
||||
```
|
||||
|
||||
In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope):
|
||||
|
@ -78,7 +78,7 @@ This document describes the shard data layer and the shard fork choice rule in P
|
||||
'slot': Slot,
|
||||
'shard': Shard,
|
||||
'beacon_chain_root': Hash,
|
||||
'previous_block_root': Hash,
|
||||
'parent_root': Hash,
|
||||
'data': ShardBlockBody,
|
||||
'state_root': Hash,
|
||||
'attestations': [ShardAttestation],
|
||||
@ -93,7 +93,7 @@ This document describes the shard data layer and the shard fork choice rule in P
|
||||
'slot': Slot,
|
||||
'shard': Shard,
|
||||
'beacon_chain_root': Hash,
|
||||
'previous_block_root': Hash,
|
||||
'parent_root': Hash,
|
||||
'body_root': Hash,
|
||||
'state_root': Hash,
|
||||
'attestations': [ShardAttestation],
|
||||
@ -180,7 +180,7 @@ def get_shard_proposer_index(state: BeaconState,
|
||||
slot: Slot) -> ValidatorIndex:
|
||||
# Randomly shift persistent committee
|
||||
persistent_committee = get_persistent_committee(state, shard, slot)
|
||||
seed = hash(state.current_shuffling_seed + int_to_bytes8(shard) + int_to_bytes8(slot))
|
||||
seed = hash(state.current_shuffling_seed + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
|
||||
random_index = bytes_to_int(seed[0:8]) % len(persistent_committee)
|
||||
persistent_committee = persistent_committee[random_index:] + persistent_committee[:random_index]
|
||||
|
||||
@ -201,7 +201,7 @@ def get_shard_header(block: ShardBlock) -> ShardBlockHeader:
|
||||
slot: block.slot,
|
||||
shard: block.shard,
|
||||
beacon_chain_root: block.beacon_chain_root,
|
||||
previous_block_root: block.previous_block_root,
|
||||
parent_root: block.parent_root,
|
||||
body_root: hash_tree_root(block.body),
|
||||
state_root: block.state_root,
|
||||
attestations: block.attestations,
|
||||
@ -215,7 +215,7 @@ def get_shard_header(block: ShardBlock) -> ShardBlockHeader:
|
||||
def verify_shard_attestation_signature(state: BeaconState,
|
||||
attestation: ShardAttestation) -> None:
|
||||
data = attestation.data
|
||||
persistent_committee = get_persistent_committee(state, data.shard, data.slot)
|
||||
persistent_committee = get_persistent_committee(state, data.crosslink.shard, data.slot)
|
||||
assert verify_bitfield(attestation.aggregation_bitfield, len(persistent_committee))
|
||||
pubkeys = []
|
||||
for i, index in enumerate(persistent_committee):
|
||||
@ -225,7 +225,7 @@ def verify_shard_attestation_signature(state: BeaconState,
|
||||
pubkeys.append(validator.pubkey)
|
||||
assert bls_verify(
|
||||
pubkey=bls_aggregate_pubkeys(pubkeys),
|
||||
message_hash=data.shard_block_root,
|
||||
message_hash=data.crosslink.shard_block_root,
|
||||
signature=attestation.aggregate_signature,
|
||||
domain=get_domain(state, slot_to_epoch(data.slot), DOMAIN_SHARD_ATTESTER)
|
||||
)
|
||||
@ -296,11 +296,11 @@ def is_valid_shard_block(beacon_blocks: List[BeaconBlock],
|
||||
|
||||
# Check parent block
|
||||
if block.slot == PHASE_1_GENESIS_SLOT:
|
||||
assert candidate.previous_block_root == ZERO_HASH
|
||||
assert candidate.parent_root == ZERO_HASH
|
||||
else:
|
||||
parent_block = next(
|
||||
block for block in valid_shard_blocks if
|
||||
signing_root(block) == candidate.previous_block_root
|
||||
signing_root(block) == candidate.parent_root
|
||||
, None)
|
||||
assert parent_block != None
|
||||
assert parent_block.shard == block.shard
|
||||
@ -312,7 +312,7 @@ def is_valid_shard_block(beacon_blocks: List[BeaconBlock],
|
||||
for _, attestation in enumerate(block.attestations):
|
||||
assert max(GENESIS_SHARD_SLOT, block.slot - SLOTS_PER_EPOCH) <= attestation.data.slot
|
||||
assert attestation.data.slot <= block.slot - MIN_ATTESTATION_INCLUSION_DELAY
|
||||
assert attestation.data.shard == block.shard
|
||||
assert attestation.data.crosslink.shard == block.shard
|
||||
verify_shard_attestation_signature(beacon_state, attestation)
|
||||
|
||||
# Check signature
|
||||
@ -343,11 +343,11 @@ def is_valid_shard_attestation(valid_shard_blocks: List[ShardBlock],
|
||||
# Check shard block
|
||||
shard_block = next(
|
||||
block for block in valid_shard_blocks if
|
||||
signing_root(block) == candidate.attestation.data.shard_block_root
|
||||
signing_root(block) == candidate.attestation.data.crosslink.shard_block_root
|
||||
, None)
|
||||
assert shard_block != None
|
||||
assert shard_block.slot == attestation.data.slot
|
||||
assert shard_block.shard == attestation.data.shard
|
||||
assert shard_block.shard == attestation.data.crosslink.shard
|
||||
|
||||
# Check signature
|
||||
verify_shard_attestation_signature(beacon_state, attestation)
|
||||
@ -378,22 +378,22 @@ def is_valid_beacon_attestation(shard: Shard,
|
||||
|
||||
# Check previous attestation
|
||||
if candidate.data.previous_crosslink.epoch <= PHASE_1_GENESIS_EPOCH:
|
||||
assert candidate.data.previous_crosslink.crosslink_data_root == ZERO_HASH
|
||||
assert candidate.data.previous_crosslink.data_root == ZERO_HASH
|
||||
else:
|
||||
previous_attestation = next(
|
||||
attestation for attestation in valid_attestations if
|
||||
attestation.data.crosslink_data_root == candidate.data.previous_crosslink.crosslink_data_root
|
||||
attestation.data.crosslink.data_root == candidate.data.previous_crosslink.data_root
|
||||
, None)
|
||||
assert previous_attestation != None
|
||||
assert candidate.data.previous_attestation.epoch < slot_to_epoch(candidate.data.slot)
|
||||
|
||||
# Check crosslink data root
|
||||
start_epoch = state.latest_crosslinks[shard].epoch
|
||||
end_epoch = min(slot_to_epoch(candidate.data.slot) - CROSSLINK_LOOKBACK, start_epoch + MAX_CROSSLINK_EPOCHS)
|
||||
end_epoch = min(slot_to_epoch(candidate.data.slot) - CROSSLINK_LOOKBACK, start_epoch + MAX_EPOCHS_PER_CROSSLINK)
|
||||
blocks = []
|
||||
for slot in range(start_epoch * SLOTS_PER_EPOCH, end_epoch * SLOTS_PER_EPOCH):
|
||||
blocks.append(shard_blocks[slot])
|
||||
assert candidate.data.crosslink_data_root == compute_crosslink_data_root(blocks)
|
||||
assert candidate.data.crosslink.data_root == compute_crosslink_data_root(blocks)
|
||||
|
||||
return True
|
||||
```
|
||||
|
@ -146,7 +146,7 @@ def compute_committee(header: BeaconBlockHeader,
|
||||
]
|
||||
def get_switchover_epoch(index):
|
||||
return (
|
||||
bytes_to_int(hash(validator_memory.earlier_period_data.seed + int_to_bytes3(index))[0:8]) %
|
||||
bytes_to_int(hash(validator_memory.earlier_period_data.seed + int_to_bytes(index, length=3))[0:8]) %
|
||||
PERSISTENT_COMMITTEE_PERIOD
|
||||
)
|
||||
|
||||
|
@ -41,7 +41,7 @@ Remote method calls are wrapped in a "request" structure:
|
||||
(
|
||||
id: uint64
|
||||
method_id: uint16
|
||||
body: Request
|
||||
body: (message_body...)
|
||||
)
|
||||
```
|
||||
|
||||
@ -55,15 +55,7 @@ and their corresponding responses are wrapped in a "response" structure:
|
||||
)
|
||||
```
|
||||
|
||||
If an error occurs, a variant of the response structure is returned:
|
||||
|
||||
```
|
||||
(
|
||||
id: uint64
|
||||
response_code: uint16
|
||||
result: bytes
|
||||
)
|
||||
```
|
||||
A union type is used to determine the contents of the `body` field in the request structure. Each "body" entry in the RPC calls below corresponds to one subtype in the `body` type union.
|
||||
|
||||
The details of the RPC-Over-`libp2p` protocol are similar to [JSON-RPC 2.0](https://www.jsonrpc.org/specification). Specifically:
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
- [Serialization](#serialization)
|
||||
- [`"uintN"`](#uintn)
|
||||
- [`"bool"`](#bool)
|
||||
- [Containers, vectors, lists](#containers-vectors-lists)
|
||||
- [Vectors, containers, lists, unions](#vectors-containers-lists-unions)
|
||||
- [Deserialization](#deserialization)
|
||||
- [Merkleization](#merkleization)
|
||||
- [Self-signed containers](#self-signed-containers)
|
||||
@ -46,8 +46,12 @@
|
||||
* angle bracket notation `[type, N]`, e.g. `["uint64", N]`
|
||||
* **list**: ordered variable-length homogeneous collection of values
|
||||
* angle bracket notation `[type]`, e.g. `["uint64"]`
|
||||
* **union**: union type containing one of the given subtypes
|
||||
* round bracket notation `(type_1, type_2, ...)`, e.g. `("null", "uint64")`
|
||||
|
||||
We recursively define "variable-size" types to be lists and all types that contains a variable-size type. All other types are said to be "fixed-size".
|
||||
### Variable-size and fixed-size
|
||||
|
||||
We recursively define "variable-size" types to be lists and unions and all types that contain a variable-size type. All other types are said to be "fixed-size".
|
||||
|
||||
### Aliases
|
||||
|
||||
@ -56,10 +60,15 @@ For convenience we alias:
|
||||
* `"byte"` to `"uint8"` (this is a basic type)
|
||||
* `"bytes"` to `["byte"]` (this is *not* a basic type)
|
||||
* `"bytesN"` to `["byte", N]` (this is *not* a basic type)
|
||||
* `"null"`: `{}`, i.e. the empty container
|
||||
|
||||
### Default values
|
||||
|
||||
The default value of a type upon initialization is recursively defined using `0` for `"uintN"`, `False` for `"bool"`, and `[]` for lists.
|
||||
The default value of a type upon initialization is recursively defined using `0` for `"uintN"`, `False` for `"bool"`, and `[]` for lists. Unions default to the first type in the union (with type index zero), which is `"null"` if present in the union.
|
||||
|
||||
### Illegal types
|
||||
|
||||
Empty vector types (i.e. `[subtype, 0]` for some `subtype`) are not legal. The `"null"` type is only legal as the first type in a union subtype (i.e., with type index zero).
|
||||
|
||||
## Serialization
|
||||
|
||||
@ -67,7 +76,6 @@ We recursively define the `serialize` function which consumes an object `value`
|
||||
|
||||
> *Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, `is_variable_size`, etc.) objects implicitly carry their type.
|
||||
|
||||
|
||||
### `"uintN"`
|
||||
|
||||
```python
|
||||
@ -82,10 +90,16 @@ assert value in (True, False)
|
||||
return b"\x01" if value is True else b"\x00"
|
||||
```
|
||||
|
||||
### Containers, vectors, lists
|
||||
### `"null"`
|
||||
|
||||
```python
|
||||
# Reccursively serialize
|
||||
return b""
|
||||
```
|
||||
|
||||
### Vectors, containers, lists, unions
|
||||
|
||||
```python
|
||||
# Recursively serialize
|
||||
fixed_parts = [serialize(element) if not is_variable_size(element) else None for element in value]
|
||||
variable_parts = [serialize(element) if is_variable_size(element) else b"" for element in value]
|
||||
|
||||
@ -102,6 +116,16 @@ fixed_parts = [part if part != None else variable_offsets[i] for i, part in enum
|
||||
return b"".join(fixed_parts + variable_parts)
|
||||
```
|
||||
|
||||
If `value` is a union type:
|
||||
|
||||
Define value as an object that has properties `value.value` with the contained value, and `value.type_index` which indexes the type.
|
||||
|
||||
```python
|
||||
serialized_bytes = serialize(value.value)
|
||||
serialized_type_index = value.type_index.to_bytes(BYTES_PER_LENGTH_OFFSET, "little")
|
||||
return serialized_type_index + serialized_bytes
|
||||
```
|
||||
|
||||
## Deserialization
|
||||
|
||||
Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to. Efficient algorithms for computing this object can be found in [the implementations](#implementations).
|
||||
@ -111,8 +135,9 @@ Because serialization is an injective function (i.e. two distinct objects of the
|
||||
We first define helper functions:
|
||||
|
||||
* `pack`: Given ordered objects of the same basic type, serialize them, pack them into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks.
|
||||
* `merkleize`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root.
|
||||
* `merkleize`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root. Note that `merkleize` on a single chunk is simply that chunk, i.e. the identity when the number of chunks is one.
|
||||
* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`.
|
||||
* `mix_in_type`: Given a Merkle root `root` and a type_index `type_index` (`"uint256"` little-endian serialization) return `hash(root + type_index)`.
|
||||
|
||||
We now define Merkleization `hash_tree_root(value)` of an object `value` recursively:
|
||||
|
||||
@ -120,6 +145,7 @@ We now define Merkleization `hash_tree_root(value)` of an object `value` recursi
|
||||
* `mix_in_length(merkleize(pack(value)), len(value))` if `value` is a list of basic objects
|
||||
* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container
|
||||
* `mix_in_length(merkleize([hash_tree_root(element) for element in value]), len(value))` if `value` is a list of composite objects
|
||||
* `mix_in_type(merkleize(value.value), value.type_index)` if `value` is of union type
|
||||
|
||||
## Self-signed containers
|
||||
|
||||
|
@ -20,6 +20,8 @@
|
||||
- [Process deposit](#process-deposit)
|
||||
- [Validator index](#validator-index)
|
||||
- [Activation](#activation)
|
||||
- [Validator assignments](#validator-assignments)
|
||||
- [Lookahead](#lookahead)
|
||||
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
||||
- [Block proposal](#block-proposal)
|
||||
- [Block header](#block-header)
|
||||
@ -37,21 +39,14 @@
|
||||
- [Voluntary exits](#voluntary-exits)
|
||||
- [Attestations](#attestations-1)
|
||||
- [Attestation data](#attestation-data)
|
||||
- [Slot](#slot-1)
|
||||
- [Beacon block root](#beacon-block-root)
|
||||
- [Source epoch](#source-epoch)
|
||||
- [Source root](#source-root)
|
||||
- [Target root](#target-root)
|
||||
- [Shard](#shard)
|
||||
- [Previous crosslink root](#previous-crosslink-root)
|
||||
- [Crosslink data root](#crosslink-data-root)
|
||||
- [LMD GHOST vote](#lmd-ghost-vote)
|
||||
- [FFG vote](#ffg-vote)
|
||||
- [Crosslink vote](#crosslink-vote)
|
||||
- [Construct attestation](#construct-attestation)
|
||||
- [Data](#data)
|
||||
- [Aggregation bitfield](#aggregation-bitfield)
|
||||
- [Custody bitfield](#custody-bitfield)
|
||||
- [Aggregate signature](#aggregate-signature)
|
||||
- [Validator assignments](#validator-assignments)
|
||||
- [Lookahead](#lookahead)
|
||||
- [How to avoid slashing](#how-to-avoid-slashing)
|
||||
- [Proposer slashing](#proposer-slashing)
|
||||
- [Attester slashing](#attester-slashing)
|
||||
@ -132,13 +127,62 @@ Once a validator is activated, the validator is assigned [responsibilities](#bea
|
||||
|
||||
*Note*: There is a maximum validator churn per finalized epoch so the delay until activation is variable depending upon finality, total active validator balance, and the number of validators in the queue to be activated.
|
||||
|
||||
## Validator assignments
|
||||
|
||||
A validator can get committee assignments for a given epoch using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`.
|
||||
|
||||
```python
|
||||
def get_committee_assignment(
|
||||
state: BeaconState,
|
||||
epoch: Epoch,
|
||||
validator_index: ValidatorIndex) -> Tuple[List[ValidatorIndex], Shard, Slot]:
|
||||
"""
|
||||
Return the committee assignment in the ``epoch`` for ``validator_index``.
|
||||
``assignment`` returned is a tuple of the following form:
|
||||
* ``assignment[0]`` is the list of validators in the committee
|
||||
* ``assignment[1]`` is the shard to which the committee is assigned
|
||||
* ``assignment[2]`` is the slot at which the committee is assigned
|
||||
"""
|
||||
next_epoch = get_current_epoch(state) + 1
|
||||
assert epoch <= next_epoch
|
||||
|
||||
committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH
|
||||
epoch_start_slot = get_epoch_start_slot(epoch)
|
||||
for slot in range(epoch_start_slot, epoch_start_slot + SLOTS_PER_EPOCH)
|
||||
offset = committees_per_slot * (slot % SLOTS_PER_EPOCH)
|
||||
slot_start_shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT
|
||||
for i in range(committees_per_slot):
|
||||
shard = (slot_start_shard + i) % SHARD_COUNT
|
||||
committee = get_crosslink_committee(state, epoch, shard)
|
||||
if validator_index in committee:
|
||||
return committee, shard, slot
|
||||
```
|
||||
|
||||
A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run with a `state` of the slot in question. Proposer selection is only stable within the context of the current epoch.
|
||||
|
||||
```python
|
||||
def is_proposer(state: BeaconState,
|
||||
validator_index: ValidatorIndex) -> bool:
|
||||
return get_beacon_proposer_index(state) == validator_index
|
||||
```
|
||||
|
||||
*Note*: To see if a validator is assigned to propose during the slot, the beacon state must be in the epoch in question. At the epoch boundaries, the validator must run an epoch transition into the epoch to successfully check the proposal assignment of the first slot.
|
||||
|
||||
### Lookahead
|
||||
|
||||
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.
|
||||
|
||||
`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting at which future slot they will have to attest and also which shard they should begin syncing (in Phase 1+).
|
||||
|
||||
Specifically, a validator should call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
|
||||
|
||||
## Beacon chain responsibilities
|
||||
|
||||
A validator has two primary responsibilities to the beacon chain: [proposing blocks](block-proposal) and [creating attestations](attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch.
|
||||
A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch.
|
||||
|
||||
### Block proposal
|
||||
|
||||
A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `get_beacon_proposer_index(state)` returns the validator's `validator_index`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator is to create, sign, and broadcast a `block` that is a child of `parent` and that executes a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function).
|
||||
A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function).
|
||||
|
||||
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312500 validators = 10 million ETH, that's once per ~3 weeks).
|
||||
|
||||
@ -152,7 +196,7 @@ Set `block.slot = slot` where `slot` is the current slot at which the validator
|
||||
|
||||
##### Parent root
|
||||
|
||||
Set `block.previous_block_root = signing_root(parent)`.
|
||||
Set `block.parent_root = signing_root(parent)`.
|
||||
|
||||
##### State root
|
||||
|
||||
@ -234,7 +278,7 @@ Up to `MAX_VOLUNTARY_EXITS` [`VoluntaryExit`](../core/0_beacon-chain.md#voluntar
|
||||
|
||||
### Attestations
|
||||
|
||||
A validator is expected to create, sign, and broadcast an attestation during each epoch. The slot during which the validator performs this role is any slot at which `get_crosslink_committees_at_slot(state, slot)` contains a committee that contains `validator_index`.
|
||||
A validator is expected to create, sign, and broadcast an attestation during each epoch. The committee, assigned shard, and assigned slot for which the validator performs this role during an epoch is defined by `get_committee_assignment(state, epoch, validator_index)`.
|
||||
|
||||
A validator should create and broadcast the attestation halfway through the `slot` during which the validator is assigned ― that is, `SECONDS_PER_SLOT * 0.5` seconds after the start of `slot`.
|
||||
|
||||
@ -243,45 +287,33 @@ A validator should create and broadcast the attestation halfway through the `slo
|
||||
First the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot.
|
||||
|
||||
* Let `head_block` be the result of running the fork choice during the assigned slot.
|
||||
* Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot.
|
||||
* Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`.
|
||||
|
||||
##### Slot
|
||||
|
||||
Set `attestation_data.slot = head_state.slot`.
|
||||
|
||||
##### Beacon block root
|
||||
##### LMD GHOST vote
|
||||
|
||||
Set `attestation_data.beacon_block_root = signing_root(head_block)`.
|
||||
|
||||
##### Source epoch
|
||||
##### FFG vote
|
||||
|
||||
Set `attestation_data.source_epoch = head_state.justified_epoch`.
|
||||
* Set `attestation_data.source_epoch = head_state.current_justified_epoch`.
|
||||
* Set `attestation_data.source_root = head_state.current_justified_root`.
|
||||
* Set `attestation_data.target_epoch = get_current_epoch(head_state)`
|
||||
* Set `attestation_data.target_root = signing_root(epoch_boundary_block)` where `epoch_boundary_block` is the block at the most recent epoch boundary.
|
||||
|
||||
##### Source root
|
||||
|
||||
Set `attestation_data.source_root = head_state.current_justified_root`.
|
||||
|
||||
##### Target root
|
||||
|
||||
Set `attestation_data.target_root = signing_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
|
||||
|
||||
*Note*: This can be looked up in the state using:
|
||||
*Note*: `epoch_boundary_block` can be looked up in the state using:
|
||||
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
|
||||
* Set `epoch_boundary = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
|
||||
* Let `epoch_boundary_block = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
|
||||
|
||||
##### Shard
|
||||
##### Crosslink vote
|
||||
|
||||
Set `attestation_data.shard = shard` where `shard` is the shard associated with the validator's committee defined by `get_crosslink_committees_at_slot`.
|
||||
Construct `attestation_data.crosslink` via the following.
|
||||
|
||||
##### Previous crosslink root
|
||||
|
||||
Set `attestation_data.previous_crosslink_root = hash_tree_root(head_state.current_crosslinks[shard])`.
|
||||
|
||||
##### Crosslink data root
|
||||
|
||||
Set `attestation_data.crosslink_data_root = ZERO_HASH`.
|
||||
|
||||
*Note*: This is a stub for Phase 0.
|
||||
* Set `attestation_data.crosslink.shard = shard` where `shard` is the shard associated with the validator's committee.
|
||||
* Let `parent_crosslink = head_state.current_crosslinks[shard]`.
|
||||
* Set `attestation_data.crosslink.start_epoch = parent_crosslink.end_epoch`.
|
||||
* Set `attestation_data.crosslink.end_epoch = min(attestation_data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)`.
|
||||
* Set `attestation_data.crosslink.parent_root = hash_tree_root(head_state.current_crosslinks[shard])`.
|
||||
* Set `attestation_data.crosslink.data_root = ZERO_HASH`. *Note*: This is a stub for Phase 0.
|
||||
|
||||
#### Construct attestation
|
||||
|
||||
@ -329,67 +361,6 @@ signed_attestation_data = bls_sign(
|
||||
)
|
||||
```
|
||||
|
||||
## Validator assignments
|
||||
|
||||
A validator can get the current, previous, and next epoch committee assignments using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `previous_epoch <= epoch <= next_epoch`.
|
||||
|
||||
```python
|
||||
def get_committee_assignment(
|
||||
state: BeaconState,
|
||||
epoch: Epoch,
|
||||
validator_index: ValidatorIndex) -> Tuple[List[ValidatorIndex], Shard, Slot]:
|
||||
"""
|
||||
Return the committee assignment in the ``epoch`` for ``validator_index``.
|
||||
``assignment`` returned is a tuple of the following form:
|
||||
* ``assignment[0]`` is the list of validators in the committee
|
||||
* ``assignment[1]`` is the shard to which the committee is assigned
|
||||
* ``assignment[2]`` is the slot at which the committee is assigned
|
||||
"""
|
||||
previous_epoch = get_previous_epoch(state)
|
||||
next_epoch = get_current_epoch(state) + 1
|
||||
assert previous_epoch <= epoch <= next_epoch
|
||||
|
||||
epoch_start_slot = get_epoch_start_slot(epoch)
|
||||
for slot in range(epoch_start_slot, epoch_start_slot + SLOTS_PER_EPOCH):
|
||||
crosslink_committees = get_crosslink_committees_at_slot(
|
||||
state,
|
||||
slot,
|
||||
)
|
||||
selected_committees = [
|
||||
committee # Tuple[List[ValidatorIndex], Shard]
|
||||
for committee in crosslink_committees
|
||||
if validator_index in committee[0]
|
||||
]
|
||||
if len(selected_committees) > 0:
|
||||
validators = selected_committees[0][0]
|
||||
shard = selected_committees[0][1]
|
||||
|
||||
assignment = (validators, shard, slot)
|
||||
return assignment
|
||||
```
|
||||
|
||||
A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the slot in question. Proposer selection is only stable within the context of the current epoch.
|
||||
|
||||
```python
|
||||
def is_proposer_at_slot(state: BeaconState,
|
||||
slot: Slot,
|
||||
validator_index: ValidatorIndex) -> bool:
|
||||
assert state.slot == slot
|
||||
|
||||
return get_beacon_proposer_index(state) == validator_index
|
||||
```
|
||||
|
||||
*Note*: To see if a validator is assigned to proposer during the slot, the validator must run an empty slot transition from the previous state to the current slot.
|
||||
|
||||
|
||||
### Lookahead
|
||||
|
||||
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing, which must checked during the slot in question.
|
||||
|
||||
`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments which involves noting at which future slot one will have to attest and also which shard one should begin syncing (in Phase 1+).
|
||||
|
||||
Specifically, a validator should call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
|
||||
|
||||
## How to avoid slashing
|
||||
|
||||
"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed -- [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed.
|
||||
|
@ -15,7 +15,7 @@ def shuffling_case(seed: spec.Bytes32, count: int):
|
||||
|
||||
@to_tuple
|
||||
def shuffling_test_cases():
|
||||
for seed in [spec.hash(spec.int_to_bytes4(seed_init_value)) for seed_init_value in range(30)]:
|
||||
for seed in [spec.hash(spec.int_to_bytes(seed_init_value, length=4)) for seed_init_value in range(30)]:
|
||||
for count in [0, 1, 2, 3, 5, 10, 33, 100, 1000]:
|
||||
yield shuffling_case(seed, count)
|
||||
|
||||
|
@ -16,11 +16,11 @@ def decode(json, typ):
|
||||
for field, subtype in typ.fields.items():
|
||||
temp[field] = decode(json[field], subtype)
|
||||
if field + "_hash_tree_root" in json:
|
||||
assert(json[field + "_hash_tree_root"][2:] ==
|
||||
assert(json[field + "_hash_tree_root"][2:] ==
|
||||
hash_tree_root(temp[field], subtype).hex())
|
||||
ret = typ(**temp)
|
||||
if "hash_tree_root" in json:
|
||||
assert(json["hash_tree_root"][2:] ==
|
||||
assert(json["hash_tree_root"][2:] ==
|
||||
hash_tree_root(ret, typ).hex())
|
||||
return ret
|
||||
else:
|
||||
|
@ -25,4 +25,3 @@ def encode(value, typ, include_hash_tree_roots=False):
|
||||
else:
|
||||
print(value, typ)
|
||||
raise Exception("Type not recognized")
|
||||
|
||||
|
@ -31,7 +31,12 @@ class RandomizationMode(Enum):
|
||||
return self.value in [0, 4, 5]
|
||||
|
||||
|
||||
def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, chaos: bool) -> Any:
|
||||
def get_random_ssz_object(rng: Random,
|
||||
typ: Any,
|
||||
max_bytes_length: int,
|
||||
max_list_length: int,
|
||||
mode: RandomizationMode,
|
||||
chaos: bool) -> Any:
|
||||
"""
|
||||
Create an object for a given type, filled with random data.
|
||||
:param rng: The random number generator to use.
|
||||
@ -77,7 +82,10 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list
|
||||
return get_random_basic_value(rng, typ)
|
||||
# Vector:
|
||||
elif isinstance(typ, list) and len(typ) == 2:
|
||||
return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(typ[1])]
|
||||
return [
|
||||
get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos)
|
||||
for _ in range(typ[1])
|
||||
]
|
||||
# List:
|
||||
elif isinstance(typ, list) and len(typ) == 1:
|
||||
length = rng.randint(0, max_list_length)
|
||||
@ -85,10 +93,17 @@ def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list
|
||||
length = 1
|
||||
if mode == RandomizationMode.mode_max_count:
|
||||
length = max_list_length
|
||||
return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(length)]
|
||||
return [
|
||||
get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos)
|
||||
for _ in range(length)
|
||||
]
|
||||
# Container:
|
||||
elif hasattr(typ, 'fields'):
|
||||
return typ(**{field: get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) for field, subtype in typ.fields.items()})
|
||||
return typ(**{
|
||||
field:
|
||||
get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos)
|
||||
for field, subtype in typ.fields.items()
|
||||
})
|
||||
else:
|
||||
print(typ)
|
||||
raise Exception("Type not recognized")
|
||||
|
@ -1,112 +0,0 @@
|
||||
from . import spec
|
||||
|
||||
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
List
|
||||
)
|
||||
|
||||
from .spec import (
|
||||
BeaconState,
|
||||
BeaconBlock,
|
||||
Slot,
|
||||
)
|
||||
|
||||
|
||||
def expected_deposit_count(state: BeaconState) -> int:
|
||||
return min(
|
||||
spec.MAX_DEPOSITS,
|
||||
state.latest_eth1_data.deposit_count - state.deposit_index
|
||||
)
|
||||
|
||||
|
||||
def process_operation_type(state: BeaconState,
|
||||
operations: List[Any],
|
||||
max_operations: int,
|
||||
tx_fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
assert len(operations) <= max_operations
|
||||
for operation in operations:
|
||||
tx_fn(state, operation)
|
||||
|
||||
|
||||
def process_operations(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.proposer_slashings,
|
||||
spec.MAX_PROPOSER_SLASHINGS,
|
||||
spec.process_proposer_slashing,
|
||||
)
|
||||
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.attester_slashings,
|
||||
spec.MAX_ATTESTER_SLASHINGS,
|
||||
spec.process_attester_slashing,
|
||||
)
|
||||
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.attestations,
|
||||
spec.MAX_ATTESTATIONS,
|
||||
spec.process_attestation,
|
||||
)
|
||||
|
||||
assert len(block.body.deposits) == expected_deposit_count(state)
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.deposits,
|
||||
spec.MAX_DEPOSITS,
|
||||
spec.process_deposit,
|
||||
)
|
||||
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.voluntary_exits,
|
||||
spec.MAX_VOLUNTARY_EXITS,
|
||||
spec.process_voluntary_exit,
|
||||
)
|
||||
|
||||
assert len(block.body.transfers) == len(set(block.body.transfers))
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.transfers,
|
||||
spec.MAX_TRANSFERS,
|
||||
spec.process_transfer,
|
||||
)
|
||||
|
||||
|
||||
def process_block(state: BeaconState,
|
||||
block: BeaconBlock,
|
||||
verify_state_root: bool=False) -> None:
|
||||
spec.process_block_header(state, block)
|
||||
spec.process_randao(state, block)
|
||||
spec.process_eth1_data(state, block)
|
||||
|
||||
process_operations(state, block)
|
||||
if verify_state_root:
|
||||
spec.verify_block_state_root(state, block)
|
||||
|
||||
|
||||
def process_epoch_transition(state: BeaconState) -> None:
|
||||
spec.process_justification_and_finalization(state)
|
||||
spec.process_crosslinks(state)
|
||||
spec.process_rewards_and_penalties(state)
|
||||
spec.process_registry_updates(state)
|
||||
spec.process_slashings(state)
|
||||
spec.process_final_updates(state)
|
||||
|
||||
|
||||
def state_transition_to(state: BeaconState, up_to: Slot) -> BeaconState:
|
||||
while state.slot < up_to:
|
||||
spec.cache_state(state)
|
||||
if (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0:
|
||||
process_epoch_transition(state)
|
||||
spec.advance_slot(state)
|
||||
|
||||
|
||||
def state_transition(state: BeaconState,
|
||||
block: BeaconBlock,
|
||||
verify_state_root: bool=False) -> BeaconState:
|
||||
state_transition_to(state, block.slot)
|
||||
process_block(state, block, verify_state_root)
|
@ -9,4 +9,4 @@ def bls_verify_multiple(pubkeys, message_hashes, signature, domain):
|
||||
|
||||
|
||||
def bls_aggregate_pubkeys(pubkeys):
|
||||
return b'\x42' * 96
|
||||
return b'\x42' * 48
|
||||
|
@ -3,13 +3,11 @@ import pytest
|
||||
|
||||
import eth2spec.phase0.spec as spec
|
||||
|
||||
from eth2spec.phase0.state_transition import (
|
||||
state_transition,
|
||||
)
|
||||
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,
|
||||
@ -66,6 +64,22 @@ def test_success_prevous_epoch(state):
|
||||
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
|
||||
@ -113,7 +127,7 @@ 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
|
||||
attestation.data.crosslink.data_root = b'\x42' * 32
|
||||
|
||||
pre_state, post_state = run_attestation_processing(state, attestation, False)
|
||||
|
||||
@ -126,7 +140,33 @@ def test_bad_previous_crosslink(state):
|
||||
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
||||
next_slot(state)
|
||||
|
||||
state.current_crosslinks[attestation.data.shard].epoch += 10
|
||||
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)
|
||||
|
||||
@ -150,6 +190,6 @@ def test_empty_aggregation_bitfield(state):
|
||||
|
||||
attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield)
|
||||
|
||||
pre_state, post_state = run_attestation_processing(state, attestation, False)
|
||||
pre_state, post_state = run_attestation_processing(state, attestation)
|
||||
|
||||
return pre_state, attestation, post_state
|
||||
|
@ -4,11 +4,11 @@ import pytest
|
||||
|
||||
from eth2spec.phase0.spec import (
|
||||
get_beacon_proposer_index,
|
||||
cache_state,
|
||||
advance_slot,
|
||||
process_slot,
|
||||
process_block_header,
|
||||
)
|
||||
from tests.helpers import (
|
||||
advance_slot,
|
||||
build_empty_block_for_next_slot,
|
||||
next_slot,
|
||||
)
|
||||
@ -18,7 +18,7 @@ pytestmark = pytest.mark.header
|
||||
|
||||
|
||||
def prepare_state_for_header_processing(state):
|
||||
cache_state(state)
|
||||
process_slot(state)
|
||||
advance_slot(state)
|
||||
|
||||
|
||||
@ -53,9 +53,9 @@ def test_invalid_slot(state):
|
||||
return pre_state, block, None
|
||||
|
||||
|
||||
def test_invalid_previous_block_root(state):
|
||||
def test_invalid_parent_block_root(state):
|
||||
block = build_empty_block_for_next_slot(state)
|
||||
block.previous_block_root = b'\12' * 32 # invalid prev root
|
||||
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
|
||||
|
@ -3,13 +3,11 @@ import pytest
|
||||
|
||||
import eth2spec.phase0.spec as spec
|
||||
|
||||
from eth2spec.phase0.state_transition import (
|
||||
state_transition,
|
||||
)
|
||||
from eth2spec.phase0.spec import (
|
||||
cache_state,
|
||||
process_slot,
|
||||
get_crosslink_deltas,
|
||||
process_crosslinks,
|
||||
state_transition,
|
||||
)
|
||||
from tests.helpers import (
|
||||
add_attestation_to_state,
|
||||
@ -35,7 +33,7 @@ def run_process_crosslinks(state, valid=True):
|
||||
state_transition(state, block)
|
||||
|
||||
# cache state before epoch transition
|
||||
cache_state(state)
|
||||
process_slot(state)
|
||||
|
||||
post_state = deepcopy(state)
|
||||
process_crosslinks(post_state)
|
||||
@ -64,7 +62,7 @@ def test_single_crosslink_update_from_current_epoch(state):
|
||||
|
||||
pre_state, post_state = run_process_crosslinks(state)
|
||||
|
||||
shard = attestation.data.shard
|
||||
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]
|
||||
|
||||
@ -84,11 +82,11 @@ def test_single_crosslink_update_from_previous_epoch(state):
|
||||
pre_state, post_state = run_process_crosslinks(state)
|
||||
crosslink_deltas = get_crosslink_deltas(state)
|
||||
|
||||
shard = attestation.data.shard
|
||||
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]
|
||||
# ensure rewarded
|
||||
for index in get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.shard):
|
||||
for index in get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.crosslink.shard):
|
||||
assert crosslink_deltas[0][index] > 0
|
||||
assert crosslink_deltas[1][index] == 0
|
||||
|
||||
@ -108,7 +106,7 @@ def test_double_late_crosslink(state):
|
||||
|
||||
for slot in range(spec.SLOTS_PER_EPOCH):
|
||||
attestation_2 = get_valid_attestation(state)
|
||||
if attestation_2.data.shard == attestation_1.data.shard:
|
||||
if attestation_2.data.crosslink.shard == attestation_1.data.crosslink.shard:
|
||||
break
|
||||
next_slot(state)
|
||||
fill_aggregate_attestation(state, attestation_2)
|
||||
@ -124,12 +122,12 @@ def test_double_late_crosslink(state):
|
||||
pre_state, post_state = run_process_crosslinks(state)
|
||||
crosslink_deltas = get_crosslink_deltas(state)
|
||||
|
||||
shard = attestation_2.data.shard
|
||||
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]
|
||||
# ensure no reward, only penalties for the failed crosslink
|
||||
for index in get_crosslink_committee(state, attestation_2.data.target_epoch, attestation_2.data.shard):
|
||||
for index in get_crosslink_committee(state, attestation_2.data.target_epoch, attestation_2.data.crosslink.shard):
|
||||
assert crosslink_deltas[0][index] == 0
|
||||
assert crosslink_deltas[1][index] > 0
|
||||
|
||||
|
@ -2,14 +2,12 @@ from copy import deepcopy
|
||||
|
||||
from py_ecc import bls
|
||||
|
||||
from eth2spec.phase0.state_transition import (
|
||||
state_transition,
|
||||
)
|
||||
import eth2spec.phase0.spec as spec
|
||||
from eth2spec.utils.minimal_ssz import signing_root
|
||||
from eth2spec.phase0.spec import (
|
||||
# constants
|
||||
ZERO_HASH,
|
||||
MAX_EPOCHS_PER_CROSSLINK,
|
||||
# SSZ
|
||||
Attestation,
|
||||
AttestationData,
|
||||
@ -17,6 +15,7 @@ from eth2spec.phase0.spec import (
|
||||
AttesterSlashing,
|
||||
BeaconBlock,
|
||||
BeaconBlockHeader,
|
||||
Crosslink,
|
||||
Deposit,
|
||||
DepositData,
|
||||
Eth1Data,
|
||||
@ -38,6 +37,7 @@ from eth2spec.phase0.spec import (
|
||||
get_shard_delta,
|
||||
hash_tree_root,
|
||||
slot_to_epoch,
|
||||
state_transition,
|
||||
verify_merkle_branch,
|
||||
hash,
|
||||
)
|
||||
@ -53,6 +53,10 @@ 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]
|
||||
|
||||
@ -126,7 +130,7 @@ def build_empty_block_for_next_slot(state):
|
||||
previous_block_header = deepcopy(state.latest_block_header)
|
||||
if previous_block_header.state_root == spec.ZERO_HASH:
|
||||
previous_block_header.state_root = state.hash_tree_root()
|
||||
empty_block.previous_block_root = signing_root(previous_block_header)
|
||||
empty_block.parent_root = signing_root(previous_block_header)
|
||||
return empty_block
|
||||
|
||||
|
||||
@ -153,7 +157,7 @@ def build_attestation_data(state, slot, shard):
|
||||
assert state.slot >= slot
|
||||
|
||||
if slot == state.slot:
|
||||
block_root = build_empty_block_for_next_slot(state).previous_block_root
|
||||
block_root = build_empty_block_for_next_slot(state).parent_root
|
||||
else:
|
||||
block_root = get_block_root_at_slot(state, slot)
|
||||
|
||||
@ -173,15 +177,20 @@ def build_attestation_data(state, slot, shard):
|
||||
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(
|
||||
shard=shard,
|
||||
beacon_block_root=block_root,
|
||||
source_epoch=justified_epoch,
|
||||
source_root=justified_block_root,
|
||||
target_epoch=slot_to_epoch(slot),
|
||||
target_root=epoch_boundary_root,
|
||||
crosslink_data_root=spec.ZERO_HASH,
|
||||
previous_crosslink_root=hash_tree_root(crosslinks[shard]),
|
||||
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),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -235,12 +244,12 @@ def get_valid_proposer_slashing(state):
|
||||
|
||||
header_1 = BeaconBlockHeader(
|
||||
slot=slot,
|
||||
previous_block_root=ZERO_HASH,
|
||||
parent_root=ZERO_HASH,
|
||||
state_root=ZERO_HASH,
|
||||
block_body_root=ZERO_HASH,
|
||||
body_root=ZERO_HASH,
|
||||
)
|
||||
header_2 = deepcopy(header_1)
|
||||
header_2.previous_block_root = b'\x02' * 32
|
||||
header_2.parent_root = b'\x02' * 32
|
||||
header_2.slot = slot + 1
|
||||
|
||||
domain = get_domain(
|
||||
@ -288,7 +297,7 @@ def get_valid_attestation(state, slot=None):
|
||||
|
||||
attestation_data = build_attestation_data(state, slot, shard)
|
||||
|
||||
crosslink_committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.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
|
||||
@ -381,7 +390,7 @@ def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0)
|
||||
|
||||
|
||||
def fill_aggregate_attestation(state, attestation):
|
||||
crosslink_committee = get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.shard)
|
||||
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)
|
||||
|
||||
|
@ -4,9 +4,6 @@ import pytest
|
||||
|
||||
import eth2spec.phase0.spec as spec
|
||||
|
||||
from eth2spec.phase0.state_transition import (
|
||||
state_transition,
|
||||
)
|
||||
from .helpers import (
|
||||
build_empty_block_for_next_slot,
|
||||
fill_aggregate_attestation,
|
||||
@ -67,7 +64,7 @@ def next_epoch_with_attestations(state,
|
||||
fill_aggregate_attestation(post_state, prev_attestation)
|
||||
block.body.attestations.append(prev_attestation)
|
||||
|
||||
state_transition(post_state, block)
|
||||
spec.state_transition(post_state, block)
|
||||
blocks.append(block)
|
||||
|
||||
return state, blocks, post_state
|
||||
|
@ -20,13 +20,10 @@ from eth2spec.phase0.spec import (
|
||||
get_block_root_at_slot,
|
||||
get_current_epoch,
|
||||
get_domain,
|
||||
advance_slot,
|
||||
cache_state,
|
||||
process_slot,
|
||||
verify_merkle_branch,
|
||||
hash,
|
||||
)
|
||||
from eth2spec.phase0.state_transition import (
|
||||
state_transition,
|
||||
hash,
|
||||
)
|
||||
from eth2spec.utils.merkle_minimal import (
|
||||
calc_merkle_tree_from_leaves,
|
||||
@ -34,6 +31,7 @@ from eth2spec.utils.merkle_minimal import (
|
||||
get_merkle_root,
|
||||
)
|
||||
from .helpers import (
|
||||
advance_slot,
|
||||
get_balance,
|
||||
build_deposit_data,
|
||||
build_empty_block_for_next_slot,
|
||||
@ -54,7 +52,7 @@ pytestmark = pytest.mark.sanity
|
||||
|
||||
def test_slot_transition(state):
|
||||
test_state = deepcopy(state)
|
||||
cache_state(test_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()
|
||||
@ -68,7 +66,7 @@ def test_empty_block_transition(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.previous_block_root
|
||||
assert get_block_root_at_slot(test_state, state.slot) == block.parent_root
|
||||
|
||||
return state, [block], test_state
|
||||
|
||||
@ -82,7 +80,7 @@ def test_skipped_slots(state):
|
||||
|
||||
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.previous_block_root
|
||||
assert get_block_root_at_slot(test_state, slot) == block.parent_root
|
||||
|
||||
return state, [block], test_state
|
||||
|
||||
@ -96,7 +94,7 @@ def test_empty_epoch_transition(state):
|
||||
|
||||
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.previous_block_root
|
||||
assert get_block_root_at_slot(test_state, slot) == block.parent_root
|
||||
|
||||
return state, [block], test_state
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user