Merge branch 'dev' into deposit-queue

This commit is contained in:
Justin Traglia 2024-10-03 13:49:42 -05:00 committed by GitHub
commit 358378dccf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 724 additions and 83 deletions

View File

@ -244,7 +244,7 @@ In this construction, we extend the blobs using a one-dimensional erasure coding
### Parameters
For each column -- use `data_column_sidecar_{subnet_id}` subnets, where `subnet_id` can be computed with the `compute_subnet_for_data_column_sidecar(column_index: ColumnIndex)` helper. The sidecars can be computed with the `get_data_column_sidecars(signed_block: SignedBeaconBlock, blobs: Sequence[Blob])` helper.
For each column -- use `data_column_sidecar_{subnet_id}` subnets, where `subnet_id` can be computed with the `compute_subnet_for_data_column_sidecar(column_index: ColumnIndex)` helper. The sidecars can be computed with `cells_and_kzg_proofs = [compute_cells_and_kzg_proofs(blob) for blob in blobs]` and then `get_data_column_sidecars(signed_block, cells_and_kzg_proofs)`.
Verifiable samples from their respective column are distributed on the assigned subnet. To custody a particular column, a node joins the respective gossipsub subnet. If a node fails to get a column on the column subnet, a node can also utilize the Req/Resp protocol to query the missing column from other peers.

View File

@ -31,7 +31,8 @@ def is_data_available(beacon_block_root: Root) -> bool:
# `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs.
column_sidecars = retrieve_column_sidecars(beacon_block_root)
return all(
verify_data_column_sidecar_kzg_proofs(column_sidecar)
verify_data_column_sidecar(column_sidecar)
and verify_data_column_sidecar_kzg_proofs(column_sidecar)
for column_sidecar in column_sidecars
)
```

View File

@ -14,6 +14,7 @@
- [Containers](#containers)
- [`DataColumnIdentifier`](#datacolumnidentifier)
- [Helpers](#helpers)
- [`verify_data_column_sidecar`](#verify_data_column_sidecar)
- [`verify_data_column_sidecar_kzg_proofs`](#verify_data_column_sidecar_kzg_proofs)
- [`verify_data_column_sidecar_inclusion_proof`](#verify_data_column_sidecar_inclusion_proof)
- [`compute_subnet_for_data_column_sidecar`](#compute_subnet_for_data_column_sidecar)
@ -64,16 +65,35 @@ class DataColumnIdentifier(Container):
### Helpers
##### `verify_data_column_sidecar`
```python
def verify_data_column_sidecar(sidecar: DataColumnSidecar) -> bool:
"""
Verify if the data column sidecar is valid.
"""
# The sidecar index must be within the valid range
if sidecar.index >= NUMBER_OF_COLUMNS:
return False
# A sidecar for zero blobs is invalid
if len(sidecar.kzg_commitments) == 0:
return False
# The column length must be equal to the number of commitments/proofs
if len(sidecar.column) != len(sidecar.kzg_commitments) or len(sidecar.column) != len(sidecar.kzg_proofs):
return False
return True
```
##### `verify_data_column_sidecar_kzg_proofs`
```python
def verify_data_column_sidecar_kzg_proofs(sidecar: DataColumnSidecar) -> bool:
"""
Verify if the proofs are correct.
Verify if the KZG proofs are correct.
"""
assert sidecar.index < NUMBER_OF_COLUMNS
assert len(sidecar.column) == len(sidecar.kzg_commitments) == len(sidecar.kzg_proofs)
# The column index also represents the cell index
cell_indices = [CellIndex(sidecar.index)] * len(sidecar.column)
@ -148,7 +168,7 @@ The *type* of the payload of this topic is `DataColumnSidecar`.
The following validations MUST pass before forwarding the `sidecar: DataColumnSidecar` on the network, assuming the alias `block_header = sidecar.signed_block_header.message`:
- _[REJECT]_ The sidecar's index is consistent with `NUMBER_OF_COLUMNS` -- i.e. `sidecar.index < NUMBER_OF_COLUMNS`.
- _[REJECT]_ The sidecar is valid as verified by `verify_data_column_sidecar(sidecar)`.
- _[REJECT]_ The sidecar is for the correct subnet -- i.e. `compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id`.
- _[IGNORE]_ The sidecar is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `block_header.slot <= current_slot` (a client MAY queue future sidecars for processing at the appropriate slot).
- _[IGNORE]_ The sidecar is from a slot greater than the latest finalized slot -- i.e. validate that `block_header.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)`

View File

@ -102,6 +102,7 @@
- [Deposit requests](#deposit-requests)
- [New `process_deposit_request`](#new-process_deposit_request)
- [Execution layer consolidation requests](#execution-layer-consolidation-requests)
- [New `is_valid_switch_to_compounding_request`](#new-is_valid_switch_to_compounding_request)
- [New `process_consolidation_request`](#new-process_consolidation_request)
- [Testing](#testing)
@ -639,9 +640,8 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
```python
def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
validator = state.validators[index]
if has_eth1_withdrawal_credential(validator):
validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
queue_excess_active_balance(state, index)
validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
queue_excess_active_balance(state, index)
```
#### New `queue_excess_active_balance`
@ -932,9 +932,6 @@ def process_pending_consolidations(state: BeaconState) -> None:
if source_validator.withdrawable_epoch > next_epoch:
break
# Churn any target excess active balance of target and raise its max
switch_to_compounding_validator(state, pending_consolidation.target_index)
# Calculate the consolidated balance
max_effective_balance = get_max_effective_balance(source_validator)
source_effective_balance = min(state.balances[pending_consolidation.source_index], max_effective_balance)
@ -1508,6 +1505,45 @@ def process_deposit_request(state: BeaconState, deposit_request: DepositRequest)
##### Execution layer consolidation requests
###### New `is_valid_switch_to_compounding_request`
```python
def is_valid_switch_to_compounding_request(
state: BeaconState,
consolidation_request: ConsolidationRequest
) -> bool:
# Switch to compounding requires source and target be equal
if consolidation_request.source_pubkey != consolidation_request.target_pubkey:
return False
# Verify pubkey exists
source_pubkey = consolidation_request.source_pubkey
validator_pubkeys = [v.pubkey for v in state.validators]
if source_pubkey not in validator_pubkeys:
return False
source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))]
# Verify request has been authorized
if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address:
return False
# Verify source withdrawal credentials
if not has_eth1_withdrawal_credential(source_validator):
return False
# Verify the source is active
current_epoch = get_current_epoch(state)
if not is_active_validator(source_validator, current_epoch):
return False
# Verify exit for source has not been initiated
if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
return False
return True
```
###### New `process_consolidation_request`
```python
@ -1515,6 +1551,16 @@ def process_consolidation_request(
state: BeaconState,
consolidation_request: ConsolidationRequest
) -> None:
if is_valid_switch_to_compounding_request(state, consolidation_request):
validator_pubkeys = [v.pubkey for v in state.validators]
request_source_pubkey = consolidation_request.source_pubkey
source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
switch_to_compounding_validator(state, source_index)
return
# Verify that source != target, so a consolidation cannot be used as an exit.
if consolidation_request.source_pubkey == consolidation_request.target_pubkey:
return
# If the pending consolidations queue is full, consolidation requests are ignored
if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
return
@ -1535,10 +1581,6 @@ def process_consolidation_request(
source_validator = state.validators[source_index]
target_validator = state.validators[target_index]
# Verify that source != target, so a consolidation cannot be used as an exit.
if source_index == target_index:
return
# Verify source withdrawal credentials
has_correct_credential = has_execution_withdrawal_credential(source_validator)
is_correct_source_address = (
@ -1574,6 +1616,10 @@ def process_consolidation_request(
source_index=source_index,
target_index=target_index
))
# Churn any target excess active balance of target and raise its max
if has_eth1_withdrawal_credential(target_validator):
switch_to_compounding_validator(state, target_index)
```
## Testing

View File

@ -1 +1 @@
1.5.0-alpha.6
1.5.0-alpha.7

View File

@ -1,8 +1,173 @@
import random
from eth2spec.test.context import (
spec_test,
single_phase,
spec_state_test,
spec_test,
with_eip7594_and_later,
)
from eth2spec.debug.random_value import (
RandomizationMode,
get_random_ssz_object,
)
from eth2spec.test.helpers.block import (
sign_block,
)
from eth2spec.test.helpers.execution_payload import (
compute_el_block_hash,
)
from eth2spec.test.helpers.sharding import (
get_sample_opaque_tx,
)
# Helper functions
def compute_data_column_sidecar(spec, state):
rng = random.Random(5566)
opaque_tx, blobs, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=2)
block = get_random_ssz_object(
rng,
spec.BeaconBlock,
max_bytes_length=2000,
max_list_length=2000,
mode=RandomizationMode,
chaos=True,
)
block.body.blob_kzg_commitments = blob_kzg_commitments
block.body.execution_payload.transactions = [opaque_tx]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload, state)
signed_block = sign_block(spec, state, block, proposer_index=0)
cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs]
return spec.get_data_column_sidecars(signed_block, cells_and_kzg_proofs)[0]
# Tests for verify_data_column_sidecar
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar__valid(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
assert spec.verify_data_column_sidecar(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar__invalid_zero_blobs(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.column = []
sidecar.kzg_commitments = []
sidecar.kzg_proofs = []
assert not spec.verify_data_column_sidecar(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar__invalid_index(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.index = 128
assert not spec.verify_data_column_sidecar(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar__invalid_mismatch_len_column(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.column = sidecar.column[1:]
assert not spec.verify_data_column_sidecar(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar__invalid_mismatch_len_kzg_commitments(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_commitments = sidecar.kzg_commitments[1:]
assert not spec.verify_data_column_sidecar(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecars__invalid_mismatch_len_kzg_proofs(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_proofs = sidecar.kzg_proofs[1:]
assert not spec.verify_data_column_sidecar(sidecar)
# Tests for verify_data_column_sidecar_kzg_proofs
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_kzg_proofs__valid(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
assert spec.verify_data_column_sidecar_kzg_proofs(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_column(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.column[0] = sidecar.column[1]
assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_commitment(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_commitments[0] = sidecar.kzg_commitments[1]
assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_proof(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_proofs[0] = sidecar.kzg_proofs[1]
assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar)
# Tests for verify_data_column_sidecar_inclusion_proof
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_inclusion_proof__valid(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
assert spec.verify_data_column_sidecar_inclusion_proof(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_inclusion_proof__invalid_missing_commitment(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_commitments = sidecar.kzg_commitments[1:]
assert not spec.verify_data_column_sidecar_inclusion_proof(sidecar)
@with_eip7594_and_later
@spec_state_test
@single_phase
def test_verify_data_column_sidecar_inclusion_proof__invalid_duplicate_commitment(spec, state):
sidecar = compute_data_column_sidecar(spec, state)
sidecar.kzg_commitments = sidecar.kzg_commitments + [sidecar.kzg_commitments[0]]
assert not spec.verify_data_column_sidecar_inclusion_proof(sidecar)
# Tests for compute_subnet_for_data_column_sidecar
@with_eip7594_and_later

View File

@ -66,6 +66,106 @@ def test_basic_consolidation_in_current_consolidation_epoch(spec, state):
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_excess_target_balance(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_eth1_withdrawal_credential_with_balance(spec, state, target_index)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
# Add excess balance
state.balances[target_index] = state.balances[target_index] + spec.EFFECTIVE_BALANCE_INCREMENT
yield from run_consolidation_processing(spec, state, consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_basic_consolidation_with_excess_target_balance_and_compounding_credentials(spec, state):
# This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
target_index = spec.get_active_validator_indices(state, current_epoch)[1]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with source address
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
set_compounding_withdrawal_credential(spec, state, target_index)
# Set earliest consolidation epoch to the expected exit epoch
expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch)
state.earliest_consolidation_epoch = expected_exit_epoch
consolidation_churn_limit = spec.get_consolidation_churn_limit(state)
# Set the consolidation balance to consume equal to churn limit
state.consolidation_balance_to_consume = consolidation_churn_limit
# Add excess balance
state.balances[target_index] = state.balances[target_index] + spec.EFFECTIVE_BALANCE_INCREMENT
yield from run_consolidation_processing(spec, state, consolidation)
# Check consolidation churn is decremented correctly
assert (
state.consolidation_balance_to_consume
== consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE
)
# Check exit epoch
assert state.validators[source_index].exit_epoch == expected_exit_epoch
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
@ -235,7 +335,7 @@ def test_basic_consolidation_with_compounding_credentials(spec, state):
target_pubkey=state.validators[target_index].pubkey,
)
# Set target to eth1 credentials
# Set target to compounding credentials
set_compounding_withdrawal_credential(spec, state, target_index)
# Set the consolidation balance to consume equal to churn limit
@ -395,17 +495,9 @@ def test_consolidation_balance_through_two_churn_epochs(spec, state):
assert state.consolidation_balance_to_consume == expected_balance
# Failing tests
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit,
threshold_fn=default_activation_threshold,
)
@spec_test
@single_phase
def test_incorrect_source_equals_target(spec, state):
@spec_state_test
def test_basic_switch_to_compounding(spec, state):
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
@ -421,14 +513,67 @@ def test_incorrect_source_equals_target(spec, state):
target_pubkey=state.validators[source_index].pubkey,
)
# Check the the return condition
assert consolidation.source_pubkey == consolidation.target_pubkey
yield from run_consolidation_processing(
spec, state, consolidation, success=False
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=True
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_with_excess(spec, state):
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Add excess balance
state.balances[source_index] = state.balances[source_index] + spec.EFFECTIVE_BALANCE_INCREMENT
# Make consolidation from source to source
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=True
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_with_pending_consolidations_at_limit(spec, state):
state.pending_consolidations = [
spec.PendingConsolidation(source_index=0, target_index=1)
] * spec.PENDING_CONSOLIDATIONS_LIMIT
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
# Set source to eth1 credentials
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Add excess balance
state.balances[source_index] = state.balances[source_index] + spec.EFFECTIVE_BALANCE_INCREMENT
# Make consolidation from source to source
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=True
)
# Tests that should fail
@with_electra_and_later
@with_presets([MINIMAL], "need sufficient consolidation churn limit")
@with_custom_state(
@ -797,6 +942,155 @@ def test_incorrect_unknown_target_pubkey(spec, state):
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_exited_source(spec, state):
# Set up an otherwise correct request
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
# Initiate exit for source
spec.initiate_validator_exit(state, source_index)
# Check the return condition
assert state.validators[source_index].exit_epoch != spec.FAR_FUTURE_EPOCH
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_inactive_source(spec, state):
# Set up an otherwise correct request
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
# Set source validator as not yet activated
state.validators[source_index].activation_epoch = spec.FAR_FUTURE_EPOCH
# Check the the return condition
assert not spec.is_active_validator(state.validators[source_index], current_epoch)
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_source_bls_withdrawal_credential(spec, state):
# Set up a correct request, but source does have
# a bls withdrawal credential
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
consolidation = spec.ConsolidationRequest(
source_address=state.validators[source_index].withdrawal_credentials[12:],
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
# Check the the return condition
assert not spec.has_eth1_withdrawal_credential(state.validators[source_index])
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_source_coumpounding_withdrawal_credential(spec, state):
# Set up a correct request, but source does have
# a compounding withdrawal credential and excess balance
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_address = b"\x22" * 20
set_compounding_withdrawal_credential(spec, state, source_index, address=source_address)
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
state.balances[source_index] = spec.MIN_ACTIVATION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT
# Check the the return condition
assert not spec.has_eth1_withdrawal_credential(state.validators[source_index])
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_not_authorized(spec, state):
# Set up an otherwise correct request
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make request with different source address
consolidation = spec.ConsolidationRequest(
source_address=b"\x33" * 20,
source_pubkey=state.validators[source_index].pubkey,
target_pubkey=state.validators[source_index].pubkey,
)
# Check the the return condition
assert not state.validators[source_index].withdrawal_credentials[12:] == consolidation.source_address
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
@with_electra_and_later
@spec_state_test
def test_switch_to_compounding_unknown_source_pubkey(spec, state):
# Set up an otherwise correct request
current_epoch = spec.get_current_epoch(state)
source_index = spec.get_active_validator_indices(state, current_epoch)[0]
source_address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, source_index, address=source_address
)
# Make consolidation with different source pubkey
consolidation = spec.ConsolidationRequest(
source_address=source_address,
source_pubkey=b"\x00" * 48,
target_pubkey=b"\x00" * 48,
)
# Check the the return condition
assert not state.validators[source_index].pubkey == consolidation.source_pubkey
yield from run_switch_to_compounding_processing(
spec, state, consolidation, success=False
)
def run_consolidation_processing(spec, state, consolidation, success=True):
"""
Run ``process_consolidation``, yielding:
@ -805,7 +1099,6 @@ def run_consolidation_processing(spec, state, consolidation, success=True):
- post-state ('post').
If ``success == False``, ``process_consolidation_request`` would return without any state change.
"""
if success:
validator_pubkeys = [v.pubkey for v in state.validators]
source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey))
@ -815,6 +1108,8 @@ def run_consolidation_processing(spec, state, consolidation, success=True):
pre_exit_epoch_source = source_validator.exit_epoch
pre_exit_epoch_target = target_validator.exit_epoch
pre_pending_consolidations = state.pending_consolidations.copy()
pre_target_withdrawal_credentials = target_validator.withdrawal_credentials
pre_target_balance = state.balances[target_index]
else:
pre_state = state.copy()
@ -826,9 +1121,10 @@ def run_consolidation_processing(spec, state, consolidation, success=True):
yield 'post', state
if success:
# Check source and target have execution credentials
# Check source has execution credentials
assert spec.has_execution_withdrawal_credential(source_validator)
assert spec.has_execution_withdrawal_credential(target_validator)
# Check target has compounding credentials
assert spec.has_compounding_withdrawal_credential(state.validators[target_index])
# Check source address in the consolidation fits the withdrawal credentials
assert source_validator.withdrawal_credentials[12:] == consolidation.source_address
# Check source and target are not the same
@ -840,11 +1136,74 @@ def run_consolidation_processing(spec, state, consolidation, success=True):
assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH
# Check that the exit epoch matches earliest_consolidation_epoch
assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch
# Check that the withdrawable_epoch is set correctly
assert state.validators[source_index].withdrawable_epoch == (
state.validators[source_index].exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
)
# Check that the correct consolidation has been appended
expected_new_pending_consolidation = spec.PendingConsolidation(
source_index=source_index,
target_index=target_index,
)
assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation]
# Check excess balance is queued if the target switched to compounding
if pre_target_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX:
assert state.validators[target_index].withdrawal_credentials == (
spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_target_withdrawal_credentials[1:])
assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE
if pre_target_balance > spec.MIN_ACTIVATION_BALANCE:
assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(
index=target_index, amount=(pre_target_balance - spec.MIN_ACTIVATION_BALANCE))]
else:
assert state.balances[target_index] == pre_target_balance
else:
assert pre_state == state
def run_switch_to_compounding_processing(spec, state, consolidation, success=True):
"""
Run ``process_consolidation``, yielding:
- pre-state ('pre')
- consolidation_request ('consolidation_request')
- post-state ('post').
If ``success == False``, ``process_consolidation_request`` would return without any state change.
"""
if success:
validator_pubkeys = [v.pubkey for v in state.validators]
source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey))
target_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.target_pubkey))
source_validator = state.validators[source_index]
pre_pending_consolidations = state.pending_consolidations.copy()
pre_withdrawal_credentials = source_validator.withdrawal_credentials
pre_balance = state.balances[source_index]
else:
pre_state = state.copy()
yield 'pre', state
yield 'consolidation_request', consolidation
spec.process_consolidation_request(state, consolidation)
yield 'post', state
if success:
# Check that source and target are same
assert source_index == target_index
# Check that the credentials before the switch are of ETH1 type
assert pre_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX
# Check source address in the consolidation fits the withdrawal credentials
assert state.validators[source_index].withdrawal_credentials[12:] == consolidation.source_address
# Check that the source has switched to compounding
assert state.validators[source_index].withdrawal_credentials == (
spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_withdrawal_credentials[1:]
)
# Check excess balance is queued
assert state.balances[source_index] == spec.MIN_ACTIVATION_BALANCE
if pre_balance > spec.MIN_ACTIVATION_BALANCE:
assert state.pending_balance_deposits == [spec.PendingBalanceDeposit(
index=source_index, amount=(pre_balance - spec.MIN_ACTIVATION_BALANCE))]
# Check no consolidation has been initiated
assert state.validators[source_index].exit_epoch == spec.FAR_FUTURE_EPOCH
assert state.pending_consolidations == pre_pending_consolidations
else:
assert pre_state == state

View File

@ -658,6 +658,8 @@ def test_insufficient_effective_balance(spec, state):
state.validators[
validator_index
].effective_balance -= spec.EFFECTIVE_BALANCE_INCREMENT
# Make sure validator has enough balance to withdraw
state.balances[validator_index] += spec.EFFECTIVE_BALANCE_INCREMENT
set_compounding_withdrawal_credential(spec, state, validator_index, address=address)
withdrawal_request = spec.WithdrawalRequest(
@ -787,6 +789,97 @@ def test_partial_withdrawal_activation_epoch_less_than_shard_committee_period(
)
@with_electra_and_later
@spec_state_test
def test_insufficient_balance(spec, state):
rng = random.Random(1361)
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
current_epoch = spec.get_current_epoch(state)
validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch))
validator_pubkey = state.validators[validator_index].pubkey
address = b"\x22" * 20
amount = spec.EFFECTIVE_BALANCE_INCREMENT
# Validator will not be able to partial withdrawal because MIN_ACTIVATION_BALANCE + amount > balance
set_compounding_withdrawal_credential(spec, state, validator_index, address=address)
withdrawal_request = spec.WithdrawalRequest(
source_address=address,
validator_pubkey=validator_pubkey,
amount=amount,
)
yield from run_withdrawal_request_processing(
spec,
state,
withdrawal_request,
success=False,
)
@with_electra_and_later
@spec_state_test
def test_full_exit_request_has_partial_withdrawal(spec, state):
rng = random.Random(1361)
# Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
current_epoch = spec.get_current_epoch(state)
validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch))
validator_pubkey = state.validators[validator_index].pubkey
address = b"\x22" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, validator_index, address=address
)
withdrawal_request = spec.WithdrawalRequest(
source_address=address,
validator_pubkey=validator_pubkey,
amount=spec.FULL_EXIT_REQUEST_AMOUNT,
)
# Validator can only be exited if there's no pending partial withdrawals in state
state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA
state.pending_partial_withdrawals.append(
spec.PendingPartialWithdrawal(
index=validator_index,
amount=1,
withdrawable_epoch=spec.compute_activation_exit_epoch(current_epoch),
)
)
yield from run_withdrawal_request_processing(
spec, state, withdrawal_request, success=False
)
@with_electra_and_later
@spec_state_test
def test_incorrect_inactive_validator(spec, state):
rng = random.Random(1361)
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
current_epoch = spec.get_current_epoch(state)
validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch))
validator_pubkey = state.validators[validator_index].pubkey
address = b"\x22" * 20
incorrect_address = b"\x33" * 20
set_eth1_withdrawal_credential_with_balance(
spec, state, validator_index, address=address
)
withdrawal_request = spec.WithdrawalRequest(
source_address=incorrect_address,
validator_pubkey=validator_pubkey,
amount=spec.FULL_EXIT_REQUEST_AMOUNT,
)
# set validator as not yet activated
state.validators[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(state.validators[validator_index], current_epoch)
yield from run_withdrawal_request_processing(
spec, state, withdrawal_request, success=False
)
#
# Run processing
#

View File

@ -36,10 +36,6 @@ def test_basic_pending_consolidation(spec, state):
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
# Pending consolidation was successfully processed
assert (
state.validators[target_index].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE
assert state.balances[source_index] == 0
assert state.pending_consolidations == []
@ -65,9 +61,6 @@ def test_consolidation_not_yet_withdrawable_validator(spec, state):
pre_pending_consolidations = state.pending_consolidations.copy()
pre_balances = state.balances.copy()
pre_target_withdrawal_credential = state.validators[
target_index
].withdrawal_credentials[:1]
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
@ -75,11 +68,6 @@ def test_consolidation_not_yet_withdrawable_validator(spec, state):
# Balances are unchanged
assert state.balances[source_index] == pre_balances[0]
assert state.balances[target_index] == pre_balances[1]
# Target withdrawal credential is unchanged
assert (
state.validators[target_index].withdrawal_credentials[:1]
== pre_target_withdrawal_credential
)
# Pending consolidation is still in the queue
assert state.pending_consolidations == pre_pending_consolidations
@ -121,17 +109,9 @@ def test_skip_consolidation_when_source_slashed(spec, state):
# first pending consolidation should not be processed
assert state.balances[target0_index] == spec.MIN_ACTIVATION_BALANCE
assert state.balances[source0_index] == spec.MIN_ACTIVATION_BALANCE
assert (
state.validators[target0_index].withdrawal_credentials[:1]
== spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX
)
# second pending consolidation should be processed: first one is skipped and doesn't block the queue
assert state.balances[target1_index] == 2 * spec.MIN_ACTIVATION_BALANCE
assert state.balances[source1_index] == 0
assert (
state.validators[target1_index].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
@with_electra_and_later
@ -167,26 +147,14 @@ def test_all_consolidation_cases_together(spec, state):
spec.initiate_validator_exit(state, 2)
pre_balances = state.balances.copy()
pre_target_withdrawal_prefixes = [
state.validators[target_index[i]].withdrawal_credentials[:1]
for i in [0, 1, 2, 3]
]
pre_pending_consolidations = state.pending_consolidations.copy()
yield from run_epoch_processing_with(spec, state, "process_pending_consolidations")
# First consolidation is successfully processed
assert (
state.validators[target_index[0]].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
assert state.balances[target_index[0]] == 2 * spec.MIN_ACTIVATION_BALANCE
assert state.balances[source_index[0]] == 0
# All other consolidations are not processed
for i in [1, 2, 3]:
assert (
state.validators[target_index[i]].withdrawal_credentials[:1]
== pre_target_withdrawal_prefixes[i]
)
assert state.balances[source_index[i]] == pre_balances[source_index[i]]
assert state.balances[target_index[i]] == pre_balances[target_index[i]]
# First consolidation is processed, second is skipped, last two are left in the queue
@ -226,12 +194,9 @@ def test_pending_consolidation_future_epoch(spec, state):
# Pending consolidation was successfully processed
expected_source_balance = state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE
assert (
state.validators[target_index].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE
expected_target_balance = state_before_consolidation.balances[target_index] + spec.MIN_ACTIVATION_BALANCE
assert state.balances[source_index] == expected_source_balance
assert state.balances[target_index] == expected_target_balance
assert state.pending_consolidations == []
# Pending balance deposit to the target is created as part of `switch_to_compounding_validator`.
@ -285,10 +250,6 @@ def test_pending_consolidation_compounding_creds(spec, state):
expected_target_balance = (
state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index]
)
assert (
state.validators[target_index].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
assert state.balances[target_index] == expected_target_balance
# All source balance is active and moved to the target,
# because the source validator has compounding credentials
@ -347,10 +308,6 @@ def test_pending_consolidation_with_pending_deposit(spec, state):
expected_target_balance = (
state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index]
)
assert (
state.validators[target_index].withdrawal_credentials[:1]
== spec.COMPOUNDING_WITHDRAWAL_PREFIX
)
assert state.balances[target_index] == expected_target_balance
assert state.balances[source_index] == 0
assert state.pending_consolidations == []