forked from ethereum/consensus-specs
-
Notifications
You must be signed in to change notification settings - Fork 0
In protocol CL triggerable consolidation #12
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
Closed
mkalinin
wants to merge
5
commits into
michaelneuder:maxeb-pyspec-min-viable
from
mkalinin:cl-consolidation
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
dbebc21
Implement CL triggered validator consolidation
mkalinin 06ad33c
Allow consolidation of vals with compounding creds
mkalinin ec50806
Apply suggestions from code review
mkalinin 2ed131a
Apply consolidation if source or target exited/slashed
mkalinin 5ef0141
Abort consolidation if the target has been consolidated
mkalinin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -236,7 +236,10 @@ def floorlog2(x: int) -> uint64: | |||||||
| MAX_BLS_TO_EXECUTION_CHANGES = 16 | ||||||||
| MAX_WITHDRAWALS_PER_PAYLOAD = uint64(16) | ||||||||
| MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP = 16384 | ||||||||
|
|
||||||||
| DOMAIN_CONSOLIDATION = DomainType('0x0B000000') | ||||||||
| MAX_CONSOLIDATIONS = 4 | ||||||||
| PENDING_CONSOLIDATIONS_LIMIT = uint64(1048576) # MAX_CONSOLIDATIONS * SLOTS_PER_EPOCH * 8192 | ||||||||
| UNSET_CONSOLIDATED_TO = ValidatorIndex(2**64 - 1) | ||||||||
|
|
||||||||
| class Configuration(NamedTuple): | ||||||||
| PRESET_BASE: str | ||||||||
|
|
@@ -321,6 +324,8 @@ class Validator(Container): | |||||||
| activation_epoch: Epoch | ||||||||
| exit_epoch: Epoch | ||||||||
| withdrawable_epoch: Epoch # When validator can withdraw funds | ||||||||
| # TODO: may compress into some other validator field | ||||||||
| consolidated_to: ValidatorIndex | ||||||||
|
|
||||||||
|
|
||||||||
| class AttestationData(Container): | ||||||||
|
|
@@ -426,6 +431,22 @@ class SignedVoluntaryExit(Container): | |||||||
| signature: BLSSignature | ||||||||
|
|
||||||||
|
|
||||||||
| class Consolidation(Container): | ||||||||
| source_index: ValidatorIndex | ||||||||
| target_index: ValidatorIndex | ||||||||
|
|
||||||||
|
|
||||||||
| class SignedConsolidation(Container): | ||||||||
| message: Consolidation | ||||||||
| signature: BLSSignature | ||||||||
|
|
||||||||
|
|
||||||||
| class PendingConsolidation(Container): | ||||||||
| source_index: ValidatorIndex | ||||||||
| target_index: ValidatorIndex | ||||||||
| epoch: Epoch | ||||||||
|
|
||||||||
|
|
||||||||
| class SignedBeaconBlockHeader(Container): | ||||||||
| message: BeaconBlockHeader | ||||||||
| signature: BLSSignature | ||||||||
|
|
@@ -636,6 +657,8 @@ class BeaconBlockBody(Container): | |||||||
| execution_payload: ExecutionPayload | ||||||||
| # Capella operations | ||||||||
| bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella] | ||||||||
| # MAXEB operations | ||||||||
| consolidations: List[SignedConsolidation, MAX_CONSOLIDATIONS] # [New in MAXEB] | ||||||||
|
|
||||||||
|
|
||||||||
| class BeaconBlock(Container): | ||||||||
|
|
@@ -707,6 +730,7 @@ class BeaconState(Container): | |||||||
| historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella] | ||||||||
| pending_balance_deposits: List[PendingBalanceDeposit] | ||||||||
| pending_partial_withdrawals: List[PartialWithdrawal] | ||||||||
| pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] | ||||||||
|
|
||||||||
|
|
||||||||
| @dataclass(eq=True, frozen=True) | ||||||||
|
|
@@ -1327,6 +1351,7 @@ def process_epoch(state: BeaconState) -> None: | |||||||
| process_slashings(state) | ||||||||
| process_eth1_data_reset(state) | ||||||||
| process_pending_balance_deposits(state) | ||||||||
| process_pending_consolidations(state) | ||||||||
| process_effective_balance_updates(state) | ||||||||
| process_slashings_reset(state) | ||||||||
| process_randao_mixes_reset(state) | ||||||||
|
|
@@ -1627,6 +1652,86 @@ def process_pending_balance_deposits(state: BeaconState) -> None: | |||||||
| state.pending_balance_deposits = state.pending_balance_deposits[next_pending_deposit_index:] | ||||||||
|
|
||||||||
|
|
||||||||
| def apply_pending_consolidation(state: BeaconState, pending_consolidation: PendingConsolidation) -> None: | ||||||||
| current_epoch = get_current_epoch(state) | ||||||||
|
|
||||||||
| # Resolve the target of consolidation | ||||||||
| target_validator = state.validators[pending_consolidation.target_index] | ||||||||
| source_validator = state.validators[pending_consolidation.source_index] | ||||||||
|
|
||||||||
| # Abort consolidation if the target has been consolidated | ||||||||
| if target_validator.consolidated_to != UNSET_CONSOLIDATED_TO: | ||||||||
| return | ||||||||
|
|
||||||||
| # Apply consolidation | ||||||||
| source_validator.consolidated_to = pending_consolidation.target_index | ||||||||
|
|
||||||||
| # Don't move the balance and slash the target if the source was slashed | ||||||||
| if is_slashed(source_validator): | ||||||||
| if is_slashed_proposer(source_validator): | ||||||||
| penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT | ||||||||
| decrease_balance(state, target_validator, penalty) | ||||||||
| initiate_validator_exit(state, pending_consolidation.target_index) | ||||||||
| target_validator.slashed = add_flag(target_validator.slashed, SLASHED_PROPOSER_FLAG_INDEX) | ||||||||
|
|
||||||||
| if is_slashed_attester(source_validator): | ||||||||
| if is_attester_slashable_validator(target_validator, current_epoch): | ||||||||
| slash_validator(state, pending_consolidation.target_index) | ||||||||
|
|
||||||||
| return | ||||||||
|
|
||||||||
| # Don't move the balance and slash the source if the target was slashed | ||||||||
| if is_slashed(target_validator): | ||||||||
| if is_slashed_proposer(target_validator): | ||||||||
| penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT | ||||||||
| decrease_balance(state, source_validator, penalty) | ||||||||
| initiate_validator_exit(state, pending_consolidation.source_index) | ||||||||
| source_validator.slashed = add_flag(source_validator.slashed, SLASHED_PROPOSER_FLAG_INDEX) | ||||||||
|
|
||||||||
| if is_slashed_attester(target_validator): | ||||||||
| if is_attester_slashable_validator(source_validator, current_epoch): | ||||||||
| slash_validator(state, pending_consolidation.source_index) | ||||||||
|
|
||||||||
| return | ||||||||
|
|
||||||||
| # Don't move the balance if the source withdrew | ||||||||
| if source_validator.withdrawable_epoch < current_epoch: | ||||||||
| return | ||||||||
|
fradamt marked this conversation as resolved.
|
||||||||
|
|
||||||||
| # Move active balance | ||||||||
| active_balance_ceil = MIN_ACTIVATION_BALANCE if has_eth1_withdrawal_credential(source_validator) else MAX_EFFECTIVE_BALANCE | ||||||||
| active_balance = min(state.balances[pending_consolidation.source_index], active_balance_ceil) | ||||||||
| state.balances[pending_consolidation.target_index] += active_balance | ||||||||
| # Excess balance above current active balance ceil will be withdrawn | ||||||||
| state.balances[pending_consolidation.source_index] = state.balances[pending_consolidation.source_index] - active_balance | ||||||||
|
|
||||||||
| # Update target exit and withdrawable epochs if the target exited | ||||||||
| if target_validator.exit_epoch != FAR_FUTURE_EPOCH: | ||||||||
| target_validator.exit_epoch = compute_exit_epoch_and_update_churn(state, active_balance) | ||||||||
| target_validator.withdrawable_epoch = Epoch(target_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) | ||||||||
|
|
||||||||
| # Balance is not exiting the active set, do not apply churn | ||||||||
| if source_validator.exit_epoch > current_epoch: | ||||||||
| source_validator.exit_epoch = current_epoch | ||||||||
|
Comment on lines
+1714
to
+1715
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||
|
|
||||||||
| # Reset withdrawable epoch when it is closer than expected | ||||||||
| withdrawable_epoch = Epoch(source_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) | ||||||||
| if source_validator.withdrawable_epoch < withdrawable_epoch: | ||||||||
| source_validator.withdrawable_epoch = withdrawable_epoch | ||||||||
|
|
||||||||
|
|
||||||||
| def process_pending_consolidations(state: BeaconState) -> None: | ||||||||
| next_pending_consolidation = 0 | ||||||||
| for pending_consolidation in state.pending_consolidations: | ||||||||
| if pending_consolidation.epoch >= state.finalized_checkpoint.epoch: | ||||||||
| break | ||||||||
|
|
||||||||
| apply_pending_consolidation(state, pending_consolidation) | ||||||||
| next_pending_consolidation += 1 | ||||||||
|
|
||||||||
| state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:] | ||||||||
|
|
||||||||
|
|
||||||||
| def process_effective_balance_updates(state: BeaconState) -> None: | ||||||||
| # Update effective balances with hysteresis | ||||||||
| for index, validator in enumerate(state.validators): | ||||||||
|
|
@@ -1735,6 +1840,7 @@ def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) - | |||||||
| for_ops(body.voluntary_exits, process_voluntary_exit) | ||||||||
| for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) # [New in Capella] | ||||||||
| for_ops(body.execution_payload.withdraw_request, process_execution_layer_withdraw_request) | ||||||||
| for_ops(body.consolidations, process_consolidation) | ||||||||
|
|
||||||||
|
|
||||||||
| def process_execution_layer_withdraw_request( | ||||||||
|
|
@@ -1784,11 +1890,13 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla | |||||||
| # Verify header slots match | ||||||||
| assert header_1.slot == header_2.slot | ||||||||
| # Verify header proposer indices match | ||||||||
| assert header_1.proposer_index == header_2.proposer_index | ||||||||
| header_1_proposer_index = resolve_consolidated_to(state, header_1.proposer_index) | ||||||||
| header_2_proposer_index = resolve_consolidated_to(state, header_2.proposer_index) | ||||||||
| assert header_1_proposer_index == header_2_proposer_index | ||||||||
| # Verify the headers are different | ||||||||
| assert header_1 != header_2 | ||||||||
| # Verify the proposer is slashable | ||||||||
| proposer = state.validators[header_1.proposer_index] | ||||||||
| proposer = state.validators[header_1_proposer_index] | ||||||||
| assert proposer.activation_epoch <= get_current_epoch(state) and not is_slashed_proposer(proposer) | ||||||||
| # Verify signatures | ||||||||
| for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): | ||||||||
|
|
@@ -1798,8 +1906,8 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla | |||||||
|
|
||||||||
| # Apply penalty | ||||||||
| penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT | ||||||||
| decrease_balance(state, header_1.proposer_index, penalty) | ||||||||
| initiate_validator_exit(state, header_1.proposer_index) | ||||||||
| decrease_balance(state, header_1_proposer_index, penalty) | ||||||||
| initiate_validator_exit(state, header_1_proposer_index) | ||||||||
| proposer.slashed = add_flag(proposer.slashed, SLASHED_PROPOSER_FLAG_INDEX) | ||||||||
|
|
||||||||
| # Apply proposer and whistleblower rewards | ||||||||
|
|
@@ -1815,7 +1923,8 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla | |||||||
| assert is_valid_indexed_attestation(state, attestation_2) | ||||||||
|
|
||||||||
| slashed_any = False | ||||||||
| indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) | ||||||||
| indices = set([resolve_consolidated_to(i) for i in attestation_1.attesting_indices]).intersection( | ||||||||
| [resolve_consolidated_to(i) for i in attestation_2.attesting_indices]) | ||||||||
| for index in sorted(indices): | ||||||||
| if is_attester_slashable_validator(state.validators[index], get_current_epoch(state)): | ||||||||
| slash_validator(state, index) | ||||||||
|
|
@@ -1939,6 +2048,44 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu | |||||||
| initiate_validator_exit(state, voluntary_exit.validator_index) | ||||||||
|
|
||||||||
|
|
||||||||
| def resolve_consolidated_to(state: BeaconState, index: ValidatorIndex) -> ValidatorIndex: | ||||||||
| if state.validators[index].consolidated_to != UNSET_CONSOLIDATED_TO: | ||||||||
| # Recursively resolve consolidated index | ||||||||
| return resolve_consolidated_to(state, state.validators[index].consolidated_to) | ||||||||
| else: | ||||||||
| return index | ||||||||
|
|
||||||||
|
|
||||||||
| def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None: | ||||||||
| consolidation = signed_consolidation.message | ||||||||
| target_validator = state.validators[consolidation.target_index] | ||||||||
| source_validator = state.validators[consolidation.source_index] | ||||||||
|
|
||||||||
| # Verify the source and the target are active and not yet exited | ||||||||
| assert is_active_validator(source_validator) | ||||||||
| assert is_active_validator(target_validator) | ||||||||
| assert source_validator.exit_epoch == FAR_FUTURE_EPOCH | ||||||||
| assert target_validator.exit_epoch == FAR_FUTURE_EPOCH | ||||||||
|
|
||||||||
| # Verify the source and the target have Execution layer withdrawal credentials | ||||||||
| assert source_validator.withdrawal_credentials[:1] in (ETH1_ADDRESS_WITHDRAWAL_PREFIX, COMPOUNDING_WITHDRAWAL_PREFIX) | ||||||||
| assert target_validator.withdrawal_credentials[:1] in (ETH1_ADDRESS_WITHDRAWAL_PREFIX, COMPOUNDING_WITHDRAWAL_PREFIX) | ||||||||
| # Verify the same withdrawal address | ||||||||
| assert source_validator.withdrawal_credentials[1:] == target_validator.withdrawal_credentials[1:] | ||||||||
|
|
||||||||
| # Verify consolidation is signed by the source and the target | ||||||||
| domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root) | ||||||||
| signing_root = compute_signing_root(consolidation, domain) | ||||||||
| pubkeys = [source_validator.pubkey, target_validator.pubkey] | ||||||||
| assert bls.FastAggregateVerify(pubkeys, signing_root, signed_consolidation.signature) | ||||||||
|
|
||||||||
| # Queue consolidation for further processing | ||||||||
| consolidation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) | ||||||||
| state.pending_consolidations.append(PendingConsolidation(source_index = consolidation.source_index, | ||||||||
| target_index = consolidation.target_index, | ||||||||
| epoch = consolidation_epoch)) | ||||||||
|
|
||||||||
|
|
||||||||
| def is_previous_epoch_justified(store: Store) -> bool: | ||||||||
| current_slot = get_current_slot(store) | ||||||||
| current_epoch = compute_epoch_at_slot(current_slot) | ||||||||
|
|
@@ -2976,6 +3123,10 @@ def is_slashed_attester(validator: Validator) -> bool: | |||||||
| return has_flag(ParticipationFlags(validator.slashed), SLASHED_ATTESTER_FLAG_INDEX) | ||||||||
|
|
||||||||
|
|
||||||||
| def is_slashed(validator: Validator) -> bool: | ||||||||
| return is_slashed_attester(validator) or is_slashed_proposer(validator) | ||||||||
|
|
||||||||
|
|
||||||||
| def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: | ||||||||
| """ | ||||||||
| Return the sync committee indices, with possible duplicates, for the next sync committee. | ||||||||
|
|
||||||||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.