Merge pull request #1626 from ethereum/proposer-index
add proposer index to BeaconBlock
This commit is contained in:
commit
e2918c6364
|
@ -382,6 +382,7 @@ class DepositData(Container):
|
|||
```python
|
||||
class BeaconBlockHeader(Container):
|
||||
slot: Slot
|
||||
proposer_index: ValidatorIndex
|
||||
parent_root: Root
|
||||
state_root: Root
|
||||
body_root: Root
|
||||
|
@ -401,7 +402,6 @@ class SigningRoot(Container):
|
|||
|
||||
```python
|
||||
class ProposerSlashing(Container):
|
||||
proposer_index: ValidatorIndex
|
||||
signed_header_1: SignedBeaconBlockHeader
|
||||
signed_header_2: SignedBeaconBlockHeader
|
||||
```
|
||||
|
@ -461,6 +461,7 @@ class BeaconBlockBody(Container):
|
|||
```python
|
||||
class BeaconBlock(Container):
|
||||
slot: Slot
|
||||
proposer_index: ValidatorIndex
|
||||
parent_root: Root
|
||||
state_root: Root
|
||||
body: BeaconBlockBody
|
||||
|
@ -1175,7 +1176,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)
|
||||
```
|
||||
|
@ -1453,18 +1454,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
|
||||
```
|
||||
|
||||
|
@ -1513,12 +1517,17 @@ 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):
|
||||
|
@ -1526,7 +1535,7 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla
|
|||
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
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
- [Block proposal](#block-proposal)
|
||||
- [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock)
|
||||
- [Slot](#slot)
|
||||
- [Proposer index](#proposer-index)
|
||||
- [Parent root](#parent-root)
|
||||
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
||||
- [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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
|
@ -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.
|
||||
|
||||
##### 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -119,6 +119,49 @@ def test_invalid_block_sig(spec, state):
|
|||
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
|
||||
@spec_state_test
|
||||
def test_skipped_slots(spec, state):
|
||||
|
@ -187,7 +230,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
|
||||
|
||||
|
|
Loading…
Reference in New Issue