Merge pull request #1018 from ethereum/JustinDrake-patch-14

Incorporate state_transition.py into state transition spec
This commit is contained in:
Danny Ryan 2019-05-14 15:36:09 -04:00 committed by GitHub
commit 23b1cb225d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 249 deletions

View File

@ -5,6 +5,7 @@ import function_puller
def build_phase0_spec(sourcefile, outfile):
code_lines = []
code_lines.append("""
from typing import (
Any,
Dict,

View File

@ -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 -->
@ -375,6 +372,7 @@ The types are defined topologically to aid in facilitating an executable version
'signature': 'bytes96',
}
```
#### `Validator`
```python
@ -579,9 +577,7 @@ 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],
# Balances slashed at every withdrawal period
'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH],
# `latest_block_header.state_root == ZERO_HASH` temporarily
'latest_block_header': BeaconBlockHeader,
'historical_roots': ['bytes32'],
@ -1219,50 +1215,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)))
@ -1323,8 +1330,6 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState,
#### Justification and finalization
Run the following function:
```python
def process_justification_and_finalization(state: BeaconState) -> None:
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
@ -1376,8 +1381,6 @@ def process_justification_and_finalization(state: BeaconState) -> None:
#### Crosslinks
Run the following function:
```python
def process_crosslinks(state: BeaconState) -> None:
state.previous_crosslinks = [c for c in state.current_crosslinks]
@ -1392,8 +1395,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
@ -1469,8 +1470,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:
@ -1485,8 +1484,6 @@ 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
@ -1515,8 +1512,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)
@ -1539,8 +1534,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)
@ -1579,19 +1572,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
@ -1616,44 +1606,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,
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.
"""
@ -1674,13 +1677,8 @@ 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.
"""
@ -1702,10 +1700,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:
"""
@ -1742,10 +1736,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
##### 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:
"""
@ -1793,10 +1783,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:
"""
@ -1820,10 +1806,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:
"""
@ -1854,12 +1836,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)
```

View File

@ -238,7 +238,7 @@ 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)`.
##### LMD GHOST vote
@ -360,7 +360,7 @@ def is_proposer_at_slot(state: BeaconState,
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.
*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 using `process_slots(state, current_slot)`.
### Lookahead

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,6 @@ 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 (
@ -40,6 +37,7 @@ from eth2spec.phase0.spec import (
get_shard_delta,
hash_tree_root,
slot_to_epoch,
state_transition,
verify_merkle_branch,
hash,
)
@ -55,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]

View File

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

View File

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