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 7 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
146 changes: 65 additions & 81 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,15 @@
- [On genesis](#on-genesis)
- [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 Down Expand Up @@ -1264,50 +1263,59 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],

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

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.
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 exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid.

```python
def state_transition(state: BeaconState, block: BeaconBlock) -> BeaconBlock:
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
# Block must post-date the state
assert state.slot < block.slot
# Process slots (including those with no blocks) since block
while state.slot < block.slot:
# Cache state at the start of every slot
cache_state(state)
# Process epoch at the start of the first slot of every epoch
if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
process_epoch(state)
# Increment slot number
state.slot += 1
# Process block
process_block(state, block)
# Return post-state
return state
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
```

### State caching

At every `slot > GENESIS_SLOT` run the following function:

```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
# Cache state root of previous slot
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 previous state root in latest_block_header, if empty
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 block root of previous slot
previous_block_root = signing_root(state.latest_block_header)
state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_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
```

### 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 @@ -1387,8 +1395,6 @@ def get_earliest_attestation(state: BeaconState, attestations: List[PendingAttes

#### 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 @@ -1436,8 +1442,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 @@ -1453,8 +1457,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 @@ -1526,8 +1528,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 @@ -1542,8 +1542,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 All @@ -1568,8 +1566,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 @@ -1592,8 +1588,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 @@ -1632,19 +1626,17 @@ 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)
process_eth1_data(state, block)
process_operations(state, block.body)
# verify_block_state_root(state, block)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say enable it in the spec, and hack it in the tests.

/cc @djrtwo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit torn here.

options:

  1. uncomment and leave as is
  2. add verify_state_root boolean in process_block and state_transition and a note about in which context you would run it as True or False (receiving a block vs building a block)
  3. have verify_block_state_root live outside of the state transition function entirely and as a note on block validity below.

I think I like (2) the best, but am open to debate.
*

Copy link
Collaborator Author

@JustinDrake JustinDrake May 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion would be for the test bench to populate the state_root immediately prior to verification in case it is set to ZERO_HASH. That way we can keep the spec clean. Could that work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some sort of monkey patch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

```

### 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 Down Expand Up @@ -1691,11 +1683,25 @@ def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None:

#### Operations

##### Proposer slashings
```python
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
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
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,
Expand All @@ -1721,10 +1727,6 @@ 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:
Expand Down Expand Up @@ -1759,10 +1761,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 @@ -1801,10 +1799,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 @@ -1851,10 +1845,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 @@ -1879,10 +1869,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 @@ -1917,8 +1903,6 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:

#### 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)
Expand Down
Loading