Restoring chunk challenges and testing
This commit is contained in:
parent
907c56dabd
commit
ab2ee0e2c2
2
Makefile
2
Makefile
|
@ -71,7 +71,7 @@ pyspec:
|
||||||
|
|
||||||
# installs the packages to run pyspec tests
|
# installs the packages to run pyspec tests
|
||||||
install_test:
|
install_test:
|
||||||
python3 -m venv venv; . venv/bin/activate; pip3 install .[test] .[lint]
|
python3.8 -m venv venv; . venv/bin/activate; pip3 install .[lint]; pip3 install -e .[test]
|
||||||
|
|
||||||
test: pyspec
|
test: pyspec
|
||||||
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
||||||
|
|
|
@ -205,9 +205,9 @@ RANDAO_PENALTY_EPOCHS: 2
|
||||||
# [customized] quicker for testing
|
# [customized] quicker for testing
|
||||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
||||||
# 2**11 (= 2,048) epochs
|
# 2**11 (= 2,048) epochs
|
||||||
EPOCHS_PER_CUSTODY_PERIOD: 2048
|
EPOCHS_PER_CUSTODY_PERIOD: 8
|
||||||
# 2**11 (= 2,048) epochs
|
# 2**11 (= 2,048) epochs
|
||||||
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
|
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
|
||||||
# 2**7 (= 128) epochs
|
# 2**7 (= 128) epochs
|
||||||
MAX_REVEAL_LATENESS_DECREMENT: 128
|
MAX_REVEAL_LATENESS_DECREMENT: 128
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -121,7 +121,7 @@ from lru import LRU
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
from eth2spec.utils.ssz.ssz_typing import (
|
from eth2spec.utils.ssz.ssz_typing import (
|
||||||
View, boolean, Container, List, Vector, uint64, uint8, bit,
|
View, boolean, Container, List, Vector, uint64, uint8, bit,
|
||||||
ByteList, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
|
ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
|
||||||
)
|
)
|
||||||
from eth2spec.utils import bls
|
from eth2spec.utils import bls
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,99 @@ Configuration is not namespaced. Instead it is strictly an extension;
|
||||||
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
|
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
|
||||||
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
|
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
|
||||||
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
|
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
|
||||||
|
| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) |
|
||||||
|
| `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes |
|
||||||
|
| `MAX_CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## New containers
|
||||||
|
|
||||||
|
### `CustodyChunkChallenge`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustodyChunkChallenge(Container):
|
||||||
|
responder_index: ValidatorIndex
|
||||||
|
shard_transition: ShardTransition
|
||||||
|
attestation: Attestation
|
||||||
|
data_index: uint64
|
||||||
|
chunk_index: uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
### `CustodyChunkChallengeRecord`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustodyChunkChallengeRecord(Container):
|
||||||
|
challenge_index: uint64
|
||||||
|
challenger_index: ValidatorIndex
|
||||||
|
responder_index: ValidatorIndex
|
||||||
|
inclusion_epoch: Epoch
|
||||||
|
data_root: Root
|
||||||
|
depth: uint64
|
||||||
|
chunk_index: uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CustodyChunkResponse`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustodyChunkResponse(Container):
|
||||||
|
challenge_index: uint64
|
||||||
|
chunk_index: uint64
|
||||||
|
chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
|
||||||
|
branch: List[Root, MAX_CUSTODY_RESPONSE_DEPTH]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CustodySlashing`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustodySlashing(Container):
|
||||||
|
# Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check.
|
||||||
|
# (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data.
|
||||||
|
data_index: uint64
|
||||||
|
malefactor_index: ValidatorIndex
|
||||||
|
malefactor_secret: BLSSignature
|
||||||
|
whistleblower_index: ValidatorIndex
|
||||||
|
shard_transition: ShardTransition
|
||||||
|
attestation: Attestation
|
||||||
|
data: ByteList[MAX_SHARD_BLOCK_SIZE]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `SignedCustodySlashing`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SignedCustodySlashing(Container):
|
||||||
|
message: CustodySlashing
|
||||||
|
signature: BLSSignature
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### `CustodyKeyReveal`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CustodyKeyReveal(Container):
|
||||||
|
# Index of the validator whose key is being revealed
|
||||||
|
revealer_index: ValidatorIndex
|
||||||
|
# Reveal (masked signature)
|
||||||
|
reveal: BLSSignature
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `EarlyDerivedSecretReveal`
|
||||||
|
|
||||||
|
Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain).
|
||||||
|
|
||||||
|
```python
|
||||||
|
class EarlyDerivedSecretReveal(Container):
|
||||||
|
# Index of the validator whose key is being revealed
|
||||||
|
revealed_index: ValidatorIndex
|
||||||
|
# RANDAO epoch of the key that is being revealed
|
||||||
|
epoch: Epoch
|
||||||
|
# Reveal (masked signature)
|
||||||
|
reveal: BLSSignature
|
||||||
|
# Index of the validator who revealed (whistleblower)
|
||||||
|
masker_index: ValidatorIndex
|
||||||
|
# Mask used to hide the actual reveal signature (prevent reveal from being stolen)
|
||||||
|
mask: Bytes32
|
||||||
|
```
|
||||||
|
|
||||||
## Updated containers
|
## Updated containers
|
||||||
|
|
||||||
|
@ -285,6 +378,8 @@ class BeaconState(Container):
|
||||||
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
|
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
|
||||||
exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH],
|
exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH],
|
||||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
|
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
|
||||||
|
custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS]
|
||||||
|
custody_chunk_challenge_index: uint64
|
||||||
```
|
```
|
||||||
|
|
||||||
## New containers
|
## New containers
|
||||||
|
|
|
@ -65,6 +65,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
||||||
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
|
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||||
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
|
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||||
| `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours |
|
| `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours |
|
||||||
|
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||||
|
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days |
|
||||||
|
| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||||
|
|
||||||
### Max operations per block
|
### Max operations per block
|
||||||
|
|
||||||
|
@ -72,6 +75,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
||||||
| - | - |
|
| - | - |
|
||||||
| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) |
|
| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) |
|
||||||
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` |
|
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` |
|
||||||
|
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) |
|
||||||
| `MAX_CUSTODY_SLASHINGS` | `1` |
|
| `MAX_CUSTODY_SLASHINGS` | `1` |
|
||||||
|
|
||||||
### Reward and penalty quotients
|
### Reward and penalty quotients
|
||||||
|
@ -93,61 +97,20 @@ The following types are defined, mapping into `DomainType` (little endian):
|
||||||
|
|
||||||
### New Beacon Chain operations
|
### New Beacon Chain operations
|
||||||
|
|
||||||
#### `CustodySlashing`
|
|
||||||
|
|
||||||
```python
|
|
||||||
class CustodySlashing(Container):
|
|
||||||
# Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check.
|
|
||||||
# (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data.
|
|
||||||
data_index: uint64
|
|
||||||
malefactor_index: ValidatorIndex
|
|
||||||
malefactor_secret: BLSSignature
|
|
||||||
whistleblower_index: ValidatorIndex
|
|
||||||
shard_transition: ShardTransition
|
|
||||||
attestation: Attestation
|
|
||||||
data: ByteList[MAX_SHARD_BLOCK_SIZE]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `SignedCustodySlashing`
|
|
||||||
|
|
||||||
```python
|
|
||||||
class SignedCustodySlashing(Container):
|
|
||||||
message: CustodySlashing
|
|
||||||
signature: BLSSignature
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### `CustodyKeyReveal`
|
|
||||||
|
|
||||||
```python
|
|
||||||
class CustodyKeyReveal(Container):
|
|
||||||
# Index of the validator whose key is being revealed
|
|
||||||
revealer_index: ValidatorIndex
|
|
||||||
# Reveal (masked signature)
|
|
||||||
reveal: BLSSignature
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `EarlyDerivedSecretReveal`
|
|
||||||
|
|
||||||
Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain).
|
|
||||||
|
|
||||||
```python
|
|
||||||
class EarlyDerivedSecretReveal(Container):
|
|
||||||
# Index of the validator whose key is being revealed
|
|
||||||
revealed_index: ValidatorIndex
|
|
||||||
# RANDAO epoch of the key that is being revealed
|
|
||||||
epoch: Epoch
|
|
||||||
# Reveal (masked signature)
|
|
||||||
reveal: BLSSignature
|
|
||||||
# Index of the validator who revealed (whistleblower)
|
|
||||||
masker_index: ValidatorIndex
|
|
||||||
# Mask used to hide the actual reveal signature (prevent reveal from being stolen)
|
|
||||||
mask: Bytes32
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
|
### `replace_empty_or_append`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def replace_empty_or_append(list: List, new_element: Any) -> int:
|
||||||
|
for i in range(len(list)):
|
||||||
|
if list[i] == empty(typeof(new_element)):
|
||||||
|
list[i] = new_element
|
||||||
|
return i
|
||||||
|
list.append(new_element)
|
||||||
|
return len(list) - 1
|
||||||
|
```
|
||||||
|
|
||||||
### `legendre_bit`
|
### `legendre_bit`
|
||||||
|
|
||||||
Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this.
|
Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this.
|
||||||
|
@ -248,6 +211,84 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) -
|
||||||
for_ops(body.custody_slashings, process_custody_slashing)
|
for_ops(body.custody_slashings, process_custody_slashing)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Chunk challenges
|
||||||
|
|
||||||
|
Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`.
|
||||||
|
|
||||||
|
For each `challenge` in `block.body.custody_chunk_challenges`, run the following function:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None:
|
||||||
|
# Verify the attestation
|
||||||
|
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation))
|
||||||
|
# Verify it is not too late to challenge
|
||||||
|
assert (challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY
|
||||||
|
>= get_current_epoch(state))
|
||||||
|
responder = state.validators[challenge.responder_index]
|
||||||
|
assert (responder.exit_epoch == FAR_FUTURE_EPOCH
|
||||||
|
or responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY >= get_current_epoch(state))
|
||||||
|
# Verify responder is slashable
|
||||||
|
assert is_slashable_validator(responder, get_current_epoch(state))
|
||||||
|
# Verify the responder participated in the attestation
|
||||||
|
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits)
|
||||||
|
assert challenge.responder_index in attesters
|
||||||
|
# Verify shard transition is correctly given
|
||||||
|
assert hash_tree_root(challenge.shard_transition) == challenge.attestation.data.shard_transition_root
|
||||||
|
data_root = challenge.shard_transition.shard_data_roots[challenge.data_index]
|
||||||
|
# Verify the challenge is not a duplicate
|
||||||
|
for record in state.custody_chunk_challenge_records:
|
||||||
|
assert (
|
||||||
|
record.data_root != challenge.attestation.data.crosslink.data_root or
|
||||||
|
record.chunk_index != challenge.chunk_index
|
||||||
|
)
|
||||||
|
# Verify depth
|
||||||
|
transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK
|
||||||
|
depth = ceillog2(transition_chunks)
|
||||||
|
assert challenge.chunk_index < transition_chunks
|
||||||
|
# Add new chunk challenge record
|
||||||
|
new_record = CustodyChunkChallengeRecord(
|
||||||
|
challenge_index=state.custody_chunk_challenge_index,
|
||||||
|
challenger_index=get_beacon_proposer_index(state),
|
||||||
|
responder_index=challenge.responder_index,
|
||||||
|
inclusion_epoch=get_current_epoch(state),
|
||||||
|
data_root=challenge.shard_transition.shard_data_roots[challenge.data_index],
|
||||||
|
depth=depth,
|
||||||
|
chunk_index=challenge.chunk_index,
|
||||||
|
)
|
||||||
|
replace_empty_or_append(state.custody_chunk_challenge_records, new_record)
|
||||||
|
|
||||||
|
state.custody_chunk_challenge_index += 1
|
||||||
|
# Postpone responder withdrawability
|
||||||
|
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custody chunk response
|
||||||
|
|
||||||
|
```python
|
||||||
|
def process_chunk_challenge_response(state: BeaconState,
|
||||||
|
response: CustodyChunkResponse) -> None:
|
||||||
|
|
||||||
|
challenge = next((record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index), None)
|
||||||
|
assert(challenge is not None)
|
||||||
|
|
||||||
|
# Verify chunk index
|
||||||
|
assert response.chunk_index == challenge.chunk_index
|
||||||
|
# Verify the chunk matches the crosslink data root
|
||||||
|
assert is_valid_merkle_branch(
|
||||||
|
leaf=hash_tree_root(response.chunk),
|
||||||
|
branch=response.branch,
|
||||||
|
depth=challenge.depth,
|
||||||
|
index=response.chunk_index,
|
||||||
|
root=challenge.data_root,
|
||||||
|
)
|
||||||
|
# Clear the challenge
|
||||||
|
records = state.custody_chunk_challenge_records
|
||||||
|
records[records.index(challenge)] = CustodyChunkChallengeRecord()
|
||||||
|
# Reward the proposer
|
||||||
|
proposer_index = get_beacon_proposer_index(state)
|
||||||
|
increase_balance(state, proposer_index, get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)
|
||||||
|
```
|
||||||
|
|
||||||
#### Custody key reveals
|
#### Custody key reveals
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -431,14 +472,22 @@ Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`:
|
||||||
def process_reveal_deadlines(state: BeaconState) -> None:
|
def process_reveal_deadlines(state: BeaconState) -> None:
|
||||||
epoch = get_current_epoch(state)
|
epoch = get_current_epoch(state)
|
||||||
for index, validator in enumerate(state.validators):
|
for index, validator in enumerate(state.validators):
|
||||||
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal:
|
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD):
|
||||||
# ------------------ WARNING ----------------------- #
|
slash_validator(state, ValidatorIndex(index))
|
||||||
# UNSAFE REMOVAL OF SLASHING TO PRIORITIZE PHASE 0 CI #
|
```
|
||||||
# Must find generic way to handle key reveals in tests #
|
|
||||||
# ---------------------------------------------------- #
|
|
||||||
|
|
||||||
# slash_validator(state, ValidatorIndex(index))
|
Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`:
|
||||||
pass
|
|
||||||
|
```python
|
||||||
|
# begin insert @process_challenge_deadlines
|
||||||
|
process_challenge_deadlines(state)
|
||||||
|
# end insert @process_challenge_deadlines
|
||||||
|
def process_challenge_deadlines(state: BeaconState) -> None:
|
||||||
|
for custody_chunk_challenge in state.custody_chunk_challenge_records:
|
||||||
|
if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
|
||||||
|
slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index)
|
||||||
|
records = state.custody_chunk_challenge_records
|
||||||
|
records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Final updates
|
### Final updates
|
||||||
|
|
|
@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
|
||||||
yield 'post', state
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
def build_attestation_data(spec, state, slot, index):
|
def build_attestation_data(spec, state, slot, index, shard_transition_root=None):
|
||||||
assert state.slot >= slot
|
assert state.slot >= slot
|
||||||
|
|
||||||
if slot == state.slot:
|
if slot == state.slot:
|
||||||
|
@ -72,6 +72,7 @@ def build_attestation_data(spec, state, slot, index):
|
||||||
beacon_block_root=block_root,
|
beacon_block_root=block_root,
|
||||||
source=spec.Checkpoint(epoch=source_epoch, root=source_root),
|
source=spec.Checkpoint(epoch=source_epoch, root=source_root),
|
||||||
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
|
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
|
||||||
|
shard_transition_root=shard_transition_root,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False)
|
||||||
return attestation
|
return attestation
|
||||||
|
|
||||||
|
|
||||||
def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False):
|
def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None):
|
||||||
'''
|
'''
|
||||||
Construct on-time attestation for next slot
|
Construct on-time attestation for next slot
|
||||||
'''
|
'''
|
||||||
|
@ -98,10 +99,10 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True)
|
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition_root=shard_transition_root)
|
||||||
|
|
||||||
|
|
||||||
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False):
|
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None):
|
||||||
'''
|
'''
|
||||||
Construct on-time attestation for next slot
|
Construct on-time attestation for next slot
|
||||||
'''
|
'''
|
||||||
|
@ -110,16 +111,16 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False)
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False)
|
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition_root=shard_transition_root)
|
||||||
|
|
||||||
|
|
||||||
def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True):
|
def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition_root=None):
|
||||||
if slot is None:
|
if slot is None:
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
attestation_data = build_attestation_data(spec, state, slot, index)
|
attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=shard_transition_root)
|
||||||
|
|
||||||
beacon_committee = spec.get_beacon_committee(
|
beacon_committee = spec.get_beacon_committee(
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from eth2spec.test.helpers.keys import privkeys
|
from eth2spec.test.helpers.keys import privkeys
|
||||||
from eth2spec.utils import bls
|
from eth2spec.utils import bls
|
||||||
from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector
|
from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
|
from eth2spec.utils.merkle_minimal import get_merkle_root, get_merkle_tree, get_merkle_proof
|
||||||
from remerkleable.core import pack_bits_to_chunks
|
from remerkleable.core import pack_bits_to_chunks
|
||||||
from remerkleable.tree import subtree_fill_to_contents, get_depth
|
from remerkleable.tree import subtree_fill_to_contents, get_depth
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ def bitlist_from_int(max_len, num_bits, n):
|
||||||
return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)])
|
return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)])
|
||||||
|
|
||||||
|
|
||||||
def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False):
|
def get_valid_custody_slashing(spec, state, attestation, invalid_custody_bit=False):
|
||||||
beacon_committee = spec.get_beacon_committee(
|
beacon_committee = spec.get_beacon_committee(
|
||||||
state,
|
state,
|
||||||
attestation.data.slot,
|
attestation.data.slot,
|
||||||
|
@ -96,21 +96,39 @@ def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_valid_chunk_challenge(spec, state, attestation, shard_transition):
|
||||||
|
shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
|
||||||
|
crosslink_committee = spec.get_beacon_committee(
|
||||||
|
state,
|
||||||
|
attestation.data.slot,
|
||||||
|
attestation.data.index
|
||||||
|
)
|
||||||
|
responder_index = crosslink_committee[0]
|
||||||
|
data_index = len(shard_transition.shard_block_lengths) - 1
|
||||||
|
|
||||||
|
chunk_count = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK
|
||||||
|
|
||||||
|
return spec.CustodyChunkChallenge(
|
||||||
|
responder_index=responder_index,
|
||||||
|
attestation=attestation,
|
||||||
|
chunk_index=chunk_count - 1,
|
||||||
|
data_index=data_index,
|
||||||
|
shard_transition=shard_transition,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def custody_chunkify(spec, x):
|
def custody_chunkify(spec, x):
|
||||||
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
|
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
|
||||||
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
|
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False):
|
def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index,
|
||||||
|
invalid_chunk_data=False):
|
||||||
|
custody_data = get_custody_test_vector(block_length)
|
||||||
chunks = custody_chunkify(spec, custody_data)
|
chunks = custody_chunkify(spec, custody_data)
|
||||||
|
|
||||||
chunk_index = len(chunks) - 1
|
chunk_index = chunk_challenge.chunk_index
|
||||||
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
|
|
||||||
|
|
||||||
while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit:
|
|
||||||
chunk_index -= 1
|
|
||||||
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
|
|
||||||
|
|
||||||
chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
|
chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
|
||||||
chunks_hash_tree_roots += [
|
chunks_hash_tree_roots += [
|
||||||
|
@ -120,31 +138,29 @@ def get_valid_custody_response(spec, state, bit_challenge, custody_data, challen
|
||||||
|
|
||||||
data_branch = get_merkle_proof(data_tree, chunk_index)
|
data_branch = get_merkle_proof(data_tree, chunk_index)
|
||||||
|
|
||||||
bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK
|
return spec.CustodyChunkResponse(
|
||||||
print(bitlist_chunk_index)
|
|
||||||
bitlist_chunk_nodes = pack_bits_to_chunks(bit_challenge.chunk_bits)
|
|
||||||
bitlist_tree = subtree_fill_to_contents(bitlist_chunk_nodes, get_depth(spec.MAX_CUSTODY_CHUNKS))
|
|
||||||
print(bitlist_tree)
|
|
||||||
bitlist_chunk_branch = None # TODO; extract proof from merkle tree
|
|
||||||
|
|
||||||
bitlist_chunk_index = chunk_index // 256
|
|
||||||
|
|
||||||
chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256:
|
|
||||||
(bitlist_chunk_index + 1) * 256])
|
|
||||||
|
|
||||||
return spec.CustodyResponse(
|
|
||||||
challenge_index=challenge_index,
|
challenge_index=challenge_index,
|
||||||
chunk_index=chunk_index,
|
chunk_index=chunk_index,
|
||||||
chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
|
chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
|
||||||
data_branch=data_branch,
|
branch=data_branch,
|
||||||
chunk_bits_branch=bitlist_chunk_branch,
|
|
||||||
chunk_bits_leaf=chunk_bits_leaf,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_custody_test_vector(bytelength):
|
def get_custody_test_vector(bytelength):
|
||||||
ints = bytelength // 4
|
ints = bytelength // 4 + 1
|
||||||
return b"".join(i.to_bytes(4, "little") for i in range(ints))
|
return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength]
|
||||||
|
|
||||||
|
|
||||||
|
def get_shard_transition(spec, start_slot, block_lengths):
|
||||||
|
b = [hash_tree_root(ByteVector[x](get_custody_test_vector(x))) for x in block_lengths]
|
||||||
|
shard_transition = spec.ShardTransition(
|
||||||
|
start_slot=start_slot,
|
||||||
|
shard_block_lengths=block_lengths,
|
||||||
|
shard_data_roots=b,
|
||||||
|
shard_states=[spec.Root() for x in block_lengths],
|
||||||
|
proposer_signature_aggregate=spec.BLSSignature(),
|
||||||
|
)
|
||||||
|
return shard_transition
|
||||||
|
|
||||||
|
|
||||||
def get_custody_merkle_root(data):
|
def get_custody_merkle_root(data):
|
||||||
|
|
|
@ -5,21 +5,23 @@ from eth2spec.test.helpers.keys import privkeys
|
||||||
import eth2spec.test.helpers.attestations as phase0_attestations
|
import eth2spec.test.helpers.attestations as phase0_attestations
|
||||||
|
|
||||||
|
|
||||||
def get_valid_on_time_attestation(spec, state, index=None, signed=False):
|
def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_transition_root=None):
|
||||||
'''
|
'''
|
||||||
Construct on-time attestation for next slot
|
Construct on-time attestation for next slot
|
||||||
'''
|
'''
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False)
|
attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False, shard_transition_root=shard_transition_root)
|
||||||
shard = spec.get_shard(state, attestation)
|
shard = spec.get_shard(state, attestation)
|
||||||
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
|
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
|
||||||
|
print(offset_slots)
|
||||||
|
|
||||||
for _ in offset_slots:
|
for _ in offset_slots:
|
||||||
attestation.custody_bits_blocks.append(
|
attestation.custody_bits_blocks.append(
|
||||||
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
||||||
)
|
)
|
||||||
|
print(len(attestation.custody_bits_blocks))
|
||||||
|
|
||||||
if signed:
|
if signed:
|
||||||
sign_attestation(spec, state, attestation)
|
sign_attestation(spec, state, attestation)
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
from eth2spec.test.helpers.custody import (
|
||||||
|
get_valid_chunk_challenge,
|
||||||
|
get_valid_custody_chunk_response,
|
||||||
|
get_custody_test_vector,
|
||||||
|
get_custody_merkle_root,
|
||||||
|
get_shard_transition,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.attestations import (
|
||||||
|
get_valid_on_time_attestation,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.state import transition_to
|
||||||
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
expect_assertion_error,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
|
||||||
|
|
||||||
|
|
||||||
|
def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_chunk_challenge``, yielding:
|
||||||
|
- pre-state ('pre')
|
||||||
|
- CustodyBitChallenge ('custody_chunk_challenge')
|
||||||
|
- post-state ('post').
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
yield 'pre', state
|
||||||
|
yield 'custody_chunk_challenge', custody_chunk_challenge
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
expect_assertion_error(lambda: spec.custody_chunk_challenge(state, custody_chunk_challenge))
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
spec.process_chunk_challenge(state, custody_chunk_challenge)
|
||||||
|
|
||||||
|
assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].responder_index == \
|
||||||
|
custody_chunk_challenge.responder_index
|
||||||
|
assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].chunk_index == \
|
||||||
|
custody_chunk_challenge.chunk_index
|
||||||
|
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
|
def run_custody_chunk_response_processing(spec, state, custody_response, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_chunk_challenge_response``, yielding:
|
||||||
|
- pre-state ('pre')
|
||||||
|
- CustodyResponse ('custody_response')
|
||||||
|
- post-state ('post').
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
yield 'pre', state
|
||||||
|
yield 'custody_response', custody_response
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
spec.process_chunk_challenge_response(state, custody_response)
|
||||||
|
|
||||||
|
assert state.custody_chunk_challenge_records[custody_response.challenge_index] == spec.CustodyChunkChallengeRecord()
|
||||||
|
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_challenge_appended(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + 1)
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_chunks = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK
|
||||||
|
test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[data_index])
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD)
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
yield from run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_multiple_epochs_custody(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
yield from run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_many_epochs_custody(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
yield from run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_off_chain_attestation(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
yield from run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
chunk_challenge_index = state.custody_chunk_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_chunk_response_processing(spec, state, custody_response)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response_multiple_epochs(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
chunk_challenge_index = state.custody_chunk_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_chunk_response_processing(spec, state, custody_response)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response_many_epochs(spec, state):
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100)
|
||||||
|
|
||||||
|
shard = 0
|
||||||
|
offset_slots = spec.get_offset_slots(state, shard)
|
||||||
|
shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||||
|
data_index = 0
|
||||||
|
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition))
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||||
|
|
||||||
|
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
chunk_challenge_index = state.custody_chunk_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_chunk_response_processing(spec, state, custody_response)
|
|
@ -0,0 +1,349 @@
|
||||||
|
from eth2spec.test.helpers.custody import (
|
||||||
|
get_valid_bit_challenge,
|
||||||
|
get_valid_custody_bit_response,
|
||||||
|
get_custody_test_vector,
|
||||||
|
get_custody_merkle_root,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.attestations import (
|
||||||
|
get_valid_attestation,
|
||||||
|
)
|
||||||
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
|
from eth2spec.test.helpers.state import next_epoch, get_balance
|
||||||
|
from eth2spec.test.helpers.block import apply_empty_block
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
expect_assertion_error,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
|
||||||
|
|
||||||
|
|
||||||
|
def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_bit_challenge``, yielding:
|
||||||
|
- pre-state ('pre')
|
||||||
|
- CustodyBitChallenge ('custody_bit_challenge')
|
||||||
|
- post-state ('post').
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
yield 'pre', state
|
||||||
|
yield 'custody_bit_challenge', custody_bit_challenge
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge))
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
spec.process_bit_challenge(state, custody_bit_challenge)
|
||||||
|
|
||||||
|
assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].chunk_bits_merkle_root == \
|
||||||
|
hash_tree_root(custody_bit_challenge.chunk_bits)
|
||||||
|
assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].challenger_index == \
|
||||||
|
custody_bit_challenge.challenger_index
|
||||||
|
assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].responder_index == \
|
||||||
|
custody_bit_challenge.responder_index
|
||||||
|
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
|
def run_custody_bit_response_processing(spec, state, custody_response, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_bit_challenge_response``, yielding:
|
||||||
|
- pre-state ('pre')
|
||||||
|
- CustodyResponse ('custody_response')
|
||||||
|
- post-state ('post').
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
yield 'pre', state
|
||||||
|
yield 'custody_response', custody_response
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
challenge = state.custody_bit_challenge_records[custody_response.challenge_index]
|
||||||
|
pre_slashed_balance = get_balance(state, challenge.challenger_index)
|
||||||
|
|
||||||
|
spec.process_custody_response(state, custody_response)
|
||||||
|
|
||||||
|
slashed_validator = state.validators[challenge.challenger_index]
|
||||||
|
|
||||||
|
assert slashed_validator.slashed
|
||||||
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
assert get_balance(state, challenge.challenger_index) < pre_slashed_balance
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_challenge_appended(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_multiple_epochs_custody(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH * 3
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_many_epochs_custody(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH * 100
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_off_chain_attestation(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_invalid_custody_bit_challenge(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge, valid=False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_max_reveal_lateness_1(spec, state):
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
responder_index = challenge.responder_index
|
||||||
|
target_epoch = attestation.data.target.epoch
|
||||||
|
|
||||||
|
state.validators[responder_index].max_reveal_lateness = 3
|
||||||
|
|
||||||
|
latest_reveal_epoch = spec.get_randao_epoch_for_custody_period(
|
||||||
|
spec.get_custody_period_for_validator(state, responder_index, target_epoch),
|
||||||
|
responder_index
|
||||||
|
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < latest_reveal_epoch - 2:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_max_reveal_lateness_2(spec, state):
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
responder_index = challenge.responder_index
|
||||||
|
|
||||||
|
state.validators[responder_index].max_reveal_lateness = 3
|
||||||
|
|
||||||
|
for i in range(spec.get_randao_epoch_for_custody_period(
|
||||||
|
spec.get_custody_period_for_validator(state, responder_index),
|
||||||
|
responder_index
|
||||||
|
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1):
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
yield from run_bit_challenge_processing(spec, state, challenge, False)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
bit_challenge_index = state.custody_bit_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_bit_response_processing(spec, state, custody_response)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response_multiple_epochs(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH * 3
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
bit_challenge_index = state.custody_bit_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_bit_response_processing(spec, state, custody_response)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_custody_response_many_epochs(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH * 100
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
bit_challenge_index = state.custody_bit_challenge_index - 1
|
||||||
|
|
||||||
|
custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||||
|
|
||||||
|
yield from run_custody_bit_response_processing(spec, state, custody_response)
|
|
@ -0,0 +1,319 @@
|
||||||
|
from eth2spec.test.helpers.custody import (
|
||||||
|
get_valid_bit_challenge,
|
||||||
|
get_valid_chunk_challenge,
|
||||||
|
get_valid_custody_bit_response,
|
||||||
|
get_valid_custody_chunk_response,
|
||||||
|
get_valid_custody_key_reveal,
|
||||||
|
get_custody_test_vector,
|
||||||
|
get_custody_merkle_root,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.attestations import (
|
||||||
|
get_valid_attestation,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.state import next_epoch
|
||||||
|
from eth2spec.test.helpers.block import apply_empty_block
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
|
||||||
|
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
||||||
|
|
||||||
|
from eth2spec.test.phase_1.block_processing.test_process_bit_challenge import (
|
||||||
|
run_bit_challenge_processing,
|
||||||
|
run_custody_bit_response_processing,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import (
|
||||||
|
run_chunk_challenge_processing,
|
||||||
|
run_custody_chunk_response_processing,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
|
||||||
|
|
||||||
|
|
||||||
|
def run_process_final_custody_updates(spec, state):
|
||||||
|
yield from run_epoch_processing_with(spec, state, 'process_final_custody_updates')
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_delay(spec, state):
|
||||||
|
spec.initiate_validator_exit(state, 0)
|
||||||
|
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
|
||||||
|
spec.initiate_validator_exit(state, 0)
|
||||||
|
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < state.validators[0].exit_epoch:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
while (state.validators[0].next_custody_secret_to_reveal
|
||||||
|
<= spec.get_custody_period_for_validator(state, 0, state.validators[0].exit_epoch - 1)):
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0)
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_suspend_after_bit_challenge(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
validator_index = spec.get_crosslink_committee(
|
||||||
|
state,
|
||||||
|
attestation.data.target.epoch,
|
||||||
|
attestation.data.crosslink.shard
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
spec.initiate_validator_exit(state, validator_index)
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||||
|
<= spec.get_custody_period_for_validator(
|
||||||
|
state,
|
||||||
|
validator_index,
|
||||||
|
state.validators[validator_index].exit_epoch - 1)):
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
validator_index = spec.get_crosslink_committee(
|
||||||
|
state,
|
||||||
|
attestation.data.target.epoch,
|
||||||
|
attestation.data.crosslink.shard
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
spec.initiate_validator_exit(state, validator_index)
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||||
|
<= spec.get_custody_period_for_validator(
|
||||||
|
state,
|
||||||
|
validator_index,
|
||||||
|
state.validators[validator_index].exit_epoch - 1)):
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_resume_after_bit_challenge_response(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
attestation.custody_bits[0] = 0
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
validator_index = spec.get_crosslink_committee(
|
||||||
|
state,
|
||||||
|
attestation.data.target.epoch,
|
||||||
|
attestation.data.crosslink.shard
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
spec.initiate_validator_exit(state, validator_index)
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||||
|
<= spec.get_custody_period_for_validator(
|
||||||
|
state,
|
||||||
|
validator_index,
|
||||||
|
state.validators[validator_index].exit_epoch - 1)):
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
bit_challenge_index = state.custody_bit_challenge_index - 1
|
||||||
|
response = get_valid_custody_bit_response(
|
||||||
|
spec,
|
||||||
|
state,
|
||||||
|
challenge,
|
||||||
|
test_vector,
|
||||||
|
bit_challenge_index,
|
||||||
|
invalid_chunk_bit=False)
|
||||||
|
|
||||||
|
_, _, _ = run_custody_bit_response_processing(spec, state, response)
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state):
|
||||||
|
state.slot = spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(spec, state, signed=True)
|
||||||
|
|
||||||
|
test_vector = get_custody_test_vector(
|
||||||
|
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||||
|
shard_root = get_custody_merkle_root(test_vector)
|
||||||
|
attestation.data.crosslink.data_root = shard_root
|
||||||
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
validator_index = spec.get_crosslink_committee(
|
||||||
|
state,
|
||||||
|
attestation.data.target.epoch,
|
||||||
|
attestation.data.crosslink.shard
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
spec.initiate_validator_exit(state, validator_index)
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch:
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||||
|
<= spec.get_custody_period_for_validator(
|
||||||
|
state,
|
||||||
|
validator_index,
|
||||||
|
state.validators[validator_index].exit_epoch - 1)):
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index)
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
challenge = get_valid_chunk_challenge(spec, state, attestation)
|
||||||
|
|
||||||
|
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||||
|
|
||||||
|
next_epoch(spec, state)
|
||||||
|
apply_empty_block(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
chunk_challenge_index = state.custody_chunk_challenge_index - 1
|
||||||
|
response = get_valid_custody_chunk_response(spec, state, challenge, test_vector, chunk_challenge_index)
|
||||||
|
|
||||||
|
_, _, _ = run_custody_chunk_response_processing(spec, state, response)
|
||||||
|
|
||||||
|
yield from run_process_final_custody_updates(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
|
@ -0,0 +1,46 @@
|
||||||
|
from eth2spec.test.helpers.custody import (
|
||||||
|
get_valid_custody_key_reveal,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.state import next_epoch
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
)
|
||||||
|
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
||||||
|
from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
|
||||||
|
|
||||||
|
|
||||||
|
def run_process_challenge_deadlines(spec, state):
|
||||||
|
yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines')
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_slashed_after_reveal_deadline(spec, state):
|
||||||
|
assert state.validators[0].slashed == 0
|
||||||
|
|
||||||
|
state.slot += ((spec.CHUNK_RESPONSE_DEADLINE + spec.EPOCHS_PER_CUSTODY_PERIOD)
|
||||||
|
* spec.SLOTS_PER_EPOCH)
|
||||||
|
next_epoch(spec, state)
|
||||||
|
|
||||||
|
yield from run_process_challenge_deadlines(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[0].slashed == 1
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_validator_not_slashed_after_reveal(spec, state):
|
||||||
|
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH
|
||||||
|
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||||
|
|
||||||
|
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||||
|
|
||||||
|
assert state.validators[0].slashed == 0
|
||||||
|
|
||||||
|
state.slot += spec.CHUNK_RESPONSE_DEADLINE * spec.SLOTS_PER_EPOCH
|
||||||
|
next_epoch(spec, state)
|
||||||
|
|
||||||
|
yield from run_process_challenge_deadlines(spec, state)
|
||||||
|
|
||||||
|
assert state.validators[0].slashed == 0
|
Loading…
Reference in New Issue