-
Notifications
You must be signed in to change notification settings - Fork 997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP-6988: Slashed validator cannot be elected as a block proposer #3371
base: dev
Are you sure you want to change the base?
Conversation
Previously the number of subnets is equal to MAX_BLOBS_PER_BLOCK which specifies the number of blobs per block. This commit now makes the number of subnets equal to BLOB_SIDECAR_SUBNET_COUNT instead. The advantage of doing this is that we can change MAX_BLOBS_PER_BLOCK without worrying about the p2p network structure and the number of subnets.
This fix is based on Capella with the intuition that Capella-based features comprise Deneb. I don't mind to re-base it to Deneb if that is required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good! 👍
Some test case ideas:
- [sanity/block] a proposer slashes themselves in the block
- [sanity/block] slash most validators and try to find the case where
compute_proposer_index
hitseffective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte
andslashed
.
@with_phases([PHASE0, ALTAIR, BELLATRIX, CAPELLA]) | ||
@spec_state_test | ||
def test_slashed_validator_elected_for_proposal(spec, state): | ||
proposer_index = spec.get_beacon_proposer_index(state) | ||
state.validators[proposer_index].slashed = True | ||
|
||
assert spec.get_beacon_proposer_index(state) == proposer_index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test case actually works for DENEB and EIP-6988 now because the default state
is genesis and state.latest_block_header.slot
is 0
.
We should skip one slot first.
@with_phases([PHASE0, ALTAIR, BELLATRIX, CAPELLA]) | |
@spec_state_test | |
def test_slashed_validator_elected_for_proposal(spec, state): | |
proposer_index = spec.get_beacon_proposer_index(state) | |
state.validators[proposer_index].slashed = True | |
assert spec.get_beacon_proposer_index(state) == proposer_index | |
@with_phases([PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB]) | |
@spec_state_test | |
def test_slashed_validator_elected_for_proposal(spec, state): | |
next_slot(spec, state) | |
proposer_index = spec.get_beacon_proposer_index(state) | |
state.validators[proposer_index].slashed = True | |
assert spec.get_beacon_proposer_index(state) == proposer_index |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I've fixed the test, didn't add DENEB
as a target because this change should eventually be applied to Deneb and this test will fail then
I think that this is already checked by
Good idea, will add this test! |
I have added the following test cases to
|
effective_balance = state.validators[candidate_index].effective_balance | ||
# [Modified in EIP6988] | ||
slashed = state.validators[candidate_index].slashed | ||
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte and not slashed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually has a strange side effect --- the shuffling for an epoch is no longer fixed at the start of the epoch as a slashing could remove a validator from a previously selected slot and replace them with someone else.
This isn't a deal breaker but likely breaks engineering invariants. We need to discuss with engineers and make sure to have specific tests for this (i.e. predict the epoch's shuffling, then move a coupel of blocks forward with a slashing for the next slot proposer, then show that the proposer changes)
EDIT: this is directionally the test -- test_slashed_validator_is_not_proposing_block
-- but I'd want it to be a multi-block test to ensure some epoch proposer shuffling cache doesn't become an issue. That is, have a block that does the transition into the epoch, have a block that adds the slashing, then have the block of the next slot that has the changed proposer
This might also likely have interactions with VC proposer duties API which might expect epoch stability. It might actulaly ripple into a bunch of small things like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the shuffling for an epoch is no longer fixed at the start of the epoch as a slashing could remove a validator from a previously selected slot and replace them with someone else
Very good point! 👍
Strictly, the shuffling for an epoch is not fixed before this change was applied, and in the status quo slashing can become a reason of a proposer switch because of its economics component. This change moves this effect from probabilistic to deterministic area and because of that it should be taken with care.
EDIT: the shuffling for an epoch is actually fixed because effective_balance
is updated only during epoch processing, thus, balance decrease because of slashing takes into effect only at the end of the epoch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but I'd want it to be a multi-block test to ensure some epoch proposer shuffling cache doesn't become an issue
I've added test_proposer_shuffling_changes_within_epoch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly, the shuffling for an epoch is not fixed before this change was applied
but it is due to effective_balance
only being updated at epoch boundaries! let's discuss on call today
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could compute the proposers in the epoch transition and persist the result in the state. Citing the EIP motivation
The impact of the proposed fix in the case of a single slashing on Ethereum Mainnet is negligible but it becomes significant in the case of correlated slashings. For instance, a correlated slashing of
1/10th
of a validator set can lead to1/10th
of missed proposals in a number of epochs after the slashing.
This PR reduces the impact to 0 slots of potential missed proposes. With my suggestion it would be reduced to max of 32 slots.
At least in Lodestar and probably in all clients, breaking dependant_root patterns will have a significant impact on the validator client's design.
EDIT: significant in time consuming but not a breaking deal. I can try to quantify it when there's bandwidth. But definitely the EIP will require more dev work than I originally imagined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've attempted to spec out proposer shuffling in state #3405. It doesn't seem like a big change after all.
This modification requires beacon state initialization to be finished with a call to update_proposer_shuffling
like it is done in initialize_beacon_state_from_eth1
and upgrade_to_6988
. I am wondering about other implication that this requirement can have.
Why not simply drop the block (in block validation for example)? This avoids the epoch stability issues.. |
We already do drop the block if the proposer is slashed -- https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#block-header Remembering when a validator is slashed or doing something like @dapplion proposes above (#3371 (comment)) could meet the needs without breaking the pre-epoch shuffling stablility invariant |
Breaking the shuffling invariant may break some cache designs (or make them more expensive) around proposer shufflings relevant to process incoming beacon_block gossip messages. consensus-specs/specs/phase0/p2p-interface.md Lines 345 to 346 in 834f6f7
It does for Lodestar, and at least also Lighthouse, which has a proposer shuffling cache indexed by dependant_root https://github.com/sigp/lighthouse/blob/c547a11b0da48db6fdd03bca2c6ce2448bbcc3a9/beacon_node/beacon_chain/src/block_verification.rs#L780-L797 let proposer_opt = chain
.beacon_proposer_cache
.lock()
.get_slot::<T::EthSpec>(proposer_shuffling_decision_block, block.slot()); |
Noting that this EIP is future incompatible with Whisk SSLE. Whisk selects a pool of candidates 256 epochs in advance to the first elected candidate proposing a block. If there's a mass slashing event during a shuffling phase, it is not possible to know which elected candidate trackers correspond to slashed proposers. Only when said proposer publishes its block the network can assert that the slot is assigned to a slashed proposer and thus reject the block. |
Substitutes #3175 by moving the proposed change to a
_features/
folder under the specified EIP number (ethereum/EIPs#6988).This PR adds new tests for earlier phases like Phase0 and Altair, and makes tiny modification (not affecting test vectors) to already existing tests to work properly around the proposed change. It does also add a couple of unittests to the validator suite.
What's not done:
eip6988
isn't enabled in fork tests as it doesn't considered as a standalone fork and assumed to be a part of Deneb