Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorporate state_transition.py into state transition spec #1018

Merged
merged 19 commits into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/phase0/build_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
def build_phase0_spec(sourcefile, outfile):
code_lines = []
code_lines.append("""

from typing import (
Any,
Dict,
Expand Down
185 changes: 79 additions & 106 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,15 @@
- [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)
- [Rewards and penalties](#rewards-and-penalties)
- [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)
Expand All @@ -119,7 +117,6 @@
- [Deposits](#deposits)
- [Voluntary exits](#voluntary-exits)
- [Transfers](#transfers)
- [State root verification](#state-root-verification)

<!-- /TOC -->

Expand Down Expand Up @@ -375,6 +372,7 @@ The types are defined topologically to aid in facilitating an executable version
'signature': 'bytes96',
}
```

#### `Validator`

```python
Expand Down Expand Up @@ -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'],

Expand Down Expand Up @@ -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.
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved

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

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.
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved

*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
```python
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
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
```

At every `slot > GENESIS_SLOT` run the following function:
```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
```

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

# Store latest known state root (for previous slot) in latest_block_header if it is empty
# Cache latest block header state root
if state.latest_block_header.state_root == ZERO_HASH:
state.latest_block_header.state_root = latest_state_root
state.latest_block_header.state_root = previous_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
# 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
```

### Per-epoch processing
### Epoch processing

The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SLOTS_PER_EPOCH == 0`.
```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)))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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`.

##### Proposer slashings
```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)
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
# Verify that there are no duplicate transfers
assert len(body.transfers) == len(set(body.transfers))
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved

Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`.
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)
```

For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function:
##### Proposer slashings

```python
def process_proposer_slashing(state: BeaconState,
proposer_slashing: ProposerSlashing) -> None:
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
"""
Process ``ProposerSlashing`` operation.
"""
Expand All @@ -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.
"""
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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)
```
4 changes: 2 additions & 2 deletions specs/validator/0_beacon-chain-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Loading