From ee0bda475a2083adb1bcbbf44cf01a47f7369c28 Mon Sep 17 00:00:00 2001 From: mike neuder Date: Tue, 1 Aug 2023 08:27:42 -0400 Subject: [PATCH 1/5] In-protocol consolidation - target not liable --- specs/_features/maxeb_increase/capella.py | 59 ++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/specs/_features/maxeb_increase/capella.py b/specs/_features/maxeb_increase/capella.py index 39bf47cf38..11502939d3 100644 --- a/specs/_features/maxeb_increase/capella.py +++ b/specs/_features/maxeb_increase/capella.py @@ -183,6 +183,8 @@ def floorlog2(x: int) -> uint64: DOMAIN_BLS_TO_EXECUTION_CHANGE = DomainType('0x0A000000') SLASHED_ATTESTER_FLAG_INDEX = 0 SLASHED_PROPOSER_FLAG_INDEX = 1 +DOMAIN_CONSOLIDATION = DomainType('0x0B000000') +UNSET_CONSOLIDATED_TO = Epoch(2**64 - 1) # Preset vars MAX_COMMITTEES_PER_SLOT = uint64(64) @@ -321,6 +323,9 @@ class Validator(Container): activation_epoch: Epoch exit_epoch: Epoch withdrawable_epoch: Epoch # When validator can withdraw funds + # TODO: may compress as; consolidated effective balance increments << 32 || consolidated to index + consolidated_to: ValidatorIndex + consolidated_balance: Gwei class AttestationData(Container): @@ -621,6 +626,13 @@ class SignedBLSToExecutionChange(Container): signature: BLSSignature +class Consolidation(Container): + source_index: ValidatorIndex + target_index: ValidatorIndex + target_signature: BLSSignature + source_address: ExecutionAddress + + class BeaconBlockBody(Container): randao_reveal: BLSSignature eth1_data: Eth1Data # Eth1 data vote @@ -1203,9 +1215,11 @@ def slash_validator(state: BeaconState, """ Slash the validator with index ``slashed_index``. """ + validator = state.validators[slashed_index] + if is_consolidated(validator): + unconsolidate_validator(state, slashed_index) epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) - validator = state.validators[slashed_index] validator.slashed = add_flag(validator.slashed, SLASHED_ATTESTER_FLAG_INDEX) validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance @@ -1776,6 +1790,49 @@ def process_execution_layer_withdraw_request( )) +def is_consolidated(validator: Validator) -> bool: + validator.consolidated_to != UNSET_CONSOLIDATED_TO + + +def process_consolidation(state: BeaconState, consolidation: Consolidation) -> None: + target_validator = state[consolidation.target_index] + source_validator = state[consolidation.source_index] + + assert is_active_validator(target_validator) and not target_validator.slashed and not is_consolidated(target_validator) + assert not source_validator.slashed and not is_consolidated(source_validator) + + # verify source withdrawal credentials, which have authority over validating keys + assert source_validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert source_validator.withdrawal_credentials[12:] == consolidation.source_address + + # target validator accepts consolidation operation onto itself + # Fork-agnostic domain since address changes are valid across forks + domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root) + signing_root = compute_signing_root(source_validator.pubkey, domain) + assert bls.Verify(target_validator.pubkey, signing_root, consolidation.target_signature) + + consolidated_balance = state.balances[consolidation.source_index] + state.balances[consolidation.target_index] += consolidated_balance + state.balances[consolidation.source_index] = 0 + + source_validator.consolidated_to = consolidation.target_index + source_validator.consolidated_balance = consolidated_balance + # Balance is not exiting the active set, do not apply churn + source_validator.exit_epoch = get_current_epoch(state) + source_validator.withdrawable_epoch = Epoch(source_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + + +def unconsolidate_validator(state: BeaconState, validator_index: ValidatorIndex): + validator = state[validator_index] + state.balances[validator.consolidated_to] -= validator.consolidated_balance # TODO: handle underflow + state.balances[validator_index] += validator.consolidated_balance + validator.consolidated_to = UNSET_CONSOLIDATED_TO + validator.consolidated_balance = 0 + # Balance will exit the active set, reset exit_epoch to apply churn + validator.exit_epoch = FAR_FUTURE_EPOCH + validator.withdrawable_epoch = FAR_FUTURE_EPOCH + + 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 From a325a9759d676432f15889f3fb69c9c0bbce8aec Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Wed, 2 Aug 2023 00:51:20 +0200 Subject: [PATCH 2/5] Target is liable --- specs/_features/maxeb_increase/capella.py | 31 +++++++++-------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/specs/_features/maxeb_increase/capella.py b/specs/_features/maxeb_increase/capella.py index 11502939d3..7f5150424f 100644 --- a/specs/_features/maxeb_increase/capella.py +++ b/specs/_features/maxeb_increase/capella.py @@ -323,9 +323,8 @@ class Validator(Container): activation_epoch: Epoch exit_epoch: Epoch withdrawable_epoch: Epoch # When validator can withdraw funds - # TODO: may compress as; consolidated effective balance increments << 32 || consolidated to index + # TODO: may compress into some other validator field consolidated_to: ValidatorIndex - consolidated_balance: Gwei class AttestationData(Container): @@ -1215,11 +1214,10 @@ def slash_validator(state: BeaconState, """ Slash the validator with index ``slashed_index``. """ - validator = state.validators[slashed_index] - if is_consolidated(validator): - unconsolidate_validator(state, slashed_index) + slashed_index = resolve_slashed_index(state, slashed_index) epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] validator.slashed = add_flag(validator.slashed, SLASHED_ATTESTER_FLAG_INDEX) validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance @@ -1794,6 +1792,14 @@ def is_consolidated(validator: Validator) -> bool: validator.consolidated_to != UNSET_CONSOLIDATED_TO +def resolve_slashed_index(state: BeaconState, index: ValidatorIndex) -> ValidatorIndex: + if is_consolidated(state.validators[index]): + # Recursively resolve consolidated index + return resolve_slashed_index(state, state.validators[index].consolidated_to) + else: + return index + + def process_consolidation(state: BeaconState, consolidation: Consolidation) -> None: target_validator = state[consolidation.target_index] source_validator = state[consolidation.source_index] @@ -1811,28 +1817,15 @@ def process_consolidation(state: BeaconState, consolidation: Consolidation) -> N signing_root = compute_signing_root(source_validator.pubkey, domain) assert bls.Verify(target_validator.pubkey, signing_root, consolidation.target_signature) - consolidated_balance = state.balances[consolidation.source_index] - state.balances[consolidation.target_index] += consolidated_balance + state.balances[consolidation.target_index] += state.balances[consolidation.source_index] state.balances[consolidation.source_index] = 0 source_validator.consolidated_to = consolidation.target_index - source_validator.consolidated_balance = consolidated_balance # Balance is not exiting the active set, do not apply churn source_validator.exit_epoch = get_current_epoch(state) source_validator.withdrawable_epoch = Epoch(source_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY) -def unconsolidate_validator(state: BeaconState, validator_index: ValidatorIndex): - validator = state[validator_index] - state.balances[validator.consolidated_to] -= validator.consolidated_balance # TODO: handle underflow - state.balances[validator_index] += validator.consolidated_balance - validator.consolidated_to = UNSET_CONSOLIDATED_TO - validator.consolidated_balance = 0 - # Balance will exit the active set, reset exit_epoch to apply churn - validator.exit_epoch = FAR_FUTURE_EPOCH - validator.withdrawable_epoch = FAR_FUTURE_EPOCH - - 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 From 95209f85cc04164d43ab3a46c618389479a082d1 Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:20:16 +0200 Subject: [PATCH 3/5] Update specs/_features/maxeb_increase/capella.py Co-authored-by: Mikhail Kalinin --- specs/_features/maxeb_increase/capella.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/_features/maxeb_increase/capella.py b/specs/_features/maxeb_increase/capella.py index 7f5150424f..16e6ed726a 100644 --- a/specs/_features/maxeb_increase/capella.py +++ b/specs/_features/maxeb_increase/capella.py @@ -1804,7 +1804,7 @@ def process_consolidation(state: BeaconState, consolidation: Consolidation) -> N target_validator = state[consolidation.target_index] source_validator = state[consolidation.source_index] - assert is_active_validator(target_validator) and not target_validator.slashed and not is_consolidated(target_validator) + assert target_validator.exit_epoch == FAR_FUTURE_EPOCH assert not source_validator.slashed and not is_consolidated(source_validator) # verify source withdrawal credentials, which have authority over validating keys From 80bc11c20c9fd0efa317867ad42c81b0433e11e7 Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:20:25 +0200 Subject: [PATCH 4/5] Update specs/_features/maxeb_increase/capella.py Co-authored-by: Mikhail Kalinin --- specs/_features/maxeb_increase/capella.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/_features/maxeb_increase/capella.py b/specs/_features/maxeb_increase/capella.py index 16e6ed726a..1b05fbd814 100644 --- a/specs/_features/maxeb_increase/capella.py +++ b/specs/_features/maxeb_increase/capella.py @@ -1805,7 +1805,7 @@ def process_consolidation(state: BeaconState, consolidation: Consolidation) -> N source_validator = state[consolidation.source_index] assert target_validator.exit_epoch == FAR_FUTURE_EPOCH - assert not source_validator.slashed and not is_consolidated(source_validator) + assert source_validator.exit_epoch == FAR_FUTURE_EPOCH # verify source withdrawal credentials, which have authority over validating keys assert source_validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX From f0003122bf8f29c0855219b1580532bbbd75596a Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Sun, 19 Nov 2023 15:57:15 +0300 Subject: [PATCH 5/5] fixes --- specs/_features/maxeb_increase/capella.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/specs/_features/maxeb_increase/capella.py b/specs/_features/maxeb_increase/capella.py index 1b05fbd814..d0ede5b910 100644 --- a/specs/_features/maxeb_increase/capella.py +++ b/specs/_features/maxeb_increase/capella.py @@ -1817,9 +1817,14 @@ def process_consolidation(state: BeaconState, consolidation: Consolidation) -> N signing_root = compute_signing_root(source_validator.pubkey, domain) assert bls.Verify(target_validator.pubkey, signing_root, consolidation.target_signature) - state.balances[consolidation.target_index] += state.balances[consolidation.source_index] + active_balance = min(state.balances[consolidation.source_index], get_max_effective_balance(source_validator)) + excess_balance = state.balances[consolidation.source_index] - active_balance + state.balances[consolidation.target_index] += active_balance state.balances[consolidation.source_index] = 0 + # Excess balance above current active balance ceil, send to inbound churn + state.pending_balance_deposits.append(PendingBalanceDeposit(consolidation.target_index, excess_balance)) + source_validator.consolidated_to = consolidation.target_index # Balance is not exiting the active set, do not apply churn source_validator.exit_epoch = get_current_epoch(state) @@ -3718,6 +3723,14 @@ def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: and balance > 0 ) + +def get_max_effective_balance(validator: Validator) -> Gwei: + if (has_compounding_withdrawal_credential(validator): + return MAX_EFFECTIVE_BALANCE + else: + return MIN_ACTIVATION_BALANCE + + def get_validator_excess_balance(validator: Validator, balance: Gwei) -> Gwei: """ Get excess balance for partial withdrawals for ``validator``.