Merge branch 'dev' into phase1-validator
This commit is contained in:
commit
759185632f
|
@ -28,3 +28,13 @@ tests/core/pyspec/test-reports
|
|||
tests/core/pyspec/eth2spec/test_results.xml
|
||||
|
||||
*.egg-info
|
||||
|
||||
# flake8 config
|
||||
tox.ini
|
||||
|
||||
# VS code files
|
||||
.vscode
|
||||
*.code-workspace
|
||||
|
||||
# npm (for doctoc)
|
||||
package-lock.json
|
2
Makefile
2
Makefile
|
@ -71,7 +71,7 @@ pyspec:
|
|||
|
||||
# installs the packages to run pyspec tests
|
||||
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
|
||||
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
||||
|
|
|
@ -28,6 +28,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144
|
|||
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
|
||||
# len(SHARD_BLOCK_OFFSETS)
|
||||
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
|
||||
# 2**12 (= 4,096)
|
||||
BYTES_PER_CUSTODY_CHUNK: 4096
|
||||
# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)
|
||||
CUSTODY_RESPONSE_DEPTH: 8
|
||||
|
||||
# Gwei values
|
||||
# 2**14 (= 16,384) Gwei
|
||||
|
@ -41,10 +45,15 @@ ONLINE_PERIOD: 8
|
|||
# 2**8 (= 256) | epochs
|
||||
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
|
||||
|
||||
# Max operations per block
|
||||
# 2**20 (= 1,048,576)
|
||||
MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576
|
||||
|
||||
# Domain types
|
||||
DOMAIN_SHARD_PROPOSAL: 0x80000000
|
||||
DOMAIN_SHARD_COMMITTEE: 0x81000000
|
||||
DOMAIN_LIGHT_CLIENT: 0x82000000
|
||||
# custody-game spec
|
||||
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
|
||||
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
||||
|
@ -54,19 +63,37 @@ DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
|||
# Time parameters
|
||||
# 2**1 (= 2) epochs, 12.8 minutes
|
||||
RANDAO_PENALTY_EPOCHS: 2
|
||||
# 2**15 (= 32,768) epochs, ~146 days
|
||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
|
||||
# 2**14 (= 16,384) epochs ~73 days
|
||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384
|
||||
# 2**11 (= 2,048) epochs, ~9 days
|
||||
EPOCHS_PER_CUSTODY_PERIOD: 2048
|
||||
EPOCHS_PER_CUSTODY_PERIOD: 16384
|
||||
# 2**11 (= 2,048) epochs, ~9 days
|
||||
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
|
||||
# 2**7 (= 128) epochs, ~14 hours
|
||||
MAX_REVEAL_LATENESS_DECREMENT: 128
|
||||
# 2**14 (= 16,384) epochs
|
||||
CUSTODY_RESPONSE_DEADLINE: 16384
|
||||
# 2**15 (= 32,768) epochs, ~146 days
|
||||
MAX_CHUNK_CHALLENGE_DELAY: 32768
|
||||
|
||||
# Misc parameters
|
||||
# 2**256 - 189
|
||||
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
|
||||
# 3
|
||||
CUSTODY_SECRETS: 3
|
||||
# 2**5 (= 32) bytes
|
||||
BYTES_PER_CUSTODY_ATOM: 32
|
||||
# 1/1024 chance of custody bit 1
|
||||
CUSTODY_PROBABILITY_EXPONENT: 10
|
||||
|
||||
# Max operations
|
||||
# 2**8 (= 256)
|
||||
MAX_CUSTODY_KEY_REVEALS: 256
|
||||
# 2**0 (= 1)
|
||||
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
|
||||
# 2**2 (= 2)
|
||||
MAX_CUSTODY_CHUNK_CHALLENGES: 4
|
||||
# 2** 4 (= 16)
|
||||
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16
|
||||
# 2**0 (= 1)
|
||||
MAX_CUSTODY_SLASHINGS: 1
|
||||
|
||||
# Reward and penalty quotients
|
||||
|
|
|
@ -30,6 +30,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144
|
|||
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
|
||||
# len(SHARD_BLOCK_OFFSETS)
|
||||
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
|
||||
# 2**12 (= 4,096)
|
||||
BYTES_PER_CUSTODY_CHUNK: 4096
|
||||
# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)
|
||||
CUSTODY_RESPONSE_DEPTH: 8
|
||||
|
||||
# Gwei values
|
||||
# 2**14 (= 16,384) Gwei
|
||||
|
@ -43,10 +47,15 @@ ONLINE_PERIOD: 8
|
|||
# 2**8 (= 256) | epochs
|
||||
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
|
||||
|
||||
# Max operations per block
|
||||
# 2**20 (= 1,048,576)
|
||||
MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576
|
||||
|
||||
# Domain types
|
||||
DOMAIN_SHARD_PROPOSAL: 0x80000000
|
||||
DOMAIN_SHARD_COMMITTEE: 0x81000000
|
||||
DOMAIN_LIGHT_CLIENT: 0x82000000
|
||||
# custody-game spec
|
||||
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
|
||||
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
||||
|
@ -57,18 +66,38 @@ DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
|||
# 2**1 (= 2) epochs
|
||||
RANDAO_PENALTY_EPOCHS: 2
|
||||
# [customized] quicker for testing
|
||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
||||
# 2**11 (= 2,048) epochs
|
||||
EPOCHS_PER_CUSTODY_PERIOD: 2048
|
||||
# 2**11 (= 2,048) epochs
|
||||
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
|
||||
# 2**7 (= 128) epochs
|
||||
MAX_REVEAL_LATENESS_DECREMENT: 128
|
||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128
|
||||
# [customized] quicker for testing
|
||||
EPOCHS_PER_CUSTODY_PERIOD: 64
|
||||
# [customized] quicker for testing
|
||||
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
|
||||
# [customized] quicker for testing
|
||||
CUSTODY_RESPONSE_DEADLINE: 128
|
||||
# [customize for faster testing]
|
||||
MAX_CHUNK_CHALLENGE_DELAY: 128
|
||||
|
||||
|
||||
# Misc parameters
|
||||
# 2**256 - 189
|
||||
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
|
||||
# 3
|
||||
CUSTODY_SECRETS: 3
|
||||
# 2**5 (= 32) bytes
|
||||
BYTES_PER_CUSTODY_ATOM: 32
|
||||
# 1/4 chance of custody bit 1 [customized for faster testing]
|
||||
CUSTODY_PROBABILITY_EXPONENT: 2
|
||||
|
||||
|
||||
# Max operations
|
||||
# 2**8 (= 256)
|
||||
MAX_CUSTODY_KEY_REVEALS: 256
|
||||
# 2**0 (= 1)
|
||||
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
|
||||
# [customized]
|
||||
MAX_CUSTODY_CHUNK_CHALLENGES: 2
|
||||
# [customized]
|
||||
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8
|
||||
# 2**0 (= 1)
|
||||
MAX_CUSTODY_SLASHINGS: 1
|
||||
|
||||
# Reward and penalty quotients
|
||||
|
|
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_typing import (
|
||||
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
|
||||
|
||||
|
|
|
@ -1748,6 +1748,22 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
|
||||
##### Deposits
|
||||
|
||||
```python
|
||||
def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator:
|
||||
amount = deposit.data.amount
|
||||
effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
|
||||
return Validator(
|
||||
pubkey=deposit.data.pubkey,
|
||||
withdrawal_credentials=deposit.data.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,
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
# Verify the Merkle branch
|
||||
|
@ -1778,15 +1794,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
return
|
||||
|
||||
# Add validator and balance entries
|
||||
state.validators.append(Validator(
|
||||
pubkey=pubkey,
|
||||
withdrawal_credentials=deposit.data.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=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE),
|
||||
))
|
||||
state.validators.append(get_validator_from_deposit(state, deposit))
|
||||
state.balances.append(amount)
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
|
|
|
@ -52,15 +52,12 @@
|
|||
- [`get_shard_committee`](#get_shard_committee)
|
||||
- [`get_light_client_committee`](#get_light_client_committee)
|
||||
- [`get_shard_proposer_index`](#get_shard_proposer_index)
|
||||
- [`get_indexed_attestation`](#get_indexed_attestation)
|
||||
- [`get_committee_count_delta`](#get_committee_count_delta)
|
||||
- [`get_start_shard`](#get_start_shard)
|
||||
- [`get_shard`](#get_shard)
|
||||
- [`get_latest_slot_for_shard`](#get_latest_slot_for_shard)
|
||||
- [`get_offset_slots`](#get_offset_slots)
|
||||
- [Predicates](#predicates)
|
||||
- [`verify_attestation_custody`](#verify_attestation_custody)
|
||||
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
|
||||
- [`is_on_time_attestation`](#is_on_time_attestation)
|
||||
- [`is_winning_attestation`](#is_winning_attestation)
|
||||
- [`optional_aggregate_verify`](#optional_aggregate_verify)
|
||||
|
@ -76,7 +73,7 @@
|
|||
- [`process_crosslinks`](#process_crosslinks)
|
||||
- [`verify_empty_shard_transition`](#verify_empty_shard_transition)
|
||||
- [`process_shard_transitions`](#process_shard_transitions)
|
||||
- [New Attester slashing processing](#new-attester-slashing-processing)
|
||||
- [New default validator for deposits](#new-default-validator-for-deposits)
|
||||
- [Light client processing](#light-client-processing)
|
||||
- [Epoch transition](#epoch-transition)
|
||||
- [Phase 1 final updates](#phase-1-final-updates)
|
||||
|
@ -115,12 +112,14 @@ Configuration is not namespaced. Instead it is strictly an extension;
|
|||
|
||||
### Shard block configs
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) |
|
||||
| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) |
|
||||
| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` |
|
||||
| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` |
|
||||
| Name | Value | Unit |
|
||||
| - | - | - |
|
||||
| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes |
|
||||
| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes |
|
||||
| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | - |
|
||||
| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - |
|
||||
| `BYTES_PER_CUSTODY_CHUNK` | `2**12` (= 4,096) | bytes |
|
||||
| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - |
|
||||
|
||||
### Gwei values
|
||||
|
||||
|
@ -180,7 +179,6 @@ class AttestationData(Container):
|
|||
class Attestation(Container):
|
||||
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
|
@ -200,8 +198,9 @@ class PendingAttestation(Container):
|
|||
|
||||
```python
|
||||
class IndexedAttestation(Container):
|
||||
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
|
||||
attestation: Attestation
|
||||
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
### Extended `AttesterSlashing`
|
||||
|
@ -232,7 +231,9 @@ class Validator(Container):
|
|||
# (of the particular validator) in which the validator is activated
|
||||
# = get_custody_period_for_validator(...)
|
||||
next_custody_secret_to_reveal: uint64
|
||||
max_reveal_lateness: Epoch
|
||||
# TODO: The max_reveal_lateness doesn't really make sense anymore.
|
||||
# So how do we incentivise early custody key reveals now?
|
||||
all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH
|
||||
```
|
||||
|
||||
### Extended `BeaconBlockBody`
|
||||
|
@ -251,9 +252,11 @@ class BeaconBlockBody(Container):
|
|||
deposits: List[Deposit, MAX_DEPOSITS]
|
||||
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
|
||||
# Custody game
|
||||
custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS]
|
||||
chunk_challenges: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGES]
|
||||
chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES]
|
||||
custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS]
|
||||
early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS]
|
||||
custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS]
|
||||
# Shards
|
||||
shard_transitions: Vector[ShardTransition, MAX_SHARDS]
|
||||
# Light clients
|
||||
|
@ -330,6 +333,8 @@ class BeaconState(Container):
|
|||
# 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],
|
||||
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
|
||||
|
@ -387,6 +392,7 @@ class ShardTransition(Container):
|
|||
# Shard block lengths
|
||||
shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION]
|
||||
# Shard data roots
|
||||
# The root is of ByteList[MAX_SHARD_BLOCK_SIZE]
|
||||
shard_data_roots: List[Bytes32, MAX_SHARD_BLOCKS_PER_ATTESTATION]
|
||||
# Intermediate shard states
|
||||
shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION]
|
||||
|
@ -580,17 +586,6 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard
|
|||
return committee[r % len(committee)]
|
||||
```
|
||||
|
||||
#### `get_indexed_attestation`
|
||||
|
||||
```python
|
||||
def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation:
|
||||
committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index)
|
||||
return IndexedAttestation(
|
||||
committee=committee,
|
||||
attestation=attestation,
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_committee_count_delta`
|
||||
|
||||
```python
|
||||
|
@ -660,65 +655,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]:
|
|||
|
||||
### Predicates
|
||||
|
||||
#### `verify_attestation_custody`
|
||||
|
||||
```python
|
||||
def verify_attestation_custody(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
|
||||
"""
|
||||
Check if ``indexed_attestation`` has valid signature against non-empty custody bits.
|
||||
"""
|
||||
attestation = indexed_attestation.attestation
|
||||
aggregation_bits = attestation.aggregation_bits
|
||||
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
|
||||
all_pubkeys = []
|
||||
all_signing_roots = []
|
||||
for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
|
||||
assert len(custody_bits) == len(indexed_attestation.committee)
|
||||
for participant, aggregation_bit, custody_bit in zip(
|
||||
indexed_attestation.committee, aggregation_bits, custody_bits
|
||||
):
|
||||
if aggregation_bit:
|
||||
all_pubkeys.append(state.validators[participant].pubkey)
|
||||
# Note: only 2N distinct message hashes
|
||||
attestation_wrapper = AttestationCustodyBitWrapper(
|
||||
attestation_data_root=hash_tree_root(attestation.data),
|
||||
block_index=block_index,
|
||||
bit=custody_bit,
|
||||
)
|
||||
all_signing_roots.append(compute_signing_root(attestation_wrapper, domain))
|
||||
else:
|
||||
assert not custody_bit
|
||||
return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature)
|
||||
```
|
||||
|
||||
#### Updated `is_valid_indexed_attestation`
|
||||
|
||||
Note that this replaces the Phase 0 `is_valid_indexed_attestation`.
|
||||
|
||||
```python
|
||||
def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
|
||||
"""
|
||||
Check if ``indexed_attestation`` has valid indices and signature.
|
||||
"""
|
||||
# Verify aggregate signature
|
||||
attestation = indexed_attestation.attestation
|
||||
aggregation_bits = attestation.aggregation_bits
|
||||
if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee):
|
||||
return False
|
||||
|
||||
if len(attestation.custody_bits_blocks) == 0:
|
||||
# fall back on phase0 behavior if there is no shard data.
|
||||
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
|
||||
all_pubkeys = []
|
||||
for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits):
|
||||
if aggregation_bit:
|
||||
all_pubkeys.append(state.validators[participant].pubkey)
|
||||
signing_root = compute_signing_root(indexed_attestation.attestation.data, domain)
|
||||
return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature)
|
||||
else:
|
||||
return verify_attestation_custody(state, indexed_attestation)
|
||||
```
|
||||
|
||||
#### `is_on_time_attestation`
|
||||
|
||||
```python
|
||||
|
@ -836,16 +772,11 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
else:
|
||||
assert attestation.data.source == state.previous_justified_checkpoint
|
||||
|
||||
# Type 1: on-time attestations, the custody bits should be non-empty.
|
||||
if attestation.custody_bits_blocks != []:
|
||||
# Ensure on-time attestation
|
||||
assert is_on_time_attestation(state, attestation)
|
||||
# Correct data root count
|
||||
shard = get_shard(state, attestation)
|
||||
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard))
|
||||
# Type 1: on-time attestations
|
||||
if is_on_time_attestation(state, attestation):
|
||||
# Correct parent block root
|
||||
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
|
||||
# Type 2: no shard transition, no custody bits
|
||||
# Type 2: no shard transition
|
||||
else:
|
||||
# Ensure delayed attestation
|
||||
assert data.slot < compute_previous_slot(state.slot)
|
||||
|
@ -1047,44 +978,28 @@ def process_shard_transitions(state: BeaconState,
|
|||
assert verify_empty_shard_transition(state, shard_transitions)
|
||||
```
|
||||
|
||||
##### New Attester slashing processing
|
||||
##### New default validator for deposits
|
||||
|
||||
```python
|
||||
def get_indices_from_committee(
|
||||
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE],
|
||||
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]:
|
||||
assert len(bits) == len(committee)
|
||||
return [validator_index for i, validator_index in enumerate(committee) if bits[i]]
|
||||
```
|
||||
|
||||
```python
|
||||
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
|
||||
indexed_attestation_1 = attester_slashing.attestation_1
|
||||
indexed_attestation_2 = attester_slashing.attestation_2
|
||||
|
||||
assert is_slashable_attestation_data(
|
||||
indexed_attestation_1.attestation.data,
|
||||
indexed_attestation_2.attestation.data,
|
||||
)
|
||||
assert is_valid_indexed_attestation(state, indexed_attestation_1)
|
||||
assert is_valid_indexed_attestation(state, indexed_attestation_2)
|
||||
|
||||
indices_1 = get_indices_from_committee(
|
||||
indexed_attestation_1.committee,
|
||||
indexed_attestation_1.attestation.aggregation_bits,
|
||||
)
|
||||
indices_2 = get_indices_from_committee(
|
||||
indexed_attestation_2.committee,
|
||||
indexed_attestation_2.attestation.aggregation_bits,
|
||||
def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator:
|
||||
amount = deposit.data.amount
|
||||
effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
next_custody_secret_to_reveal = get_custody_period_for_validator(
|
||||
ValidatorIndex(len(state.validators)),
|
||||
get_current_epoch(state),
|
||||
)
|
||||
|
||||
slashed_any = False
|
||||
indices = set(indices_1).intersection(indices_2)
|
||||
for index in sorted(indices):
|
||||
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
|
||||
slash_validator(state, index)
|
||||
slashed_any = True
|
||||
assert slashed_any
|
||||
return Validator(
|
||||
pubkey=deposit.data.pubkey,
|
||||
withdrawal_credentials=deposit.data.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,
|
||||
next_custody_secret_to_reveal=next_custody_secret_to_reveal,
|
||||
all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH,
|
||||
)
|
||||
```
|
||||
|
||||
#### Light client processing
|
||||
|
@ -1121,6 +1036,7 @@ def process_epoch(state: BeaconState) -> None:
|
|||
process_rewards_and_penalties(state)
|
||||
process_registry_updates(state)
|
||||
process_reveal_deadlines(state)
|
||||
process_challenge_deadlines(state)
|
||||
process_slashings(state)
|
||||
process_final_updates(state) # phase 0 final updates
|
||||
process_phase_1_final_updates(state)
|
||||
|
@ -1140,7 +1056,7 @@ def process_phase_1_final_updates(state: BeaconState) -> None:
|
|||
|
||||
#### Custody game updates
|
||||
|
||||
`process_reveal_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md),
|
||||
`process_reveal_deadlines`, `process_challenge_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md),
|
||||
|
||||
#### Online-tracking
|
||||
|
||||
|
|
|
@ -17,18 +17,26 @@
|
|||
- [Reward and penalty quotients](#reward-and-penalty-quotients)
|
||||
- [Data structures](#data-structures)
|
||||
- [New Beacon Chain operations](#new-beacon-chain-operations)
|
||||
- [`CustodyChunkChallenge`](#custodychunkchallenge)
|
||||
- [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord)
|
||||
- [`CustodyChunkResponse`](#custodychunkresponse)
|
||||
- [`CustodySlashing`](#custodyslashing)
|
||||
- [`SignedCustodySlashing`](#signedcustodyslashing)
|
||||
- [`CustodyKeyReveal`](#custodykeyreveal)
|
||||
- [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal)
|
||||
- [Helpers](#helpers)
|
||||
- [`replace_empty_or_append`](#replace_empty_or_append)
|
||||
- [`legendre_bit`](#legendre_bit)
|
||||
- [`custody_atoms`](#custody_atoms)
|
||||
- [`get_custody_atoms`](#get_custody_atoms)
|
||||
- [`get_custody_secrets`](#get_custody_secrets)
|
||||
- [`universal_hash_function`](#universal_hash_function)
|
||||
- [`compute_custody_bit`](#compute_custody_bit)
|
||||
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
|
||||
- [`get_custody_period_for_validator`](#get_custody_period_for_validator)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Custody Game Operations](#custody-game-operations)
|
||||
- [Chunk challenges](#chunk-challenges)
|
||||
- [Custody chunk response](#custody-chunk-response)
|
||||
- [Custody key reveals](#custody-key-reveals)
|
||||
- [Early derived secret reveals](#early-derived-secret-reveals)
|
||||
- [Custody Slashings](#custody-slashings)
|
||||
|
@ -48,8 +56,10 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
|||
|
||||
| Name | Value | Unit |
|
||||
| - | - | - |
|
||||
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` | - |
|
||||
| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes |
|
||||
| `CUSTODY_PRIME` | `2 ** 256 - 189` | - |
|
||||
| `CUSTODY_SECRETS` | `3` | - |
|
||||
| `BYTES_PER_CUSTODY_ATOM` | `32` | bytes |
|
||||
| `CUSTODY_PROBABILITY_EXPONENT` | `10` | - |
|
||||
|
||||
## Configuration
|
||||
|
||||
|
@ -58,18 +68,22 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
|||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes |
|
||||
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**15` (= 32,768) | epochs | ~146 days |
|
||||
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||
| `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours |
|
||||
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days |
|
||||
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||
|
||||
### Max operations per block
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) |
|
||||
| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) |
|
||||
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` |
|
||||
| `MAX_CUSTODY_SLASHINGS` | `1` |
|
||||
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `2**0` (= 1) |
|
||||
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) |
|
||||
| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `2**4` (= 16) |
|
||||
| `MAX_CUSTODY_SLASHINGS` | `2**0` (= 1) |
|
||||
|
||||
### Reward and penalty quotients
|
||||
|
||||
|
@ -82,11 +96,43 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
|||
|
||||
### New Beacon Chain operations
|
||||
|
||||
#### `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
|
||||
chunk_index: uint64
|
||||
```
|
||||
|
||||
#### `CustodyChunkResponse`
|
||||
|
||||
```python
|
||||
class CustodyChunkResponse(Container):
|
||||
challenge_index: uint64
|
||||
chunk_index: uint64
|
||||
chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
|
||||
branch: Vector[Root, 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
|
||||
|
@ -105,7 +151,6 @@ class SignedCustodySlashing(Container):
|
|||
signature: BLSSignature
|
||||
```
|
||||
|
||||
|
||||
#### `CustodyKeyReveal`
|
||||
|
||||
```python
|
||||
|
@ -137,6 +182,18 @@ class EarlyDerivedSecretReveal(Container):
|
|||
|
||||
## Helpers
|
||||
|
||||
### `replace_empty_or_append`
|
||||
|
||||
```python
|
||||
def replace_empty_or_append(l: List, new_element: Any) -> int:
|
||||
for i in range(len(l)):
|
||||
if l[i] == type(new_element)():
|
||||
l[i] = new_element
|
||||
return i
|
||||
l.append(new_element)
|
||||
return len(l) - 1
|
||||
```
|
||||
|
||||
### `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.
|
||||
|
@ -166,7 +223,7 @@ def legendre_bit(a: int, q: int) -> int:
|
|||
return 0
|
||||
```
|
||||
|
||||
### `custody_atoms`
|
||||
### `get_custody_atoms`
|
||||
|
||||
Given one set of data, return the custody atoms: each atom will be combined with one legendre bit.
|
||||
|
||||
|
@ -177,16 +234,42 @@ def get_custody_atoms(bytez: bytes) -> Sequence[bytes]:
|
|||
for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)]
|
||||
```
|
||||
|
||||
### `get_custody_secrets`
|
||||
|
||||
Extract the custody secrets from the signature
|
||||
|
||||
```python
|
||||
def get_custody_secrets(key: BLSSignature) -> Sequence[int]:
|
||||
full_G2_element = bls.signature_to_G2(key)
|
||||
signature = full_G2_element[0].coeffs
|
||||
signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature)
|
||||
secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little")
|
||||
for i in range(0, len(signature_bytes), 32)]
|
||||
return secrets
|
||||
```
|
||||
|
||||
### `universal_hash_function`
|
||||
|
||||
```python
|
||||
def universal_hash_function(data_chunks: Sequence[bytes], secrets: Sequence[int]) -> int:
|
||||
n = len(data_chunks)
|
||||
return (
|
||||
sum(
|
||||
secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME
|
||||
for i, atom in enumerate(data_chunks)
|
||||
) + secrets[n % CUSTODY_SECRETS]**n
|
||||
) % CUSTODY_PRIME
|
||||
```
|
||||
|
||||
### `compute_custody_bit`
|
||||
|
||||
```python
|
||||
def compute_custody_bit(key: BLSSignature, data: bytes) -> bit:
|
||||
full_G2_element = bls.signature_to_G2(key)
|
||||
s = full_G2_element[0].coeffs
|
||||
def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit:
|
||||
custody_atoms = get_custody_atoms(data)
|
||||
n = len(custody_atoms)
|
||||
a = sum(s[i % 2]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms) + s[n % 2]**n)
|
||||
return legendre_bit(a, BLS12_381_Q)
|
||||
secrets = get_custody_secrets(key)
|
||||
uhf = universal_hash_function(custody_atoms, secrets)
|
||||
legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)]
|
||||
return all(legendre_bits)
|
||||
```
|
||||
|
||||
### `get_randao_epoch_for_custody_period`
|
||||
|
@ -218,11 +301,90 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) -
|
|||
for operation in operations:
|
||||
fn(state, operation)
|
||||
|
||||
for_ops(body.chunk_challenges, process_chunk_challenge)
|
||||
for_ops(body.chunk_challenge_responses, process_chunk_challenge)
|
||||
for_ops(body.custody_key_reveals, process_custody_key_reveal)
|
||||
for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal)
|
||||
for_ops(body.custody_slashings, process_custody_slashing)
|
||||
```
|
||||
|
||||
#### Chunk challenges
|
||||
|
||||
```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 the attestation
|
||||
max_attestation_challenge_epoch = challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY
|
||||
assert get_current_epoch(state) <= max_attestation_challenge_epoch
|
||||
# Verify it is not too late to challenge the responder
|
||||
responder = state.validators[challenge.responder_index]
|
||||
if responder.exit_epoch < FAR_FUTURE_EPOCH:
|
||||
assert get_current_epoch(state) <= responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY
|
||||
# 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 != data_root or
|
||||
record.chunk_index != challenge.chunk_index
|
||||
)
|
||||
# Verify depth
|
||||
shard_block_length = challenge.shard_transition.shard_block_lengths[challenge.data_index]
|
||||
transition_chunks = (shard_block_length + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK
|
||||
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],
|
||||
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:
|
||||
# Get matching challenge (if any) from records
|
||||
matching_challenges = [
|
||||
record for record in state.custody_chunk_challenge_records
|
||||
if record.challenge_index == response.challenge_index
|
||||
]
|
||||
assert len(matching_challenges) == 1
|
||||
challenge = matching_challenges[0]
|
||||
# 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=CUSTODY_RESPONSE_DEPTH,
|
||||
index=response.chunk_index,
|
||||
root=challenge.data_root,
|
||||
)
|
||||
# Clear the challenge
|
||||
index_in_records = state.custody_chunk_challenge_records.index(challenge)
|
||||
state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord()
|
||||
# Reward the proposer
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT))
|
||||
```
|
||||
|
||||
#### Custody key reveals
|
||||
|
||||
```python
|
||||
|
@ -235,7 +397,14 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
|
|||
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index)
|
||||
|
||||
custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state))
|
||||
assert revealer.next_custody_secret_to_reveal < custody_reveal_period
|
||||
# Only past custody periods can be revealed, except after exiting the exit period can be revealed
|
||||
is_past_reveal = revealer.next_custody_secret_to_reveal < custody_reveal_period
|
||||
is_exited = revealer.exit_epoch <= get_current_epoch(state)
|
||||
is_exit_period_reveal = (
|
||||
revealer.next_custody_secret_to_reveal
|
||||
== get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1)
|
||||
)
|
||||
assert is_past_reveal or (is_exited and is_exit_period_reveal)
|
||||
|
||||
# Revealed validator is active or exited, but not withdrawn
|
||||
assert is_slashable_validator(revealer, get_current_epoch(state))
|
||||
|
@ -245,19 +414,9 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
|
|||
signing_root = compute_signing_root(epoch_to_sign, domain)
|
||||
assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal)
|
||||
|
||||
# Decrement max reveal lateness if response is timely
|
||||
if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state):
|
||||
if revealer.max_reveal_lateness >= MAX_REVEAL_LATENESS_DECREMENT:
|
||||
revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT
|
||||
else:
|
||||
revealer.max_reveal_lateness = 0
|
||||
else:
|
||||
revealer.max_reveal_lateness = max(
|
||||
revealer.max_reveal_lateness,
|
||||
get_current_epoch(state) - epoch_to_sign - EPOCHS_PER_CUSTODY_PERIOD
|
||||
)
|
||||
|
||||
# Process reveal
|
||||
if is_exited and is_exit_period_reveal:
|
||||
revealer.all_custody_secrets_revealed_epoch = get_current_epoch(state)
|
||||
revealer.next_custody_secret_to_reveal += 1
|
||||
|
||||
# Reward Block Proposer
|
||||
|
@ -350,14 +509,16 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
|
|||
# Verify the attestation
|
||||
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
|
||||
|
||||
# TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding.
|
||||
|
||||
# TODO: can do a single combined merkle proof of data being attested.
|
||||
# Verify the shard transition is indeed attested by the attestation
|
||||
shard_transition = custody_slashing.shard_transition
|
||||
assert hash_tree_root(shard_transition) == attestation.shard_transition_root
|
||||
assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root
|
||||
# Verify that the provided data matches the shard-transition
|
||||
assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index]
|
||||
assert (
|
||||
custody_slashing.data.get_backing().get_left().merkle_root()
|
||||
== shard_transition.shard_data_roots[custody_slashing.data_index]
|
||||
)
|
||||
assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index]
|
||||
|
||||
# Verify existence and participation of claimed malefactor
|
||||
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
|
@ -372,18 +533,14 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
|
|||
signing_root = compute_signing_root(epoch_to_sign, domain)
|
||||
assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret)
|
||||
|
||||
# Get the custody bit
|
||||
custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index]
|
||||
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
|
||||
claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)]
|
||||
|
||||
# Compute the custody bit
|
||||
computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data)
|
||||
|
||||
|
||||
# Verify the claim
|
||||
if claimed_custody_bit != computed_custody_bit:
|
||||
if computed_custody_bit == 1:
|
||||
# Slash the malefactor, reward the other committee members
|
||||
slash_validator(state, custody_slashing.malefactor_index)
|
||||
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
|
||||
others_count = len(committee) - 1
|
||||
whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count)
|
||||
for attester_index in attesters:
|
||||
|
@ -400,28 +557,44 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
|
|||
|
||||
### Handling of reveal deadlines
|
||||
|
||||
Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`:
|
||||
|
||||
```python
|
||||
def process_reveal_deadlines(state: BeaconState) -> None:
|
||||
epoch = get_current_epoch(state)
|
||||
for index, validator in enumerate(state.validators):
|
||||
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal:
|
||||
# ------------------ WARNING ----------------------- #
|
||||
# UNSAFE REMOVAL OF SLASHING TO PRIORITIZE PHASE 0 CI #
|
||||
# Must find generic way to handle key reveals in tests #
|
||||
# ---------------------------------------------------- #
|
||||
deadline = validator.next_custody_secret_to_reveal + 1
|
||||
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline:
|
||||
slash_validator(state, ValidatorIndex(index))
|
||||
```
|
||||
|
||||
# slash_validator(state, ValidatorIndex(index))
|
||||
pass
|
||||
```python
|
||||
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 + EPOCHS_PER_CUSTODY_PERIOD:
|
||||
slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index)
|
||||
index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge)
|
||||
state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord()
|
||||
```
|
||||
|
||||
### Final updates
|
||||
|
||||
After `process_final_updates(state)`, additional updates are made for the custody game:
|
||||
|
||||
```python
|
||||
def process_custody_final_updates(state: BeaconState) -> None:
|
||||
# Clean up exposed RANDAO key reveals
|
||||
state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = []
|
||||
|
||||
# Reset withdrawable epochs if challenge records are empty
|
||||
records = state.custody_chunk_challenge_records
|
||||
validator_indices_in_records = set([record.responder_index for record in records])
|
||||
for index, validator in enumerate(state.validators):
|
||||
if validator.exit_epoch != FAR_FUTURE_EPOCH:
|
||||
not_all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH
|
||||
if index in validator_indices_in_records or not_all_secrets_are_revealed:
|
||||
# Delay withdrawable epochs if challenge records are not empty or not all
|
||||
# custody secrets revealed
|
||||
validator.withdrawable_epoch = FAR_FUTURE_EPOCH
|
||||
else:
|
||||
# Reset withdrawable epochs if challenge records are empty and all secrets are revealed
|
||||
if validator.withdrawable_epoch == FAR_FUTURE_EPOCH:
|
||||
validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch
|
||||
+ MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
|
||||
```
|
||||
|
|
|
@ -9,11 +9,9 @@
|
|||
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Fork choice](#fork-choice)
|
||||
- [Helpers](#helpers)
|
||||
- [Extended `LatestMessage`](#extended-latestmessage)
|
||||
- [Updated `update_latest_messages`](#updated-update_latest_messages)
|
||||
- [Handlers](#handlers)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
@ -22,12 +20,6 @@
|
|||
|
||||
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
|
||||
|
||||
## Fork choice
|
||||
|
||||
Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible.
|
||||
|
||||
The rest of the fork choice remains stable.
|
||||
|
||||
### Helpers
|
||||
|
||||
#### Extended `LatestMessage`
|
||||
|
@ -54,29 +46,3 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
|
|||
epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root
|
||||
)
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
```python
|
||||
def on_attestation(store: Store, attestation: Attestation) -> None:
|
||||
"""
|
||||
Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire.
|
||||
|
||||
An ``attestation`` that is asserted as invalid may be valid at a later time,
|
||||
consider scheduling it for later processing in such case.
|
||||
"""
|
||||
validate_on_attestation(store, attestation)
|
||||
store_target_checkpoint_state(store, attestation.data.target)
|
||||
|
||||
# Get state at the `target` to fully validate attestation
|
||||
target_state = store.checkpoint_states[attestation.data.target]
|
||||
indexed_attestation = get_indexed_attestation(target_state, attestation)
|
||||
assert is_valid_indexed_attestation(target_state, indexed_attestation)
|
||||
|
||||
# Update latest messages for attesting indices
|
||||
attesting_indices = [
|
||||
index for i, index in enumerate(indexed_attestation.committee)
|
||||
if attestation.aggregation_bits[i]
|
||||
]
|
||||
update_latest_messages(store, attesting_indices, attestation)
|
||||
```
|
||||
|
|
|
@ -80,7 +80,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
|||
exit_epoch=phase0_validator.exit_epoch,
|
||||
withdrawable_epoch=phase0_validator.withdrawable_epoch,
|
||||
next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch),
|
||||
max_reveal_lateness=0, # TODO custody refactor. Outdated?
|
||||
all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH,
|
||||
) for i, phase0_validator in enumerate(pre.validators)
|
||||
),
|
||||
balances=pre.balances,
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
- [Broadcast aggregate](#broadcast-aggregate-1)
|
||||
- [`LightAggregateAndProof`](#lightaggregateandproof)
|
||||
- [`SignedLightAggregateAndProof`](#signedlightaggregateandproof)
|
||||
- [How to avoid slashing](#how-to-avoid-slashing)
|
||||
- [Custody slashing](#custody-slashing)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- /TOC -->
|
||||
|
@ -566,3 +568,23 @@ class SignedLightAggregateAndProof(Container):
|
|||
signature: BLSSignature
|
||||
```
|
||||
|
||||
## How to avoid slashing
|
||||
|
||||
Proposer and Attester slashings described in Phase 0 remain in place with the
|
||||
addition of the following.
|
||||
|
||||
### Custody slashing
|
||||
|
||||
To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret:
|
||||
|
||||
```python
|
||||
def get_custody_secret(spec, state, validator_index, epoch=None):
|
||||
period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
|
||||
else spec.get_current_epoch(state))
|
||||
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
|
||||
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
|
||||
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
|
||||
return bls.Sign(privkeys[validator_index], signing_root)
|
||||
```
|
||||
|
||||
Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing.
|
||||
|
|
|
@ -16,18 +16,13 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
|
|||
indexed_attestation = spec.get_indexed_attestation(state, attestation)
|
||||
spec.on_attestation(store, attestation)
|
||||
|
||||
sample_index = indexed_attestation.attesting_indices[0]
|
||||
if spec.fork == PHASE0:
|
||||
sample_index = indexed_attestation.attesting_indices[0]
|
||||
latest_message = spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
)
|
||||
else:
|
||||
attesting_indices = [
|
||||
index for i, index in enumerate(indexed_attestation.committee)
|
||||
if attestation.aggregation_bits[i]
|
||||
]
|
||||
sample_index = attesting_indices[0]
|
||||
latest_message = spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from lru import LRU
|
||||
|
||||
from typing import List
|
||||
|
||||
from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1
|
||||
from eth2spec.test.context import expect_assertion_error, PHASE1
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist
|
||||
from lru import LRU
|
||||
|
||||
|
||||
def run_attestation_processing(spec, state, attestation, valid=True):
|
||||
|
@ -95,23 +96,6 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t
|
|||
return attestation_data
|
||||
|
||||
|
||||
def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False):
|
||||
shard = spec.get_shard(state, attestation)
|
||||
offset_slots = spec.compute_offset_slots(
|
||||
spec.get_latest_slot_for_shard(state, shard),
|
||||
attestation.data.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY,
|
||||
)
|
||||
for _ in offset_slots:
|
||||
attestation.custody_bits_blocks.append(
|
||||
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
||||
)
|
||||
|
||||
if signed:
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
return attestation
|
||||
|
||||
|
||||
def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
|
||||
'''
|
||||
Construct on-time attestation for next slot
|
||||
|
@ -132,7 +116,7 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_tran
|
|||
)
|
||||
|
||||
|
||||
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=None):
|
||||
'''
|
||||
Construct on-time attestation for next slot
|
||||
'''
|
||||
|
@ -141,7 +125,8 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False)
|
|||
if index is None:
|
||||
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=shard_transition)
|
||||
|
||||
|
||||
def get_valid_attestation(spec,
|
||||
|
@ -178,9 +163,6 @@ def get_valid_attestation(spec,
|
|||
# fill the attestation with (optionally filtered) participants, and optionally sign it
|
||||
fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set)
|
||||
|
||||
if spec.fork == PHASE1 and on_time:
|
||||
attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed=signed)
|
||||
|
||||
return attestation
|
||||
|
||||
|
||||
|
@ -200,43 +182,9 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
|
|||
|
||||
|
||||
def sign_indexed_attestation(spec, state, indexed_attestation):
|
||||
if spec.fork == PHASE0:
|
||||
participants = indexed_attestation.attesting_indices
|
||||
data = indexed_attestation.data
|
||||
indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
|
||||
else:
|
||||
participants = spec.get_indices_from_committee(
|
||||
indexed_attestation.committee,
|
||||
indexed_attestation.attestation.aggregation_bits,
|
||||
)
|
||||
data = indexed_attestation.attestation.data
|
||||
if any(indexed_attestation.attestation.custody_bits_blocks):
|
||||
sign_on_time_attestation(spec, state, indexed_attestation.attestation)
|
||||
else:
|
||||
indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
|
||||
|
||||
|
||||
def sign_on_time_attestation(spec, state, attestation):
|
||||
if not any(attestation.custody_bits_blocks):
|
||||
sign_attestation(spec, state, attestation)
|
||||
return
|
||||
|
||||
committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index)
|
||||
signatures = []
|
||||
for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
|
||||
for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits):
|
||||
if not abit:
|
||||
continue
|
||||
signatures.append(get_attestation_custody_signature(
|
||||
spec,
|
||||
state,
|
||||
attestation.data,
|
||||
block_index,
|
||||
cbit,
|
||||
privkeys[participant]
|
||||
))
|
||||
|
||||
attestation.signature = bls.Aggregate(signatures)
|
||||
participants = indexed_attestation.attesting_indices
|
||||
data = indexed_attestation.data
|
||||
indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
|
||||
|
||||
|
||||
def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey):
|
||||
|
@ -253,10 +201,6 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index
|
|||
|
||||
|
||||
def sign_attestation(spec, state, attestation):
|
||||
if spec.fork == PHASE1 and any(attestation.custody_bits_blocks):
|
||||
sign_on_time_attestation(spec, state, attestation)
|
||||
return
|
||||
|
||||
participants = spec.get_attesting_indices(
|
||||
state,
|
||||
attestation.data,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from eth2spec.test.context import PHASE1
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation
|
||||
|
||||
|
||||
|
@ -41,34 +40,19 @@ def get_indexed_attestation_participants(spec, indexed_att):
|
|||
"""
|
||||
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
|
||||
"""
|
||||
if spec.fork == PHASE1:
|
||||
return list(spec.get_indices_from_committee(
|
||||
indexed_att.committee,
|
||||
indexed_att.attestation.aggregation_bits,
|
||||
))
|
||||
else:
|
||||
return list(indexed_att.attesting_indices)
|
||||
return list(indexed_att.attesting_indices)
|
||||
|
||||
|
||||
def set_indexed_attestation_participants(spec, indexed_att, participants):
|
||||
"""
|
||||
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
|
||||
"""
|
||||
if spec.fork == PHASE1:
|
||||
indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee]
|
||||
else:
|
||||
indexed_att.attesting_indices = participants
|
||||
indexed_att.attesting_indices = participants
|
||||
|
||||
|
||||
def get_attestation_1_data(spec, att_slashing):
|
||||
if spec.fork == PHASE1:
|
||||
return att_slashing.attestation_1.attestation.data
|
||||
else:
|
||||
return att_slashing.attestation_1.data
|
||||
return att_slashing.attestation_1.data
|
||||
|
||||
|
||||
def get_attestation_2_data(spec, att_slashing):
|
||||
if spec.fork == PHASE1:
|
||||
return att_slashing.attestation_2.attestation.data
|
||||
else:
|
||||
return att_slashing.attestation_2.data
|
||||
return att_slashing.attestation_2.data
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
|
||||
from remerkleable.core import pack_bits_to_chunks
|
||||
from remerkleable.tree import subtree_fill_to_contents, get_depth
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, ByteList
|
||||
from remerkleable.tree import gindex_bit_iter
|
||||
|
||||
BYTES_PER_CHUNK = 32
|
||||
|
||||
|
@ -37,9 +34,10 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
|||
)
|
||||
|
||||
|
||||
def get_valid_custody_key_reveal(spec, state, period=None):
|
||||
def get_valid_custody_key_reveal(spec, state, period=None, validator_index=None):
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
revealer_index = spec.get_active_validator_indices(state, current_epoch)[0]
|
||||
revealer_index = (spec.get_active_validator_indices(state, current_epoch)[0]
|
||||
if validator_index is None else validator_index)
|
||||
revealer = state.validators[revealer_index]
|
||||
|
||||
if period is None:
|
||||
|
@ -61,38 +59,54 @@ def bitlist_from_int(max_len, num_bits, n):
|
|||
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, shard_transition, custody_secret, data, data_index=0):
|
||||
beacon_committee = spec.get_beacon_committee(
|
||||
state,
|
||||
attestation.data.slot,
|
||||
attestation.data.crosslink.shard,
|
||||
attestation.data.index,
|
||||
)
|
||||
responder_index = beacon_committee[0]
|
||||
challenger_index = beacon_committee[-1]
|
||||
malefactor_index = beacon_committee[0]
|
||||
whistleblower_index = beacon_committee[-1]
|
||||
|
||||
epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch,
|
||||
responder_index)
|
||||
slashing = spec.CustodySlashing(
|
||||
data_index=data_index,
|
||||
malefactor_index=malefactor_index,
|
||||
malefactor_secret=custody_secret,
|
||||
whistleblower_index=whistleblower_index,
|
||||
shard_transition=shard_transition,
|
||||
attestation=attestation,
|
||||
data=data,
|
||||
)
|
||||
slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING)
|
||||
slashing_root = spec.compute_signing_root(slashing, slashing_domain)
|
||||
|
||||
# Generate the responder key
|
||||
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch)
|
||||
signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
|
||||
responder_key = bls.Sign(privkeys[responder_index], signing_root)
|
||||
signed_slashing = spec.SignedCustodySlashing(
|
||||
message=slashing,
|
||||
signature=bls.Sign(privkeys[whistleblower_index], slashing_root)
|
||||
)
|
||||
|
||||
chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink)
|
||||
return signed_slashing
|
||||
|
||||
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0)
|
||||
|
||||
n = 0
|
||||
while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit:
|
||||
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n)
|
||||
n += 1
|
||||
def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_index=None, chunk_index=None):
|
||||
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 if not data_index else data_index
|
||||
|
||||
return spec.CustodyBitChallenge(
|
||||
chunk_count = (shard_transition.shard_block_lengths[data_index]
|
||||
+ spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK
|
||||
chunk_index = chunk_count - 1 if not chunk_index else chunk_index
|
||||
|
||||
return spec.CustodyChunkChallenge(
|
||||
responder_index=responder_index,
|
||||
attestation=attestation,
|
||||
challenger_index=challenger_index,
|
||||
responder_key=responder_key,
|
||||
chunk_bits=chunk_bits,
|
||||
chunk_index=chunk_index,
|
||||
data_index=data_index,
|
||||
shard_transition=shard_transition,
|
||||
)
|
||||
|
||||
|
||||
|
@ -102,50 +116,90 @@ def custody_chunkify(spec, x):
|
|||
return chunks
|
||||
|
||||
|
||||
def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False):
|
||||
def build_proof(anchor, leaf_index):
|
||||
if leaf_index <= 1:
|
||||
return [] # Nothing to prove / invalid index
|
||||
node = anchor
|
||||
proof = []
|
||||
# Walk down, top to bottom to the leaf
|
||||
bit_iter, _ = gindex_bit_iter(leaf_index)
|
||||
for bit in bit_iter:
|
||||
# Always take the opposite hand for the proof.
|
||||
# 1 = right as leaf, thus get left
|
||||
if bit:
|
||||
proof.append(node.get_left().merkle_root())
|
||||
node = node.get_right()
|
||||
else:
|
||||
proof.append(node.get_right().merkle_root())
|
||||
node = node.get_left()
|
||||
|
||||
return list(reversed(proof))
|
||||
|
||||
|
||||
def get_block_data_merkle_root(data_as_bytelist):
|
||||
# To get the Merkle root of the block data, we need the Merkle root without the length Mixing
|
||||
# The below implements this in the Remerkleable framework
|
||||
return data_as_bytelist.get_backing().get_left().merkle_root()
|
||||
|
||||
|
||||
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)
|
||||
custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data)
|
||||
chunks = custody_chunkify(spec, custody_data)
|
||||
|
||||
chunk_index = len(chunks) - 1
|
||||
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
|
||||
chunk_index = chunk_challenge.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])
|
||||
data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH)
|
||||
|
||||
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](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK))
|
||||
for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))]
|
||||
data_tree = get_merkle_tree(chunks_hash_tree_roots)
|
||||
|
||||
data_branch = get_merkle_proof(data_tree, chunk_index)
|
||||
|
||||
bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK
|
||||
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(
|
||||
return spec.CustodyChunkResponse(
|
||||
challenge_index=challenge_index,
|
||||
chunk_index=chunk_index,
|
||||
chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
|
||||
data_branch=data_branch,
|
||||
chunk_bits_branch=bitlist_chunk_branch,
|
||||
chunk_bits_leaf=chunk_bits_leaf,
|
||||
branch=data_branch,
|
||||
)
|
||||
|
||||
|
||||
def get_custody_test_vector(bytelength):
|
||||
ints = bytelength // 4
|
||||
return b"".join(i.to_bytes(4, "little") for i in range(ints))
|
||||
def get_custody_test_vector(bytelength, offset=0):
|
||||
ints = bytelength // 4 + 1
|
||||
return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength]
|
||||
|
||||
|
||||
def get_custody_merkle_root(data):
|
||||
return None # get_merkle_tree(chunkify(data))[-1][0]
|
||||
def get_sample_shard_transition(spec, start_slot, block_lengths):
|
||||
b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](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_secret(spec, state, validator_index, epoch=None):
|
||||
period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
|
||||
else spec.get_current_epoch(state))
|
||||
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
|
||||
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
|
||||
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
|
||||
return bls.Sign(privkeys[validator_index], signing_root)
|
||||
|
||||
|
||||
def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True):
|
||||
test_vector = get_custody_test_vector(length)
|
||||
offset = 0
|
||||
while spec.compute_custody_bit(custody_secret, test_vector) != slashable:
|
||||
offset += 1
|
||||
test_vector = test_vector = get_custody_test_vector(length, offset)
|
||||
return test_vector
|
||||
|
||||
|
||||
def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True):
|
||||
shard_transition = get_sample_shard_transition(spec, start_slot, block_lengths)
|
||||
slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret,
|
||||
block_lengths[0], slashable=slashable)
|
||||
block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector)
|
||||
shard_transition.shard_data_roots[0] = get_block_data_merkle_root(block_data)
|
||||
return shard_transition, slashable_test_vector
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from eth2spec.test.context import (
|
||||
PHASE0, PHASE1,
|
||||
PHASE0,
|
||||
spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import sign_indexed_attestation
|
||||
|
@ -162,10 +162,7 @@ def test_same_data(spec, state):
|
|||
|
||||
indexed_att_1 = attester_slashing.attestation_1
|
||||
att_2_data = get_attestation_2_data(spec, attester_slashing)
|
||||
if spec.fork == PHASE1:
|
||||
indexed_att_1.attestation.data = att_2_data
|
||||
else:
|
||||
indexed_att_1.data = att_2_data
|
||||
indexed_att_1.data = att_2_data
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
|
|
@ -25,22 +25,9 @@ def test_on_time_success(spec, state):
|
|||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_on_time_empty_custody_bits_blocks(spec, state):
|
||||
def test_late_success(spec, state):
|
||||
attestation = get_valid_late_attestation(spec, state, signed=True)
|
||||
|
||||
assert not any(attestation.custody_bits_blocks)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_late_with_custody_bits_blocks(spec, state):
|
||||
attestation = get_valid_on_time_attestation(spec, state, signed=True)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
yield from run_attestation_processing(spec, state, attestation)
|
||||
|
|
|
@ -0,0 +1,302 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_chunk_challenge,
|
||||
get_valid_custody_chunk_response,
|
||||
get_sample_shard_transition,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_on_time_attestation,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to
|
||||
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.process_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_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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)
|
||||
|
||||
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_challenge_empty_element_replaced(spec, state):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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)
|
||||
|
||||
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||
|
||||
state.custody_chunk_challenge_records.append(spec.CustodyChunkChallengeRecord())
|
||||
|
||||
yield from run_chunk_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_duplicate_challenge(spec, state):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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)
|
||||
|
||||
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||
|
||||
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||
|
||||
yield from run_chunk_challenge_processing(spec, state, challenge, valid=False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_second_challenge(spec, state):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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)
|
||||
|
||||
challenge0 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=0)
|
||||
|
||||
_, _, _ = run_chunk_challenge_processing(spec, state, challenge0)
|
||||
|
||||
challenge1 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=1)
|
||||
|
||||
yield from run_chunk_challenge_processing(spec, state, challenge1)
|
||||
|
||||
|
||||
@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_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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 * 20)
|
||||
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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 * 20)
|
||||
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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)
|
|
@ -28,30 +28,14 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru
|
|||
|
||||
pre_next_custody_secret_to_reveal = \
|
||||
state.validators[revealer_index].next_custody_secret_to_reveal
|
||||
pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
|
||||
|
||||
spec.process_custody_key_reveal(state, custody_key_reveal)
|
||||
|
||||
post_next_custody_secret_to_reveal = \
|
||||
state.validators[revealer_index].next_custody_secret_to_reveal
|
||||
post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
|
||||
|
||||
assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1
|
||||
|
||||
if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period(
|
||||
pre_next_custody_secret_to_reveal,
|
||||
revealer_index
|
||||
) + spec.EPOCHS_PER_CUSTODY_PERIOD:
|
||||
assert post_reveal_lateness > 0
|
||||
if pre_reveal_lateness == 0:
|
||||
assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period(
|
||||
pre_next_custody_secret_to_reveal,
|
||||
revealer_index
|
||||
) - spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
else:
|
||||
if pre_reveal_lateness > 0:
|
||||
assert post_reveal_lateness < pre_reveal_lateness
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
||||
|
@ -103,17 +87,3 @@ def test_double_reveal(spec, state):
|
|||
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_max_decrement(spec, state):
|
||||
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
custody_key_reveal2 = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2)
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_custody_slashing,
|
||||
get_custody_secret,
|
||||
get_custody_slashable_shard_transition,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_on_time_attestation,
|
||||
)
|
||||
from eth2spec.utils.ssz.ssz_typing import ByteList
|
||||
from eth2spec.test.helpers.state import get_balance, transition_to
|
||||
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_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True):
|
||||
"""
|
||||
Run ``process_bit_challenge``, yielding:
|
||||
- pre-state ('pre')
|
||||
- CustodySlashing ('custody_slashing')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
yield 'pre', state
|
||||
yield 'custody_slashing', custody_slashing
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
if correct:
|
||||
pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index)
|
||||
else:
|
||||
pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index)
|
||||
|
||||
spec.process_custody_slashing(state, custody_slashing)
|
||||
|
||||
if correct:
|
||||
slashed_validator = state.validators[custody_slashing.message.malefactor_index]
|
||||
assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance
|
||||
else:
|
||||
slashed_validator = state.validators[custody_slashing.message.whistleblower_index]
|
||||
assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance
|
||||
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
||||
def run_standard_custody_slashing_test(spec,
|
||||
state,
|
||||
shard_lateness=None,
|
||||
shard=None,
|
||||
validator_index=None,
|
||||
block_lengths=None,
|
||||
slashing_message_data=None,
|
||||
correct=True,
|
||||
valid=True):
|
||||
if shard_lateness is None:
|
||||
shard_lateness = spec.SLOTS_PER_EPOCH
|
||||
transition_to(spec, state, state.slot + shard_lateness)
|
||||
|
||||
if shard is None:
|
||||
shard = 0
|
||||
if validator_index is None:
|
||||
validator_index = spec.get_beacon_committee(state, state.slot, shard)[0]
|
||||
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
if block_lengths is None:
|
||||
block_lengths = [2**15 // 3] * len(offset_slots)
|
||||
|
||||
custody_secret = get_custody_secret(spec, state, validator_index)
|
||||
shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(
|
||||
spec,
|
||||
state.slot,
|
||||
block_lengths,
|
||||
custody_secret,
|
||||
slashable=correct,
|
||||
)
|
||||
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=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))
|
||||
|
||||
slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition,
|
||||
custody_secret, slashable_test_vector)
|
||||
|
||||
if slashing_message_data is not None:
|
||||
slashing.message.data = slashing_message_data
|
||||
|
||||
yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_custody_slashing(spec, state):
|
||||
yield from run_standard_custody_slashing_test(spec, state)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_incorrect_custody_slashing(spec, state):
|
||||
yield from run_standard_custody_slashing_test(spec, state, correct=False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_multiple_epochs_custody(spec, state):
|
||||
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_many_epochs_custody(spec, state):
|
||||
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_invalid_custody_slashing(spec, state):
|
||||
yield from run_standard_custody_slashing_test(
|
||||
spec,
|
||||
state,
|
||||
slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](),
|
||||
valid=False,
|
||||
)
|
|
@ -0,0 +1,57 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_chunk_challenge,
|
||||
get_sample_shard_transition,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_on_time_attestation,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to
|
||||
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_chunk_challenge import (
|
||||
run_chunk_challenge_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_chunk_challenge(spec, state):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=shard_transition)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
validator_index = spec.get_beacon_committee(
|
||||
state,
|
||||
attestation.data.slot,
|
||||
attestation.data.index
|
||||
)[0]
|
||||
|
||||
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||
|
||||
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||
|
||||
assert state.validators[validator_index].slashed == 0
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH)
|
||||
|
||||
state.validators[validator_index].slashed = 0
|
||||
|
||||
yield from run_process_challenge_deadlines(spec, state)
|
||||
|
||||
assert state.validators[validator_index].slashed == 1
|
|
@ -0,0 +1,165 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_chunk_challenge,
|
||||
get_valid_custody_chunk_response,
|
||||
get_valid_custody_key_reveal,
|
||||
get_sample_shard_transition
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_on_time_attestation,
|
||||
)
|
||||
from eth2spec.test.helpers.state import next_epoch_via_block, transition_to
|
||||
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_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_custody_final_updates(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_custody_final_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_custody_final_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_via_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_via_block(spec, state)
|
||||
|
||||
while (state.validators[0].next_custody_secret_to_reveal
|
||||
<= spec.get_custody_period_for_validator(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_custody_final_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_chunk_challenge(spec, state):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=shard_transition)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
validator_index = spec.get_beacon_committee(
|
||||
state,
|
||||
attestation.data.slot,
|
||||
attestation.data.index
|
||||
)[0]
|
||||
|
||||
spec.initiate_validator_exit(state, validator_index)
|
||||
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
|
||||
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
||||
|
||||
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_via_block(spec, state)
|
||||
|
||||
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||
<= spec.get_custody_period_for_validator(
|
||||
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_via_block(spec, state)
|
||||
|
||||
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||
|
||||
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||
|
||||
yield from run_process_custody_final_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):
|
||||
transition_to(spec, state, state.slot + 1)
|
||||
shard = 0
|
||||
offset_slots = spec.get_offset_slots(state, shard)
|
||||
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
|
||||
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
|
||||
shard_transition=shard_transition)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
validator_index = spec.get_beacon_committee(
|
||||
state,
|
||||
attestation.data.slot,
|
||||
attestation.data.index
|
||||
)[0]
|
||||
|
||||
spec.initiate_validator_exit(state, validator_index)
|
||||
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
|
||||
next_epoch_via_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_via_block(spec, state)
|
||||
|
||||
while (state.validators[validator_index].next_custody_secret_to_reveal
|
||||
<= spec.get_custody_period_for_validator(
|
||||
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_via_block(spec, state)
|
||||
|
||||
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition)
|
||||
|
||||
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
|
||||
|
||||
next_epoch_via_block(spec, state)
|
||||
|
||||
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
|
||||
|
||||
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)
|
||||
|
||||
_, _, _ = run_custody_chunk_response_processing(spec, state, custody_response)
|
||||
|
||||
yield from run_process_custody_final_updates(spec, state)
|
||||
|
||||
assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
|
@ -0,0 +1,50 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_custody_key_reveal,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to
|
||||
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
|
||||
transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH)
|
||||
|
||||
# Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers)
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1)
|
||||
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
|
||||
|
||||
state.validators[0].slashed = 0
|
||||
|
||||
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):
|
||||
transition_to(spec, state, 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
|
||||
|
||||
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
|
||||
|
||||
yield from run_process_challenge_deadlines(spec, state)
|
||||
|
||||
assert state.validators[0].slashed == 0
|
Loading…
Reference in New Issue