Skip to content

Commit

Permalink
add proposer index and add/modify tests
Browse files Browse the repository at this point in the history
  • Loading branch information
djrtwo committed Feb 18, 2020
1 parent e112679 commit fb98f52
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 24 deletions.
23 changes: 16 additions & 7 deletions specs/phase0/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ class DepositData(Container):
```python
class BeaconBlockHeader(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body_root: Root
Expand All @@ -396,7 +397,6 @@ class SigningRoot(Container):

```python
class ProposerSlashing(Container):
proposer_index: ValidatorIndex
signed_header_1: SignedBeaconBlockHeader
signed_header_2: SignedBeaconBlockHeader
```
Expand Down Expand Up @@ -456,6 +456,7 @@ class BeaconBlockBody(Container):
```python
class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
Expand Down Expand Up @@ -1163,7 +1164,7 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida

```python
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool:
proposer = state.validators[get_beacon_proposer_index(state)]
proposer = state.validators[signed_block.message.proposer_index]
signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
```
Expand Down Expand Up @@ -1434,18 +1435,21 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
# Verify that the slots match
assert block.slot == state.slot
# Verify that proposer index is the correct index
assert block.proposer_index == get_beacon_proposer_index(state)
# Verify that the parent matches
assert block.parent_root == hash_tree_root(state.latest_block_header)
# Cache current block as the new latest block
state.latest_block_header = BeaconBlockHeader(
slot=block.slot,
proposer_index=block.proposer_index,
parent_root=block.parent_root,
state_root=Bytes32(), # Overwritten in the next process_slot call
body_root=hash_tree_root(block.body),
)

# Verify proposer is not slashed
proposer = state.validators[get_beacon_proposer_index(state)]
proposer = state.validators[block.proposer_index]
assert not proposer.slashed
```

Expand Down Expand Up @@ -1494,20 +1498,25 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:

```python
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
header_1 = proposer_slashing.signed_header_1.message
header_2 = proposer_slashing.signed_header_2.message

# Verify header slots match
assert proposer_slashing.signed_header_1.message.slot == proposer_slashing.signed_header_2.message.slot
assert header_1.slot == header_2.slot
# Verify header proposer indices match
assert header_1.proposer_index == header_2.proposer_index
# Verify the headers are different
assert proposer_slashing.signed_header_1 != proposer_slashing.signed_header_2
assert header_1 != header_2
# Verify the proposer is slashable
proposer = state.validators[proposer_slashing.proposer_index]
proposer = state.validators[header_1.proposer_index]
assert is_slashable_validator(proposer, get_current_epoch(state))
# Verify signatures
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot))
signing_root = compute_signing_root(signed_header.message, domain)
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature)

slash_validator(state, proposer_slashing.proposer_index)
slash_validator(state, header_1.proposer_index)
```

##### Attester slashings
Expand Down
8 changes: 5 additions & 3 deletions specs/phase0/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@ def get_committee_assignment(state: BeaconState,
A validator can use the following function to see if they are supposed to propose during a 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:
def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool:
return get_beacon_proposer_index(state) == validator_index
```

Expand Down Expand Up @@ -224,11 +223,14 @@ Set `block.slot = slot` where `slot` is the current slot at which the validator

*Note*: There might be "skipped" slots between the `parent` and `block`. These skipped slots are processed in the state transition function without per-block processing.

##### Proposer index

Set `block.proposer_index = validator_index` where `validator_index` is the validator chosen to propose at this slot. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the block.

##### Parent root

Set `block.parent_root = hash_tree_root(parent)`.


#### Constructing the `BeaconBlockBody`

##### Randao reveal
Expand Down
1 change: 1 addition & 0 deletions specs/phase1/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Note that the `body` has a new `BeaconBlockBody` definition.
```python
class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
Expand Down
12 changes: 12 additions & 0 deletions tests/core/pyspec/eth2spec/test/helpers/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,22 @@ def apply_empty_block(spec, state):


def build_empty_block(spec, state, slot=None):
"""
Build empty block for ``slot``, built upon the latest block header seen by ``state``.
Slot must be greater than or equal to the current slot in ``state``.
"""
if slot is None:
slot = state.slot
if slot < state.slot:
raise Exception("build_empty_block cannot build blocks for past slots")
if slot > state.slot:
# transition forward in copied state to grab relevant data from state
state = state.copy()
spec.process_slots(state, slot)

empty_block = spec.BeaconBlock()
empty_block.slot = slot
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
previous_block_header = state.latest_block_header.copy()
if previous_block_header.state_root == spec.Root():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):

header_1 = spec.BeaconBlockHeader(
slot=slot,
proposer_index=validator_index,
parent_root=b'\x33' * 32,
state_root=b'\x44' * 32,
body_root=b'\x55' * 32,
Expand All @@ -27,7 +28,6 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
signed_header_2 = spec.SignedBeaconBlockHeader(message=header_2)

return spec.ProposerSlashing(
proposer_index=validator_index,
signed_header_1=signed_header_1,
signed_header_2=signed_header_2,
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ def test_invalid_slot_block_header(spec, state):
yield from run_block_header_processing(spec, state, block, valid=False)


@with_all_phases
@spec_state_test
def test_invalid_proposer_index(spec, state):
block = build_empty_block_for_next_slot(spec, state)

active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != block.proposer_index]
block.proposer_index = active_indices[0] # invalid proposer index

yield from run_block_header_processing(spec, state, block, valid=False)


@with_all_phases
@spec_state_test
def test_invalid_parent_root(spec, state):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,20 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
yield 'post', None
return

pre_proposer_balance = get_balance(state, proposer_slashing.proposer_index)
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
pre_proposer_balance = get_balance(state, proposer_index)

spec.process_proposer_slashing(state, proposer_slashing)
yield 'post', state

# check if slashed
slashed_validator = state.validators[proposer_slashing.proposer_index]
slashed_validator = state.validators[proposer_index]
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(state, proposer_slashing.proposer_index) <
pre_proposer_balance
)
assert get_balance(state, proposer_index) < pre_proposer_balance


@with_all_phases
Expand Down Expand Up @@ -77,7 +75,24 @@ def test_invalid_sig_1_and_2(spec, state):
def test_invalid_proposer_index(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# Index just too high (by 1)
proposer_slashing.proposer_index = len(state.validators)
proposer_slashing.signed_header_1.message.proposer_index = len(state.validators)
proposer_slashing.signed_header_2.message.proposer_index = len(state.validators)

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)


@with_all_phases
@spec_state_test
def test_invalid_different_proposer_indices(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set different index and sign
header_1 = proposer_slashing.signed_header_1.message
header_2 = proposer_slashing.signed_header_2.message
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != header_1.proposer_index]

header_2.proposer_index = active_indices[0]
proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[header_2.proposer_index])

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

Expand All @@ -89,9 +104,9 @@ def test_epochs_are_different(spec, state):

# set slots to be in different epochs
header_2 = proposer_slashing.signed_header_2.message
proposer_index = header_2.proposer_index
header_2.slot += spec.SLOTS_PER_EPOCH
proposer_slashing.signed_header_2 = sign_block_header(
spec, state, header_2, privkeys[proposer_slashing.proposer_index])
proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[proposer_index])

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

Expand All @@ -113,7 +128,8 @@ def test_proposer_is_not_activated(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)

# set proposer to be not active yet
state.validators[proposer_slashing.proposer_index].activation_epoch = spec.get_current_epoch(state) + 1
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].activation_epoch = spec.get_current_epoch(state) + 1

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

Expand All @@ -124,7 +140,8 @@ def test_proposer_is_slashed(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)

# set proposer to slashed
state.validators[proposer_slashing.proposer_index].slashed = True
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].slashed = True

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

Expand All @@ -138,7 +155,7 @@ def test_proposer_is_withdrawn(spec, state):
state.slot += spec.SLOTS_PER_EPOCH
# set proposer withdrawable_epoch in past
current_epoch = spec.get_current_epoch(state)
proposer_index = proposer_slashing.proposer_index
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].withdrawable_epoch = current_epoch - 1

yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
2 changes: 1 addition & 1 deletion tests/core/pyspec/eth2spec/test/sanity/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def test_proposer_slashing(spec, state):
# copy for later balance lookups.
pre_state = deepcopy(state)
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
validator_index = proposer_slashing.proposer_index
validator_index = proposer_slashing.signed_header_1.message.proposer_index

assert not state.validators[validator_index].slashed

Expand Down

0 comments on commit fb98f52

Please sign in to comment.