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
|
```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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue