Merge branch 'dev' into vbuterin-patch-20

This commit is contained in:
Hsiao-Wei Wang 2019-03-22 11:58:32 +08:00 committed by GitHub
commit 512ceff1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 273 additions and 91 deletions

View File

@ -59,7 +59,12 @@
- [`get_current_epoch`](#get_current_epoch) - [`get_current_epoch`](#get_current_epoch)
- [`get_epoch_start_slot`](#get_epoch_start_slot) - [`get_epoch_start_slot`](#get_epoch_start_slot)
- [`is_active_validator`](#is_active_validator) - [`is_active_validator`](#is_active_validator)
- [`is_slashable_validator`](#is_slashable_validator)
- [`get_active_validator_indices`](#get_active_validator_indices) - [`get_active_validator_indices`](#get_active_validator_indices)
- [`get_balance`](#get_balance)
- [`set_balance`](#set_balance)
- [`increase_balance`](#increase_balance)
- [`decrease_balance`](#decrease_balance)
- [`get_permuted_index`](#get_permuted_index) - [`get_permuted_index`](#get_permuted_index)
- [`get_split_offset`](#get_split_offset) - [`get_split_offset`](#get_split_offset)
- [`get_epoch_committee_count`](#get_epoch_committee_count) - [`get_epoch_committee_count`](#get_epoch_committee_count)
@ -115,7 +120,7 @@
- [Helper functions](#helper-functions-1) - [Helper functions](#helper-functions-1)
- [Justification](#justification) - [Justification](#justification)
- [Crosslinks](#crosslinks) - [Crosslinks](#crosslinks)
- [Eth1 data](#eth1-data-1) - [Eth1 data](#eth1-data)
- [Rewards and penalties](#rewards-and-penalties) - [Rewards and penalties](#rewards-and-penalties)
- [Justification and finalization](#justification-and-finalization) - [Justification and finalization](#justification-and-finalization)
- [Crosslinks](#crosslinks-1) - [Crosslinks](#crosslinks-1)
@ -128,7 +133,7 @@
- [Per-block processing](#per-block-processing) - [Per-block processing](#per-block-processing)
- [Block header](#block-header) - [Block header](#block-header)
- [RANDAO](#randao) - [RANDAO](#randao)
- [Eth1 data](#eth1-data) - [Eth1 data](#eth1-data-1)
- [Transactions](#transactions) - [Transactions](#transactions)
- [Proposer slashings](#proposer-slashings) - [Proposer slashings](#proposer-slashings)
- [Attester slashings](#attester-slashings) - [Attester slashings](#attester-slashings)
@ -182,7 +187,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
| `SHARD_COUNT` | `2**10` (= 1,024) | | `SHARD_COUNT` | `2**10` (= 1,024) |
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) | | `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
| `MAX_BALANCE_CHURN_QUOTIENT` | `2**5` (= 32) | | `MAX_BALANCE_CHURN_QUOTIENT` | `2**5` (= 32) |
| `MAX_INDICES_PER_SLASHABLE_VOTE` | `2**12` (= 4,096) | | `MAX_SLASHABLE_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
| `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) | | `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) |
| `SHUFFLE_ROUND_COUNT` | 90 | | `SHUFFLE_ROUND_COUNT` | 90 |
@ -201,8 +206,8 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
| - | - | :-: | | - | - | :-: |
| `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | | `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei |
| `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | | `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei |
| `FORK_CHOICE_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei |
| `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei |
| `HIGH_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei |
### Initial values ### Initial values
@ -416,7 +421,6 @@ The types are defined topologically to aid in facilitating an executable version
'signature': 'bytes96', 'signature': 'bytes96',
} }
``` ```
#### `Validator` #### `Validator`
```python ```python
@ -435,6 +439,8 @@ The types are defined topologically to aid in facilitating an executable version
'initiated_exit': 'bool', 'initiated_exit': 'bool',
# Was the validator slashed # Was the validator slashed
'slashed': 'bool', 'slashed': 'bool',
# Rounded balance
'high_balance': 'uint64'
} }
``` ```
@ -595,7 +601,7 @@ The types are defined topologically to aid in facilitating an executable version
# Validator registry # Validator registry
'validator_registry': [Validator], 'validator_registry': [Validator],
'validator_balances': ['uint64'], 'balances': ['uint64'],
'validator_registry_update_epoch': 'uint64', 'validator_registry_update_epoch': 'uint64',
# Randomness and committees # Randomness and committees
@ -739,6 +745,18 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool:
return validator.activation_epoch <= epoch < validator.exit_epoch return validator.activation_epoch <= epoch < validator.exit_epoch
``` ```
### `is_slashable_validator`
```python
def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool:
"""
Check if ``validator`` is slashable.
"""
return (
validator.activation_epoch <= epoch < validator.withdrawable_epoch and
validator.slashed is False
)
```
### `get_active_validator_indices` ### `get_active_validator_indices`
```python ```python
@ -749,6 +767,39 @@ def get_active_validator_indices(validators: List[Validator], epoch: Epoch) -> L
return [i for i, v in enumerate(validators) if is_active_validator(v, epoch)] return [i for i, v in enumerate(validators) if is_active_validator(v, epoch)]
``` ```
### `get_balance`
```python
def get_balance(state: BeaconState, index: int) -> int:
return state.balances[index]
```
### `set_balance`
```python
def set_balance(state: BeaconState, index: int, balance: int) -> None:
validator = state.validator_registry[index]
HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2
if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance:
validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT
state.balances[index] = balance
```
### `increase_balance`
```python
def increase_balance(state: BeaconState, index: int, delta: int) -> None:
set_balance(state, index, get_balance(state, index) + delta)
```
### `decrease_balance`
```python
def decrease_balance(state: BeaconState, index: int, delta: int) -> None:
cur_balance = get_balance(state, index)
set_balance(state, index, cur_balance - delta if cur_balance >= delta else 0)
```
### `get_permuted_index` ### `get_permuted_index`
```python ```python
@ -1082,7 +1133,7 @@ def get_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei:
""" """
Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. Return the effective balance (also known as "balance at stake") for a validator with the given ``index``.
""" """
return min(state.validator_balances[index], MAX_DEPOSIT_AMOUNT) return min(get_balance(state, index), MAX_DEPOSIT_AMOUNT)
``` ```
### `get_total_balance` ### `get_total_balance`
@ -1159,7 +1210,7 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas
if slashable_attestation.custody_bitfield != b'\x00' * len(slashable_attestation.custody_bitfield): # [TO BE REMOVED IN PHASE 1] if slashable_attestation.custody_bitfield != b'\x00' * len(slashable_attestation.custody_bitfield): # [TO BE REMOVED IN PHASE 1]
return False return False
if len(slashable_attestation.validator_indices) == 0: if not (1 <= len(slashable_attestation.validator_indices) <= MAX_SLASHABLE_ATTESTATION_PARTICIPANTS):
return False return False
for i in range(len(slashable_attestation.validator_indices) - 1): for i in range(len(slashable_attestation.validator_indices) - 1):
@ -1169,9 +1220,6 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas
if not verify_bitfield(slashable_attestation.custody_bitfield, len(slashable_attestation.validator_indices)): if not verify_bitfield(slashable_attestation.custody_bitfield, len(slashable_attestation.validator_indices)):
return False return False
if len(slashable_attestation.validator_indices) > MAX_INDICES_PER_SLASHABLE_VOTE:
return False
custody_bit_0_indices = [] custody_bit_0_indices = []
custody_bit_1_indices = [] custody_bit_1_indices = []
for i, validator_index in enumerate(slashable_attestation.validator_indices): for i, validator_index in enumerate(slashable_attestation.validator_indices):
@ -1326,14 +1374,17 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
withdrawable_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH,
initiated_exit=False, initiated_exit=False,
slashed=False, slashed=False,
high_balance=0
) )
# Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled.
state.validator_registry.append(validator) state.validator_registry.append(validator)
state.validator_balances.append(amount) state.balances.append(0)
set_balance(state, len(state.validator_registry) - 1, amount)
else: else:
# Increase balance by deposit amount # Increase balance by deposit amount
state.validator_balances[validator_pubkeys.index(pubkey)] += amount index = validator_pubkeys.index(pubkey)
increase_balance(state, index, amount)
``` ```
### Routines for updating validator status ### Routines for updating validator status
@ -1389,14 +1440,13 @@ def slash_validator(state: BeaconState, index: ValidatorIndex) -> None:
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
validator = state.validator_registry[index] validator = state.validator_registry[index]
assert state.slot < get_epoch_start_slot(validator.withdrawable_epoch) # [TO BE REMOVED IN PHASE 2]
exit_validator(state, index) exit_validator(state, index)
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index) state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index)
whistleblower_index = get_beacon_proposer_index(state, state.slot) whistleblower_index = get_beacon_proposer_index(state, state.slot)
whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT
state.validator_balances[whistleblower_index] += whistleblower_reward increase_balance(state, whistleblower_index, whistleblower_reward)
state.validator_balances[index] -= whistleblower_reward decrease_balance(state, index, whistleblower_reward)
validator.slashed = True validator.slashed = True
validator.withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH validator.withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH
``` ```
@ -1517,7 +1567,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
# Validator registry # Validator registry
validator_registry=[], validator_registry=[],
validator_balances=[], balances=[],
validator_registry_update_epoch=GENESIS_EPOCH, validator_registry_update_epoch=GENESIS_EPOCH,
# Randomness and committees # Randomness and committees
@ -1632,9 +1682,12 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock)
for validator_index in active_validator_indices for validator_index in active_validator_indices
] ]
# Use the rounded-balance-with-hysteresis supplied by the protocol for fork
# choice voting. This reduces the number of recomputations that need to be
# made for optimized implementations that precompute and save data
def get_vote_count(block: BeaconBlock) -> int: def get_vote_count(block: BeaconBlock) -> int:
return sum( return sum(
get_effective_balance(start_state.validator_balances[validator_index]) // FORK_CHOICE_BALANCE_INCREMENT start_state.validator_registry[validator_index].high_balance
for validator_index, target in attestation_targets for validator_index, target in attestation_targets
if get_ancestor(store, target, block.slot) == block if get_ancestor(store, target, block.slot) == block
) )
@ -2021,9 +2074,13 @@ def apply_rewards(state: BeaconState) -> None:
rewards1, penalties1 = get_justification_and_finalization_deltas(state) rewards1, penalties1 = get_justification_and_finalization_deltas(state)
rewards2, penalties2 = get_crosslink_deltas(state) rewards2, penalties2 = get_crosslink_deltas(state)
for i in range(len(state.validator_registry)): for i in range(len(state.validator_registry)):
state.validator_balances[i] = max( set_balance(
state,
i,
max(
0, 0,
state.validator_balances[i] + rewards1[i] + rewards2[i] - penalties1[i] - penalties2[i] get_balance(state, i) + rewards1[i] + rewards2[i] - penalties1[i] - penalties2[i],
),
) )
``` ```
@ -2038,7 +2095,7 @@ def process_ejections(state: BeaconState) -> None:
and eject active validators with balance below ``EJECTION_BALANCE``. and eject active validators with balance below ``EJECTION_BALANCE``.
""" """
for index in get_active_validator_indices(state.validator_registry, get_current_epoch(state)): for index in get_active_validator_indices(state.validator_registry, get_current_epoch(state)):
if state.validator_balances[index] < EJECTION_BALANCE: if get_balance(state, index) < EJECTION_BALANCE:
initiate_validator_exit(state, index) initiate_validator_exit(state, index)
``` ```
@ -2081,7 +2138,7 @@ def update_validator_registry(state: BeaconState) -> None:
# Activate validators within the allowable balance churn # Activate validators within the allowable balance churn
balance_churn = 0 balance_churn = 0
for index, validator in enumerate(state.validator_registry): for index, validator in enumerate(state.validator_registry):
if validator.activation_epoch == FAR_FUTURE_EPOCH and state.validator_balances[index] >= MAX_DEPOSIT_AMOUNT: if validator.activation_epoch == FAR_FUTURE_EPOCH and get_balance(state, index) >= MAX_DEPOSIT_AMOUNT:
# Check the balance churn would be within the allowance # Check the balance churn would be within the allowance
balance_churn += get_effective_balance(state, index) balance_churn += get_effective_balance(state, index)
if balance_churn > max_balance_churn: if balance_churn > max_balance_churn:
@ -2166,7 +2223,7 @@ def process_slashings(state: BeaconState) -> None:
get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance, get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance,
get_effective_balance(state, index) // MIN_PENALTY_QUOTIENT get_effective_balance(state, index) // MIN_PENALTY_QUOTIENT
) )
state.validator_balances[index] -= penalty decrease_balance(state, index, penalty)
``` ```
```python ```python
@ -2311,8 +2368,8 @@ def process_proposer_slashing(state: BeaconState,
assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot) assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot)
# But the headers are different # But the headers are different
assert proposer_slashing.header_1 != proposer_slashing.header_2 assert proposer_slashing.header_1 != proposer_slashing.header_2
# Proposer is not yet slashed # Check proposer is slashable
assert proposer.slashed is False assert is_slashable_validator(proposer, get_current_epoch(state))
# Signatures are valid # Signatures are valid
for header in (proposer_slashing.header_1, proposer_slashing.header_2): for header in (proposer_slashing.header_1, proposer_slashing.header_2):
assert bls_verify( assert bls_verify(
@ -2351,7 +2408,7 @@ def process_attester_slashing(state: BeaconState,
index for index in attestation1.validator_indices index for index in attestation1.validator_indices
if ( if (
index in attestation2.validator_indices and index in attestation2.validator_indices and
state.validator_registry[index].slashed is False is_slashable_validator(state.validator_registry[index], get_current_epoch(state))
) )
] ]
assert len(slashable_indices) >= 1 assert len(slashable_indices) >= 1
@ -2472,12 +2529,12 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
# Verify the amount and fee aren't individually too big (for anti-overflow purposes) # Verify the amount and fee aren't individually too big (for anti-overflow purposes)
assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee) assert get_balance(state, transfer.sender) >= max(transfer.amount, transfer.fee)
# Verify that we have enough ETH to send, and that after the transfer the balance will be either # Verify that we have enough ETH to send, and that after the transfer the balance will be either
# exactly zero or at least MIN_DEPOSIT_AMOUNT # exactly zero or at least MIN_DEPOSIT_AMOUNT
assert ( assert (
state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or get_balance(state, transfer.sender) == transfer.amount + transfer.fee or
state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT get_balance(state, transfer.sender) >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT
) )
# A transfer is valid in only one slot # A transfer is valid in only one slot
assert state.slot == transfer.slot assert state.slot == transfer.slot
@ -2499,9 +2556,9 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER) domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER)
) )
# Process the transfer # Process the transfer
state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee decrease_balance(state, transfer.sender, transfer.amount + transfer.fee)
state.validator_balances[transfer.recipient] += transfer.amount increase_balance(state, transfer.recipient, transfer.amount)
state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee increase_balance(state, get_beacon_proposer_index(state, state.slot), transfer.fee)
``` ```
#### State root verification #### State root verification

View File

@ -1,6 +1,18 @@
# Beacon chain light client syncing # Beacon Chain Light Client Syncing
__NOTICE__: This document is a work-in-progress for researchers and implementers. One of the design goals of the eth2 beacon chain is light-client friendlines, both to allow low-resource clients (mobile phones, IoT, etc) to maintain access to the blockchain in a reasonably safe way, but also to facilitate the development of "bridges" between the eth2 beacon chain and other chains.
## Table of Contents
<!-- TOC -->
- [Beacon Chain Light Client Syncing](#beacon-chain-light-client-syncing)
- [Table of Contents](#table-of-contents)
- [Light client state](#light-client-state)
- [Updating the shuffled committee](#updating-the-shuffled-committee)
- [Computing the current committee](#computing-the-current-committee)
- [Verifying blocks](#verifying-blocks)
<!-- /TOC -->
One of the design goals of the eth2 beacon chain is light-client friendlines, both to allow low-resource clients (mobile phones, IoT, etc) to maintain access to the blockchain in a reasonably safe way, but also to facilitate the development of "bridges" between the eth2 beacon chain and other chains.
### Preliminaries ### Preliminaries
@ -40,8 +52,8 @@ def get_later_start_epoch(slot: Slot) -> int:
return slot - slot % PERSISTENT_COMMITTEE_PERIOD - PERSISTENT_COMMITTEE_PERIOD return slot - slot % PERSISTENT_COMMITTEE_PERIOD - PERSISTENT_COMMITTEE_PERIOD
def get_earlier_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData: def get_earlier_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData:
period_start = get_earlier_start_epoch(header.slot) period_start = get_earlier_start_epoch(block.slot)
validator_count = len(get_active_validator_indices(state, period_start)) validator_count = len(get_active_validator_indices(block.state, period_start))
committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1 committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1
indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count) indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count)
return PeriodData( return PeriodData(
@ -51,8 +63,8 @@ def get_earlier_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> Peri
) )
def get_later_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData: def get_later_period_data(block: ExtendedBeaconBlock, shard_id: Shard) -> PeriodData:
period_start = get_later_start_epoch(header.slot) period_start = get_later_start_epoch(block.slot)
validator_count = len(get_active_validator_indices(state, period_start)) validator_count = len(get_active_validator_indices(block.state, period_start))
committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1 committee_count = validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE) + 1
indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count) indices = get_shuffled_committee(block.state, shard_id, period_start, 0, committee_count)
return PeriodData( return PeriodData(

View File

@ -5,6 +5,7 @@ import build.phase0.spec as spec
from build.phase0.spec import ( from build.phase0.spec import (
Deposit, Deposit,
get_balance,
process_deposit, process_deposit,
) )
from tests.phase0.helpers import ( from tests.phase0.helpers import (
@ -38,8 +39,9 @@ def test_success(state, deposit_data_leaves, pubkeys, privkeys):
process_deposit(post_state, deposit) process_deposit(post_state, deposit)
assert len(post_state.validator_registry) == len(state.validator_registry) + 1 assert len(post_state.validator_registry) == len(state.validator_registry) + 1
assert len(post_state.validator_balances) == len(state.validator_balances) + 1 assert len(post_state.balances) == len(state.balances) + 1
assert post_state.validator_registry[index].pubkey == pubkeys[index] assert post_state.validator_registry[index].pubkey == pubkeys[index]
assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
return pre_state, deposit, post_state return pre_state, deposit, post_state
@ -62,16 +64,16 @@ def test_success_top_up(state, deposit_data_leaves, pubkeys, privkeys):
pre_state.latest_eth1_data.deposit_root = root pre_state.latest_eth1_data.deposit_root = root
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves) pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
pre_balance = pre_state.validator_balances[validator_index] pre_balance = get_balance(pre_state, validator_index)
post_state = deepcopy(pre_state) post_state = deepcopy(pre_state)
process_deposit(post_state, deposit) process_deposit(post_state, deposit)
assert len(post_state.validator_registry) == len(state.validator_registry) assert len(post_state.validator_registry) == len(state.validator_registry)
assert len(post_state.validator_balances) == len(state.validator_balances) assert len(post_state.balances) == len(state.balances)
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
assert post_state.validator_balances[validator_index] == pre_balance + amount assert get_balance(post_state, validator_index) == pre_balance + amount
return pre_state, deposit, post_state return pre_state, deposit, post_state

View File

@ -0,0 +1,97 @@
from copy import deepcopy
import pytest
import build.phase0.spec as spec
from build.phase0.spec import (
get_balance,
get_current_epoch,
process_proposer_slashing,
)
from tests.phase0.helpers import (
get_valid_proposer_slashing,
)
# mark entire file as 'header'
pytestmark = pytest.mark.proposer_slashings
def run_proposer_slashing_processing(state, proposer_slashing, valid=True):
"""
Run ``process_proposer_slashing`` returning the pre and post state.
If ``valid == False``, run expecting ``AssertionError``
"""
post_state = deepcopy(state)
if not valid:
with pytest.raises(AssertionError):
process_proposer_slashing(post_state, proposer_slashing)
return state, None
process_proposer_slashing(post_state, proposer_slashing)
slashed_validator = post_state.validator_registry[proposer_slashing.proposer_index]
assert not slashed_validator.initiated_exit
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
# lost whistleblower reward
assert (
get_balance(post_state, proposer_slashing.proposer_index) <
get_balance(state, proposer_slashing.proposer_index)
)
return state, post_state
def test_success(state):
proposer_slashing = get_valid_proposer_slashing(state)
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing)
return pre_state, proposer_slashing, post_state
def test_epochs_are_different(state):
proposer_slashing = get_valid_proposer_slashing(state)
# set slots to be in different epochs
proposer_slashing.header_2.slot += spec.SLOTS_PER_EPOCH
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
return pre_state, proposer_slashing, post_state
def test_headers_are_same(state):
proposer_slashing = get_valid_proposer_slashing(state)
# set headers to be the same
proposer_slashing.header_2 = proposer_slashing.header_1
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
return pre_state, proposer_slashing, post_state
def test_proposer_is_slashed(state):
proposer_slashing = get_valid_proposer_slashing(state)
# set proposer to slashed
state.validator_registry[proposer_slashing.proposer_index].slashed = True
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
return pre_state, proposer_slashing, post_state
def test_proposer_is_withdrawn(state):
proposer_slashing = get_valid_proposer_slashing(state)
# set proposer withdrawable_epoch in past
current_epoch = get_current_epoch(state)
proposer_index = proposer_slashing.proposer_index
state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1
pre_state, post_state = run_proposer_slashing_processing(state, proposer_slashing, False)
return pre_state, proposer_slashing, post_state

View File

@ -7,14 +7,18 @@ from build.phase0.utils.minimal_ssz import signed_root
from build.phase0.spec import ( from build.phase0.spec import (
# constants # constants
EMPTY_SIGNATURE, EMPTY_SIGNATURE,
ZERO_HASH,
# SSZ # SSZ
AttestationData, AttestationData,
BeaconBlockHeader,
Deposit, Deposit,
DepositInput, DepositInput,
DepositData, DepositData,
Eth1Data, Eth1Data,
ProposerSlashing,
VoluntaryExit, VoluntaryExit,
# functions # functions
get_active_validator_indices,
get_block_root, get_block_root,
get_current_epoch, get_current_epoch,
get_domain, get_domain,
@ -199,3 +203,43 @@ def build_deposit(state,
) )
return deposit, root, deposit_data_leaves return deposit, root, deposit_data_leaves
def get_valid_proposer_slashing(state):
current_epoch = get_current_epoch(state)
validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[-1]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
slot = state.slot
header_1 = BeaconBlockHeader(
slot=slot,
previous_block_root=ZERO_HASH,
state_root=ZERO_HASH,
block_body_root=ZERO_HASH,
signature=EMPTY_SIGNATURE,
)
header_2 = deepcopy(header_1)
header_2.previous_block_root = b'\x02' * 32
header_2.slot = slot + 1
domain = get_domain(
fork=state.fork,
epoch=get_current_epoch(state),
domain_type=spec.DOMAIN_BEACON_BLOCK,
)
header_1.signature = bls.sign(
message_hash=signed_root(header_1),
privkey=privkey,
domain=domain,
)
header_2.signature = bls.sign(
message_hash=signed_root(header_2),
privkey=privkey,
domain=domain,
)
return ProposerSlashing(
proposer_index=validator_index,
header_1=header_1,
header_2=header_2,
)

View File

@ -21,6 +21,7 @@ from build.phase0.spec import (
# functions # functions
get_active_validator_indices, get_active_validator_indices,
get_attestation_participants, get_attestation_participants,
get_balance,
get_block_root, get_block_root,
get_crosslink_committees_at_slot, get_crosslink_committees_at_slot,
get_current_epoch, get_current_epoch,
@ -28,6 +29,7 @@ from build.phase0.spec import (
get_state_root, get_state_root,
advance_slot, advance_slot,
cache_state, cache_state,
set_balance,
verify_merkle_branch, verify_merkle_branch,
hash, hash,
) )
@ -44,6 +46,7 @@ from tests.phase0.helpers import (
build_deposit_data, build_deposit_data,
build_empty_block_for_next_slot, build_empty_block_for_next_slot,
force_registry_change_at_next_epoch, force_registry_change_at_next_epoch,
get_valid_proposer_slashing,
) )
@ -115,42 +118,8 @@ def test_empty_epoch_transition_not_finalizing(state):
def test_proposer_slashing(state, pubkeys, privkeys): def test_proposer_slashing(state, pubkeys, privkeys):
test_state = deepcopy(state) test_state = deepcopy(state)
current_epoch = get_current_epoch(test_state) proposer_slashing = get_valid_proposer_slashing(state)
validator_index = get_active_validator_indices(test_state.validator_registry, current_epoch)[-1] validator_index = proposer_slashing.proposer_index
privkey = privkeys[validator_index]
slot = spec.GENESIS_SLOT
header_1 = BeaconBlockHeader(
slot=slot,
previous_block_root=ZERO_HASH,
state_root=ZERO_HASH,
block_body_root=ZERO_HASH,
signature=EMPTY_SIGNATURE,
)
header_2 = deepcopy(header_1)
header_2.previous_block_root = b'\x02' * 32
header_2.slot = slot + 1
domain = get_domain(
fork=test_state.fork,
epoch=get_current_epoch(test_state),
domain_type=spec.DOMAIN_BEACON_BLOCK,
)
header_1.signature = bls.sign(
message_hash=signed_root(header_1),
privkey=privkey,
domain=domain,
)
header_2.signature = bls.sign(
message_hash=signed_root(header_2),
privkey=privkey,
domain=domain,
)
proposer_slashing = ProposerSlashing(
proposer_index=validator_index,
header_1=header_1,
header_2=header_2,
)
# #
# Add to state via block transition # Add to state via block transition
@ -168,7 +137,7 @@ def test_proposer_slashing(state, pubkeys, privkeys):
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
# lost whistleblower reward # lost whistleblower reward
assert test_state.validator_balances[validator_index] < state.validator_balances[validator_index] assert get_balance(test_state, validator_index) < get_balance(state, validator_index)
return state, [block], test_state return state, [block], test_state
@ -203,7 +172,8 @@ def test_deposit_in_block(state, deposit_data_leaves, pubkeys, privkeys):
state_transition(post_state, block) state_transition(post_state, block)
assert len(post_state.validator_registry) == len(state.validator_registry) + 1 assert len(post_state.validator_registry) == len(state.validator_registry) + 1
assert len(post_state.validator_balances) == len(state.validator_balances) + 1 assert len(post_state.balances) == len(state.balances) + 1
assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT
assert post_state.validator_registry[index].pubkey == pubkeys[index] assert post_state.validator_registry[index].pubkey == pubkeys[index]
return pre_state, [block], post_state return pre_state, [block], post_state
@ -238,12 +208,12 @@ def test_deposit_top_up(state, pubkeys, privkeys, deposit_data_leaves):
block = build_empty_block_for_next_slot(pre_state) block = build_empty_block_for_next_slot(pre_state)
block.body.deposits.append(deposit) block.body.deposits.append(deposit)
pre_balance = pre_state.validator_balances[validator_index] pre_balance = get_balance(pre_state, validator_index)
post_state = deepcopy(pre_state) post_state = deepcopy(pre_state)
state_transition(post_state, block) state_transition(post_state, block)
assert len(post_state.validator_registry) == len(pre_state.validator_registry) assert len(post_state.validator_registry) == len(pre_state.validator_registry)
assert len(post_state.validator_balances) == len(pre_state.validator_balances) assert len(post_state.balances) == len(pre_state.balances)
assert post_state.validator_balances[validator_index] == pre_balance + amount assert get_balance(post_state, validator_index) == pre_balance + amount
return pre_state, [block], post_state return pre_state, [block], post_state
@ -412,8 +382,8 @@ def test_transfer(state, pubkeys, privkeys):
recipient_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] recipient_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
transfer_pubkey = pubkeys[-1] transfer_pubkey = pubkeys[-1]
transfer_privkey = privkeys[-1] transfer_privkey = privkeys[-1]
amount = pre_state.validator_balances[sender_index] amount = get_balance(pre_state, sender_index)
pre_transfer_recipient_balance = pre_state.validator_balances[recipient_index] pre_transfer_recipient_balance = get_balance(pre_state, recipient_index)
transfer = Transfer( transfer = Transfer(
sender=sender_index, sender=sender_index,
recipient=recipient_index, recipient=recipient_index,
@ -448,8 +418,8 @@ def test_transfer(state, pubkeys, privkeys):
block.body.transfers.append(transfer) block.body.transfers.append(transfer)
state_transition(post_state, block) state_transition(post_state, block)
sender_balance = post_state.validator_balances[sender_index] sender_balance = get_balance(post_state, sender_index)
recipient_balance = post_state.validator_balances[recipient_index] recipient_balance = get_balance(post_state, recipient_index)
assert sender_balance == 0 assert sender_balance == 0
assert recipient_balance == pre_transfer_recipient_balance + amount assert recipient_balance == pre_transfer_recipient_balance + amount
@ -465,7 +435,7 @@ def test_ejection(state):
assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
# set validator balance to below ejection threshold # set validator balance to below ejection threshold
pre_state.validator_balances[validator_index] = spec.EJECTION_BALANCE - 1 set_balance(pre_state, validator_index, spec.EJECTION_BALANCE - 1)
post_state = deepcopy(pre_state) post_state = deepcopy(pre_state)
# #