mirror of
https://github.com/ethereum/consensus-specs.git
synced 2026-02-13 12:25:09 -05:00
Merge branch 'ethereum:dev' into fork-choice-changes-for-confirmaton-rule
This commit is contained in:
@@ -176,7 +176,7 @@ jobs:
|
||||
- checkout
|
||||
- run:
|
||||
name: Check table of contents
|
||||
command: sudo npm install -g doctoc@2 && make check_toc
|
||||
command: sudo npm install -g doctoc@2.2.0 && make check_toc
|
||||
codespell:
|
||||
docker:
|
||||
- image: circleci/python:3.9
|
||||
|
||||
2
.github/workflows/run-tests.yml
vendored
2
.github/workflows/run-tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commitRef || env.DEFAULT_BRANCH }}
|
||||
- name: Check table of contents
|
||||
run: sudo npm install -g doctoc@2 && make check_toc
|
||||
run: sudo npm install -g doctoc@2.2.0 && make check_toc
|
||||
|
||||
codespell:
|
||||
runs-on: self-hosted
|
||||
|
||||
@@ -53,6 +53,9 @@ DENEB_FORK_EPOCH: 18446744073709551615
|
||||
# EIP6110
|
||||
EIP6110_FORK_VERSION: 0x05000000 # temporary stub
|
||||
EIP6110_FORK_EPOCH: 18446744073709551615
|
||||
# WHISK
|
||||
WHISK_FORK_VERSION: 0x06000000 # temporary stub
|
||||
WHISK_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
|
||||
# Time parameters
|
||||
|
||||
@@ -52,6 +52,9 @@ DENEB_FORK_EPOCH: 18446744073709551615
|
||||
# EIP6110
|
||||
EIP6110_FORK_VERSION: 0x05000001
|
||||
EIP6110_FORK_EPOCH: 18446744073709551615
|
||||
# WHISK
|
||||
WHISK_FORK_VERSION: 0x06000001
|
||||
WHISK_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
|
||||
# Time parameters
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
- [Curdleproofs and opening proofs](#curdleproofs-and-opening-proofs)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [`WhiskTracker`](#whisktracker)
|
||||
- [`Validator`](#validator)
|
||||
- [`BeaconState`](#beaconstate)
|
||||
- [Block processing](#block-processing)
|
||||
- [Block header](#block-header)
|
||||
@@ -23,6 +22,7 @@
|
||||
- [`BeaconBlockBody`](#beaconblockbody)
|
||||
- [Deposits](#deposits)
|
||||
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
||||
- [Testing](#testing)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
@@ -124,23 +124,6 @@ class WhiskTracker(Container):
|
||||
k_r_G: BLSG1Point # k * r * G
|
||||
```
|
||||
|
||||
### `Validator`
|
||||
|
||||
```python
|
||||
class Validator(Container):
|
||||
pubkey: BLSPubkey
|
||||
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
|
||||
effective_balance: Gwei # Balance at stake
|
||||
slashed: boolean
|
||||
# Status epochs
|
||||
activation_eligibility_epoch: Epoch # When criteria for activation were met
|
||||
activation_epoch: Epoch
|
||||
exit_epoch: Epoch
|
||||
withdrawable_epoch: Epoch # When validator can withdraw funds
|
||||
whisk_tracker: WhiskTracker # Whisk tracker (r * G, k * r * G) [New in Whisk]
|
||||
whisk_k_commitment: BLSG1Point # Whisk k commitment k * BLS_G1_GENERATOR [New in Whisk]
|
||||
```
|
||||
|
||||
### `BeaconState`
|
||||
|
||||
```python
|
||||
@@ -186,8 +169,11 @@ class BeaconState(Container):
|
||||
next_withdrawal_validator_index: ValidatorIndex
|
||||
# Deep history valid from Capella onwards
|
||||
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]
|
||||
# Whisk
|
||||
whisk_candidate_trackers: Vector[WhiskTracker, WHISK_CANDIDATE_TRACKERS_COUNT] # [New in Whisk]
|
||||
whisk_proposer_trackers: Vector[WhiskTracker, WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk]
|
||||
whisk_trackers: List[WhiskTracker, VALIDATOR_REGISTRY_LIMIT] # [New in Whisk]
|
||||
whisk_k_commitments: List[BLSG1Point, VALIDATOR_REGISTRY_LIMIT] # [New in Whisk]
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -203,7 +189,7 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None:
|
||||
for i in range(WHISK_CANDIDATE_TRACKERS_COUNT):
|
||||
seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i))
|
||||
candidate_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance
|
||||
state.whisk_candidate_trackers[i] = state.validators[candidate_index].whisk_tracker
|
||||
state.whisk_candidate_trackers[i] = state.whisk_trackers[candidate_index]
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -237,7 +223,7 @@ def process_epoch(state: BeaconState) -> None:
|
||||
```python
|
||||
def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None:
|
||||
tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT]
|
||||
k_commitment = state.validators[block.proposer_index].whisk_k_commitment
|
||||
k_commitment = state.whisk_k_commitments[block.proposer_index]
|
||||
assert IsValidWhiskOpeningProof(tracker, k_commitment, block.body.whisk_opening_proof)
|
||||
```
|
||||
|
||||
@@ -297,7 +283,7 @@ class BeaconBlockBody(capella.BeaconBlockBody):
|
||||
whisk_shuffle_proof_M_commitment: BLSG1Point # [New in Whisk]
|
||||
whisk_registration_proof: WhiskTrackerProof # [New in Whisk]
|
||||
whisk_tracker: WhiskTracker # [New in Whisk]
|
||||
whisk_k_commitment: BLSG1Point # [New in Whisk]
|
||||
whisk_k_commitment: BLSG1Point # k * BLS_G1_GENERATOR [New in Whisk]
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -306,9 +292,10 @@ def get_shuffle_indices(randao_reveal: BLSSignature) -> Sequence[uint64]:
|
||||
Given a `randao_reveal` return the list of indices that got shuffled from the entire candidate set
|
||||
"""
|
||||
indices = []
|
||||
for i in WHISK_VALIDATORS_PER_SHUFFLE:
|
||||
for i in range(0, WHISK_VALIDATORS_PER_SHUFFLE):
|
||||
# XXX ensure we are not suffering from modulo bias
|
||||
shuffle_index = uint256(hash(randao_reveal + uint_to_bytes(i))) % WHISK_CANDIDATE_TRACKERS_COUNT
|
||||
pre_image = randao_reveal + uint_to_bytes(uint64(i))
|
||||
shuffle_index = bytes_to_uint64(hash(pre_image)[0:8]) % WHISK_CANDIDATE_TRACKERS_COUNT
|
||||
indices.append(shuffle_index)
|
||||
|
||||
return indices
|
||||
@@ -319,20 +306,23 @@ def process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None
|
||||
# Check the shuffle proof
|
||||
shuffle_indices = get_shuffle_indices(body.randao_reveal)
|
||||
pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices]
|
||||
post_shuffle_trackers = body.whisk_post_shuffle_trackers
|
||||
|
||||
shuffle_epoch = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE
|
||||
if shuffle_epoch + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE:
|
||||
# Require unchanged trackers during cooldown
|
||||
assert pre_shuffle_trackers == post_shuffle_trackers
|
||||
# Require trackers set to zero during cooldown
|
||||
assert body.whisk_post_shuffle_trackers == Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE]()
|
||||
assert body.whisk_shuffle_proof_M_commitment == BLSG1Point()
|
||||
assert body.whisk_shuffle_proof == WhiskShuffleProof()
|
||||
post_shuffle_trackers = pre_shuffle_trackers
|
||||
else:
|
||||
# Require shuffled trackers during shuffle
|
||||
assert IsValidWhiskShuffleProof(
|
||||
pre_shuffle_trackers,
|
||||
post_shuffle_trackers,
|
||||
body.whisk_post_shuffle_trackers,
|
||||
body.whisk_shuffle_proof_M_commitment,
|
||||
body.whisk_shuffle_proof,
|
||||
)
|
||||
post_shuffle_trackers = body.whisk_post_shuffle_trackers
|
||||
|
||||
# Shuffle candidate trackers
|
||||
for i, shuffle_index in enumerate(shuffle_indices):
|
||||
@@ -341,7 +331,7 @@ def process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None
|
||||
|
||||
```python
|
||||
def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool:
|
||||
return all([validator.whisk_k_commitment != k_commitment for validator in state.validators])
|
||||
return all([whisk_k_commitment != k_commitment for whisk_k_commitment in state.whisk_k_commitments])
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -382,49 +372,30 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
|
||||
### Deposits
|
||||
|
||||
```python
|
||||
def get_initial_whisk_k(validator_index: ValidatorIndex, counter: int) -> BLSFieldElement:
|
||||
# hash `validator_index || counter`
|
||||
return BLSFieldElement(bytes_to_bls_field(hash(uint_to_bytes(validator_index) + uint_to_bytes(uint64(counter)))))
|
||||
```
|
||||
|
||||
```python
|
||||
def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFieldElement:
|
||||
counter = 0
|
||||
while True:
|
||||
# hash `validator_index || counter`
|
||||
k = BLSFieldElement(bytes_to_bls_field(hash(uint_to_bytes(validator_index) + uint_to_bytes(uint64(counter)))))
|
||||
k = get_initial_whisk_k(validator_index, counter)
|
||||
if is_k_commitment_unique(state, BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)):
|
||||
return k # unique by trial and error
|
||||
counter += 1
|
||||
```
|
||||
|
||||
```python
|
||||
def get_initial_commitments(k: BLSFieldElement) -> Tuple[BLSG1Point, WhiskTracker]:
|
||||
return (
|
||||
BLSG1ScalarMultiply(k, BLS_G1_GENERATOR),
|
||||
WhiskTracker(r_G=BLS_G1_GENERATOR, k_r_G=BLSG1ScalarMultiply(k, BLS_G1_GENERATOR))
|
||||
)
|
||||
def get_k_commitment(k: BLSFieldElement) -> BLSG1Point:
|
||||
return BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)
|
||||
```
|
||||
|
||||
```python
|
||||
def get_validator_from_deposit_whisk(
|
||||
state: BeaconState,
|
||||
pubkey: BLSPubkey,
|
||||
withdrawal_credentials: Bytes32,
|
||||
amount: uint64
|
||||
) -> Validator:
|
||||
effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
k = get_unique_whisk_k(state, ValidatorIndex(len(state.validators)))
|
||||
whisk_k_commitment, whisk_tracker = get_initial_commitments(k)
|
||||
|
||||
validator = Validator(
|
||||
pubkey=pubkey,
|
||||
withdrawal_credentials=withdrawal_credentials,
|
||||
activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
||||
activation_epoch=FAR_FUTURE_EPOCH,
|
||||
exit_epoch=FAR_FUTURE_EPOCH,
|
||||
withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
effective_balance=effective_balance,
|
||||
# Whisk fields
|
||||
whisk_tracker=whisk_tracker,
|
||||
whisk_k_commitment=whisk_k_commitment,
|
||||
)
|
||||
return validator
|
||||
def get_initial_tracker(k: BLSFieldElement) -> WhiskTracker:
|
||||
return WhiskTracker(r_G=BLS_G1_GENERATOR, k_r_G=BLSG1ScalarMultiply(k, BLS_G1_GENERATOR))
|
||||
```
|
||||
|
||||
```python
|
||||
@@ -446,13 +417,16 @@ def apply_deposit(state: BeaconState,
|
||||
# Initialize validator if the deposit signature is valid
|
||||
if bls.Verify(pubkey, signing_root, signature):
|
||||
index = get_index_for_new_validator(state)
|
||||
validator = get_validator_from_deposit_whisk(state, pubkey, withdrawal_credentials, amount)
|
||||
validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
|
||||
set_or_append_list(state.validators, index, validator)
|
||||
set_or_append_list(state.balances, index, amount)
|
||||
# [New in Altair]
|
||||
set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.inactivity_scores, index, uint64(0))
|
||||
# [New in Whisk]
|
||||
k = get_unique_whisk_k(state, ValidatorIndex(len(state.validators) - 1))
|
||||
state.whisk_trackers.append(get_initial_tracker(k))
|
||||
state.whisk_k_commitments.append(get_k_commitment(k))
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
@@ -469,3 +443,25 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
||||
assert state.latest_block_header.slot == state.slot # sanity check `process_block_header` has been called
|
||||
return state.latest_block_header.proposer_index
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Whisk testing only.
|
||||
|
||||
```python
|
||||
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32,
|
||||
eth1_timestamp: uint64,
|
||||
deposits: Sequence[Deposit],
|
||||
execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader()
|
||||
) -> BeaconState:
|
||||
state_capella = capella.initialize_beacon_state_from_eth1(
|
||||
eth1_block_hash,
|
||||
eth1_timestamp,
|
||||
deposits,
|
||||
execution_payload_header,
|
||||
)
|
||||
state = upgrade_to_whisk(state_capella)
|
||||
state.fork.previous_version = WHISK_FORK_VERSION
|
||||
state.fork.current_version = WHISK_FORK_VERSION
|
||||
return state
|
||||
```
|
||||
|
||||
@@ -68,6 +68,11 @@ def whisk_proposer_selection(state: BeaconState, epoch: Epoch) -> None:
|
||||
|
||||
```python
|
||||
def upgrade_to_whisk(pre: bellatrix.BeaconState) -> BeaconState:
|
||||
# Compute initial unsafe trackers for all validators
|
||||
ks = [get_initial_whisk_k(ValidatorIndex(validator_index), 0) for validator_index in range(len(pre.validators))]
|
||||
whisk_k_commitments = [get_k_commitment(k) for k in ks]
|
||||
whisk_trackers = [get_initial_tracker(k) for k in ks]
|
||||
|
||||
epoch = bellatrix.get_current_epoch(pre)
|
||||
post = BeaconState(
|
||||
# Versioning
|
||||
@@ -104,27 +109,19 @@ def upgrade_to_whisk(pre: bellatrix.BeaconState) -> BeaconState:
|
||||
current_justified_checkpoint=pre.current_justified_checkpoint,
|
||||
finalized_checkpoint=pre.finalized_checkpoint,
|
||||
# Inactivity
|
||||
inactivity_scores=pre.inactivity_Scores,
|
||||
inactivity_scores=pre.inactivity_scores,
|
||||
# Sync
|
||||
current_sync_committee=pre.current_sync_committee,
|
||||
next_sync_committee=pre.next_sync_committee,
|
||||
# Execution-layer
|
||||
latest_execution_payload_header=pre.latest_execution_payload_header,
|
||||
# Whisk
|
||||
whisk_proposer_trackers=[WhiskTracker() for _ in range(WHISK_PROPOSER_TRACKERS_COUNT)], # [New in Whisk]
|
||||
whisk_candidate_trackers=[WhiskTracker() for _ in range(WHISK_CANDIDATE_TRACKERS_COUNT)], # [New in Whisk]
|
||||
whisk_trackers=whisk_trackers, # [New in Whisk]
|
||||
whisk_k_commitments=whisk_k_commitments, # [New in Whisk]
|
||||
)
|
||||
|
||||
# Initialize all validators with predictable commitments
|
||||
for val_index, pre_validator in enumerate(pre.validators):
|
||||
whisk_commitment, whisk_tracker = get_initial_commitments(get_unique_whisk_k(post, ValidatorIndex(val_index)))
|
||||
|
||||
post_validator = Validator(
|
||||
pubkey=pre_validator.pubkey,
|
||||
withdrawal_credentials=pre_validator.withdrawal_credentials,
|
||||
effective_balance=pre_validator.effective_balance,
|
||||
slashed=pre_validator.slashed,
|
||||
activation_eligibility_epoch=pre_validator.activation_eligibility_epoch,
|
||||
activation_epoch=pre_validator.activation_epoch,
|
||||
exit_epoch=pre_validator.exit_epoch,
|
||||
withdrawable_epoch=pre_validator.withdrawable_epoch,
|
||||
whisk_commitment=whisk_commitment,
|
||||
whisk_tracker=whisk_tracker,
|
||||
)
|
||||
post.validators.append(post_validator)
|
||||
|
||||
# Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day
|
||||
# Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection
|
||||
whisk_candidate_selection(post, epoch - WHISK_PROPOSER_SELECTION_GAP - 1)
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
- [Modified `slash_validator`](#modified-slash_validator)
|
||||
- [Block processing](#block-processing)
|
||||
- [Modified `process_attestation`](#modified-process_attestation)
|
||||
- [Modified `apply_deposit`](#modified-apply_deposit)
|
||||
- [Modified `add_validator_to_registry`](#modified-add_validator_to_registry)
|
||||
- [Sync aggregate processing](#sync-aggregate-processing)
|
||||
- [Epoch processing](#epoch-processing)
|
||||
- [Justification and finalization](#justification-and-finalization)
|
||||
@@ -508,40 +508,23 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
|
||||
```
|
||||
|
||||
#### Modified `apply_deposit`
|
||||
#### Modified `add_validator_to_registry`
|
||||
|
||||
*Note*: The function `apply_deposit` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`.
|
||||
*Note*: The function `add_validator_to_registry` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`.
|
||||
|
||||
```python
|
||||
def apply_deposit(state: BeaconState,
|
||||
pubkey: BLSPubkey,
|
||||
withdrawal_credentials: Bytes32,
|
||||
amount: uint64,
|
||||
signature: BLSSignature) -> None:
|
||||
validator_pubkeys = [validator.pubkey for validator in state.validators]
|
||||
if pubkey not in validator_pubkeys:
|
||||
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
deposit_message = DepositMessage(
|
||||
pubkey=pubkey,
|
||||
withdrawal_credentials=withdrawal_credentials,
|
||||
amount=amount,
|
||||
)
|
||||
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
|
||||
signing_root = compute_signing_root(deposit_message, domain)
|
||||
# Initialize validator if the deposit signature is valid
|
||||
if bls.Verify(pubkey, signing_root, signature):
|
||||
index = get_index_for_new_validator(state)
|
||||
validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
|
||||
set_or_append_list(state.validators, index, validator)
|
||||
set_or_append_list(state.balances, index, amount)
|
||||
# [New in Altair]
|
||||
set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.inactivity_scores, index, uint64(0))
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
increase_balance(state, index, amount)
|
||||
def add_validator_to_registry(state: BeaconState,
|
||||
pubkey: BLSPubkey,
|
||||
withdrawal_credentials: Bytes32,
|
||||
amount: uint64) -> None:
|
||||
index = get_index_for_new_validator(state)
|
||||
validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
|
||||
set_or_append_list(state.validators, index, validator)
|
||||
set_or_append_list(state.balances, index, amount)
|
||||
# [New in Altair]
|
||||
set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
set_or_append_list(state.inactivity_scores, index, uint64(0))
|
||||
```
|
||||
|
||||
#### Sync aggregate processing
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
- [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload)
|
||||
- [Block processing](#block-processing)
|
||||
- [Execution payload](#execution-payload)
|
||||
- [`process_execution_payload`](#process_execution_payload)
|
||||
- [Modified `process_execution_payload`](#modified-process_execution_payload)
|
||||
- [Modified `process_voluntary_exit`](#modified-process_voluntary_exit)
|
||||
- [Testing](#testing)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
@@ -42,7 +43,8 @@
|
||||
## Introduction
|
||||
|
||||
Deneb is a consensus-layer upgrade containing a number of features. Including:
|
||||
* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions scale data-availability of Ethereum in a simple, forwards-compatible manner.
|
||||
* [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions scale data-availability of Ethereum in a simple, forwards-compatible manner
|
||||
* [EIP-7044](https://github.com/ethereum/EIPs/pull/7044): Perpetually Valid Signed Voluntary Exits
|
||||
|
||||
## Custom types
|
||||
|
||||
@@ -221,7 +223,7 @@ def verify_and_notify_new_payload(self: ExecutionEngine,
|
||||
|
||||
#### Execution payload
|
||||
|
||||
##### `process_execution_payload`
|
||||
##### Modified `process_execution_payload`
|
||||
|
||||
```python
|
||||
def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None:
|
||||
@@ -266,6 +268,31 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi
|
||||
)
|
||||
```
|
||||
|
||||
#### Modified `process_voluntary_exit`
|
||||
|
||||
*Note*: The function `process_voluntary_exit` is modified to use the a fixed fork version -- `CAPELLA_FORK_VERSION` -- for EIP-7044
|
||||
|
||||
```python
|
||||
def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None:
|
||||
voluntary_exit = signed_voluntary_exit.message
|
||||
validator = state.validators[voluntary_exit.validator_index]
|
||||
# Verify the validator is active
|
||||
assert is_active_validator(validator, get_current_epoch(state))
|
||||
# Verify exit has not been initiated
|
||||
assert validator.exit_epoch == FAR_FUTURE_EPOCH
|
||||
# Exits must specify an epoch when they become valid; they are not valid before then
|
||||
assert get_current_epoch(state) >= voluntary_exit.epoch
|
||||
# Verify the validator has been active long enough
|
||||
assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD
|
||||
# Verify signature
|
||||
# [Modified in Deneb:EIP7044]
|
||||
domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root)
|
||||
signing_root = compute_signing_root(voluntary_exit, domain)
|
||||
assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
|
||||
# Initiate exit
|
||||
initiate_validator_exit(state, voluntary_exit.validator_index)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Deneb testing only.
|
||||
|
||||
@@ -1849,6 +1849,15 @@ def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes3
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
def add_validator_to_registry(state: BeaconState,
|
||||
pubkey: BLSPubkey,
|
||||
withdrawal_credentials: Bytes32,
|
||||
amount: uint64) -> None:
|
||||
state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount))
|
||||
state.balances.append(amount)
|
||||
```
|
||||
|
||||
```python
|
||||
def apply_deposit(state: BeaconState,
|
||||
pubkey: BLSPubkey,
|
||||
@@ -1865,12 +1874,8 @@ def apply_deposit(state: BeaconState,
|
||||
)
|
||||
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
|
||||
signing_root = compute_signing_root(deposit_message, domain)
|
||||
if not bls.Verify(pubkey, signing_root, signature):
|
||||
return
|
||||
|
||||
# Add validator and balance entries
|
||||
state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount))
|
||||
state.balances.append(amount)
|
||||
if bls.Verify(pubkey, signing_root, signature):
|
||||
add_validator_to_registry(state, pubkey, withdrawal_credentials, amount)
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
|
||||
@@ -55,7 +55,7 @@ It consists of four main sections:
|
||||
- [ENR structure](#enr-structure)
|
||||
- [Attestation subnet bitfield](#attestation-subnet-bitfield)
|
||||
- [`eth2` field](#eth2-field)
|
||||
- [Attestation subnet subcription](#attestation-subnet-subcription)
|
||||
- [Attestation subnet subscription](#attestation-subnet-subscription)
|
||||
- [Design decision rationale](#design-decision-rationale)
|
||||
- [Transport](#transport-1)
|
||||
- [Why are we defining specific transports?](#why-are-we-defining-specific-transports)
|
||||
@@ -1002,7 +1002,7 @@ Clients MAY connect to peers with the same `fork_digest` but a different `next_f
|
||||
Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients,
|
||||
these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`.
|
||||
|
||||
### Attestation subnet subcription
|
||||
### Attestation subnet subscription
|
||||
|
||||
Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each beacon node should:
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ from eth2spec.test.context import (
|
||||
spec_state_test,
|
||||
always_bls,
|
||||
with_bellatrix_and_later,
|
||||
with_phases,
|
||||
)
|
||||
from eth2spec.test.helpers.constants import (
|
||||
BELLATRIX,
|
||||
CAPELLA,
|
||||
)
|
||||
from eth2spec.test.helpers.keys import pubkey_to_privkey
|
||||
from eth2spec.test.helpers.state import (
|
||||
@@ -12,8 +17,10 @@ from eth2spec.test.helpers.voluntary_exits import (
|
||||
sign_voluntary_exit,
|
||||
)
|
||||
|
||||
BELLATRIX_AND_CAPELLA = [BELLATRIX, CAPELLA]
|
||||
|
||||
def _run_voluntary_exit_processing_test(
|
||||
|
||||
def run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version,
|
||||
@@ -51,7 +58,7 @@ def _run_voluntary_exit_processing_test(
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_voluntary_exit_with_current_fork_version_is_before_fork_epoch(spec, state):
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.current_version,
|
||||
@@ -60,11 +67,11 @@ def test_invalid_voluntary_exit_with_current_fork_version_is_before_fork_epoch(s
|
||||
)
|
||||
|
||||
|
||||
@with_bellatrix_and_later
|
||||
@with_phases(BELLATRIX_AND_CAPELLA)
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state):
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.current_version,
|
||||
@@ -72,13 +79,13 @@ def test_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec,
|
||||
)
|
||||
|
||||
|
||||
@with_bellatrix_and_later
|
||||
@with_phases([BELLATRIX, CAPELLA])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state):
|
||||
assert state.fork.previous_version != state.fork.current_version
|
||||
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
@@ -86,13 +93,13 @@ def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, st
|
||||
)
|
||||
|
||||
|
||||
@with_bellatrix_and_later
|
||||
@with_phases(BELLATRIX_AND_CAPELLA)
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state):
|
||||
assert state.fork.previous_version != state.fork.current_version
|
||||
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
@@ -107,7 +114,7 @@ def test_invalid_voluntary_exit_with_previous_fork_version_not_is_before_fork_ep
|
||||
def test_invalid_voluntary_exit_with_genesis_fork_version_is_before_fork_epoch(spec, state):
|
||||
assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version)
|
||||
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=spec.config.GENESIS_FORK_VERSION,
|
||||
@@ -122,7 +129,7 @@ def test_invalid_voluntary_exit_with_genesis_fork_version_is_before_fork_epoch(s
|
||||
def test_invalid_voluntary_exit_with_genesis_fork_version_not_is_before_fork_epoch(spec, state):
|
||||
assert spec.config.GENESIS_FORK_VERSION not in (state.fork.previous_version, state.fork.current_version)
|
||||
|
||||
yield from _run_voluntary_exit_processing_test(
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=spec.config.GENESIS_FORK_VERSION,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
from eth2spec.test.context import (
|
||||
always_bls,
|
||||
spec_state_test,
|
||||
with_deneb_and_later,
|
||||
)
|
||||
from eth2spec.test.helpers.constants import (
|
||||
DENEB,
|
||||
)
|
||||
from eth2spec.test.bellatrix.block_processing.test_process_voluntary_exit import (
|
||||
run_voluntary_exit_processing_test,
|
||||
)
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state):
|
||||
"""
|
||||
Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION`
|
||||
"""
|
||||
assert state.fork.current_version != spec.config.CAPELLA_FORK_VERSION
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.current_version,
|
||||
is_before_fork_epoch=False,
|
||||
valid=False,
|
||||
)
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state):
|
||||
"""
|
||||
Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION`
|
||||
|
||||
Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks
|
||||
"""
|
||||
assert state.fork.previous_version != state.fork.current_version
|
||||
|
||||
if spec.fork == DENEB:
|
||||
assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
is_before_fork_epoch=False,
|
||||
)
|
||||
else:
|
||||
assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
is_before_fork_epoch=False,
|
||||
valid=False,
|
||||
)
|
||||
|
||||
|
||||
@with_deneb_and_later
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state):
|
||||
"""
|
||||
Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION`
|
||||
|
||||
Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks
|
||||
"""
|
||||
assert state.fork.previous_version != state.fork.current_version
|
||||
|
||||
if spec.fork == DENEB:
|
||||
assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
is_before_fork_epoch=True,
|
||||
)
|
||||
else:
|
||||
assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION
|
||||
yield from run_voluntary_exit_processing_test(
|
||||
spec,
|
||||
state,
|
||||
fork_version=state.fork.previous_version,
|
||||
is_before_fork_epoch=True,
|
||||
valid=False,
|
||||
)
|
||||
@@ -1,29 +1,31 @@
|
||||
from random import Random
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.test.context import expect_assertion_error
|
||||
from eth2spec.test.helpers.forks import is_post_deneb
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
|
||||
|
||||
def prepare_signed_exits(spec, state, indices, fork_version=None):
|
||||
if fork_version is None:
|
||||
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT)
|
||||
else:
|
||||
domain = spec.compute_domain(spec.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesis_validators_root)
|
||||
|
||||
def create_signed_exit(index):
|
||||
exit = spec.VoluntaryExit(
|
||||
voluntary_exit = spec.VoluntaryExit(
|
||||
epoch=spec.get_current_epoch(state),
|
||||
validator_index=index,
|
||||
)
|
||||
signing_root = spec.compute_signing_root(exit, domain)
|
||||
return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root))
|
||||
return sign_voluntary_exit(spec, state, voluntary_exit, privkeys[index], fork_version=fork_version)
|
||||
|
||||
return [create_signed_exit(index) for index in indices]
|
||||
|
||||
|
||||
def sign_voluntary_exit(spec, state, voluntary_exit, privkey, fork_version=None):
|
||||
if fork_version is None:
|
||||
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
|
||||
if is_post_deneb(spec):
|
||||
domain = spec.compute_domain(
|
||||
spec.DOMAIN_VOLUNTARY_EXIT,
|
||||
spec.config.CAPELLA_FORK_VERSION,
|
||||
state.genesis_validators_root,
|
||||
)
|
||||
else:
|
||||
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
|
||||
else:
|
||||
domain = spec.compute_domain(spec.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesis_validators_root)
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ if __name__ == "__main__":
|
||||
|
||||
_new_deneb_mods = {key: 'eth2spec.test.deneb.block_processing.test_process_' + key for key in [
|
||||
'execution_payload',
|
||||
'voluntary_exit',
|
||||
]}
|
||||
deneb_mods = combine_mods(_new_deneb_mods, capella_mods)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user