Merge pull request #1626 from ethereum/proposer-index

add proposer index to BeaconBlock
This commit is contained in:
Danny Ryan 2020-03-10 15:10:07 -06:00 committed by GitHub
commit e2918c6364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 24 deletions

View File

@ -382,6 +382,7 @@ class DepositData(Container):
```python ```python
class BeaconBlockHeader(Container): class BeaconBlockHeader(Container):
slot: Slot slot: Slot
proposer_index: ValidatorIndex
parent_root: Root parent_root: Root
state_root: Root state_root: Root
body_root: Root body_root: Root
@ -401,7 +402,6 @@ class SigningRoot(Container):
```python ```python
class ProposerSlashing(Container): class ProposerSlashing(Container):
proposer_index: ValidatorIndex
signed_header_1: SignedBeaconBlockHeader signed_header_1: SignedBeaconBlockHeader
signed_header_2: SignedBeaconBlockHeader signed_header_2: SignedBeaconBlockHeader
``` ```
@ -461,6 +461,7 @@ class BeaconBlockBody(Container):
```python ```python
class BeaconBlock(Container): class BeaconBlock(Container):
slot: Slot slot: Slot
proposer_index: ValidatorIndex
parent_root: Root parent_root: Root
state_root: Root state_root: Root
body: BeaconBlockBody body: BeaconBlockBody
@ -1175,7 +1176,7 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida
```python ```python
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: 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)) signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
``` ```
@ -1453,18 +1454,21 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
def process_block_header(state: BeaconState, block: BeaconBlock) -> None: def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
# Verify that the slots match # Verify that the slots match
assert block.slot == state.slot 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 # Verify that the parent matches
assert block.parent_root == hash_tree_root(state.latest_block_header) assert block.parent_root == hash_tree_root(state.latest_block_header)
# Cache current block as the new latest block # Cache current block as the new latest block
state.latest_block_header = BeaconBlockHeader( state.latest_block_header = BeaconBlockHeader(
slot=block.slot, slot=block.slot,
proposer_index=block.proposer_index,
parent_root=block.parent_root, parent_root=block.parent_root,
state_root=Bytes32(), # Overwritten in the next process_slot call state_root=Bytes32(), # Overwritten in the next process_slot call
body_root=hash_tree_root(block.body), body_root=hash_tree_root(block.body),
) )
# Verify proposer is not slashed # Verify proposer is not slashed
proposer = state.validators[get_beacon_proposer_index(state)] proposer = state.validators[block.proposer_index]
assert not proposer.slashed assert not proposer.slashed
``` ```
@ -1513,12 +1517,17 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
```python ```python
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: 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 # 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 # 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 # 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)) assert is_slashable_validator(proposer, get_current_epoch(state))
# Verify signatures # Verify signatures
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
@ -1526,7 +1535,7 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla
signing_root = compute_signing_root(signed_header.message, domain) signing_root = compute_signing_root(signed_header.message, domain)
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) 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 ##### Attester slashings

View File

@ -27,6 +27,7 @@
- [Block proposal](#block-proposal) - [Block proposal](#block-proposal)
- [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock) - [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock)
- [Slot](#slot) - [Slot](#slot)
- [Proposer index](#proposer-index)
- [Parent root](#parent-root) - [Parent root](#parent-root)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Randao reveal](#randao-reveal) - [Randao reveal](#randao-reveal)
@ -183,8 +184,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. 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 ```python
def is_proposer(state: BeaconState, def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool:
validator_index: ValidatorIndex) -> bool:
return get_beacon_proposer_index(state) == validator_index return get_beacon_proposer_index(state) == validator_index
``` ```
@ -224,11 +224,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. *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 ##### Parent root
Set `block.parent_root = hash_tree_root(parent)`. Set `block.parent_root = hash_tree_root(parent)`.
#### Constructing the `BeaconBlockBody` #### Constructing the `BeaconBlockBody`
##### Randao reveal ##### Randao reveal

View File

@ -221,6 +221,7 @@ Note that the `body` has a new `BeaconBlockBody` definition.
```python ```python
class BeaconBlock(Container): class BeaconBlock(Container):
slot: Slot slot: Slot
proposer_index: ValidatorIndex
parent_root: Root parent_root: Root
state_root: Root state_root: Root
body: BeaconBlockBody body: BeaconBlockBody

View File

@ -65,10 +65,22 @@ def apply_empty_block(spec, state):
def build_empty_block(spec, state, slot=None): 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: if slot is None:
slot = state.slot 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 = spec.BeaconBlock()
empty_block.slot = slot 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 empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
previous_block_header = state.latest_block_header.copy() previous_block_header = state.latest_block_header.copy()
if previous_block_header.state_root == spec.Root(): if previous_block_header.state_root == spec.Root():

View File

@ -10,6 +10,7 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
header_1 = spec.BeaconBlockHeader( header_1 = spec.BeaconBlockHeader(
slot=slot, slot=slot,
proposer_index=validator_index,
parent_root=b'\x33' * 32, parent_root=b'\x33' * 32,
state_root=b'\x44' * 32, state_root=b'\x44' * 32,
body_root=b'\x55' * 32, body_root=b'\x55' * 32,
@ -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) signed_header_2 = spec.SignedBeaconBlockHeader(message=header_2)
return spec.ProposerSlashing( return spec.ProposerSlashing(
proposer_index=validator_index,
signed_header_1=signed_header_1, signed_header_1=signed_header_1,
signed_header_2=signed_header_2, signed_header_2=signed_header_2,
) )

View File

@ -47,6 +47,18 @@ def test_invalid_slot_block_header(spec, state):
yield from run_block_header_processing(spec, state, block, valid=False) 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 @with_all_phases
@spec_state_test @spec_state_test
def test_invalid_parent_root(spec, state): def test_invalid_parent_root(spec, state):

View File

@ -22,22 +22,20 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
yield 'post', None yield 'post', None
return 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) spec.process_proposer_slashing(state, proposer_slashing)
yield 'post', state yield 'post', state
# check if slashed # check if slashed
slashed_validator = state.validators[proposer_slashing.proposer_index] slashed_validator = state.validators[proposer_index]
assert slashed_validator.slashed assert slashed_validator.slashed
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 ( assert get_balance(state, proposer_index) < pre_proposer_balance
get_balance(state, proposer_slashing.proposer_index) <
pre_proposer_balance
)
@with_all_phases @with_all_phases
@ -77,7 +75,24 @@ def test_invalid_sig_1_and_2(spec, state):
def test_invalid_proposer_index(spec, state): def test_invalid_proposer_index(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# Index just too high (by 1) # 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) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -89,9 +104,9 @@ def test_epochs_are_different(spec, state):
# set slots to be in different epochs # set slots to be in different epochs
header_2 = proposer_slashing.signed_header_2.message header_2 = proposer_slashing.signed_header_2.message
proposer_index = header_2.proposer_index
header_2.slot += spec.SLOTS_PER_EPOCH header_2.slot += spec.SLOTS_PER_EPOCH
proposer_slashing.signed_header_2 = sign_block_header( proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[proposer_index])
spec, state, header_2, privkeys[proposer_slashing.proposer_index])
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -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) proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to be not active yet # 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) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -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) proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to slashed # 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) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -138,7 +155,7 @@ def test_proposer_is_withdrawn(spec, state):
state.slot += spec.SLOTS_PER_EPOCH state.slot += spec.SLOTS_PER_EPOCH
# set proposer withdrawable_epoch in past # set proposer withdrawable_epoch in past
current_epoch = spec.get_current_epoch(state) 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 state.validators[proposer_index].withdrawable_epoch = current_epoch - 1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

View File

@ -119,6 +119,49 @@ def test_invalid_block_sig(spec, state):
yield 'post', None yield 'post', None
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_proposer_index_sig_from_expected_proposer(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
expect_proposer_index = block.proposer_index
# Set invalid proposer index but correct signature wrt expected proposer
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
invalid_signed_block = sign_block(spec, state, block, expect_proposer_index)
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
yield 'blocks', [invalid_signed_block]
yield 'post', None
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_proposer_index_sig_from_proposer_index(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
# Set invalid proposer index but correct signature wrt proposer_index
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
invalid_signed_block = sign_block(spec, state, block, block.proposer_index)
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
yield 'blocks', [invalid_signed_block]
yield 'post', None
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
def test_skipped_slots(spec, state): def test_skipped_slots(spec, state):
@ -187,7 +230,7 @@ def test_proposer_slashing(spec, state):
# copy for later balance lookups. # copy for later balance lookups.
pre_state = deepcopy(state) pre_state = deepcopy(state)
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) 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 assert not state.validators[validator_index].slashed