Merge branch 'dev' into ralexstokes-patch-3
This commit is contained in:
commit
f0e65709c2
|
@ -10,7 +10,7 @@ SHARD_COUNT: 1024
|
|||
# 2**7 (= 128)
|
||||
TARGET_COMMITTEE_SIZE: 128
|
||||
# 2**12 (= 4,096)
|
||||
MAX_INDICES_PER_ATTESTATION: 4096
|
||||
MAX_VALIDATORS_PER_COMMITTEE: 4096
|
||||
# 2**2 (= 4)
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# 2**16 (= 65,536)
|
||||
|
@ -76,7 +76,7 @@ MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
|
|||
# 2**16 (= 65,536) epochs ~0.8 years
|
||||
EPOCHS_PER_HISTORICAL_VECTOR: 65536
|
||||
# 2**13 (= 8,192) epochs ~36 days
|
||||
EPOCHS_PER_SLASHED_BALANCES_VECTOR: 8192
|
||||
EPOCHS_PER_SLASHINGS_VECTOR: 8192
|
||||
# 2**24 (= 16,777,216) historical roots, ~26,131 years
|
||||
HISTORICAL_ROOTS_LIMIT: 16777216
|
||||
# 2**40 (= 1,099,511,627,776) validator spots
|
||||
|
@ -88,7 +88,7 @@ VALIDATOR_REGISTRY_LIMIT: 1099511627776
|
|||
# 2**5 (= 32)
|
||||
BASE_REWARD_FACTOR: 32
|
||||
# 2**9 (= 512)
|
||||
WHISTLEBLOWING_REWARD_QUOTIENT: 512
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT: 512
|
||||
# 2**3 (= 8)
|
||||
PROPOSER_REWARD_QUOTIENT: 8
|
||||
# 2**25 (= 33,554,432)
|
||||
|
|
|
@ -9,7 +9,7 @@ SHARD_COUNT: 8
|
|||
# [customized] unsecure, but fast
|
||||
TARGET_COMMITTEE_SIZE: 4
|
||||
# 2**12 (= 4,096)
|
||||
MAX_INDICES_PER_ATTESTATION: 4096
|
||||
MAX_VALIDATORS_PER_COMMITTEE: 4096
|
||||
# 2**2 (= 4)
|
||||
MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||
# 2**16 (= 65,536)
|
||||
|
@ -77,7 +77,7 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
|||
# [customized] smaller state
|
||||
EPOCHS_PER_HISTORICAL_VECTOR: 64
|
||||
# [customized] smaller state
|
||||
EPOCHS_PER_SLASHED_BALANCES_VECTOR: 64
|
||||
EPOCHS_PER_SLASHINGS_VECTOR: 64
|
||||
# 2**24 (= 16,777,216) historical roots
|
||||
HISTORICAL_ROOTS_LIMIT: 16777216
|
||||
# 2**40 (= 1,099,511,627,776) validator spots
|
||||
|
@ -89,7 +89,7 @@ VALIDATOR_REGISTRY_LIMIT: 1099511627776
|
|||
# 2**5 (= 32)
|
||||
BASE_REWARD_FACTOR: 32
|
||||
# 2**9 (= 512)
|
||||
WHISTLEBLOWING_REWARD_QUOTIENT: 512
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT: 512
|
||||
# 2**3 (= 8)
|
||||
PROPOSER_REWARD_QUOTIENT: 8
|
||||
# 2**25 (= 33,554,432)
|
||||
|
|
|
@ -25,8 +25,8 @@ from eth2spec.utils.ssz.ssz_impl import (
|
|||
signing_root,
|
||||
)
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
Bit, Bool, Container, List, Vector, Bytes, uint64,
|
||||
Bytes4, Bytes32, Bytes48, Bytes96,
|
||||
bit, boolean, Container, List, Vector, uint64,
|
||||
Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
|
||||
)
|
||||
from eth2spec.utils.bls import (
|
||||
bls_aggregate_pubkeys,
|
||||
|
@ -52,8 +52,8 @@ from eth2spec.utils.ssz.ssz_impl import (
|
|||
is_empty,
|
||||
)
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
Bit, Bool, Container, List, Vector, Bytes, uint64,
|
||||
Bytes4, Bytes32, Bytes48, Bytes96,
|
||||
bit, boolean, Container, List, Vector, Bytes, uint64,
|
||||
Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
|
||||
)
|
||||
from eth2spec.utils.bls import (
|
||||
bls_aggregate_pubkeys,
|
||||
|
@ -174,8 +174,8 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st
|
|||
|
||||
|
||||
ignored_dependencies = [
|
||||
'Bit', 'Bool', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN'
|
||||
'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96',
|
||||
'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN'
|
||||
'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
|
||||
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
|
||||
'bytes' # to be removed after updating spec doc
|
||||
]
|
||||
|
|
|
@ -71,7 +71,7 @@ We require:
|
|||
G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109
|
||||
q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
|
||||
|
||||
def hash_to_G2(message_hash: Bytes32, domain: uint64) -> [uint384]:
|
||||
def hash_to_G2(message_hash: Bytes32, domain: uint64) -> Tuple[uint384, uint384]:
|
||||
# Initial candidate x coordinate
|
||||
x_re = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x01'), 'big')
|
||||
x_im = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x02'), 'big')
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
- [Containers](#containers)
|
||||
- [Misc dependencies](#misc-dependencies)
|
||||
- [`Fork`](#fork)
|
||||
- [`Checkpoint`](#checkpoint)
|
||||
- [`Validator`](#validator)
|
||||
- [`Crosslink`](#crosslink)
|
||||
- [`AttestationData`](#attestationdata)
|
||||
|
@ -33,6 +34,7 @@
|
|||
- [`Eth1Data`](#eth1data)
|
||||
- [`HistoricalBatch`](#historicalbatch)
|
||||
- [`DepositData`](#depositdata)
|
||||
- [`CompactCommittee`](#compactcommittee)
|
||||
- [`BeaconBlockHeader`](#beaconblockheader)
|
||||
- [Beacon operations](#beacon-operations)
|
||||
- [`ProposerSlashing`](#proposerslashing)
|
||||
|
@ -68,7 +70,7 @@
|
|||
- [`get_block_root_at_slot`](#get_block_root_at_slot)
|
||||
- [`get_block_root`](#get_block_root)
|
||||
- [`get_randao_mix`](#get_randao_mix)
|
||||
- [`get_active_index_root`](#get_active_index_root)
|
||||
- [`get_compact_committees_root`](#get_compact_committees_root)
|
||||
- [`generate_seed`](#generate_seed)
|
||||
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
||||
- [`verify_merkle_branch`](#verify_merkle_branch)
|
||||
|
@ -80,8 +82,6 @@
|
|||
- [`bytes_to_int`](#bytes_to_int)
|
||||
- [`get_total_balance`](#get_total_balance)
|
||||
- [`get_domain`](#get_domain)
|
||||
- [`get_bitfield_bit`](#get_bitfield_bit)
|
||||
- [`verify_bitfield`](#verify_bitfield)
|
||||
- [`convert_to_indexed`](#convert_to_indexed)
|
||||
- [`validate_indexed_attestation`](#validate_indexed_attestation)
|
||||
- [`is_slashable_attestation_data`](#is_slashable_attestation_data)
|
||||
|
@ -187,10 +187,11 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
| - | - |
|
||||
| `SHARD_COUNT` | `2**10` (= 1,024) |
|
||||
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
|
||||
| `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) |
|
||||
| `MAX_VALIDATORS_PER_COMMITTEE` | `2**12` (= 4,096) |
|
||||
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
|
||||
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
|
||||
| `SHUFFLE_ROUND_COUNT` | `90` |
|
||||
| `JUSTIFICATION_BITS_LENGTH` | `4` |
|
||||
|
||||
* For the safety of crosslinks, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
|
||||
|
||||
|
@ -233,7 +234,7 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years |
|
||||
| `EPOCHS_PER_SLASHED_BALANCES_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
|
||||
| `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
|
||||
| `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~26,131 years |
|
||||
| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validator spots | |
|
||||
|
||||
|
@ -242,7 +243,7 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
| Name | Value |
|
||||
| - | - |
|
||||
| `BASE_REWARD_FACTOR` | `2**6` (= 64) |
|
||||
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
|
||||
| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) |
|
||||
| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) |
|
||||
|
@ -290,6 +291,14 @@ class Fork(Container):
|
|||
epoch: Epoch # Epoch of latest fork
|
||||
```
|
||||
|
||||
#### `Checkpoint`
|
||||
|
||||
```python
|
||||
class Checkpoint(Container):
|
||||
epoch: Epoch
|
||||
root: Hash
|
||||
```
|
||||
|
||||
#### `Validator`
|
||||
|
||||
```python
|
||||
|
@ -297,7 +306,7 @@ class Validator(Container):
|
|||
pubkey: BLSPubkey
|
||||
withdrawal_credentials: Hash # Commitment to pubkey for withdrawals and transfers
|
||||
effective_balance: Gwei # Balance at stake
|
||||
slashed: Bool
|
||||
slashed: boolean
|
||||
# Status epochs
|
||||
activation_eligibility_epoch: Epoch # When criteria for activation were met
|
||||
activation_epoch: Epoch
|
||||
|
@ -324,10 +333,8 @@ class AttestationData(Container):
|
|||
# LMD GHOST vote
|
||||
beacon_block_root: Hash
|
||||
# FFG vote
|
||||
source_epoch: Epoch
|
||||
source_root: Hash
|
||||
target_epoch: Epoch
|
||||
target_root: Hash
|
||||
source: Checkpoint
|
||||
target: Checkpoint
|
||||
# Crosslink vote
|
||||
crosslink: Crosslink
|
||||
```
|
||||
|
@ -337,15 +344,15 @@ class AttestationData(Container):
|
|||
```python
|
||||
class AttestationDataAndCustodyBit(Container):
|
||||
data: AttestationData
|
||||
custody_bit: Bit # Challengeable bit (SSZ-bool, 1 byte) for the custody of crosslink data
|
||||
custody_bit: bit # Challengeable bit (SSZ-bool, 1 byte) for the custody of crosslink data
|
||||
```
|
||||
|
||||
#### `IndexedAttestation`
|
||||
|
||||
```python
|
||||
class IndexedAttestation(Container):
|
||||
custody_bit_0_indices: List[ValidatorIndex, MAX_INDICES_PER_ATTESTATION] # Indices with custody bit equal to 0
|
||||
custody_bit_1_indices: List[ValidatorIndex, MAX_INDICES_PER_ATTESTATION] # Indices with custody bit equal to 1
|
||||
custody_bit_0_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 0
|
||||
custody_bit_1_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 1
|
||||
data: AttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
@ -354,7 +361,7 @@ class IndexedAttestation(Container):
|
|||
|
||||
```python
|
||||
class PendingAttestation(Container):
|
||||
aggregation_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
|
||||
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
inclusion_delay: Slot
|
||||
proposer_index: ValidatorIndex
|
||||
|
@ -387,6 +394,14 @@ class DepositData(Container):
|
|||
signature: BLSSignature
|
||||
```
|
||||
|
||||
#### `CompactCommittee`
|
||||
|
||||
```python
|
||||
class CompactCommittee(Container):
|
||||
pubkeys: List[Bytes48, MAX_VALIDATORS_PER_COMMITTEE]
|
||||
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
|
||||
```
|
||||
|
||||
#### `BeaconBlockHeader`
|
||||
|
||||
```python
|
||||
|
@ -421,9 +436,9 @@ class AttesterSlashing(Container):
|
|||
|
||||
```python
|
||||
class Attestation(Container):
|
||||
aggregation_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
|
||||
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
custody_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
|
||||
custody_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
|
@ -511,24 +526,20 @@ class BeaconState(Container):
|
|||
# Shuffling
|
||||
start_shard: Shard
|
||||
randao_mixes: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
active_index_roots: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR] # Active registry digests for light clients
|
||||
compact_committees_roots: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR] # Committee digests for light clients
|
||||
# Slashings
|
||||
slashed_balances: Vector[Gwei, EPOCHS_PER_SLASHED_BALANCES_VECTOR] # Sums of slashed effective balances
|
||||
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
|
||||
# Attestations
|
||||
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
||||
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
|
||||
# Crosslinks
|
||||
previous_crosslinks: Vector[Crosslink, SHARD_COUNT] # Previous epoch snapshot
|
||||
current_crosslinks: Vector[Crosslink, SHARD_COUNT]
|
||||
# Justification
|
||||
previous_justified_epoch: Epoch # Previous epoch snapshot
|
||||
previous_justified_root: Hash # Previous epoch snapshot
|
||||
current_justified_epoch: Epoch
|
||||
current_justified_root: Hash
|
||||
justification_bitfield: uint64 # Bit set for every recent justified epoch
|
||||
# Finality
|
||||
finalized_epoch: Epoch
|
||||
finalized_root: Hash
|
||||
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
|
||||
previous_justified_checkpoint: Checkpoint # Previous epoch snapshot
|
||||
current_justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
```
|
||||
|
||||
## Helper functions
|
||||
|
@ -689,6 +700,9 @@ def get_shard_delta(state: BeaconState, epoch: Epoch) -> int:
|
|||
|
||||
```python
|
||||
def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard:
|
||||
"""
|
||||
Return the start shard of the 0th committee in an epoch.
|
||||
"""
|
||||
assert epoch <= get_current_epoch(state) + 1
|
||||
check_epoch = Epoch(get_current_epoch(state) + 1)
|
||||
shard = Shard((state.start_shard + get_shard_delta(state, get_current_epoch(state))) % SHARD_COUNT)
|
||||
|
@ -702,9 +716,9 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard:
|
|||
|
||||
```python
|
||||
def get_attestation_data_slot(state: BeaconState, data: AttestationData) -> Slot:
|
||||
committee_count = get_epoch_committee_count(state, data.target_epoch)
|
||||
offset = (data.crosslink.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target_epoch)) % SHARD_COUNT
|
||||
return Slot(get_epoch_start_slot(data.target_epoch) + offset // (committee_count // SLOTS_PER_EPOCH))
|
||||
committee_count = get_epoch_committee_count(state, data.target.epoch)
|
||||
offset = (data.crosslink.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target.epoch)) % SHARD_COUNT
|
||||
return Slot(get_epoch_start_slot(data.target.epoch) + offset // (committee_count // SLOTS_PER_EPOCH))
|
||||
```
|
||||
|
||||
### `get_block_root_at_slot`
|
||||
|
@ -742,17 +756,25 @@ def get_randao_mix(state: BeaconState,
|
|||
return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
```
|
||||
|
||||
### `get_active_index_root`
|
||||
### `get_compact_committees_root`
|
||||
|
||||
```python
|
||||
def get_active_index_root(state: BeaconState,
|
||||
epoch: Epoch) -> Hash:
|
||||
def get_compact_committees_root(state: BeaconState, epoch: Epoch) -> Hash:
|
||||
"""
|
||||
Return the index root at a recent ``epoch``.
|
||||
``epoch`` expected to be between
|
||||
(current_epoch - EPOCHS_PER_HISTORICAL_VECTOR + ACTIVATION_EXIT_DELAY, current_epoch + ACTIVATION_EXIT_DELAY].
|
||||
Return the compact committee root for the current epoch.
|
||||
"""
|
||||
return state.active_index_roots[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
committees = [CompactCommittee() for _ in range(SHARD_COUNT)]
|
||||
start_shard = get_epoch_start_shard(state, epoch)
|
||||
for committee_number in range(get_epoch_committee_count(state, epoch)):
|
||||
shard = Shard((start_shard + committee_number) % SHARD_COUNT)
|
||||
for index in get_crosslink_committee(state, epoch, shard):
|
||||
validator = state.validators[index]
|
||||
committees[shard].pubkeys.append(validator.pubkey)
|
||||
compact_balance = validator.effective_balance // EFFECTIVE_BALANCE_INCREMENT
|
||||
# `index` (top 6 bytes) + `slashed` (16th bit) + `compact_balance` (bottom 15 bits)
|
||||
compact_validator = uint64((index << 16) + (validator.slashed << 15) + compact_balance)
|
||||
committees[shard].compact_validators.append(compact_validator)
|
||||
return hash_tree_root(Vector[CompactCommittee, SHARD_COUNT](committees))
|
||||
```
|
||||
|
||||
### `generate_seed`
|
||||
|
@ -764,8 +786,8 @@ def generate_seed(state: BeaconState,
|
|||
Generate a seed for the given ``epoch``.
|
||||
"""
|
||||
return hash(
|
||||
get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD)) + #avoid underflow
|
||||
get_active_index_root(state, epoch) +
|
||||
get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD)) + # avoid underflow
|
||||
hash_tree_root(List[ValidatorIndex, VALIDATOR_REGISTRY_LIMIT](get_active_validator_indices(state, epoch))) +
|
||||
int_to_bytes(epoch, length=32)
|
||||
)
|
||||
```
|
||||
|
@ -864,14 +886,13 @@ def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> S
|
|||
|
||||
```python
|
||||
def get_attesting_indices(state: BeaconState,
|
||||
attestation_data: AttestationData,
|
||||
bitfield: bytes) -> Sequence[ValidatorIndex]:
|
||||
data: AttestationData,
|
||||
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]:
|
||||
"""
|
||||
Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``.
|
||||
Return the set of attesting indices corresponding to ``data`` and ``bitfield``.
|
||||
"""
|
||||
committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.crosslink.shard)
|
||||
assert verify_bitfield(bitfield, len(committee))
|
||||
return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1])
|
||||
committee = get_crosslink_committee(state, data.target.epoch, data.crosslink.shard)
|
||||
return set(index for i, index in enumerate(committee) if bits[i])
|
||||
```
|
||||
|
||||
### `int_to_bytes`
|
||||
|
@ -912,34 +933,6 @@ def get_domain(state: BeaconState,
|
|||
return bls_domain(domain_type, fork_version)
|
||||
```
|
||||
|
||||
### `get_bitfield_bit`
|
||||
|
||||
```python
|
||||
def get_bitfield_bit(bitfield: bytes, i: int) -> int:
|
||||
"""
|
||||
Extract the bit in ``bitfield`` at position ``i``.
|
||||
"""
|
||||
return (bitfield[i // 8] >> (i % 8)) % 2
|
||||
```
|
||||
|
||||
### `verify_bitfield`
|
||||
|
||||
```python
|
||||
def verify_bitfield(bitfield: bytes, committee_size: int) -> bool:
|
||||
"""
|
||||
Verify ``bitfield`` against the ``committee_size``.
|
||||
"""
|
||||
if len(bitfield) != (committee_size + 7) // 8:
|
||||
return False
|
||||
|
||||
# Check `bitfield` is padded with zero bits only
|
||||
for i in range(committee_size, len(bitfield) * 8):
|
||||
if get_bitfield_bit(bitfield, i) == 0b1:
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
### `convert_to_indexed`
|
||||
|
||||
```python
|
||||
|
@ -947,14 +940,14 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA
|
|||
"""
|
||||
Convert ``attestation`` to (almost) indexed-verifiable form.
|
||||
"""
|
||||
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||
custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bitfield)
|
||||
assert set(custody_bit_1_indices).issubset(attesting_indices)
|
||||
custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices]
|
||||
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bits)
|
||||
assert custody_bit_1_indices.issubset(attesting_indices)
|
||||
custody_bit_0_indices = attesting_indices.difference(custody_bit_1_indices)
|
||||
|
||||
return IndexedAttestation(
|
||||
custody_bit_0_indices=custody_bit_0_indices,
|
||||
custody_bit_1_indices=custody_bit_1_indices,
|
||||
custody_bit_0_indices=sorted(custody_bit_0_indices),
|
||||
custody_bit_1_indices=sorted(custody_bit_1_indices),
|
||||
data=attestation.data,
|
||||
signature=attestation.signature,
|
||||
)
|
||||
|
@ -973,7 +966,7 @@ def validate_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
|||
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
|
||||
assert len(bit_1_indices) == 0
|
||||
# Verify max number of indices
|
||||
assert len(bit_0_indices) + len(bit_1_indices) <= MAX_INDICES_PER_ATTESTATION
|
||||
assert len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE
|
||||
# Verify index sets are disjoint
|
||||
assert len(set(bit_0_indices).intersection(bit_1_indices)) == 0
|
||||
# Verify indices are sorted
|
||||
|
@ -989,7 +982,7 @@ def validate_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
|||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
signature=indexed_attestation.signature,
|
||||
domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.target_epoch),
|
||||
domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.target.epoch),
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -1002,9 +995,9 @@ def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationDa
|
|||
"""
|
||||
return (
|
||||
# Double vote
|
||||
(data_1 != data_2 and data_1.target_epoch == data_2.target_epoch) or
|
||||
(data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or
|
||||
# Surround vote
|
||||
(data_1.source_epoch < data_2.source_epoch and data_2.target_epoch < data_1.target_epoch)
|
||||
(data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch)
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -1096,21 +1089,22 @@ def slash_validator(state: BeaconState,
|
|||
"""
|
||||
Slash the validator with index ``slashed_index``.
|
||||
"""
|
||||
current_epoch = get_current_epoch(state)
|
||||
epoch = get_current_epoch(state)
|
||||
initiate_validator_exit(state, slashed_index)
|
||||
state.validators[slashed_index].slashed = True
|
||||
state.validators[slashed_index].withdrawable_epoch = Epoch(current_epoch + EPOCHS_PER_SLASHED_BALANCES_VECTOR)
|
||||
slashed_balance = state.validators[slashed_index].effective_balance
|
||||
state.slashed_balances[current_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR] += slashed_balance
|
||||
validator = state.validators[slashed_index]
|
||||
validator.slashed = True
|
||||
validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR))
|
||||
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
|
||||
decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT)
|
||||
|
||||
# Apply proposer and whistleblower rewards
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
if whistleblower_index is None:
|
||||
whistleblower_index = proposer_index
|
||||
whistleblowing_reward = Gwei(slashed_balance // WHISTLEBLOWING_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblowing_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblower_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
decrease_balance(state, slashed_index, whistleblowing_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblower_reward - proposer_reward)
|
||||
```
|
||||
|
||||
## Genesis
|
||||
|
@ -1173,13 +1167,10 @@ def get_genesis_beacon_state(deposits: Sequence[Deposit], genesis_time: int, eth
|
|||
validator.activation_eligibility_epoch = GENESIS_EPOCH
|
||||
validator.activation_epoch = GENESIS_EPOCH
|
||||
|
||||
# Populate active_index_roots
|
||||
genesis_active_index_root = hash_tree_root(
|
||||
List[ValidatorIndex, VALIDATOR_REGISTRY_LIMIT](get_active_validator_indices(state, GENESIS_EPOCH))
|
||||
)
|
||||
# Populate compact_committees_roots
|
||||
genesis_committee_root = get_compact_committees_root(state, GENESIS_EPOCH)
|
||||
for index in range(EPOCHS_PER_HISTORICAL_VECTOR):
|
||||
state.active_index_roots[index] = genesis_active_index_root
|
||||
|
||||
state.compact_committees_roots[index] = genesis_committee_root
|
||||
return state
|
||||
```
|
||||
|
||||
|
@ -1256,7 +1247,7 @@ def get_total_active_balance(state: BeaconState) -> Gwei:
|
|||
|
||||
```python
|
||||
def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
|
||||
assert epoch in (get_current_epoch(state), get_previous_epoch(state))
|
||||
assert epoch in (get_previous_epoch(state), get_current_epoch(state))
|
||||
return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations
|
||||
```
|
||||
|
||||
|
@ -1264,7 +1255,7 @@ def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequen
|
|||
def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
|
||||
return [
|
||||
a for a in get_matching_source_attestations(state, epoch)
|
||||
if a.data.target_root == get_block_root(state, epoch)
|
||||
if a.data.target.root == get_block_root(state, epoch)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -1281,7 +1272,7 @@ def get_unslashed_attesting_indices(state: BeaconState,
|
|||
attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]:
|
||||
output = set() # type: Set[ValidatorIndex]
|
||||
for a in attestations:
|
||||
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield))
|
||||
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits))
|
||||
return set(filter(lambda index: not state.validators[index].slashed, list(output)))
|
||||
```
|
||||
|
||||
|
@ -1316,46 +1307,38 @@ def process_justification_and_finalization(state: BeaconState) -> None:
|
|||
|
||||
previous_epoch = get_previous_epoch(state)
|
||||
current_epoch = get_current_epoch(state)
|
||||
old_previous_justified_epoch = state.previous_justified_epoch
|
||||
old_current_justified_epoch = state.current_justified_epoch
|
||||
old_previous_justified_checkpoint = state.previous_justified_checkpoint
|
||||
old_current_justified_checkpoint = state.current_justified_checkpoint
|
||||
|
||||
# Process justifications
|
||||
state.previous_justified_epoch = state.current_justified_epoch
|
||||
state.previous_justified_root = state.current_justified_root
|
||||
state.justification_bitfield = (state.justification_bitfield << 1) % 2**64
|
||||
previous_epoch_matching_target_balance = get_attesting_balance(
|
||||
state, get_matching_target_attestations(state, previous_epoch)
|
||||
)
|
||||
if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_epoch = previous_epoch
|
||||
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
|
||||
state.justification_bitfield |= (1 << 1)
|
||||
current_epoch_matching_target_balance = get_attesting_balance(
|
||||
state, get_matching_target_attestations(state, current_epoch)
|
||||
)
|
||||
if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_epoch = current_epoch
|
||||
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
|
||||
state.justification_bitfield |= (1 << 0)
|
||||
state.previous_justified_checkpoint = state.current_justified_checkpoint
|
||||
state.justification_bits[1:] = state.justification_bits[:-1]
|
||||
state.justification_bits[0] = 0b0
|
||||
matching_target_attestations = get_matching_target_attestations(state, previous_epoch) # Previous epoch
|
||||
if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch,
|
||||
root=get_block_root(state, previous_epoch))
|
||||
state.justification_bits[1] = 0b1
|
||||
matching_target_attestations = get_matching_target_attestations(state, current_epoch) # Current epoch
|
||||
if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2:
|
||||
state.current_justified_checkpoint = Checkpoint(epoch=current_epoch,
|
||||
root=get_block_root(state, current_epoch))
|
||||
state.justification_bits[0] = 0b1
|
||||
|
||||
# Process finalizations
|
||||
bitfield = state.justification_bitfield
|
||||
bits = state.justification_bits
|
||||
# The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source
|
||||
if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch + 3 == current_epoch:
|
||||
state.finalized_epoch = old_previous_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
if all(bits[1:4]) and old_previous_justified_checkpoint.epoch + 3 == current_epoch:
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint
|
||||
# The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source
|
||||
if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch + 2 == current_epoch:
|
||||
state.finalized_epoch = old_previous_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
if all(bits[1:3]) and old_previous_justified_checkpoint.epoch + 2 == current_epoch:
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint
|
||||
# The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source
|
||||
if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch + 2 == current_epoch:
|
||||
state.finalized_epoch = old_current_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
if all(bits[0:3]) and old_current_justified_checkpoint.epoch + 2 == current_epoch:
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint
|
||||
# The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source
|
||||
if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch + 1 == current_epoch:
|
||||
state.finalized_epoch = old_current_justified_epoch
|
||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||
if all(bits[0:2]) and old_current_justified_checkpoint.epoch + 1 == current_epoch:
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint
|
||||
```
|
||||
|
||||
#### Crosslinks
|
||||
|
@ -1410,7 +1393,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
|
|||
index = ValidatorIndex(index)
|
||||
attestation = min([
|
||||
a for a in matching_source_attestations
|
||||
if index in get_attesting_indices(state, a.data, a.aggregation_bitfield)
|
||||
if index in get_attesting_indices(state, a.data, a.aggregation_bits)
|
||||
], key=lambda a: a.inclusion_delay)
|
||||
proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT)
|
||||
rewards[attestation.proposer_index] += proposer_reward
|
||||
|
@ -1418,7 +1401,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
|
|||
rewards[index] += Gwei(max_attester_reward * MIN_ATTESTATION_INCLUSION_DELAY // attestation.inclusion_delay)
|
||||
|
||||
# Inactivity penalty
|
||||
finality_delay = previous_epoch - state.finalized_epoch
|
||||
finality_delay = previous_epoch - state.finalized_checkpoint.epoch
|
||||
if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
||||
matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations)
|
||||
for index in eligible_validator_indices:
|
||||
|
@ -1483,7 +1466,7 @@ def process_registry_updates(state: BeaconState) -> None:
|
|||
activation_queue = sorted([
|
||||
index for index, validator in enumerate(state.validators) if
|
||||
validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH and
|
||||
validator.activation_epoch >= get_delayed_activation_exit_epoch(state.finalized_epoch)
|
||||
validator.activation_epoch >= get_delayed_activation_exit_epoch(state.finalized_checkpoint.epoch)
|
||||
], key=lambda index: state.validators[index].activation_eligibility_epoch)
|
||||
# Dequeued validators for activation up to churn limit (without resetting activation epoch)
|
||||
for index in activation_queue[:get_churn_limit(state)]:
|
||||
|
@ -1498,18 +1481,9 @@ def process_registry_updates(state: BeaconState) -> None:
|
|||
def process_slashings(state: BeaconState) -> None:
|
||||
epoch = get_current_epoch(state)
|
||||
total_balance = get_total_active_balance(state)
|
||||
|
||||
# Compute slashed balances in the current epoch
|
||||
total_at_start = state.slashed_balances[(epoch + 1) % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
total_at_end = state.slashed_balances[epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
total_penalties = total_at_end - total_at_start
|
||||
|
||||
for index, validator in enumerate(state.validators):
|
||||
if validator.slashed and epoch + EPOCHS_PER_SLASHED_BALANCES_VECTOR // 2 == validator.withdrawable_epoch:
|
||||
penalty = max(
|
||||
validator.effective_balance * min(total_penalties * 3, total_balance) // total_balance,
|
||||
validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT
|
||||
)
|
||||
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
|
||||
penalty = validator.effective_balance * min(sum(state.slashings) * 3, total_balance) // total_balance
|
||||
decrease_balance(state, ValidatorIndex(index), penalty)
|
||||
```
|
||||
|
||||
|
@ -1518,7 +1492,7 @@ def process_slashings(state: BeaconState) -> None:
|
|||
```python
|
||||
def process_final_updates(state: BeaconState) -> None:
|
||||
current_epoch = get_current_epoch(state)
|
||||
next_epoch = current_epoch + 1
|
||||
next_epoch = Shard(current_epoch + 1)
|
||||
# Reset eth1 data votes
|
||||
if (state.slot + 1) % SLOTS_PER_ETH1_VOTING_PERIOD == 0:
|
||||
state.eth1_data_votes = []
|
||||
|
@ -1531,16 +1505,10 @@ def process_final_updates(state: BeaconState) -> None:
|
|||
# Update start shard
|
||||
state.start_shard = Shard((state.start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT)
|
||||
# Set active index root
|
||||
index_root_position = (next_epoch + ACTIVATION_EXIT_DELAY) % EPOCHS_PER_HISTORICAL_VECTOR
|
||||
state.active_index_roots[index_root_position] = hash_tree_root(
|
||||
List[ValidatorIndex, VALIDATOR_REGISTRY_LIMIT](
|
||||
get_active_validator_indices(state, Epoch(next_epoch + ACTIVATION_EXIT_DELAY))
|
||||
)
|
||||
)
|
||||
# Set total slashed balances
|
||||
state.slashed_balances[next_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR] = (
|
||||
state.slashed_balances[current_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
|
||||
)
|
||||
committee_root_position = (next_epoch + ACTIVATION_EXIT_DELAY) % EPOCHS_PER_HISTORICAL_VECTOR
|
||||
state.compact_committees_roots[committee_root_position] = get_compact_committees_root(state, next_epoch)
|
||||
# Reset slashings
|
||||
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
|
||||
# Set randao mix
|
||||
state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch)
|
||||
# Set historical root accumulator
|
||||
|
@ -1684,35 +1652,35 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
Process ``Attestation`` operation.
|
||||
"""
|
||||
data = attestation.data
|
||||
|
||||
assert data.crosslink.shard < SHARD_COUNT
|
||||
assert data.target_epoch in (get_previous_epoch(state), get_current_epoch(state))
|
||||
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
|
||||
|
||||
attestation_slot = get_attestation_data_slot(state, data)
|
||||
assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH
|
||||
|
||||
pending_attestation = PendingAttestation(
|
||||
data=data,
|
||||
aggregation_bitfield=attestation.aggregation_bitfield,
|
||||
aggregation_bits=attestation.aggregation_bits,
|
||||
inclusion_delay=state.slot - attestation_slot,
|
||||
proposer_index=get_beacon_proposer_index(state),
|
||||
)
|
||||
|
||||
if data.target_epoch == get_current_epoch(state):
|
||||
ffg_data = (state.current_justified_epoch, state.current_justified_root, get_current_epoch(state))
|
||||
if data.target.epoch == get_current_epoch(state):
|
||||
assert data.source == state.current_justified_checkpoint
|
||||
parent_crosslink = state.current_crosslinks[data.crosslink.shard]
|
||||
state.current_epoch_attestations.append(pending_attestation)
|
||||
else:
|
||||
ffg_data = (state.previous_justified_epoch, state.previous_justified_root, get_previous_epoch(state))
|
||||
assert data.source == state.previous_justified_checkpoint
|
||||
parent_crosslink = state.previous_crosslinks[data.crosslink.shard]
|
||||
state.previous_epoch_attestations.append(pending_attestation)
|
||||
|
||||
# Check FFG data, crosslink data, and signature
|
||||
assert ffg_data == (data.source_epoch, data.source_root, data.target_epoch)
|
||||
assert data.crosslink.start_epoch == parent_crosslink.end_epoch
|
||||
assert data.crosslink.end_epoch == min(data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)
|
||||
# Check crosslink against expected parent crosslink
|
||||
assert data.crosslink.parent_root == hash_tree_root(parent_crosslink)
|
||||
assert data.crosslink.start_epoch == parent_crosslink.end_epoch
|
||||
assert data.crosslink.end_epoch == min(data.target.epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)
|
||||
assert data.crosslink.data_root == ZERO_HASH # [to be removed in phase 1]
|
||||
|
||||
# Check signature
|
||||
validate_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||
```
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
- [Time parameters](#time-parameters)
|
||||
- [Fork choice](#fork-choice)
|
||||
- [Helpers](#helpers)
|
||||
- [`Target`](#target)
|
||||
- [`Checkpoint`](#checkpoint)
|
||||
- [`Store`](#store)
|
||||
- [`get_genesis_store`](#get_genesis_store)
|
||||
- [`get_ancestor`](#get_ancestor)
|
||||
|
@ -55,11 +55,11 @@ The head block root associated with a `store` is defined as `get_head(store)`. A
|
|||
|
||||
### Helpers
|
||||
|
||||
#### `Target`
|
||||
#### `LatestMessage`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Target(object):
|
||||
@dataclass(eq=True, frozen=True)
|
||||
class LatestMessage(object):
|
||||
epoch: Epoch
|
||||
root: Hash
|
||||
```
|
||||
|
@ -69,12 +69,13 @@ class Target(object):
|
|||
```python
|
||||
@dataclass
|
||||
class Store(object):
|
||||
time: int
|
||||
justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
|
||||
states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
||||
time: int = 0
|
||||
latest_targets: Dict[ValidatorIndex, Target] = field(default_factory=dict)
|
||||
justified_root: Hash = ZERO_HASH
|
||||
finalized_root: Hash = ZERO_HASH
|
||||
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
|
||||
```
|
||||
|
||||
#### `get_genesis_store`
|
||||
|
@ -83,12 +84,15 @@ class Store(object):
|
|||
def get_genesis_store(genesis_state: BeaconState) -> Store:
|
||||
genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))
|
||||
root = signing_root(genesis_block)
|
||||
justified_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
|
||||
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
|
||||
return Store(
|
||||
blocks={root: genesis_block},
|
||||
states={root: genesis_state},
|
||||
time=genesis_state.genesis_time,
|
||||
justified_root=root,
|
||||
finalized_root=root,
|
||||
justified_checkpoint=justified_checkpoint,
|
||||
finalized_checkpoint=finalized_checkpoint,
|
||||
blocks={root: genesis_block},
|
||||
block_states={root: genesis_state.copy()},
|
||||
checkpoint_states={justified_checkpoint: genesis_state.copy()},
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -105,11 +109,12 @@ def get_ancestor(store: Store, root: Hash, slot: Slot) -> Hash:
|
|||
|
||||
```python
|
||||
def get_latest_attesting_balance(store: Store, root: Hash) -> Gwei:
|
||||
state = store.states[store.justified_root]
|
||||
active_indices = get_active_validator_indices(state.validator_registry, get_current_epoch(state))
|
||||
state = store.checkpoint_states[store.justified_checkpoint]
|
||||
active_indices = get_active_validator_indices(state, get_current_epoch(state))
|
||||
return Gwei(sum(
|
||||
state.validator_registry[i].effective_balance for i in active_indices
|
||||
if get_ancestor(store, store.latest_targets[i].root, store.blocks[root].slot) == root
|
||||
state.validators[i].effective_balance for i in active_indices
|
||||
if (i in store.latest_messages and
|
||||
get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
|
||||
))
|
||||
```
|
||||
|
||||
|
@ -118,9 +123,13 @@ def get_latest_attesting_balance(store: Store, root: Hash) -> Gwei:
|
|||
```python
|
||||
def get_head(store: Store) -> Hash:
|
||||
# Execute the LMD-GHOST fork choice
|
||||
head = store.justified_root
|
||||
head = store.justified_checkpoint.root
|
||||
justified_slot = get_epoch_start_slot(store.justified_checkpoint.epoch)
|
||||
while True:
|
||||
children = [root for root in store.blocks.keys() if store.blocks[root].parent_root == head]
|
||||
children = [
|
||||
root for root in store.blocks.keys()
|
||||
if store.blocks[root].parent_root == head and store.blocks[root].slot > justified_slot
|
||||
]
|
||||
if len(children) == 0:
|
||||
return head
|
||||
# Sort by latest attesting balance with ties broken lexicographically
|
||||
|
@ -141,35 +150,65 @@ def on_tick(store: Store, time: int) -> None:
|
|||
```python
|
||||
def on_block(store: Store, block: BeaconBlock) -> None:
|
||||
# Make a copy of the state to avoid mutability issues
|
||||
pre_state = store.states[block.parent_root].copy()
|
||||
assert block.parent_root in store.block_states
|
||||
pre_state = store.block_states[block.parent_root].copy()
|
||||
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
|
||||
assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT
|
||||
# Add new block to the store
|
||||
store.blocks[signing_root(block)] = block
|
||||
# Check block is a descendant of the finalized block
|
||||
assert get_ancestor(store, signing_root(block), store.blocks[store.finalized_root].slot) == store.finalized_root
|
||||
assert (
|
||||
get_ancestor(store, signing_root(block), store.blocks[store.finalized_checkpoint.root].slot) ==
|
||||
store.finalized_checkpoint.root
|
||||
)
|
||||
# Check that block is later than the finalized epoch slot
|
||||
assert block.slot > get_epoch_start_slot(store.finalized_checkpoint.epoch)
|
||||
# Check the block is valid and compute the post-state
|
||||
state = state_transition(pre_state, block)
|
||||
# Add new state to the store
|
||||
store.states[signing_root(block)] = state
|
||||
# Update justified block root
|
||||
if state.current_justified_epoch > slot_to_epoch(store.blocks[store.justified_root].slot):
|
||||
store.justified_root = state.current_justified_root
|
||||
elif state.previous_justified_epoch > slot_to_epoch(store.blocks[store.justified_root].slot):
|
||||
store.justified_root = state.previous_justified_root
|
||||
# Update finalized block root
|
||||
if state.finalized_epoch > slot_to_epoch(store.blocks[store.finalized_root].slot):
|
||||
store.finalized_root = state.finalized_root
|
||||
# Add new state for this block to the store
|
||||
store.block_states[signing_root(block)] = state
|
||||
|
||||
# Update justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.justified_checkpoint = state.current_justified_checkpoint
|
||||
elif state.previous_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.justified_checkpoint = state.previous_justified_checkpoint
|
||||
|
||||
# Update finalized checkpoint
|
||||
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
|
||||
store.finalized_checkpoint = state.finalized_checkpoint
|
||||
```
|
||||
|
||||
#### `on_attestation`
|
||||
|
||||
```python
|
||||
def on_attestation(store: Store, attestation: Attestation) -> None:
|
||||
state = store.states[get_head(store)]
|
||||
indexed_attestation = convert_to_indexed(state, attestation)
|
||||
validate_indexed_attestation(state, indexed_attestation)
|
||||
target = attestation.data.target
|
||||
|
||||
# Cannot calculate the current shuffling if have not seen the target
|
||||
assert target.root in store.blocks
|
||||
|
||||
# Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrivesr
|
||||
base_state = store.block_states[target.root].copy()
|
||||
assert store.time >= base_state.genesis_time + get_epoch_start_slot(target.epoch) * SECONDS_PER_SLOT
|
||||
|
||||
# Store target checkpoint state if not yet seen
|
||||
if target not in store.checkpoint_states:
|
||||
process_slots(base_state, get_epoch_start_slot(target.epoch))
|
||||
store.checkpoint_states[target] = base_state
|
||||
target_state = store.checkpoint_states[target]
|
||||
|
||||
# Attestations can only affect the fork choice of subsequent slots.
|
||||
# Delay consideration in the fork choice until their slot is in the past.
|
||||
attestation_slot = get_attestation_data_slot(target_state, attestation.data)
|
||||
assert store.time >= (attestation_slot + 1) * SECONDS_PER_SLOT
|
||||
|
||||
# Get state at the `target` to validate attestation and calculate the committees
|
||||
indexed_attestation = convert_to_indexed(target_state, attestation)
|
||||
validate_indexed_attestation(target_state, indexed_attestation)
|
||||
|
||||
# Update latest messages
|
||||
for i in indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_indices:
|
||||
if i not in store.latest_targets or attestation.data.target_epoch > store.latest_targets[i].epoch:
|
||||
store.latest_targets[i] = Target(attestation.data.target_epoch, attestation.data.target_root)
|
||||
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
|
||||
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=attestation.data.beacon_block_root)
|
||||
```
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
|
||||
- [`get_chunk_bits_root`](#get_chunk_bits_root)
|
||||
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
|
||||
- [`get_validators_custody_reveal_period`](#get_validators_custody_reveal_period)
|
||||
- [`get_reveal_period`](#get_reveal_period)
|
||||
- [`replace_empty_or_append`](#replace_empty_or_append)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Operations](#operations)
|
||||
|
@ -211,7 +211,7 @@ class EarlyDerivedSecretReveal(Container):
|
|||
# Index of the validator who revealed (whistleblower)
|
||||
masker_index: ValidatorIndex
|
||||
# Mask used to hide the actual reveal signature (prevent reveal from being stolen)
|
||||
mask: Bytes32
|
||||
mask: Hash
|
||||
```
|
||||
|
||||
### Phase 0 container updates
|
||||
|
@ -224,7 +224,7 @@ Add the following fields to the end of the specified container objects. Fields w
|
|||
class Validator(Container):
|
||||
# next_custody_reveal_period is initialised to the custody period
|
||||
# (of the particular validator) in which the validator is activated
|
||||
# = get_validators_custody_reveal_period(...)
|
||||
# = get_reveal_period(...)
|
||||
next_custody_reveal_period: uint64
|
||||
max_reveal_lateness: uint64
|
||||
```
|
||||
|
@ -272,22 +272,32 @@ def get_custody_chunk_count(crosslink: Crosslink) -> int:
|
|||
return crosslink_length * chunks_per_epoch
|
||||
```
|
||||
|
||||
### `get_bit`
|
||||
|
||||
```python
|
||||
def get_bit(serialization: bytes, i: int) -> int:
|
||||
"""
|
||||
Extract the bit in ``serialization`` at position ``i``.
|
||||
"""
|
||||
return (serialization[i // 8] >> (i % 8)) % 2
|
||||
```
|
||||
|
||||
### `get_custody_chunk_bit`
|
||||
|
||||
```python
|
||||
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
|
||||
# TODO: Replace with something MPC-friendly, e.g. the Legendre symbol
|
||||
return bool(get_bitfield_bit(hash(key + chunk), 0))
|
||||
return bool(get_bit(hash(key + chunk), 0))
|
||||
```
|
||||
|
||||
### `get_chunk_bits_root`
|
||||
|
||||
```python
|
||||
def get_chunk_bits_root(chunk_bitfield: bytes) -> Bytes32:
|
||||
def get_chunk_bits_root(chunk_bits: bytes) -> Bytes32:
|
||||
aggregated_bits = bytearray([0] * 32)
|
||||
for i in range(0, len(chunk_bitfield), 32):
|
||||
for i in range(0, len(chunk_bits), 32):
|
||||
for j in range(32):
|
||||
aggregated_bits[j] ^= chunk_bitfield[i + j]
|
||||
aggregated_bits[j] ^= chunk_bits[i + j]
|
||||
return hash(aggregated_bits)
|
||||
```
|
||||
|
||||
|
@ -299,17 +309,12 @@ def get_randao_epoch_for_custody_period(period: int, validator_index: ValidatorI
|
|||
return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING)
|
||||
```
|
||||
|
||||
### `get_validators_custody_reveal_period`
|
||||
### `get_reveal_period`
|
||||
|
||||
```python
|
||||
def get_validators_custody_reveal_period(state: BeaconState,
|
||||
validator_index: ValidatorIndex,
|
||||
epoch: Epoch=None) -> int:
|
||||
def get_reveal_period(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
|
||||
'''
|
||||
This function returns the reveal period for a given validator.
|
||||
If no epoch is supplied, the current epoch is assumed.
|
||||
Note: This function implicitly requires that validators are not removed from the
|
||||
validator set in fewer than EPOCHS_PER_CUSTODY_PERIOD epochs
|
||||
Return the reveal period for a given validator.
|
||||
'''
|
||||
epoch = get_current_epoch(state) if epoch is None else epoch
|
||||
return (epoch + validator_index % EPOCHS_PER_CUSTODY_PERIOD) // EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
@ -340,17 +345,15 @@ Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`.
|
|||
For each `reveal` in `block.body.custody_key_reveals`, run the following function:
|
||||
|
||||
```python
|
||||
def process_custody_key_reveal(state: BeaconState,
|
||||
reveal: CustodyKeyReveal) -> None:
|
||||
def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> None:
|
||||
"""
|
||||
Process ``CustodyKeyReveal`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
|
||||
revealer = state.validators[reveal.revealer_index]
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_reveal_period, reveal.revealed_index)
|
||||
|
||||
assert revealer.next_custody_reveal_period < get_validators_custody_reveal_period(state, reveal.revealed_index)
|
||||
assert revealer.next_custody_reveal_period < get_reveal_period(state, reveal.revealed_index)
|
||||
|
||||
# Revealed validator is active or exited, but not withdrawn
|
||||
assert is_slashable_validator(revealer, get_current_epoch(state))
|
||||
|
@ -368,11 +371,11 @@ def process_custody_key_reveal(state: BeaconState,
|
|||
)
|
||||
|
||||
# Decrement max reveal lateness if response is timely
|
||||
if revealer.next_custody_reveal_period == get_validators_custody_reveal_period(state, reveal.revealer_index) - 2:
|
||||
if revealer.next_custody_reveal_period == get_reveal_period(state, reveal.revealer_index) - 2:
|
||||
revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT
|
||||
revealer.max_reveal_lateness = max(
|
||||
revealer.max_reveal_lateness,
|
||||
get_validators_custody_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period
|
||||
get_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period
|
||||
)
|
||||
|
||||
# Process reveal
|
||||
|
@ -394,13 +397,11 @@ Verify that `len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_S
|
|||
For each `reveal` in `block.body.early_derived_secret_reveals`, run the following function:
|
||||
|
||||
```python
|
||||
def process_early_derived_secret_reveal(state: BeaconState,
|
||||
reveal: EarlyDerivedSecretReveal) -> None:
|
||||
def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None:
|
||||
"""
|
||||
Process ``EarlyDerivedSecretReveal`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
|
||||
revealed_validator = state.validators[reveal.revealed_index]
|
||||
derived_secret_location = reveal.epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
|
||||
|
||||
|
@ -453,7 +454,7 @@ def process_early_derived_secret_reveal(state: BeaconState,
|
|||
# Apply penalty
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
whistleblower_index = reveal.masker_index
|
||||
whistleblowing_reward = Gwei(penalty // WHISTLEBLOWING_REWARD_QUOTIENT)
|
||||
whistleblowing_reward = Gwei(penalty // WHISTLEBLOWER_REWARD_QUOTIENT)
|
||||
proposer_reward = Gwei(whistleblowing_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
|
@ -470,8 +471,7 @@ Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALL
|
|||
For each `challenge` in `block.body.custody_chunk_challenges`, run the following function:
|
||||
|
||||
```python
|
||||
def process_chunk_challenge(state: BeaconState,
|
||||
challenge: CustodyChunkChallenge) -> None:
|
||||
def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None:
|
||||
# Verify the attestation
|
||||
validate_indexed_attestation(state, convert_to_indexed(state, challenge.attestation))
|
||||
# Verify it is not too late to challenge
|
||||
|
@ -479,7 +479,7 @@ def process_chunk_challenge(state: BeaconState,
|
|||
responder = state.validators[challenge.responder_index]
|
||||
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
|
||||
# Verify the responder participated in the attestation
|
||||
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bitfield)
|
||||
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits)
|
||||
assert challenge.responder_index in attesters
|
||||
# Verify the challenge is not a duplicate
|
||||
for record in state.custody_chunk_challenge_records:
|
||||
|
@ -514,60 +514,42 @@ Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGE
|
|||
For each `challenge` in `block.body.custody_bit_challenges`, run the following function:
|
||||
|
||||
```python
|
||||
def process_bit_challenge(state: BeaconState,
|
||||
challenge: CustodyBitChallenge) -> None:
|
||||
def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None:
|
||||
attestation = challenge.attestation
|
||||
epoch = slot_to_epoch(attestation.data.slot)
|
||||
shard = attestation.data.crosslink.shard
|
||||
|
||||
# Verify challenge signature
|
||||
challenger = state.validators[challenge.challenger_index]
|
||||
assert bls_verify(
|
||||
pubkey=challenger.pubkey,
|
||||
message_hash=signing_root(challenge),
|
||||
signature=challenge.signature,
|
||||
domain=get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state)),
|
||||
)
|
||||
domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state))
|
||||
assert bls_verify(challenger.pubkey, signing_root(challenge), challenge.signature, domain)
|
||||
# Verify challenger is slashable
|
||||
assert is_slashable_validator(challenger, get_current_epoch(state))
|
||||
|
||||
# Verify the attestation
|
||||
attestation = challenge.attestation
|
||||
# Verify attestation
|
||||
validate_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||
# Verify the attestation is eligible for challenging
|
||||
# Verify attestation is eligible for challenging
|
||||
responder = state.validators[challenge.responder_index]
|
||||
assert (slot_to_epoch(attestation.data.slot) + responder.max_reveal_lateness <=
|
||||
get_validators_custody_reveal_period(state, challenge.responder_index))
|
||||
assert epoch + responder.max_reveal_lateness <= get_reveal_period(state, challenge.responder_index)
|
||||
|
||||
# Verify the responder participated in the attestation
|
||||
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
assert challenge.responder_index in attesters
|
||||
|
||||
# A validator can be the challenger for at most one challenge at a time
|
||||
# Verifier challenger is not already challenging
|
||||
for record in state.custody_bit_challenge_records:
|
||||
assert record.challenger_index != challenge.challenger_index
|
||||
|
||||
# Verify the responder is a valid custody key
|
||||
# Verify the responder custody key
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(
|
||||
get_validators_custody_reveal_period(
|
||||
state,
|
||||
challenge.responder_index,
|
||||
epoch=slot_to_epoch(attestation.data.slot)),
|
||||
challenge.responder_index
|
||||
get_reveal_period(state, challenge.responder_index, epoch),
|
||||
challenge.responder_index,
|
||||
)
|
||||
assert bls_verify(
|
||||
pubkey=responder.pubkey,
|
||||
message_hash=hash_tree_root(epoch_to_sign),
|
||||
signature=challenge.responder_key,
|
||||
domain=get_domain(
|
||||
state=state,
|
||||
domain_type=DOMAIN_RANDAO,
|
||||
message_epoch=epoch_to_sign,
|
||||
),
|
||||
)
|
||||
|
||||
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
|
||||
assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain)
|
||||
# Verify the chunk count
|
||||
chunk_count = get_custody_chunk_count(attestation.data.crosslink)
|
||||
assert verify_bitfield(challenge.chunk_bits, chunk_count)
|
||||
# Verify the first bit of the hash of the chunk bits does not equal the custody bit
|
||||
custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(challenge.responder_index))
|
||||
assert custody_bit != get_bitfield_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
|
||||
committee = get_crosslink_committee(state, epoch, shard)
|
||||
custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)]
|
||||
assert custody_bit != get_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
|
||||
# Add new bit challenge record
|
||||
new_record = CustodyBitChallengeRecord(
|
||||
challenge_index=state.custody_challenge_index,
|
||||
|
@ -581,7 +563,6 @@ def process_bit_challenge(state: BeaconState,
|
|||
)
|
||||
replace_empty_or_append(state.custody_bit_challenge_records, new_record)
|
||||
state.custody_challenge_index += 1
|
||||
|
||||
# Postpone responder withdrawability
|
||||
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
|
||||
```
|
||||
|
@ -593,8 +574,7 @@ Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`.
|
|||
For each `response` in `block.body.custody_responses`, run the following function:
|
||||
|
||||
```python
|
||||
def process_custody_response(state: BeaconState,
|
||||
response: CustodyResponse) -> None:
|
||||
def process_custody_response(state: BeaconState, response: CustodyResponse) -> None:
|
||||
chunk_challenge = next((record for record in state.custody_chunk_challenge_records
|
||||
if record.challenge_index == response.challenge_index), None)
|
||||
if chunk_challenge is not None:
|
||||
|
@ -661,7 +641,7 @@ def process_bit_challenge_response(state: BeaconState,
|
|||
)
|
||||
# Verify the chunk bit does not match the challenge chunk bit
|
||||
assert (get_custody_chunk_bit(challenge.responder_key, response.chunk)
|
||||
!= get_bitfield_bit(challenge.chunk_bits_leaf, response.chunk_index % 256))
|
||||
!= get_bit(challenge.chunk_bits_leaf, response.chunk_index % 256))
|
||||
# Clear the challenge
|
||||
records = state.custody_bit_challenge_records
|
||||
records[records.index(challenge)] = CustodyBitChallengeRecord()
|
||||
|
@ -682,7 +662,7 @@ Run `process_reveal_deadlines(state)` immediately after `process_registry_update
|
|||
def process_reveal_deadlines(state: BeaconState) -> None:
|
||||
for index, validator in enumerate(state.validators):
|
||||
deadline = validator.next_custody_reveal_period + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
|
||||
if get_validators_custody_reveal_period(state, ValidatorIndex(index)) > deadline:
|
||||
if get_reveal_period(state, ValidatorIndex(index)) > deadline:
|
||||
slash_validator(state, ValidatorIndex(index))
|
||||
```
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ class ShardAttestation(Container):
|
|||
slot: Slot
|
||||
shard: Shard
|
||||
shard_block_root: Bytes32
|
||||
aggregation_bitfield: Bytes[PLACEHOLDER]
|
||||
aggregation_bits: Bitlist[PLACEHOLDER]
|
||||
aggregate_signature: BLSSignature
|
||||
```
|
||||
|
||||
|
@ -230,10 +230,9 @@ def verify_shard_attestation_signature(state: BeaconState,
|
|||
attestation: ShardAttestation) -> None:
|
||||
data = attestation.data
|
||||
persistent_committee = get_persistent_committee(state, data.shard, data.slot)
|
||||
assert verify_bitfield(attestation.aggregation_bitfield, len(persistent_committee))
|
||||
pubkeys = []
|
||||
for i, index in enumerate(persistent_committee):
|
||||
if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b1:
|
||||
if attestation.aggregation_bits[i]:
|
||||
validator = state.validators[index]
|
||||
assert is_active_validator(validator, get_current_epoch(state))
|
||||
pubkeys.append(validator.pubkey)
|
||||
|
|
|
@ -31,7 +31,7 @@ We define an "expansion" of an object as an object where a field in an object th
|
|||
|
||||
We define two expansions:
|
||||
|
||||
* `ExtendedBeaconState`, which is identical to a `BeaconState` except `active_index_roots: List[Bytes32]` is replaced by `active_indices: List[List[ValidatorIndex]]`, where `BeaconState.active_index_roots[i] = hash_tree_root(ExtendedBeaconState.active_indices[i])`.
|
||||
* `ExtendedBeaconState`, which is identical to a `BeaconState` except `compact_committees_roots: List[Bytes32]` is replaced by `active_indices: List[List[ValidatorIndex]]`, where `BeaconState.compact_committees_roots[i] = hash_tree_root(ExtendedBeaconState.active_indices[i])`.
|
||||
* `ExtendedBeaconBlock`, which is identical to a `BeaconBlock` except `state_root` is replaced with the corresponding `state: ExtendedBeaconState`.
|
||||
|
||||
### `get_active_validator_indices`
|
||||
|
@ -40,7 +40,7 @@ Note that there is now a new way to compute `get_active_validator_indices`:
|
|||
|
||||
```python
|
||||
def get_active_validator_indices(state: ExtendedBeaconState, epoch: Epoch) -> List[ValidatorIndex]:
|
||||
return state.active_indices[epoch % ACTIVE_INDEX_ROOTS_LENGTH]
|
||||
return state.active_indices[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
|
||||
```
|
||||
|
||||
Note that it takes `state` instead of `state.validators` as an argument. This does not affect its use in `get_shuffled_committee`, because `get_shuffled_committee` has access to the full `state` as one of its arguments.
|
||||
|
@ -168,7 +168,7 @@ If a client wants to update its `finalized_header` it asks the network for a `Bl
|
|||
{
|
||||
'header': BeaconBlockHeader,
|
||||
'shard_aggregate_signature': BLSSignature,
|
||||
'shard_bitfield': 'bytes',
|
||||
'shard_bits': Bitlist[PLACEHOLDER],
|
||||
'shard_parent_block': ShardBlock,
|
||||
}
|
||||
```
|
||||
|
@ -180,13 +180,13 @@ def verify_block_validity_proof(proof: BlockValidityProof, validator_memory: Val
|
|||
assert proof.shard_parent_block.beacon_chain_root == hash_tree_root(proof.header)
|
||||
committee = compute_committee(proof.header, validator_memory)
|
||||
# Verify that we have >=50% support
|
||||
support_balance = sum([v.effective_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True])
|
||||
support_balance = sum([v.effective_balance for i, v in enumerate(committee) if proof.shard_bits[i]])
|
||||
total_balance = sum([v.effective_balance for i, v in enumerate(committee)])
|
||||
assert support_balance * 2 > total_balance
|
||||
# Verify shard attestations
|
||||
group_public_key = bls_aggregate_pubkeys([
|
||||
v.pubkey for v, index in enumerate(committee)
|
||||
if get_bitfield_bit(proof.shard_bitfield, index) is True
|
||||
if proof.shard_bits[index]
|
||||
])
|
||||
assert bls_verify(
|
||||
pubkey=group_public_key,
|
||||
|
@ -196,4 +196,4 @@ def verify_block_validity_proof(proof: BlockValidityProof, validator_memory: Val
|
|||
)
|
||||
```
|
||||
|
||||
The size of this proof is only 200 (header) + 96 (signature) + 16 (bitfield) + 352 (shard block) = 664 bytes. It can be reduced further by replacing `ShardBlock` with `MerklePartial(lambda x: x.beacon_chain_root, ShardBlock)`, which would cut off ~220 bytes.
|
||||
The size of this proof is only 200 (header) + 96 (signature) + 16 (bits) + 352 (shard block) = 664 bytes. It can be reduced further by replacing `ShardBlock` with `MerklePartial(lambda x: x.beacon_chain_root, ShardBlock)`, which would cut off ~220 bytes.
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
- [Default values](#default-values)
|
||||
- [Illegal types](#illegal-types)
|
||||
- [Serialization](#serialization)
|
||||
- [`"uintN"`](#uintn)
|
||||
- [`"bool"`](#bool)
|
||||
- [`"null`](#null)
|
||||
- [`uintN`](#uintn)
|
||||
- [`boolean`](#boolean)
|
||||
- [`null`](#null)
|
||||
- [Vectors, containers, lists, unions](#vectors-containers-lists-unions)
|
||||
- [Deserialization](#deserialization)
|
||||
- [Merkleization](#merkleization)
|
||||
|
@ -37,36 +37,45 @@
|
|||
## Typing
|
||||
### Basic types
|
||||
|
||||
* `"uintN"`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`)
|
||||
* `"bool"`: `True` or `False`
|
||||
* `uintN`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`)
|
||||
* `boolean`: `True` or `False`
|
||||
|
||||
### Composite types
|
||||
|
||||
* **container**: ordered heterogeneous collection of values
|
||||
* key-pair curly bracket notation `{}`, e.g. `{"foo": "uint64", "bar": "bool"}`
|
||||
* **vector**: ordered fixed-length homogeneous collection of values
|
||||
* angle bracket notation `[type, N]`, e.g. `["uint64", N]`
|
||||
* **list**: ordered variable-length homogeneous collection of values
|
||||
* angle bracket notation `[type]`, e.g. `["uint64"]`
|
||||
* python dataclass notation with key-type pairs, e.g.
|
||||
```python
|
||||
class ContainerExample(Container):
|
||||
foo: uint64
|
||||
bar: boolean
|
||||
```
|
||||
* **vector**: ordered fixed-length homogeneous collection, with `N` values
|
||||
* notation `Vector[type, N]`, e.g. `Vector[uint64, N]`
|
||||
* **list**: ordered variable-length homogeneous collection, limited to `N` values
|
||||
* notation `List[type, N]`, e.g. `List[uint64, N]`
|
||||
* **bitvector**: ordered fixed-length collection of `boolean` values, with `N` bits
|
||||
* notation `Bitvector[N]`
|
||||
* **bitlist**: ordered variable-length collection of `boolean` values, limited to `N` bits
|
||||
* notation `Bitlist[N]`
|
||||
* **union**: union type containing one of the given subtypes
|
||||
* round bracket notation `(type_1, type_2, ...)`, e.g. `("null", "uint64")`
|
||||
* notation `Union[type_1, type_2, ...]`, e.g. `union[null, uint64]`
|
||||
|
||||
### Variable-size and fixed-size
|
||||
|
||||
We recursively define "variable-size" types to be lists and unions and all types that contain a variable-size type. All other types are said to be "fixed-size".
|
||||
We recursively define "variable-size" types to be lists, unions, `Bitlist` and all types that contain a variable-size type. All other types are said to be "fixed-size".
|
||||
|
||||
### Aliases
|
||||
|
||||
For convenience we alias:
|
||||
|
||||
* `"byte"` to `"uint8"` (this is a basic type)
|
||||
* `"bytes"` to `["byte"]` (this is *not* a basic type)
|
||||
* `"bytesN"` to `["byte", N]` (this is *not* a basic type)
|
||||
* `"null"`: `{}`, i.e. the empty container
|
||||
* `bit` to `boolean`
|
||||
* `byte` to `uint8` (this is a basic type)
|
||||
* `BytesN` to `Vector[byte, N]` (this is *not* a basic type)
|
||||
* `null`: `{}`, i.e. the empty container
|
||||
|
||||
### Default values
|
||||
|
||||
The default value of a type upon initialization is recursively defined using `0` for `"uintN"`, `False` for `"bool"`, and `[]` for lists. Unions default to the first type in the union (with type index zero), which is `"null"` if present in the union.
|
||||
The default value of a type upon initialization is recursively defined using `0` for `uintN`, `False` for `boolean` and the elements of `Bitvector`, and `[]` for lists and `Bitlist`. Unions default to the first type in the union (with type index zero), which is `null` if present in the union.
|
||||
|
||||
#### `is_empty`
|
||||
|
||||
|
@ -74,34 +83,50 @@ An SSZ object is called empty (and thus, `is_empty(object)` returns true) if it
|
|||
|
||||
### Illegal types
|
||||
|
||||
Empty vector types (i.e. `[subtype, 0]` for some `subtype`) are not legal. The `"null"` type is only legal as the first type in a union subtype (i.e. with type index zero).
|
||||
Empty vector types (i.e. `[subtype, 0]` for some `subtype`) are not legal. The `null` type is only legal as the first type in a union subtype (i.e. with type index zero).
|
||||
|
||||
## Serialization
|
||||
|
||||
We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `"bytes"`.
|
||||
We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `bytes`.
|
||||
|
||||
*Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, `is_variable_size`, etc.) objects implicitly carry their type.
|
||||
|
||||
### `"uintN"`
|
||||
### `uintN`
|
||||
|
||||
```python
|
||||
assert N in [8, 16, 32, 64, 128, 256]
|
||||
return value.to_bytes(N // 8, "little")
|
||||
```
|
||||
|
||||
### `"bool"`
|
||||
### `boolean`
|
||||
|
||||
```python
|
||||
assert value in (True, False)
|
||||
return b"\x01" if value is True else b"\x00"
|
||||
```
|
||||
|
||||
### `"null"`
|
||||
### `null`
|
||||
|
||||
```python
|
||||
return b""
|
||||
```
|
||||
|
||||
### `Bitvector[N]`
|
||||
|
||||
```python
|
||||
as_integer = sum([value[i] << i for i in range(len(value))])
|
||||
return as_integer.to_bytes((N + 7) // 8, "little")
|
||||
```
|
||||
|
||||
### `Bitlist[N]`
|
||||
|
||||
Note that from the offset coding, the length (in bytes) of the bitlist is known. An additional leading `1` bit is added so that the length in bits will also be known.
|
||||
|
||||
```python
|
||||
as_integer = (1 << len(value)) + sum([value[i] << i for i in range(len(value))])
|
||||
return as_integer.to_bytes((as_integer.bit_length() + 7) // 8, "little")
|
||||
```
|
||||
|
||||
### Vectors, containers, lists, unions
|
||||
|
||||
```python
|
||||
|
@ -136,23 +161,47 @@ return serialized_type_index + serialized_bytes
|
|||
|
||||
Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to. Efficient algorithms for computing this object can be found in [the implementations](#implementations).
|
||||
|
||||
Note that deserialization requires hardening against invalid inputs. A non-exhaustive list:
|
||||
- Offsets: out of order, out of range, mismatching minimum element size
|
||||
- Scope: Extra unused bytes, not aligned with element size.
|
||||
- More elements than a list limit allows. Part of enforcing consensus.
|
||||
|
||||
## Merkleization
|
||||
|
||||
We first define helper functions:
|
||||
|
||||
* `pack`: Given ordered objects of the same basic type, serialize them, pack them into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks.
|
||||
* `merkleize`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root. Note that `merkleize` on a single chunk is simply that chunk, i.e. the identity when the number of chunks is one.
|
||||
* `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power of 2, with 0 mapping to 1. Examples: `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16`
|
||||
* `merkleize(data, pad_for)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root.
|
||||
The merkleization depends on the effective input, which can be padded: if `pad_for=L`, then pad the `data` with zeroed chunks to `next_pow_of_two(L)` (virtually for memory efficiency).
|
||||
Then, merkleize the chunks (empty input is padded to 1 zero chunk):
|
||||
- If `1` chunk: A single chunk is simply that chunk, i.e. the identity when the number of chunks is one.
|
||||
- If `> 1` chunks: pad to `next_pow_of_two(len(chunks))`, merkleize as binary tree.
|
||||
* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`.
|
||||
* `mix_in_type`: Given a Merkle root `root` and a type_index `type_index` (`"uint256"` little-endian serialization) return `hash(root + type_index)`.
|
||||
|
||||
We now define Merkleization `hash_tree_root(value)` of an object `value` recursively:
|
||||
|
||||
* `merkleize(pack(value))` if `value` is a basic object or a vector of basic objects
|
||||
* `mix_in_length(merkleize(pack(value)), len(value))` if `value` is a list of basic objects
|
||||
* `mix_in_length(merkleize(pack(value), pad_for=(N * elem_size / BYTES_PER_CHUNK)), len(value))` if `value` is a list of basic objects.
|
||||
* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container
|
||||
* `mix_in_length(merkleize([hash_tree_root(element) for element in value]), len(value))` if `value` is a list of composite objects
|
||||
* `mix_in_length(merkleize([hash_tree_root(element) for element in value], pad_for=N), len(value))` if `value` is a list of composite objects.
|
||||
* `mix_in_type(merkleize(value.value), value.type_index)` if `value` is of union type
|
||||
|
||||
### Merkleization of `Bitvector[N]`
|
||||
|
||||
```python
|
||||
as_integer = sum([value[i] << i for i in range(len(value))])
|
||||
return merkleize(as_integer.to_bytes((N + 7) // 8, "little"))
|
||||
```
|
||||
|
||||
### `Bitlist[N]`
|
||||
|
||||
```python
|
||||
as_integer = sum([value[i] << i for i in range(len(value))])
|
||||
return mix_in_length(merkleize(as_integer.to_bytes((N + 7) // 8, "little")), len(value))
|
||||
```
|
||||
|
||||
## Self-signed containers
|
||||
|
||||
Let `value` be a self-signed container object. The convention is that the signature (e.g. a `"bytes96"` BLS12-381 signature) be the last field of `value`. Further, the signed message for `value` is `signing_root(value) = hash_tree_root(truncate_last(value))` where `truncate_last` truncates the last element of `value`.
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
- [Crosslink vote](#crosslink-vote)
|
||||
- [Construct attestation](#construct-attestation)
|
||||
- [Data](#data)
|
||||
- [Aggregation bitfield](#aggregation-bitfield)
|
||||
- [Custody bitfield](#custody-bitfield)
|
||||
- [Aggregation bits](#aggregation-bits)
|
||||
- [Custody bits](#custody-bits)
|
||||
- [Aggregate signature](#aggregate-signature)
|
||||
- [How to avoid slashing](#how-to-avoid-slashing)
|
||||
- [Proposer slashing](#proposer-slashing)
|
||||
|
@ -221,19 +221,26 @@ epoch_signature = bls_sign(
|
|||
|
||||
##### Eth1 Data
|
||||
|
||||
`block.eth1_data` is a mechanism used by block proposers vote on a recent Ethereum 1.0 block hash and an associated deposit root found in the Ethereum 1.0 deposit contract. When consensus is formed, `state.eth1_data` is updated, and validator deposits up to this root can be processed. The deposit root can be calculated by calling the `get_deposit_root()` function of the deposit contract using the post-state of the block hash.
|
||||
The `block.eth1_data` field is for block proposers to vote on recent Eth 1.0 data. This recent data contains an Eth 1.0 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth 1.0 block. If over half of the block proposers in the current Eth 1.0 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
|
||||
|
||||
* Let `D` be the list of `Eth1DataVote` objects `vote` in `state.eth1_data_votes` where:
|
||||
* `vote.eth1_data.block_hash` is the hash of an Eth 1.0 block that is (i) part of the canonical chain, (ii) >= `ETH1_FOLLOW_DISTANCE` blocks behind the head, and (iii) newer than `state.eth1_data.block_hash`.
|
||||
* `vote.eth1_data.deposit_count` is the deposit count of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
|
||||
* `vote.eth1_data.deposit_root` is the deposit root of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
|
||||
* If `D` is empty:
|
||||
* Let `block_hash` be the block hash of the `ETH1_FOLLOW_DISTANCE`'th ancestor of the head of the canonical Eth 1.0 chain.
|
||||
* Let `deposit_root` and `deposit_count` be the deposit root and deposit count of the Eth 1.0 deposit contract in the post-state of the block referenced by `block_hash`
|
||||
* Let `best_vote_data = Eth1Data(block_hash=block_hash, deposit_root=deposit_root, deposit_count=deposit_count)`.
|
||||
* If `D` is nonempty:
|
||||
* Let `best_vote_data` be the `eth1_data` member of `D` that has the highest vote count (`D.count(eth1_data)`), breaking ties by favoring block hashes with higher associated block height.
|
||||
* Set `block.eth1_data = best_vote_data`.
|
||||
Let `get_eth1_data(distance: int) -> Eth1Data` be the (subjective) function that returns the Eth 1.0 data at distance `distance` relative to the Eth 1.0 head at the start of the current Eth 1.0 voting period. Let `previous_eth1_distance` be the distance relative to the Eth 1.0 block corresponding to `state.eth1_data.block_hash` at the start of the current Eth 1.0 voting period. An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where:
|
||||
|
||||
```python
|
||||
def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Data:
|
||||
new_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, 2 * ETH1_FOLLOW_DISTANCE)]
|
||||
all_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, previous_eth1_distance)]
|
||||
|
||||
valid_votes = []
|
||||
for slot, vote in enumerate(state.eth1_data_votes):
|
||||
period_tail = slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_square_root(SLOTS_PER_ETH1_VOTING_PERIOD)
|
||||
if vote in new_eth1_data or (period_tail and vote in all_eth1_data):
|
||||
valid_votes.append(vote)
|
||||
|
||||
return max(valid_votes,
|
||||
key=lambda v: (valid_votes.count(v), -all_eth1_data.index(v)), # Tiebreak by smallest distance
|
||||
default=get_eth1_data(ETH1_FOLLOW_DISTANCE),
|
||||
)
|
||||
```
|
||||
|
||||
##### Signature
|
||||
|
||||
|
@ -322,19 +329,15 @@ Next, the validator creates `attestation`, an [`Attestation`](../core/0_beacon-c
|
|||
|
||||
Set `attestation.data = attestation_data` where `attestation_data` is the `AttestationData` object defined in the previous section, [attestation data](#attestation-data).
|
||||
|
||||
##### Aggregation bitfield
|
||||
##### Aggregation bits
|
||||
|
||||
* Let `aggregation_bitfield` be a byte array filled with zeros of length `(len(committee) + 7) // 8`.
|
||||
* Let `index_into_committee` be the index into the validator's `committee` at which `validator_index` is located.
|
||||
* Set `aggregation_bitfield[index_into_committee // 8] |= 2 ** (index_into_committee % 8)`.
|
||||
* Set `attestation.aggregation_bitfield = aggregation_bitfield`.
|
||||
* Let `attestation.aggregation_bits` be a `Bitlist[MAX_INDICES_PER_ATTESTATION]` where the bits at the index in the aggregated validator's `committee` is set to `0b1`.
|
||||
|
||||
*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.
|
||||
*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bits)` should return a list of length equal to 1, containing `validator_index`.
|
||||
|
||||
##### Custody bitfield
|
||||
##### Custody bits
|
||||
|
||||
* Let `custody_bitfield` be a byte array filled with zeros of length `(len(committee) + 7) // 8`.
|
||||
* Set `attestation.custody_bitfield = custody_bitfield`.
|
||||
* Let `attestation.custody_bits` be a `Bitlist[MAX_INDICES_PER_ATTESTATION]` filled with zeros of length `len(committee)`.
|
||||
|
||||
*Note*: This is a stub for Phase 0.
|
||||
|
||||
|
|
|
@ -415,16 +415,16 @@ components:
|
|||
type: object
|
||||
description: "The [`Attestation`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestation) object from the Eth2.0 spec."
|
||||
properties:
|
||||
aggregation_bitfield:
|
||||
aggregation_bits:
|
||||
type: string
|
||||
format: byte
|
||||
pattern: "^0x[a-fA-F0-9]+$"
|
||||
description: "Attester aggregation bitfield."
|
||||
custody_bitfield:
|
||||
description: "Attester aggregation bits."
|
||||
custody_bits:
|
||||
type: string
|
||||
format: byte
|
||||
pattern: "^0x[a-fA-F0-9]+$"
|
||||
description: "Custody bitfield."
|
||||
description: "Custody bits."
|
||||
signature:
|
||||
type: string
|
||||
format: byte
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from typing import Any
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
SSZType, SSZValue, uint, Container, Bytes, List, Bool,
|
||||
SSZType, SSZValue, uint, Container, Bytes, List, boolean,
|
||||
Vector, BytesN
|
||||
)
|
||||
|
||||
|
||||
def decode(data: Any, typ: SSZType) -> SSZValue:
|
||||
if issubclass(typ, (uint, Bool)):
|
||||
if issubclass(typ, (uint, boolean)):
|
||||
return typ(data)
|
||||
elif issubclass(typ, (List, Vector)):
|
||||
return typ(decode(element, typ.elem_type) for element in data)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
SSZValue, uint, Container, Bool
|
||||
SSZValue, uint, Container, boolean
|
||||
)
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ def encode(value: SSZValue, include_hash_tree_roots=False):
|
|||
if value.type().byte_len > 8:
|
||||
return str(int(value))
|
||||
return int(value)
|
||||
elif isinstance(value, Bool):
|
||||
elif isinstance(value, boolean):
|
||||
return value == 1
|
||||
elif isinstance(value, list): # normal python lists, ssz-List, Vector
|
||||
return [encode(element, include_hash_tree_roots) for element in value]
|
||||
|
|
|
@ -2,8 +2,8 @@ from random import Random
|
|||
from enum import Enum
|
||||
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, Bool,
|
||||
Vector, BytesN
|
||||
SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, boolean,
|
||||
Vector, BytesN, Bitlist, Bitvector
|
||||
)
|
||||
|
||||
# in bytes
|
||||
|
@ -83,12 +83,12 @@ def get_random_ssz_object(rng: Random,
|
|||
return get_max_basic_value(typ)
|
||||
else:
|
||||
return get_random_basic_value(rng, typ)
|
||||
elif issubclass(typ, Vector):
|
||||
elif issubclass(typ, Vector) or issubclass(typ, Bitvector):
|
||||
return typ(
|
||||
get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos)
|
||||
for _ in range(typ.length)
|
||||
)
|
||||
elif issubclass(typ, List):
|
||||
elif issubclass(typ, List) or issubclass(typ, Bitlist):
|
||||
length = rng.randint(0, min(typ.length, max_list_length))
|
||||
if mode == RandomizationMode.mode_one_count:
|
||||
length = 1
|
||||
|
@ -118,7 +118,7 @@ def get_random_bytes_list(rng: Random, length: int) -> bytes:
|
|||
|
||||
|
||||
def get_random_basic_value(rng: Random, typ: BasicType) -> BasicValue:
|
||||
if issubclass(typ, Bool):
|
||||
if issubclass(typ, boolean):
|
||||
return typ(rng.choice((True, False)))
|
||||
elif issubclass(typ, uint):
|
||||
assert typ.byte_len in UINT_BYTE_SIZES
|
||||
|
@ -128,7 +128,7 @@ def get_random_basic_value(rng: Random, typ: BasicType) -> BasicValue:
|
|||
|
||||
|
||||
def get_min_basic_value(typ: BasicType) -> BasicValue:
|
||||
if issubclass(typ, Bool):
|
||||
if issubclass(typ, boolean):
|
||||
return typ(False)
|
||||
elif issubclass(typ, uint):
|
||||
assert typ.byte_len in UINT_BYTE_SIZES
|
||||
|
@ -138,7 +138,7 @@ def get_min_basic_value(typ: BasicType) -> BasicValue:
|
|||
|
||||
|
||||
def get_max_basic_value(typ: BasicType) -> BasicValue:
|
||||
if issubclass(typ, Bool):
|
||||
if issubclass(typ, boolean):
|
||||
return typ(True)
|
||||
elif issubclass(typ, uint):
|
||||
assert typ.byte_len in UINT_BYTE_SIZES
|
||||
|
|
|
@ -18,8 +18,15 @@ def translate_typ(typ) -> ssz.BaseSedes:
|
|||
elif issubclass(typ, spec_ssz.Vector):
|
||||
return ssz.Vector(translate_typ(typ.elem_type), typ.length)
|
||||
elif issubclass(typ, spec_ssz.List):
|
||||
# TODO: Make py-ssz List support the new fixed length list
|
||||
return ssz.List(translate_typ(typ.elem_type))
|
||||
elif issubclass(typ, spec_ssz.Bool):
|
||||
elif issubclass(typ, spec_ssz.Bitlist):
|
||||
# TODO: Once Bitlist implemented in py-ssz, use appropriate type
|
||||
return ssz.List(translate_typ(typ.elem_type))
|
||||
elif issubclass(typ, spec_ssz.Bitvector):
|
||||
# TODO: Once Bitvector implemented in py-ssz, use appropriate type
|
||||
return ssz.Vector(translate_typ(typ.elem_type), typ.length)
|
||||
elif issubclass(typ, spec_ssz.boolean):
|
||||
return ssz.boolean
|
||||
elif issubclass(typ, spec_ssz.uint):
|
||||
if typ.byte_len == 1:
|
||||
|
@ -64,10 +71,14 @@ def translate_value(value, typ):
|
|||
raise TypeError("invalid uint size")
|
||||
elif issubclass(typ, spec_ssz.List):
|
||||
return [translate_value(elem, typ.elem_type) for elem in value]
|
||||
elif issubclass(typ, spec_ssz.Bool):
|
||||
elif issubclass(typ, spec_ssz.boolean):
|
||||
return value
|
||||
elif issubclass(typ, spec_ssz.Vector):
|
||||
return typ(*(translate_value(elem, typ.elem_type) for elem in value))
|
||||
elif issubclass(typ, spec_ssz.Bitlist):
|
||||
return typ(value)
|
||||
elif issubclass(typ, spec_ssz.Bitvector):
|
||||
return typ(value)
|
||||
elif issubclass(typ, spec_ssz.BytesN):
|
||||
return typ(value)
|
||||
elif issubclass(typ, spec_ssz.Bytes):
|
||||
|
|
|
@ -9,7 +9,9 @@ def test_decoder():
|
|||
rng = Random(123)
|
||||
|
||||
# check these types only, Block covers a lot of operation types already.
|
||||
for typ in [spec.BeaconBlock, spec.BeaconState, spec.IndexedAttestation, spec.AttestationDataAndCustodyBit]:
|
||||
# TODO: Once has Bitlists and Bitvectors, add back
|
||||
# spec.BeaconState and spec.BeaconBlock
|
||||
for typ in [spec.IndexedAttestation, spec.AttestationDataAndCustodyBit]:
|
||||
# create a random pyspec value
|
||||
original = random_value.get_random_ssz_object(rng, typ, 100, 10,
|
||||
mode=random_value.RandomizationMode.mode_random,
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block
|
||||
|
||||
|
||||
def add_block_to_store(spec, store, block):
|
||||
pre_state = store.block_states[block.parent_root]
|
||||
block_time = pre_state.genesis_time + block.slot * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < block_time:
|
||||
spec.on_tick(store, block_time)
|
||||
|
||||
spec.on_block(store, block)
|
||||
|
||||
|
||||
def add_attestation_to_store(spec, store, attestation):
|
||||
parent_block = store.blocks[attestation.data.beacon_block_root]
|
||||
pre_state = store.block_states[spec.signing_root(parent_block)]
|
||||
block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
|
||||
next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < next_epoch_time:
|
||||
spec.on_tick(store, next_epoch_time)
|
||||
|
||||
spec.on_attestation(store, attestation)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_genesis(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
|
||||
assert spec.get_head(store) == spec.signing_root(genesis_block)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_chain_no_attestations(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
|
||||
assert spec.get_head(store) == spec.signing_root(genesis_block)
|
||||
|
||||
# On receiving a block of `GENESIS_SLOT + 1` slot
|
||||
block_1 = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block_1)
|
||||
add_block_to_store(spec, store, block_1)
|
||||
|
||||
# On receiving a block of next epoch
|
||||
block_2 = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block_2)
|
||||
add_block_to_store(spec, store, block_2)
|
||||
|
||||
assert spec.get_head(store) == spec.signing_root(block_2)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_split_tie_breaker_no_attestations(spec, state):
|
||||
genesis_state = state.copy()
|
||||
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
|
||||
assert spec.get_head(store) == spec.signing_root(genesis_block)
|
||||
|
||||
# block at slot 1
|
||||
block_1_state = genesis_state.copy()
|
||||
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
|
||||
state_transition_and_sign_block(spec, block_1_state, block_1)
|
||||
add_block_to_store(spec, store, block_1)
|
||||
|
||||
# additional block at slot 1
|
||||
block_2_state = genesis_state.copy()
|
||||
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
|
||||
block_2.body.graffiti = b'\x42' * 32
|
||||
state_transition_and_sign_block(spec, block_2_state, block_2)
|
||||
add_block_to_store(spec, store, block_2)
|
||||
|
||||
highest_root = max(spec.signing_root(block_1), spec.signing_root(block_2))
|
||||
|
||||
assert spec.get_head(store) == highest_root
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_shorter_chain_but_heavier_weight(spec, state):
|
||||
genesis_state = state.copy()
|
||||
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
|
||||
assert spec.get_head(store) == spec.signing_root(genesis_block)
|
||||
|
||||
# build longer tree
|
||||
long_state = genesis_state.copy()
|
||||
for i in range(3):
|
||||
long_block = build_empty_block_for_next_slot(spec, long_state)
|
||||
state_transition_and_sign_block(spec, long_state, long_block)
|
||||
add_block_to_store(spec, store, long_block)
|
||||
|
||||
# build short tree
|
||||
short_state = genesis_state.copy()
|
||||
short_block = build_empty_block_for_next_slot(spec, short_state)
|
||||
short_block.body.graffiti = b'\x42' * 32
|
||||
state_transition_and_sign_block(spec, short_state, short_block)
|
||||
add_block_to_store(spec, store, short_block)
|
||||
|
||||
short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True)
|
||||
add_attestation_to_store(spec, store, short_attestation)
|
||||
|
||||
assert spec.get_head(store) == spec.signing_root(short_block)
|
|
@ -0,0 +1,122 @@
|
|||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.state import next_slot
|
||||
|
||||
|
||||
def run_on_attestation(spec, state, store, attestation, valid=True):
|
||||
if not valid:
|
||||
try:
|
||||
spec.on_attestation(store, attestation)
|
||||
except AssertionError:
|
||||
return
|
||||
else:
|
||||
assert False
|
||||
|
||||
indexed_attestation = spec.convert_to_indexed(state, attestation)
|
||||
spec.on_attestation(store, attestation)
|
||||
assert (
|
||||
store.latest_messages[indexed_attestation.custody_bit_0_indices[0]] ==
|
||||
spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
|
||||
# store block in store
|
||||
spec.on_block(store, block)
|
||||
|
||||
next_slot(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
run_on_attestation(spec, state, store, attestation)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation_target_not_in_store(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
# move to next epoch to make block new target
|
||||
state.slot += spec.SLOTS_PER_EPOCH
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
|
||||
# do not add block to store
|
||||
|
||||
next_slot(spec, state)
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation_future_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 3 * spec.SECONDS_PER_SLOT
|
||||
spec.on_tick(store, time)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
|
||||
# store block in store
|
||||
spec.on_block(store, block)
|
||||
next_slot(spec, state)
|
||||
|
||||
# move state forward but not store
|
||||
attestation_slot = block.slot + spec.SLOTS_PER_EPOCH
|
||||
state.slot = attestation_slot
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=state.slot)
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation_same_slot(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 1 * spec.SECONDS_PER_SLOT
|
||||
spec.on_tick(store, time)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
|
||||
spec.on_block(store, block)
|
||||
next_slot(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation_invalid_attestation(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 3 * spec.SECONDS_PER_SLOT
|
||||
spec.on_tick(store, time)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
|
||||
spec.on_block(store, block)
|
||||
next_slot(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
# make attestation invalid
|
||||
attestation.custody_bits[0:8] = [0, 0, 0, 0, 1, 1, 1, 1]
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
|
@ -0,0 +1,89 @@
|
|||
from eth2spec.utils.ssz.ssz_impl import signing_root
|
||||
|
||||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
|
||||
|
||||
def run_on_block(spec, state, store, block, valid=True):
|
||||
if not valid:
|
||||
try:
|
||||
spec.on_block(store, block)
|
||||
except AssertionError:
|
||||
return
|
||||
else:
|
||||
assert False
|
||||
|
||||
spec.on_block(store, block)
|
||||
assert store.blocks[signing_root(block)] == block
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_basic(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
assert store.time == time
|
||||
|
||||
# On receiving a block of `GENESIS_SLOT + 1` slot
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
run_on_block(spec, state, store, block)
|
||||
|
||||
# On receiving a block of next epoch
|
||||
store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.slot += spec.SLOTS_PER_EPOCH
|
||||
|
||||
run_on_block(spec, state, store, block)
|
||||
|
||||
# TODO: add tests for justified_root and finalized_root
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_block_future_block(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
|
||||
# do not tick time
|
||||
|
||||
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
run_on_block(spec, state, store, block, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_block_bad_parent_root(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.parent_root = b'\x45' * 32
|
||||
run_on_block(spec, state, store, block, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_block_before_finalized(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
store.finalized_checkpoint = spec.Checkpoint(
|
||||
epoch=store.finalized_checkpoint.epoch + 2,
|
||||
root=store.finalized_checkpoint.root
|
||||
)
|
||||
|
||||
# Fail receiving block of `GENESIS_SLOT + 1` slot
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
run_on_block(spec, state, store, block, False)
|
|
@ -1,10 +1,10 @@
|
|||
from typing import List
|
||||
|
||||
from eth2spec.test.helpers.bitfields import set_bitfield_bit
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist
|
||||
|
||||
|
||||
def build_attestation_data(spec, state, slot, shard):
|
||||
|
@ -24,11 +24,11 @@ def build_attestation_data(spec, state, slot, shard):
|
|||
epoch_boundary_root = spec.get_block_root(state, spec.get_current_epoch(state))
|
||||
|
||||
if slot < current_epoch_start_slot:
|
||||
justified_epoch = state.previous_justified_epoch
|
||||
justified_block_root = state.previous_justified_root
|
||||
source_epoch = state.previous_justified_checkpoint.epoch
|
||||
source_root = state.previous_justified_checkpoint.root
|
||||
else:
|
||||
justified_epoch = state.current_justified_epoch
|
||||
justified_block_root = state.current_justified_root
|
||||
source_epoch = state.current_justified_checkpoint.epoch
|
||||
source_root = state.current_justified_checkpoint.root
|
||||
|
||||
if spec.slot_to_epoch(slot) == spec.get_current_epoch(state):
|
||||
parent_crosslink = state.current_crosslinks[shard]
|
||||
|
@ -37,10 +37,8 @@ def build_attestation_data(spec, state, slot, shard):
|
|||
|
||||
return spec.AttestationData(
|
||||
beacon_block_root=block_root,
|
||||
source_epoch=justified_epoch,
|
||||
source_root=justified_block_root,
|
||||
target_epoch=spec.slot_to_epoch(slot),
|
||||
target_root=epoch_boundary_root,
|
||||
source=spec.Checkpoint(epoch=source_epoch, root=source_root),
|
||||
target=spec.Checkpoint(epoch=spec.slot_to_epoch(slot), root=epoch_boundary_root),
|
||||
crosslink=spec.Crosslink(
|
||||
shard=shard,
|
||||
start_epoch=parent_crosslink.end_epoch,
|
||||
|
@ -64,18 +62,17 @@ def get_valid_attestation(spec, state, slot=None, signed=False):
|
|||
|
||||
crosslink_committee = spec.get_crosslink_committee(
|
||||
state,
|
||||
attestation_data.target_epoch,
|
||||
attestation_data.crosslink.shard
|
||||
attestation_data.target.epoch,
|
||||
attestation_data.crosslink.shard,
|
||||
)
|
||||
|
||||
committee_size = len(crosslink_committee)
|
||||
bitfield_length = (committee_size + 7) // 8
|
||||
aggregation_bitfield = b'\x00' * bitfield_length
|
||||
custody_bitfield = b'\x00' * bitfield_length
|
||||
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
|
||||
custody_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
|
||||
attestation = spec.Attestation(
|
||||
aggregation_bitfield=aggregation_bitfield,
|
||||
aggregation_bits=aggregation_bits,
|
||||
data=attestation_data,
|
||||
custody_bitfield=custody_bitfield,
|
||||
custody_bits=custody_bits,
|
||||
)
|
||||
fill_aggregate_attestation(spec, state, attestation)
|
||||
if signed:
|
||||
|
@ -108,7 +105,7 @@ def sign_attestation(spec, state, attestation):
|
|||
participants = spec.get_attesting_indices(
|
||||
state,
|
||||
attestation.data,
|
||||
attestation.aggregation_bitfield,
|
||||
attestation.aggregation_bits,
|
||||
)
|
||||
|
||||
attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants)
|
||||
|
@ -126,7 +123,7 @@ def get_attestation_signature(spec, state, attestation_data, privkey, custody_bi
|
|||
domain=spec.get_domain(
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_ATTESTATION,
|
||||
message_epoch=attestation_data.target_epoch,
|
||||
message_epoch=attestation_data.target.epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -134,11 +131,11 @@ def get_attestation_signature(spec, state, attestation_data, privkey, custody_bi
|
|||
def fill_aggregate_attestation(spec, state, attestation):
|
||||
crosslink_committee = spec.get_crosslink_committee(
|
||||
state,
|
||||
attestation.data.target_epoch,
|
||||
attestation.data.target.epoch,
|
||||
attestation.data.crosslink.shard,
|
||||
)
|
||||
for i in range(len(crosslink_committee)):
|
||||
attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
|
||||
attestation.aggregation_bits[i] = True
|
||||
|
||||
|
||||
def add_attestation_to_state(spec, state, attestation, slot):
|
||||
|
|
|
@ -7,7 +7,7 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
|
|||
attestation_1 = get_valid_attestation(spec, state, signed=signed_1)
|
||||
|
||||
attestation_2 = deepcopy(attestation_1)
|
||||
attestation_2.data.target_root = b'\x01' * 32
|
||||
attestation_2.data.target.root = b'\x01' * 32
|
||||
|
||||
if signed_2:
|
||||
sign_attestation(spec, state, attestation_2)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
def set_bitfield_bit(bitfield, i):
|
||||
"""
|
||||
Set the bit in ``bitfield`` at position ``i`` to ``1``.
|
||||
"""
|
||||
byte_index = i // 8
|
||||
bit_index = i % 8
|
||||
return (
|
||||
bitfield[:byte_index] +
|
||||
bytes([bitfield[byte_index] | (1 << bit_index)]) +
|
||||
bitfield[byte_index + 1:]
|
||||
)
|
|
@ -1,5 +1,6 @@
|
|||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils.bls import bls_sign
|
||||
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
|
||||
from eth2spec.utils.hash_function import hash
|
||||
|
||||
|
||||
def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
||||
|
@ -10,6 +11,7 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
|||
if epoch is None:
|
||||
epoch = current_epoch + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING
|
||||
|
||||
# Generate the secret that is being revealed
|
||||
reveal = bls_sign(
|
||||
message_hash=spec.hash_tree_root(spec.Epoch(epoch)),
|
||||
privkey=privkeys[revealed_index],
|
||||
|
@ -19,20 +21,24 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
|||
message_epoch=epoch,
|
||||
),
|
||||
)
|
||||
mask = bls_sign(
|
||||
message_hash=spec.hash_tree_root(spec.Epoch(epoch)),
|
||||
# Generate the mask (any random 32 bytes that don't reveal the masker's secret will do)
|
||||
mask = hash(reveal)
|
||||
# Generate masker's signature on the mask
|
||||
masker_signature = bls_sign(
|
||||
message_hash=mask,
|
||||
privkey=privkeys[masker_index],
|
||||
domain=spec.get_domain(
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_RANDAO,
|
||||
message_epoch=epoch,
|
||||
),
|
||||
)[:32] # TODO(Carl): mask is 32 bytes, and signature is 96? Correct to slice the first 32 out?
|
||||
)
|
||||
masked_reveal = bls_aggregate_signatures([reveal, masker_signature])
|
||||
|
||||
return spec.EarlyDerivedSecretReveal(
|
||||
revealed_index=revealed_index,
|
||||
epoch=epoch,
|
||||
reveal=reveal,
|
||||
reveal=masked_reveal,
|
||||
masker_index=masker_index,
|
||||
mask=mask,
|
||||
)
|
||||
|
|
|
@ -28,7 +28,9 @@ def create_genesis_state(spec, num_validators):
|
|||
deposit_root=deposit_root,
|
||||
deposit_count=num_validators,
|
||||
block_hash=spec.ZERO_HASH,
|
||||
))
|
||||
),
|
||||
latest_block_header=spec.BeaconBlockHeader(body_root=spec.hash_tree_root(spec.BeaconBlockBody())),
|
||||
)
|
||||
|
||||
# We "hack" in the initial validators,
|
||||
# as it is much faster than creating and processing genesis deposits for every single test case.
|
||||
|
@ -41,9 +43,9 @@ def create_genesis_state(spec, num_validators):
|
|||
validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
|
||||
validator.activation_epoch = spec.GENESIS_EPOCH
|
||||
|
||||
genesis_active_index_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
|
||||
genesis_compact_committees_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
|
||||
spec.get_active_validator_indices(state, spec.GENESIS_EPOCH)))
|
||||
for index in range(spec.EPOCHS_PER_HISTORICAL_VECTOR):
|
||||
state.active_index_roots[index] = genesis_active_index_root
|
||||
state.compact_committees_roots[index] = genesis_compact_committees_root
|
||||
|
||||
return state
|
||||
|
|
|
@ -4,13 +4,15 @@ from eth2spec.utils.bls import bls_sign
|
|||
from eth2spec.utils.ssz.ssz_impl import signing_root
|
||||
|
||||
|
||||
def get_valid_transfer(spec, state, slot=None, sender_index=None, amount=None, fee=None, signed=False):
|
||||
def get_valid_transfer(spec, state, slot=None, sender_index=None,
|
||||
recipient_index=None, amount=None, fee=None, signed=False):
|
||||
if slot is None:
|
||||
slot = state.slot
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
if sender_index is None:
|
||||
sender_index = spec.get_active_validator_indices(state, current_epoch)[-1]
|
||||
recipient_index = spec.get_active_validator_indices(state, current_epoch)[0]
|
||||
if recipient_index is None:
|
||||
recipient_index = spec.get_active_validator_indices(state, current_epoch)[0]
|
||||
transfer_pubkey = pubkeys[-1]
|
||||
transfer_privkey = privkeys[-1]
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_attestation,
|
||||
|
@ -10,6 +8,7 @@ from eth2spec.test.helpers.state import (
|
|||
next_slot,
|
||||
)
|
||||
from eth2spec.test.helpers.block import apply_empty_block
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist
|
||||
|
||||
|
||||
def run_attestation_processing(spec, state, attestation, valid=True):
|
||||
|
@ -38,7 +37,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
|
|||
spec.process_attestation(state, attestation)
|
||||
|
||||
# Make sure the attestation has been processed
|
||||
if attestation.data.target_epoch == spec.get_current_epoch(state):
|
||||
if attestation.data.target.epoch == spec.get_current_epoch(state):
|
||||
assert len(state.current_epoch_attestations) == current_epoch_count + 1
|
||||
else:
|
||||
assert len(state.previous_epoch_attestations) == previous_epoch_count + 1
|
||||
|
@ -85,6 +84,29 @@ def test_success_since_max_epochs_per_crosslink(spec, state):
|
|||
yield from run_attestation_processing(spec, state, attestation)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_wrong_end_epoch_with_max_epochs_per_crosslink(spec, state):
|
||||
for _ in range(spec.MAX_EPOCHS_PER_CROSSLINK + 2):
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
data = attestation.data
|
||||
# test logic sanity check: make sure the attestation only includes MAX_EPOCHS_PER_CROSSLINK epochs
|
||||
assert data.crosslink.end_epoch - data.crosslink.start_epoch == spec.MAX_EPOCHS_PER_CROSSLINK
|
||||
# Now change it to be different
|
||||
data.crosslink.end_epoch += 1
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
||||
next_slot(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
|
@ -119,16 +141,16 @@ def test_after_epoch_slots(spec, state):
|
|||
@spec_state_test
|
||||
def test_old_source_epoch(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 5
|
||||
state.finalized_epoch = 2
|
||||
state.previous_justified_epoch = 3
|
||||
state.current_justified_epoch = 4
|
||||
state.finalized_checkpoint.epoch = 2
|
||||
state.previous_justified_checkpoint.epoch = 3
|
||||
state.current_justified_checkpoint.epoch = 4
|
||||
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
|
||||
|
||||
# test logic sanity check: make sure the attestation is pointing to oldest known source epoch
|
||||
assert attestation.data.source_epoch == state.previous_justified_epoch
|
||||
assert attestation.data.source.epoch == state.previous_justified_checkpoint.epoch
|
||||
|
||||
# Now go beyond that, it will be invalid
|
||||
attestation.data.source_epoch -= 1
|
||||
attestation.data.source.epoch -= 1
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -148,13 +170,54 @@ def test_wrong_shard(spec, state):
|
|||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_invalid_shard(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
# off by one (with respect to valid range) on purpose
|
||||
attestation.data.crosslink.shard = spec.SHARD_COUNT
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_old_target_epoch(spec, state):
|
||||
assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2
|
||||
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 2 # target epoch will be too old to handle
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_future_target_epoch(spec, state):
|
||||
assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2
|
||||
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.data.target.epoch = spec.get_current_epoch(state) + 1 # target epoch will be too new to handle
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_new_source_epoch(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.data.source_epoch += 1
|
||||
attestation.data.source.epoch += 1
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -167,7 +230,7 @@ def test_source_root_is_target_root(spec, state):
|
|||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.data.source_root = attestation.data.target_root
|
||||
attestation.data.source.root = attestation.data.target.root
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -178,23 +241,20 @@ def test_source_root_is_target_root(spec, state):
|
|||
@spec_state_test
|
||||
def test_invalid_current_source_root(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 5
|
||||
state.finalized_epoch = 2
|
||||
state.finalized_checkpoint.epoch = 2
|
||||
|
||||
state.previous_justified_epoch = 3
|
||||
state.previous_justified_root = b'\x01' * 32
|
||||
|
||||
state.current_justified_epoch = 4
|
||||
state.current_justified_root = b'\xff' * 32
|
||||
state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32)
|
||||
state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b'\x32' * 32)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
# Test logic sanity checks:
|
||||
assert state.current_justified_root != state.previous_justified_root
|
||||
assert attestation.data.source_root == state.previous_justified_root
|
||||
assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root
|
||||
assert attestation.data.source.root == state.previous_justified_checkpoint.root
|
||||
|
||||
# Make attestation source root invalid: should be previous justified, not current one
|
||||
attestation.data.source_root = state.current_justified_root
|
||||
attestation.data.source.root = state.current_justified_checkpoint.root
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -207,7 +267,7 @@ def test_bad_source_root(spec, state):
|
|||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.data.source_root = b'\x42' * 32
|
||||
attestation.data.source.root = b'\x42' * 32
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -277,11 +337,14 @@ def test_bad_crosslink_end_epoch(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_inconsistent_bitfields(spec, state):
|
||||
def test_inconsistent_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield) + b'\x00'
|
||||
custody_bits = attestation.aggregation_bits[:]
|
||||
custody_bits.append(False)
|
||||
|
||||
attestation.custody_bits = custody_bits
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -290,11 +353,11 @@ def test_inconsistent_bitfields(spec, state):
|
|||
|
||||
@with_phases(['phase0'])
|
||||
@spec_state_test
|
||||
def test_non_empty_custody_bitfield(spec, state):
|
||||
def test_non_empty_custody_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield)
|
||||
attestation.custody_bits = attestation.aggregation_bits[:]
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
@ -303,11 +366,12 @@ def test_non_empty_custody_bitfield(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_empty_aggregation_bitfield(spec, state):
|
||||
def test_empty_aggregation_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield)
|
||||
attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](
|
||||
*([0b0] * len(attestation.aggregation_bits)))
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
|
|
|
@ -25,31 +25,56 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
|
|||
yield 'post', None
|
||||
return
|
||||
|
||||
slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
pre_slashed_balance = get_balance(state, slashed_index)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
|
||||
proposer_index = spec.get_beacon_proposer_index(state)
|
||||
pre_proposer_balance = get_balance(state, proposer_index)
|
||||
pre_slashings = {slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices}
|
||||
pre_withdrawalable_epochs = {
|
||||
slashed_index: state.validators[slashed_index].withdrawable_epoch
|
||||
for slashed_index in slashed_indices
|
||||
}
|
||||
|
||||
total_proposer_rewards = sum(
|
||||
balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
for balance in pre_slashings.values()
|
||||
)
|
||||
|
||||
# Process slashing
|
||||
spec.process_attester_slashing(state, attester_slashing)
|
||||
|
||||
slashed_validator = state.validators[slashed_index]
|
||||
for slashed_index in slashed_indices:
|
||||
pre_withdrawalable_epoch = pre_withdrawalable_epochs[slashed_index]
|
||||
slashed_validator = state.validators[slashed_index]
|
||||
|
||||
# Check slashing
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
# Check slashing
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
if pre_withdrawalable_epoch < spec.FAR_FUTURE_EPOCH:
|
||||
expected_withdrawable_epoch = max(
|
||||
pre_withdrawalable_epoch,
|
||||
spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR
|
||||
)
|
||||
assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch
|
||||
else:
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert get_balance(state, slashed_index) < pre_slashings[slashed_index]
|
||||
|
||||
if slashed_index != proposer_index:
|
||||
# lost whistleblower reward
|
||||
assert get_balance(state, slashed_index) < pre_slashed_balance
|
||||
if proposer_index not in slashed_indices:
|
||||
# gained whistleblower reward
|
||||
assert get_balance(state, proposer_index) > pre_proposer_balance
|
||||
assert get_balance(state, proposer_index) == pre_proposer_balance + total_proposer_rewards
|
||||
else:
|
||||
# gained rewards for all slashings, which may include others. And only lost that of themselves.
|
||||
# Netto at least 0, if more people where slashed, a balance increase.
|
||||
assert get_balance(state, slashed_index) >= pre_slashed_balance
|
||||
expected_balance = (
|
||||
pre_proposer_balance
|
||||
+ total_proposer_rewards
|
||||
- pre_slashings[proposer_index] // spec.MIN_SLASHING_PENALTY_QUOTIENT
|
||||
)
|
||||
|
||||
assert get_balance(state, proposer_index) == expected_balance
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
@ -68,18 +93,51 @@ def test_success_surround(spec, state):
|
|||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
state.current_justified_epoch += 1
|
||||
state.current_justified_checkpoint.epoch += 1
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
attestation_1 = attester_slashing.attestation_1
|
||||
attestation_2 = attester_slashing.attestation_2
|
||||
|
||||
# set attestion1 to surround attestation 2
|
||||
attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1
|
||||
attester_slashing.attestation_1.data.target_epoch = attester_slashing.attestation_2.data.target_epoch + 1
|
||||
attestation_1.data.source.epoch = attestation_2.data.source.epoch - 1
|
||||
attestation_1.data.target.epoch = attestation_2.data.target.epoch + 1
|
||||
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success_already_exited_recent(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success_already_exited_long_ago(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
|
@ -120,7 +178,7 @@ def test_same_data(spec, state):
|
|||
def test_no_double_or_surround(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
|
||||
attester_slashing.attestation_1.data.target_epoch += 1
|
||||
attester_slashing.attestation_1.data.target.epoch += 1
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
@ -142,12 +200,106 @@ def test_participants_already_slashed(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_custody_bit_0_and_1(spec, state):
|
||||
def test_custody_bit_0_and_1_intersect(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
|
||||
attester_slashing.attestation_1.custody_bit_1_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
attester_slashing.attestation_1.custody_bit_1_indices.append(
|
||||
attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
)
|
||||
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@always_bls
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_att1_bad_extra_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
|
||||
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad extra index is spotted, and slashing is aborted.
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@always_bls
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_att1_bad_replaced_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
|
||||
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad replaced index is spotted, and slashing is aborted.
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@always_bls
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_att2_bad_extra_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
|
||||
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad extra index is spotted, and slashing is aborted.
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@always_bls
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_att2_bad_replaced_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
|
||||
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad replaced index is spotted, and slashing is aborted.
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_unsorted_att_1_bit0(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
assert len(indices) >= 3
|
||||
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_unsorted_att_2_bit0(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
assert len(indices) >= 3
|
||||
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_2)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
# note: unsorted indices for custody bit 0 are to be introduced in phase 1 testing.
|
||||
|
|
|
@ -49,20 +49,50 @@ def run_deposit_processing(spec, state, deposit, validator_index, valid=True, ef
|
|||
assert len(state.balances) == pre_validator_count + 1
|
||||
assert get_balance(state, validator_index) == pre_balance + deposit.data.amount
|
||||
|
||||
effective = min(spec.MAX_EFFECTIVE_BALANCE,
|
||||
pre_balance + deposit.data.amount)
|
||||
effective -= effective % spec.EFFECTIVE_BALANCE_INCREMENT
|
||||
assert state.validators[validator_index].effective_balance == effective
|
||||
|
||||
assert state.eth1_deposit_index == state.eth1_data.deposit_count
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_new_deposit(spec, state):
|
||||
def test_new_deposit_under_max(spec, state):
|
||||
# fresh deposit = next validator index = validator appended to registry
|
||||
validator_index = len(state.validators)
|
||||
# effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement.
|
||||
amount = spec.MAX_EFFECTIVE_BALANCE - 1
|
||||
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
|
||||
|
||||
yield from run_deposit_processing(spec, state, deposit, validator_index)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_new_deposit_max(spec, state):
|
||||
# fresh deposit = next validator index = validator appended to registry
|
||||
validator_index = len(state.validators)
|
||||
# effective balance will be exactly the same as balance.
|
||||
amount = spec.MAX_EFFECTIVE_BALANCE
|
||||
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
|
||||
|
||||
yield from run_deposit_processing(spec, state, deposit, validator_index)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_new_deposit_over_max(spec, state):
|
||||
# fresh deposit = next validator index = validator appended to registry
|
||||
validator_index = len(state.validators)
|
||||
# just 1 over the limit, effective balance should be set MAX_EFFECTIVE_BALANCE during processing
|
||||
amount = spec.MAX_EFFECTIVE_BALANCE + 1
|
||||
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
|
||||
|
||||
yield from run_deposit_processing(spec, state, deposit, validator_index)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
|
||||
from eth2spec.test.helpers.state import next_epoch
|
||||
from eth2spec.test.helpers.block import apply_empty_block
|
||||
from eth2spec.test.helpers.transfers import get_valid_transfer
|
||||
from eth2spec.test.helpers.transfers import get_valid_transfer, sign_transfer
|
||||
|
||||
|
||||
def run_transfer_processing(spec, state, transfer, valid=True):
|
||||
|
@ -13,11 +13,6 @@ def run_transfer_processing(spec, state, transfer, valid=True):
|
|||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
|
||||
proposer_index = spec.get_beacon_proposer_index(state)
|
||||
pre_transfer_sender_balance = state.balances[transfer.sender]
|
||||
pre_transfer_recipient_balance = state.balances[transfer.recipient]
|
||||
pre_transfer_proposer_balance = state.balances[proposer_index]
|
||||
|
||||
yield 'pre', state
|
||||
yield 'transfer', transfer
|
||||
|
||||
|
@ -26,6 +21,11 @@ def run_transfer_processing(spec, state, transfer, valid=True):
|
|||
yield 'post', None
|
||||
return
|
||||
|
||||
proposer_index = spec.get_beacon_proposer_index(state)
|
||||
pre_transfer_sender_balance = state.balances[transfer.sender]
|
||||
pre_transfer_recipient_balance = state.balances[transfer.recipient]
|
||||
pre_transfer_proposer_balance = state.balances[proposer_index]
|
||||
|
||||
spec.process_transfer(state, transfer)
|
||||
yield 'post', state
|
||||
|
||||
|
@ -107,20 +107,48 @@ def test_active_but_transfer_past_effective_balance(spec, state):
|
|||
def test_incorrect_slot(spec, state):
|
||||
transfer = get_valid_transfer(spec, state, slot=state.slot + 1, signed=True)
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_insufficient_balance_for_fee_result_dust(spec, state):
|
||||
def test_transfer_clean(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
|
||||
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
|
||||
amount=spec.MIN_DEPOSIT_AMOUNT, fee=0, signed=True)
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_transfer_clean_split_to_fee(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
|
||||
amount=spec.MIN_DEPOSIT_AMOUNT // 2, fee=spec.MIN_DEPOSIT_AMOUNT // 2, signed=True)
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_insufficient_balance_for_fee(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=0, fee=1, signed=True)
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
@ -142,11 +170,11 @@ def test_insufficient_balance_for_fee_result_full(spec, state):
|
|||
@spec_state_test
|
||||
def test_insufficient_balance_for_amount_result_dust(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
|
||||
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1, fee=0, signed=True)
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
@ -287,7 +315,7 @@ def test_no_dust_sender(spec, state):
|
|||
)
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
@ -301,7 +329,29 @@ def test_no_dust_recipient(spec, state):
|
|||
state.balances[transfer.recipient] = 0
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_non_existent_sender(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1, fee=0)
|
||||
transfer.sender = len(state.validators)
|
||||
sign_transfer(spec, state, transfer, 42) # mostly valid signature, but sender won't exist, use bogus key.
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_non_existent_recipient(spec, state):
|
||||
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
|
||||
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + 1
|
||||
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
|
||||
recipient_index=len(state.validators), amount=1, fee=0, signed=True)
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
||||
|
@ -313,6 +363,6 @@ def test_invalid_pubkey(spec, state):
|
|||
state.validators[transfer.sender].withdrawal_credentials = spec.ZERO_HASH
|
||||
|
||||
# un-activate so validator can transfer
|
||||
state.validators[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
yield from run_transfer_processing(spec, state, transfer, False)
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
process_calls = [
|
||||
'process_justification_and_finalization',
|
||||
'process_crosslinks',
|
||||
'process_rewards_and_penalties',
|
||||
'process_registry_updates',
|
||||
'process_reveal_deadlines',
|
||||
'process_challenge_deadlines',
|
||||
'process_slashings',
|
||||
'process_final_updates',
|
||||
'after_process_final_updates',
|
||||
]
|
||||
|
||||
|
||||
def run_epoch_processing_to(spec, state, process_name: str):
|
||||
"""
|
||||
Processes to the next epoch transition, up to, but not including, the sub-transition named ``process_name``
|
||||
"""
|
||||
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)
|
||||
|
||||
# transition state to slot before epoch state transition
|
||||
spec.process_slots(state, slot - 1)
|
||||
|
||||
# start transitioning, do one slot update before the epoch itself.
|
||||
spec.process_slot(state)
|
||||
|
||||
# process components of epoch transition before final-updates
|
||||
for name in process_calls:
|
||||
if name == process_name:
|
||||
break
|
||||
# only run when present. Later phases introduce more to the epoch-processing.
|
||||
if hasattr(spec, name):
|
||||
getattr(spec, name)(state)
|
||||
|
||||
|
||||
def run_epoch_processing_with(spec, state, process_name: str):
|
||||
"""
|
||||
Processes to the next epoch transition, up to and including the sub-transition named ``process_name``
|
||||
- pre-state ('pre'), state before calling ``process_name``
|
||||
- post-state ('post'), state after calling ``process_name``
|
||||
"""
|
||||
run_epoch_processing_to(spec, state, process_name)
|
||||
yield 'pre', state
|
||||
getattr(spec, process_name)(state)
|
||||
yield 'post', state
|
|
@ -3,42 +3,20 @@ from copy import deepcopy
|
|||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.helpers.state import (
|
||||
next_epoch,
|
||||
next_slot,
|
||||
state_transition_and_sign_block,
|
||||
next_slot
|
||||
)
|
||||
from eth2spec.test.helpers.block import apply_empty_block, sign_block
|
||||
from eth2spec.test.helpers.block import apply_empty_block
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
add_attestation_to_state,
|
||||
build_empty_block_for_next_slot,
|
||||
fill_aggregate_attestation,
|
||||
get_valid_attestation,
|
||||
sign_attestation,
|
||||
)
|
||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
||||
|
||||
|
||||
def run_process_crosslinks(spec, state, valid=True):
|
||||
"""
|
||||
Run ``process_crosslinks``, yielding:
|
||||
- pre-state ('pre')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
# transition state to slot before state transition
|
||||
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.slot = slot
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
# cache state before epoch transition
|
||||
spec.process_slot(state)
|
||||
|
||||
# process components of epoch transition before processing crosslinks
|
||||
spec.process_justification_and_finalization(state)
|
||||
|
||||
yield 'pre', state
|
||||
spec.process_crosslinks(state)
|
||||
yield 'post', state
|
||||
def run_process_crosslinks(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_crosslinks')
|
||||
|
||||
|
||||
@with_all_phases
|
||||
|
@ -96,7 +74,7 @@ def test_single_crosslink_update_from_previous_epoch(spec, state):
|
|||
# ensure rewarded
|
||||
for index in spec.get_crosslink_committee(
|
||||
state,
|
||||
attestation.data.target_epoch,
|
||||
attestation.data.target.epoch,
|
||||
attestation.data.crosslink.shard):
|
||||
assert crosslink_deltas[0][index] > 0
|
||||
assert crosslink_deltas[1][index] == 0
|
||||
|
@ -148,7 +126,7 @@ def test_double_late_crosslink(spec, state):
|
|||
# ensure no reward, only penalties for the failed crosslink
|
||||
for index in spec.get_crosslink_committee(
|
||||
state,
|
||||
attestation_2.data.target_epoch,
|
||||
attestation_2.data.target.epoch,
|
||||
attestation_2.data.crosslink.shard):
|
||||
assert crosslink_deltas[0][index] == 0
|
||||
assert crosslink_deltas[1][index] > 0
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
|
||||
run_epoch_processing_with, run_epoch_processing_to
|
||||
)
|
||||
|
||||
|
||||
def run_process_final_updates(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_final_updates')
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_eth1_vote_no_reset(spec, state):
|
||||
assert spec.SLOTS_PER_ETH1_VOTING_PERIOD > spec.SLOTS_PER_EPOCH
|
||||
# skip ahead to the end of the epoch
|
||||
state.slot = spec.SLOTS_PER_EPOCH - 1
|
||||
for i in range(state.slot + 1): # add a vote for each skipped slot.
|
||||
state.eth1_data_votes.append(
|
||||
spec.Eth1Data(deposit_root=b'\xaa' * 32,
|
||||
deposit_count=state.eth1_deposit_index,
|
||||
block_hash=b'\xbb' * 32))
|
||||
|
||||
yield from run_process_final_updates(spec, state)
|
||||
|
||||
assert len(state.eth1_data_votes) == spec.SLOTS_PER_EPOCH
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_eth1_vote_reset(spec, state):
|
||||
# skip ahead to the end of the voting period
|
||||
state.slot = spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1
|
||||
for i in range(state.slot + 1): # add a vote for each skipped slot.
|
||||
state.eth1_data_votes.append(
|
||||
spec.Eth1Data(deposit_root=b'\xaa' * 32,
|
||||
deposit_count=state.eth1_deposit_index,
|
||||
block_hash=b'\xbb' * 32))
|
||||
|
||||
yield from run_process_final_updates(spec, state)
|
||||
|
||||
assert len(state.eth1_data_votes) == 0
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_effective_balance_hysteresis(spec, state):
|
||||
# Prepare state up to the final-updates.
|
||||
# Then overwrite the balances, we only want to focus to be on the hysteresis based changes.
|
||||
run_epoch_processing_to(spec, state, 'process_final_updates')
|
||||
# Set some edge cases for balances
|
||||
max = spec.MAX_EFFECTIVE_BALANCE
|
||||
min = spec.EJECTION_BALANCE
|
||||
inc = spec.EFFECTIVE_BALANCE_INCREMENT
|
||||
half_inc = inc // 2
|
||||
cases = [
|
||||
(max, max, max, "as-is"),
|
||||
(max, max - 1, max - inc, "round down, step lower"),
|
||||
(max, max + 1, max, "round down"),
|
||||
(max, max - inc, max - inc, "exactly 1 step lower"),
|
||||
(max, max - inc - 1, max - (2 * inc), "just 1 over 1 step lower"),
|
||||
(max, max - inc + 1, max - inc, "close to 1 step lower"),
|
||||
(min, min + (half_inc * 3), min, "bigger balance, but not high enough"),
|
||||
(min, min + (half_inc * 3) + 1, min + inc, "bigger balance, high enough, but small step"),
|
||||
(min, min + (half_inc * 4) - 1, min + inc, "bigger balance, high enough, close to double step"),
|
||||
(min, min + (half_inc * 4), min + (2 * inc), "exact two step balance increment"),
|
||||
(min, min + (half_inc * 4) + 1, min + (2 * inc), "over two steps, round down"),
|
||||
]
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
for i, (pre_eff, bal, _, _) in enumerate(cases):
|
||||
assert spec.is_active_validator(state.validators[i], current_epoch)
|
||||
state.validators[i].effective_balance = pre_eff
|
||||
state.balances[i] = bal
|
||||
|
||||
yield 'pre', state
|
||||
spec.process_final_updates(state)
|
||||
yield 'post', state
|
||||
|
||||
for i, (_, _, post_eff, name) in enumerate(cases):
|
||||
assert state.validators[i].effective_balance == post_eff, name
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_historical_root_accumulator(spec, state):
|
||||
# skip ahead to near the end of the historical roots period (excl block before epoch processing)
|
||||
state.slot = spec.SLOTS_PER_HISTORICAL_ROOT - 1
|
||||
history_len = len(state.historical_roots)
|
||||
|
||||
yield from run_process_final_updates(spec, state)
|
||||
|
||||
assert len(state.historical_roots) == history_len + 1
|
|
@ -0,0 +1,280 @@
|
|||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
|
||||
run_epoch_processing_with
|
||||
)
|
||||
|
||||
|
||||
def run_process_just_and_fin(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_justification_and_finalization')
|
||||
|
||||
|
||||
def get_shards_for_slot(spec, state, slot):
|
||||
epoch = spec.slot_to_epoch(slot)
|
||||
epoch_start_shard = spec.get_epoch_start_shard(state, epoch)
|
||||
committees_per_slot = spec.get_epoch_committee_count(state, epoch) // spec.SLOTS_PER_EPOCH
|
||||
shard = (epoch_start_shard + committees_per_slot * (slot % spec.SLOTS_PER_EPOCH)) % spec.SHARD_COUNT
|
||||
return [shard + i for i in range(committees_per_slot)]
|
||||
|
||||
|
||||
def add_mock_attestations(spec, state, epoch, source, target, sufficient_support=False):
|
||||
# we must be at the end of the epoch
|
||||
assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0
|
||||
|
||||
previous_epoch = spec.get_previous_epoch(state)
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
|
||||
if current_epoch == epoch:
|
||||
attestations = state.current_epoch_attestations
|
||||
elif previous_epoch == epoch:
|
||||
attestations = state.previous_epoch_attestations
|
||||
else:
|
||||
raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}")
|
||||
|
||||
total_balance = spec.get_total_active_balance(state)
|
||||
remaining_balance = total_balance * 2 // 3
|
||||
|
||||
epoch_start_slot = spec.get_epoch_start_slot(epoch)
|
||||
for slot in range(epoch_start_slot, epoch_start_slot + spec.SLOTS_PER_EPOCH):
|
||||
for shard in get_shards_for_slot(spec, state, slot):
|
||||
# Check if we already have had sufficient balance. (and undone if we don't want it).
|
||||
# If so, do not create more attestations. (we do not have empty pending attestations normally anyway)
|
||||
if remaining_balance < 0:
|
||||
return
|
||||
|
||||
committee = spec.get_crosslink_committee(state, spec.slot_to_epoch(slot), shard)
|
||||
# Create a bitfield filled with the given count per attestation,
|
||||
# exactly on the right-most part of the committee field.
|
||||
|
||||
aggregation_bits = [0] * len(committee)
|
||||
for v in range(len(committee) * 2 // 3 + 1):
|
||||
if remaining_balance > 0:
|
||||
remaining_balance -= state.validators[v].effective_balance
|
||||
aggregation_bits[v] = 1
|
||||
else:
|
||||
break
|
||||
|
||||
# remove just one attester to make the marginal support insufficient
|
||||
if not sufficient_support:
|
||||
aggregation_bits[aggregation_bits.index(1)] = 0
|
||||
|
||||
attestations.append(spec.PendingAttestation(
|
||||
aggregation_bits=aggregation_bits,
|
||||
data=spec.AttestationData(
|
||||
beacon_block_root=b'\xff' * 32, # irrelevant to testing
|
||||
source=source,
|
||||
target=target,
|
||||
crosslink=spec.Crosslink(shard=shard)
|
||||
),
|
||||
inclusion_delay=1,
|
||||
))
|
||||
|
||||
|
||||
def get_checkpoints(spec, epoch):
|
||||
c1 = None if epoch < 1 else spec.Checkpoint(epoch=epoch - 1, root=b'\xaa' * 32)
|
||||
c2 = None if epoch < 2 else spec.Checkpoint(epoch=epoch - 2, root=b'\xbb' * 32)
|
||||
c3 = None if epoch < 3 else spec.Checkpoint(epoch=epoch - 3, root=b'\xcc' * 32)
|
||||
c4 = None if epoch < 4 else spec.Checkpoint(epoch=epoch - 4, root=b'\xdd' * 32)
|
||||
c5 = None if epoch < 5 else spec.Checkpoint(epoch=epoch - 5, root=b'\xee' * 32)
|
||||
return c1, c2, c3, c4, c5
|
||||
|
||||
|
||||
def put_checkpoints_in_block_roots(spec, state, checkpoints):
|
||||
for c in checkpoints:
|
||||
state.block_roots[spec.get_epoch_start_slot(c.epoch) % spec.SLOTS_PER_HISTORICAL_ROOT] = c.root
|
||||
|
||||
|
||||
def finalize_on_234(spec, state, epoch, sufficient_support):
|
||||
assert epoch > 4
|
||||
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
|
||||
|
||||
# 43210 -- epochs ago
|
||||
# 3210x -- justification bitfield indices
|
||||
# 11*0. -- justification bitfield contents, . = this epoch, * is being justified now
|
||||
# checkpoints for the epochs ago:
|
||||
c1, c2, c3, c4, _ = get_checkpoints(spec, epoch)
|
||||
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3, c4])
|
||||
|
||||
old_finalized = state.finalized_checkpoint
|
||||
state.previous_justified_checkpoint = c4
|
||||
state.current_justified_checkpoint = c3
|
||||
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
|
||||
state.justification_bits[1:3] = [1, 1] # mock 3rd and 4th latest epochs as justified (indices are pre-shift)
|
||||
# mock the 2nd latest epoch as justifiable, with 4th as source
|
||||
add_mock_attestations(spec, state,
|
||||
epoch=epoch - 2,
|
||||
source=c4,
|
||||
target=c2,
|
||||
sufficient_support=sufficient_support)
|
||||
|
||||
# process!
|
||||
yield from run_process_just_and_fin(spec, state)
|
||||
|
||||
assert state.previous_justified_checkpoint == c3 # changed to old current
|
||||
if sufficient_support:
|
||||
assert state.current_justified_checkpoint == c2 # changed to 2nd latest
|
||||
assert state.finalized_checkpoint == c4 # finalized old previous justified epoch
|
||||
else:
|
||||
assert state.current_justified_checkpoint == c3 # still old current
|
||||
assert state.finalized_checkpoint == old_finalized # no new finalized
|
||||
|
||||
|
||||
def finalize_on_23(spec, state, epoch, sufficient_support):
|
||||
assert epoch > 3
|
||||
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
|
||||
|
||||
# 43210 -- epochs ago
|
||||
# 210xx -- justification bitfield indices (pre shift)
|
||||
# 3210x -- justification bitfield indices (post shift)
|
||||
# 01*0. -- justification bitfield contents, . = this epoch, * is being justified now
|
||||
# checkpoints for the epochs ago:
|
||||
c1, c2, c3, _, _ = get_checkpoints(spec, epoch)
|
||||
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3])
|
||||
|
||||
old_finalized = state.finalized_checkpoint
|
||||
state.previous_justified_checkpoint = c3
|
||||
state.current_justified_checkpoint = c3
|
||||
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
|
||||
state.justification_bits[1] = 1 # mock 3rd latest epoch as justified (index is pre-shift)
|
||||
# mock the 2nd latest epoch as justifiable, with 3rd as source
|
||||
add_mock_attestations(spec, state,
|
||||
epoch=epoch - 2,
|
||||
source=c3,
|
||||
target=c2,
|
||||
sufficient_support=sufficient_support)
|
||||
|
||||
# process!
|
||||
yield from run_process_just_and_fin(spec, state)
|
||||
|
||||
assert state.previous_justified_checkpoint == c3 # changed to old current
|
||||
if sufficient_support:
|
||||
assert state.current_justified_checkpoint == c2 # changed to 2nd latest
|
||||
assert state.finalized_checkpoint == c3 # finalized old previous justified epoch
|
||||
else:
|
||||
assert state.current_justified_checkpoint == c3 # still old current
|
||||
assert state.finalized_checkpoint == old_finalized # no new finalized
|
||||
|
||||
|
||||
def finalize_on_123(spec, state, epoch, sufficient_support):
|
||||
assert epoch > 5
|
||||
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
|
||||
|
||||
# 43210 -- epochs ago
|
||||
# 210xx -- justification bitfield indices (pre shift)
|
||||
# 3210x -- justification bitfield indices (post shift)
|
||||
# 011*. -- justification bitfield contents, . = this epoch, * is being justified now
|
||||
# checkpoints for the epochs ago:
|
||||
c1, c2, c3, c4, c5 = get_checkpoints(spec, epoch)
|
||||
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3, c4, c5])
|
||||
|
||||
old_finalized = state.finalized_checkpoint
|
||||
state.previous_justified_checkpoint = c5
|
||||
state.current_justified_checkpoint = c3
|
||||
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
|
||||
state.justification_bits[1] = 1 # mock 3rd latest epochs as justified (index is pre-shift)
|
||||
# mock the 2nd latest epoch as justifiable, with 5th as source
|
||||
add_mock_attestations(spec, state,
|
||||
epoch=epoch - 2,
|
||||
source=c5,
|
||||
target=c2,
|
||||
sufficient_support=sufficient_support)
|
||||
# mock the 1st latest epoch as justifiable, with 3rd as source
|
||||
add_mock_attestations(spec, state,
|
||||
epoch=epoch - 1,
|
||||
source=c3,
|
||||
target=c1,
|
||||
sufficient_support=sufficient_support)
|
||||
|
||||
# process!
|
||||
yield from run_process_just_and_fin(spec, state)
|
||||
|
||||
assert state.previous_justified_checkpoint == c3 # changed to old current
|
||||
if sufficient_support:
|
||||
assert state.current_justified_checkpoint == c1 # changed to 1st latest
|
||||
assert state.finalized_checkpoint == c3 # finalized old current
|
||||
else:
|
||||
assert state.current_justified_checkpoint == c3 # still old current
|
||||
assert state.finalized_checkpoint == old_finalized # no new finalized
|
||||
|
||||
|
||||
def finalize_on_12(spec, state, epoch, sufficient_support):
|
||||
assert epoch > 2
|
||||
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
|
||||
|
||||
# 43210 -- epochs ago
|
||||
# 210xx -- justification bitfield indices (pre shift)
|
||||
# 3210x -- justification bitfield indices (post shift)
|
||||
# 001*. -- justification bitfield contents, . = this epoch, * is being justified now
|
||||
# checkpoints for the epochs ago:
|
||||
c1, c2, _, _, _ = get_checkpoints(spec, epoch)
|
||||
put_checkpoints_in_block_roots(spec, state, [c1, c2])
|
||||
|
||||
old_finalized = state.finalized_checkpoint
|
||||
state.previous_justified_checkpoint = c2
|
||||
state.current_justified_checkpoint = c2
|
||||
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
|
||||
state.justification_bits[0] = 1 # mock 2nd latest epoch as justified (this is pre-shift)
|
||||
# mock the 1st latest epoch as justifiable, with 2nd as source
|
||||
add_mock_attestations(spec, state,
|
||||
epoch=epoch - 1,
|
||||
source=c2,
|
||||
target=c1,
|
||||
sufficient_support=sufficient_support)
|
||||
|
||||
# process!
|
||||
yield from run_process_just_and_fin(spec, state)
|
||||
|
||||
assert state.previous_justified_checkpoint == c2 # changed to old current
|
||||
if sufficient_support:
|
||||
assert state.current_justified_checkpoint == c1 # changed to 1st latest
|
||||
assert state.finalized_checkpoint == c2 # finalized previous justified epoch
|
||||
else:
|
||||
assert state.current_justified_checkpoint == c2 # still old current
|
||||
assert state.finalized_checkpoint == old_finalized # no new finalized
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_234_ok_support(spec, state):
|
||||
yield from finalize_on_234(spec, state, 5, True)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_234_poor_support(spec, state):
|
||||
yield from finalize_on_234(spec, state, 5, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_23_ok_support(spec, state):
|
||||
yield from finalize_on_23(spec, state, 4, True)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_23_poor_support(spec, state):
|
||||
yield from finalize_on_23(spec, state, 4, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_123_ok_support(spec, state):
|
||||
yield from finalize_on_123(spec, state, 6, True)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_123_poor_support(spec, state):
|
||||
yield from finalize_on_123(spec, state, 6, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_12_ok_support(spec, state):
|
||||
yield from finalize_on_12(spec, state, 3, True)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_12_poor_support(spec, state):
|
||||
yield from finalize_on_12(spec, state, 3, False)
|
|
@ -1,46 +1,25 @@
|
|||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
|
||||
from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block
|
||||
from eth2spec.test.helpers.state import next_epoch
|
||||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
||||
|
||||
|
||||
def run_process_registry_updates(spec, state, valid=True):
|
||||
"""
|
||||
Run ``process_crosslinks``, yielding:
|
||||
- pre-state ('pre')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
# transition state to slot before state transition
|
||||
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.slot = slot
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
def run_process_registry_updates(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_registry_updates')
|
||||
|
||||
# cache state before epoch transition
|
||||
spec.process_slot(state)
|
||||
|
||||
# process components of epoch transition before registry update
|
||||
spec.process_justification_and_finalization(state)
|
||||
spec.process_crosslinks(state)
|
||||
spec.process_rewards_and_penalties(state)
|
||||
|
||||
yield 'pre', state
|
||||
spec.process_registry_updates(state)
|
||||
yield 'post', state
|
||||
def mock_deposit(spec, state, index):
|
||||
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
|
||||
state.validators[index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
|
||||
assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation(spec, state):
|
||||
index = 0
|
||||
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
|
||||
|
||||
# Mock a new deposit
|
||||
state.validators[index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
state.validators[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
|
||||
assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
|
||||
mock_deposit(spec, state, index)
|
||||
|
||||
for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
|
||||
next_epoch(spec, state)
|
||||
|
@ -49,10 +28,39 @@ def test_activation(spec, state):
|
|||
|
||||
assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert state.validators[index].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
assert spec.is_active_validator(
|
||||
state.validators[index],
|
||||
spec.get_current_epoch(state),
|
||||
)
|
||||
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_activation_queue_sorting(spec, state):
|
||||
mock_activations = 10
|
||||
|
||||
epoch = spec.get_current_epoch(state)
|
||||
for i in range(mock_activations):
|
||||
mock_deposit(spec, state, i)
|
||||
state.validators[i].activation_eligibility_epoch = epoch + 1
|
||||
|
||||
# give the last priority over the others
|
||||
state.validators[mock_activations - 1].activation_eligibility_epoch = epoch
|
||||
|
||||
# make sure we are hitting the churn
|
||||
churn_limit = spec.get_churn_limit(state)
|
||||
assert mock_activations > churn_limit
|
||||
|
||||
yield from run_process_registry_updates(spec, state)
|
||||
|
||||
# the first got in as second
|
||||
assert state.validators[0].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
# the prioritized got in as first
|
||||
assert state.validators[mock_activations - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
# the second last is at the end of the queue, and did not make the churn,
|
||||
# hence is not assigned an activation_epoch yet.
|
||||
assert state.validators[mock_activations - 2].activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
# the one at churn_limit - 1 did not make it, it was out-prioritized
|
||||
assert state.validators[churn_limit - 1].activation_epoch == spec.FAR_FUTURE_EPOCH
|
||||
# but the the one in front of the above did
|
||||
assert state.validators[churn_limit - 2].activation_epoch != spec.FAR_FUTURE_EPOCH
|
||||
|
||||
|
||||
@with_all_phases
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
|
||||
run_epoch_processing_with, run_epoch_processing_to
|
||||
)
|
||||
|
||||
|
||||
def run_process_slashings(spec, state):
|
||||
yield from run_epoch_processing_with(spec, state, 'process_slashings')
|
||||
|
||||
|
||||
def slash_validators(spec, state, indices, out_epochs):
|
||||
total_slashed_balance = 0
|
||||
for i, out_epoch in zip(indices, out_epochs):
|
||||
v = state.validators[i]
|
||||
v.slashed = True
|
||||
spec.initiate_validator_exit(state, i)
|
||||
v.withdrawable_epoch = out_epoch
|
||||
total_slashed_balance += v.effective_balance
|
||||
|
||||
state.slashings[
|
||||
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
|
||||
] = total_slashed_balance
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_max_penalties(spec, state):
|
||||
slashed_count = (len(state.validators) // 3) + 1
|
||||
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
|
||||
|
||||
slashed_indices = list(range(slashed_count))
|
||||
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
|
||||
|
||||
total_balance = spec.get_total_active_balance(state)
|
||||
total_penalties = sum(state.slashings)
|
||||
|
||||
assert total_balance // 3 <= total_penalties
|
||||
|
||||
yield from run_process_slashings(spec, state)
|
||||
|
||||
for i in slashed_indices:
|
||||
assert state.balances[i] == 0
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_small_penalty(spec, state):
|
||||
# Just the bare minimum for this one validator
|
||||
state.balances[0] = state.validators[0].effective_balance = spec.EJECTION_BALANCE
|
||||
# All the other validators get the maximum.
|
||||
for i in range(1, len(state.validators)):
|
||||
state.validators[i].effective_balance = state.balances[i] = spec.MAX_EFFECTIVE_BALANCE
|
||||
|
||||
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
|
||||
|
||||
slash_validators(spec, state, [0], [out_epoch])
|
||||
|
||||
total_balance = spec.get_total_active_balance(state)
|
||||
total_penalties = sum(state.slashings)
|
||||
|
||||
assert total_balance // 3 > total_penalties
|
||||
|
||||
run_epoch_processing_to(spec, state, 'process_slashings')
|
||||
pre_slash_balances = list(state.balances)
|
||||
yield 'pre', state
|
||||
spec.process_slashings(state)
|
||||
yield 'post', state
|
||||
|
||||
assert state.balances[0] == pre_slash_balances[0] - (state.validators[0].effective_balance
|
||||
* 3 * total_penalties // total_balance)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_scaled_penalties(spec, state):
|
||||
# skip to next epoch
|
||||
state.slot = spec.SLOTS_PER_EPOCH
|
||||
|
||||
# Also mock some previous slashings, so that we test to have the delta in the penalties computation.
|
||||
base = spec.EJECTION_BALANCE
|
||||
incr = spec.EFFECTIVE_BALANCE_INCREMENT
|
||||
# Just add some random slashings. non-zero slashings are at least the minimal effective balance.
|
||||
state.slashings[0] = base + (incr * 12)
|
||||
state.slashings[4] = base + (incr * 3)
|
||||
state.slashings[5] = base + (incr * 6)
|
||||
state.slashings[spec.EPOCHS_PER_SLASHINGS_VECTOR - 1] = base + (incr * 7)
|
||||
|
||||
slashed_count = len(state.validators) // 4
|
||||
|
||||
assert slashed_count > 10
|
||||
|
||||
# make the balances non-uniform.
|
||||
# Otherwise it would just be a simple 3/4 balance slashing. Test the per-validator scaled penalties.
|
||||
diff = spec.MAX_EFFECTIVE_BALANCE - base
|
||||
increments = diff // incr
|
||||
for i in range(10):
|
||||
state.validators[i].effective_balance = base + (incr * (i % increments))
|
||||
assert state.validators[i].effective_balance <= spec.MAX_EFFECTIVE_BALANCE
|
||||
# add/remove some, see if balances different than the effective balances are picked up
|
||||
state.balances[i] = state.validators[i].effective_balance + i - 5
|
||||
|
||||
total_balance = spec.get_total_active_balance(state)
|
||||
|
||||
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
|
||||
|
||||
slashed_indices = list(range(slashed_count))
|
||||
|
||||
# Process up to the sub-transition, then Hi-jack and get the balances.
|
||||
# We just want to test the slashings.
|
||||
# But we are not interested in the other balance changes during the same epoch transition.
|
||||
run_epoch_processing_to(spec, state, 'process_slashings')
|
||||
pre_slash_balances = list(state.balances)
|
||||
|
||||
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
|
||||
|
||||
yield 'pre', state
|
||||
spec.process_slashings(state)
|
||||
yield 'post', state
|
||||
|
||||
total_penalties = sum(state.slashings)
|
||||
|
||||
for i in slashed_indices:
|
||||
v = state.validators[i]
|
||||
penalty = v.effective_balance * total_penalties * 3 // total_balance
|
||||
assert state.balances[i] == pre_slash_balances[i] - penalty
|
|
@ -1,7 +1,13 @@
|
|||
from eth2spec.test.helpers.custody import get_valid_early_derived_secret_reveal
|
||||
from eth2spec.test.helpers.block import apply_empty_block
|
||||
from eth2spec.test.helpers.state import next_epoch, get_balance
|
||||
from eth2spec.test.context import with_all_phases_except, spec_state_test, expect_assertion_error
|
||||
from eth2spec.test.context import (
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
expect_assertion_error,
|
||||
always_bls,
|
||||
never_bls,
|
||||
)
|
||||
|
||||
|
||||
def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, valid=True):
|
||||
|
@ -36,6 +42,7 @@ def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, v
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state)
|
||||
|
@ -44,6 +51,7 @@ def test_success(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@never_bls
|
||||
@spec_state_test
|
||||
def test_reveal_from_current_epoch(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state))
|
||||
|
@ -52,6 +60,7 @@ def test_reveal_from_current_epoch(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@never_bls
|
||||
@spec_state_test
|
||||
def test_reveal_from_past_epoch(spec, state):
|
||||
next_epoch(spec, state)
|
||||
|
@ -62,6 +71,7 @@ def test_reveal_from_past_epoch(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_reveal_with_custody_padding(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(
|
||||
|
@ -73,6 +83,7 @@ def test_reveal_with_custody_padding(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_reveal_with_custody_padding_minus_one(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(
|
||||
|
@ -84,6 +95,7 @@ def test_reveal_with_custody_padding_minus_one(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@never_bls
|
||||
@spec_state_test
|
||||
def test_double_reveal(spec, state):
|
||||
randao_key_reveal1 = get_valid_early_derived_secret_reveal(
|
||||
|
@ -108,6 +120,7 @@ def test_double_reveal(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@never_bls
|
||||
@spec_state_test
|
||||
def test_revealer_is_slashed(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state))
|
||||
|
@ -117,6 +130,7 @@ def test_revealer_is_slashed(spec, state):
|
|||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@never_bls
|
||||
@spec_state_test
|
||||
def test_far_future_epoch(spec, state):
|
||||
randao_key_reveal = get_valid_early_derived_secret_reveal(
|
||||
|
|
|
@ -4,15 +4,46 @@ from eth2spec.utils.ssz.ssz_impl import signing_root
|
|||
from eth2spec.utils.bls import bls_sign
|
||||
|
||||
from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block
|
||||
# from eth2spec.test.helpers.transfers import get_valid_transfer
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
|
||||
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
||||
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
|
||||
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
||||
|
||||
from eth2spec.test.context import spec_state_test, with_all_phases
|
||||
from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_prev_slot_block_transition(spec, state):
|
||||
# Go to clean slot
|
||||
spec.process_slots(state, state.slot + 1)
|
||||
# Make a block for it
|
||||
block = build_empty_block(spec, state, slot=state.slot, signed=True)
|
||||
# Transition to next slot, above block will not be invalid on top of new state.
|
||||
spec.process_slots(state, state.slot + 1)
|
||||
|
||||
yield 'pre', state
|
||||
expect_assertion_error(lambda: state_transition_and_sign_block(spec, state, block))
|
||||
yield 'blocks', [block]
|
||||
yield 'post', None
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_same_slot_block_transition(spec, state):
|
||||
# Same slot on top of pre-state, but move out of slot 0 first.
|
||||
spec.process_slots(state, state.slot + 1)
|
||||
|
||||
block = build_empty_block(spec, state, slot=state.slot, signed=True)
|
||||
|
||||
yield 'pre', state
|
||||
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
yield 'blocks', [block]
|
||||
yield 'post', state
|
||||
|
||||
|
||||
@with_all_phases
|
||||
|
@ -35,6 +66,22 @@ def test_empty_block_transition(spec, state):
|
|||
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.ZERO_HASH
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_invalid_state_root(spec, state):
|
||||
yield 'pre', state
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.state_root = b"\xaa" * 32
|
||||
sign_block(spec, state, block)
|
||||
|
||||
expect_assertion_error(
|
||||
lambda: spec.state_transition(state, block, validate_state_root=True))
|
||||
|
||||
yield 'blocks', [block]
|
||||
yield 'post', None
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_skipped_slots(spec, state):
|
||||
|
@ -76,26 +123,29 @@ def test_empty_epoch_transition(spec, state):
|
|||
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
|
||||
|
||||
|
||||
# @with_all_phases
|
||||
# @spec_state_test
|
||||
# def test_empty_epoch_transition_not_finalizing(spec, state):
|
||||
# # copy for later balance lookups.
|
||||
# pre_state = deepcopy(state)
|
||||
# yield 'pre', state
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_empty_epoch_transition_not_finalizing(spec, state):
|
||||
# Don't run for non-minimal configs, it takes very long, and the effect
|
||||
# of calling finalization/justification is just the same as with the minimal configuration.
|
||||
if spec.SLOTS_PER_EPOCH > 8:
|
||||
return
|
||||
|
||||
# block = build_empty_block_for_next_slot(spec, state)
|
||||
# block.slot += spec.SLOTS_PER_EPOCH * 5
|
||||
# sign_block(spec, state, block, proposer_index=0)
|
||||
# copy for later balance lookups.
|
||||
pre_balances = list(state.balances)
|
||||
yield 'pre', state
|
||||
|
||||
# state_transition_and_sign_block(spec, state, block)
|
||||
spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH * 5))
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
# yield 'blocks', [block]
|
||||
# yield 'post', state
|
||||
yield 'blocks', [block]
|
||||
yield 'post', state
|
||||
|
||||
# assert state.slot == block.slot
|
||||
# assert state.finalized_epoch < spec.get_current_epoch(state) - 4
|
||||
# for index in range(len(state.validators)):
|
||||
# assert get_balance(state, index) < get_balance(pre_state, index)
|
||||
assert state.slot == block.slot
|
||||
assert state.finalized_checkpoint.epoch < spec.get_current_epoch(state) - 4
|
||||
for index in range(len(state.validators)):
|
||||
assert state.balances[index] < pre_balances[index]
|
||||
|
||||
|
||||
@with_all_phases
|
||||
|
@ -172,7 +222,27 @@ def test_attester_slashing(spec, state):
|
|||
)
|
||||
|
||||
|
||||
# TODO update functions below to be like above, i.e. with @spec_state_test and yielding data to put into the test vector
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_expected_deposit_in_block(spec, state):
|
||||
# Make the state expect a deposit, then don't provide it.
|
||||
state.eth1_data.deposit_count += 1
|
||||
yield 'pre', state
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
sign_block(spec, state, block)
|
||||
bad = False
|
||||
try:
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
bad = True
|
||||
except AssertionError:
|
||||
pass
|
||||
if bad:
|
||||
raise AssertionError("expected deposit was not enforced")
|
||||
|
||||
yield 'blocks', [block]
|
||||
yield 'post', None
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
|
@ -376,6 +446,7 @@ def test_historical_batch(spec, state):
|
|||
yield 'pre', state
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state, signed=True)
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
yield 'blocks', [block]
|
||||
|
@ -386,29 +457,78 @@ def test_historical_batch(spec, state):
|
|||
assert len(state.historical_roots) == pre_historical_roots_len + 1
|
||||
|
||||
|
||||
# @with_all_phases
|
||||
# @spec_state_test
|
||||
# def test_eth1_data_votes(spec, state):
|
||||
# yield 'pre', state
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_eth1_data_votes_consensus(spec, state):
|
||||
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
|
||||
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
|
||||
return
|
||||
|
||||
# expected_votes = 0
|
||||
# assert len(state.eth1_data_votes) == expected_votes
|
||||
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
|
||||
sign_block(spec, state, offset_block)
|
||||
state_transition_and_sign_block(spec, state, offset_block)
|
||||
yield 'pre', state
|
||||
|
||||
# blocks = []
|
||||
# for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1):
|
||||
# block = build_empty_block_for_next_slot(spec, state)
|
||||
# state_transition_and_sign_block(spec, state, block)
|
||||
# expected_votes += 1
|
||||
# assert len(state.eth1_data_votes) == expected_votes
|
||||
# blocks.append(block)
|
||||
a = b'\xaa' * 32
|
||||
b = b'\xbb' * 32
|
||||
c = b'\xcc' * 32
|
||||
|
||||
# block = build_empty_block_for_next_slot(spec, state)
|
||||
# blocks.append(block)
|
||||
blocks = []
|
||||
|
||||
# state_transition_and_sign_block(spec, state, block)
|
||||
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
# wait for over 50% for A, then start voting B
|
||||
block.body.eth1_data.block_hash = b if i * 2 > spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
blocks.append(block)
|
||||
|
||||
# yield 'blocks', [block]
|
||||
# yield 'post', state
|
||||
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
|
||||
assert state.eth1_data.block_hash == a
|
||||
|
||||
# assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
|
||||
# assert len(state.eth1_data_votes) == 1
|
||||
# transition to next eth1 voting period
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.body.eth1_data.block_hash = c
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
blocks.append(block)
|
||||
|
||||
yield 'blocks', blocks
|
||||
yield 'post', state
|
||||
|
||||
assert state.eth1_data.block_hash == a
|
||||
assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
|
||||
assert len(state.eth1_data_votes) == 1
|
||||
assert state.eth1_data_votes[0].block_hash == c
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_eth1_data_votes_no_consensus(spec, state):
|
||||
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
|
||||
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
|
||||
return
|
||||
|
||||
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
|
||||
sign_block(spec, state, offset_block)
|
||||
state_transition_and_sign_block(spec, state, offset_block)
|
||||
yield 'pre', state
|
||||
|
||||
a = b'\xaa' * 32
|
||||
b = b'\xbb' * 32
|
||||
|
||||
blocks = []
|
||||
|
||||
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
# wait for precisely 50% for A, then start voting B for other 50%
|
||||
block.body.eth1_data.block_hash = b if i * 2 >= spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
|
||||
sign_block(spec, state, block)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
blocks.append(block)
|
||||
|
||||
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
|
||||
assert state.eth1_data.block_hash == b'\x00' * 32
|
||||
|
||||
yield 'blocks', blocks
|
||||
yield 'post', state
|
||||
|
|
|
@ -13,25 +13,22 @@ def check_finality(spec,
|
|||
previous_justified_changed,
|
||||
finalized_changed):
|
||||
if current_justified_changed:
|
||||
assert state.current_justified_epoch > prev_state.current_justified_epoch
|
||||
assert state.current_justified_root != prev_state.current_justified_root
|
||||
assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch
|
||||
assert state.current_justified_checkpoint.root != prev_state.current_justified_checkpoint.root
|
||||
else:
|
||||
assert state.current_justified_epoch == prev_state.current_justified_epoch
|
||||
assert state.current_justified_root == prev_state.current_justified_root
|
||||
assert state.current_justified_checkpoint == prev_state.current_justified_checkpoint
|
||||
|
||||
if previous_justified_changed:
|
||||
assert state.previous_justified_epoch > prev_state.previous_justified_epoch
|
||||
assert state.previous_justified_root != prev_state.previous_justified_root
|
||||
assert state.previous_justified_checkpoint.epoch > prev_state.previous_justified_checkpoint.epoch
|
||||
assert state.previous_justified_checkpoint.root != prev_state.previous_justified_checkpoint.root
|
||||
else:
|
||||
assert state.previous_justified_epoch == prev_state.previous_justified_epoch
|
||||
assert state.previous_justified_root == prev_state.previous_justified_root
|
||||
assert state.previous_justified_checkpoint == prev_state.previous_justified_checkpoint
|
||||
|
||||
if finalized_changed:
|
||||
assert state.finalized_epoch > prev_state.finalized_epoch
|
||||
assert state.finalized_root != prev_state.finalized_root
|
||||
assert state.finalized_checkpoint.epoch > prev_state.finalized_checkpoint.epoch
|
||||
assert state.finalized_checkpoint.root != prev_state.finalized_checkpoint.root
|
||||
else:
|
||||
assert state.finalized_epoch == prev_state.finalized_epoch
|
||||
assert state.finalized_root == prev_state.finalized_root
|
||||
assert state.finalized_checkpoint == prev_state.finalized_checkpoint
|
||||
|
||||
|
||||
def next_epoch_with_attestations(spec,
|
||||
|
@ -107,8 +104,7 @@ def test_finality_rule_4(spec, state):
|
|||
elif epoch == 1:
|
||||
# rule 4 of finality
|
||||
check_finality(spec, state, prev_state, True, True, True)
|
||||
assert state.finalized_epoch == prev_state.current_justified_epoch
|
||||
assert state.finalized_root == prev_state.current_justified_root
|
||||
assert state.finalized_checkpoint == prev_state.current_justified_checkpoint
|
||||
|
||||
yield 'blocks', blocks
|
||||
yield 'post', state
|
||||
|
@ -138,8 +134,7 @@ def test_finality_rule_1(spec, state):
|
|||
elif epoch == 2:
|
||||
# finalized by rule 1
|
||||
check_finality(spec, state, prev_state, True, True, True)
|
||||
assert state.finalized_epoch == prev_state.previous_justified_epoch
|
||||
assert state.finalized_root == prev_state.previous_justified_root
|
||||
assert state.finalized_checkpoint == prev_state.previous_justified_checkpoint
|
||||
|
||||
yield 'blocks', blocks
|
||||
yield 'post', state
|
||||
|
@ -169,8 +164,7 @@ def test_finality_rule_2(spec, state):
|
|||
prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, False, True)
|
||||
# finalized by rule 2
|
||||
check_finality(spec, state, prev_state, True, False, True)
|
||||
assert state.finalized_epoch == prev_state.previous_justified_epoch
|
||||
assert state.finalized_root == prev_state.previous_justified_root
|
||||
assert state.finalized_checkpoint == prev_state.previous_justified_checkpoint
|
||||
|
||||
blocks += new_blocks
|
||||
|
||||
|
@ -221,8 +215,7 @@ def test_finality_rule_3(spec, state):
|
|||
blocks += new_blocks
|
||||
# rule 3
|
||||
check_finality(spec, state, prev_state, True, True, True)
|
||||
assert state.finalized_epoch == prev_state.current_justified_epoch
|
||||
assert state.finalized_root == prev_state.current_justified_root
|
||||
assert state.finalized_checkpoint == prev_state.current_justified_checkpoint
|
||||
|
||||
yield 'blocks', blocks
|
||||
yield 'post', state
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
|
||||
|
||||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.state import next_slot
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_basic(spec, state):
|
||||
state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody()))
|
||||
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
blocks = []
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
assert store.time == time
|
||||
|
||||
# On receiving a block of `GENESIS_SLOT + 1` slot
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
blocks.append(block)
|
||||
spec.on_block(store, block)
|
||||
assert store.blocks[signing_root(block)] == block
|
||||
|
||||
# On receiving a block of next epoch
|
||||
store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.slot += spec.SLOTS_PER_EPOCH
|
||||
blocks.append(block)
|
||||
|
||||
spec.on_block(store, block)
|
||||
assert store.blocks[signing_root(block)] == block
|
||||
|
||||
# TODO: add tests for justified_root and finalized_root
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
next_slot(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=1)
|
||||
indexed_attestation = spec.convert_to_indexed(state, attestation)
|
||||
spec.on_attestation(store, attestation)
|
||||
assert (
|
||||
store.latest_targets[indexed_attestation.custody_bit_0_indices[0]] ==
|
||||
spec.Target(
|
||||
epoch=attestation.data.target_epoch,
|
||||
root=attestation.data.target_root,
|
||||
)
|
||||
)
|
|
@ -1,7 +1,8 @@
|
|||
from ..merkle_minimal import merkleize_chunks
|
||||
from ..hash_function import hash
|
||||
from .ssz_typing import (
|
||||
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bool, Container, List, Bytes, uint,
|
||||
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bits, boolean, Container, List, Bytes,
|
||||
Bitlist, Bitvector, uint,
|
||||
)
|
||||
|
||||
# SSZ Serialization
|
||||
|
@ -13,7 +14,7 @@ BYTES_PER_LENGTH_OFFSET = 4
|
|||
def serialize_basic(value: SSZValue):
|
||||
if isinstance(value, uint):
|
||||
return value.to_bytes(value.type().byte_len, 'little')
|
||||
elif isinstance(value, Bool):
|
||||
elif isinstance(value, boolean):
|
||||
if value:
|
||||
return b'\x01'
|
||||
else:
|
||||
|
@ -25,7 +26,7 @@ def serialize_basic(value: SSZValue):
|
|||
def deserialize_basic(value, typ: BasicType):
|
||||
if issubclass(typ, uint):
|
||||
return typ(int.from_bytes(value, 'little'))
|
||||
elif issubclass(typ, Bool):
|
||||
elif issubclass(typ, boolean):
|
||||
assert value in (b'\x00', b'\x01')
|
||||
return typ(value == b'\x01')
|
||||
else:
|
||||
|
@ -39,6 +40,12 @@ def is_empty(obj: SSZValue):
|
|||
def serialize(obj: SSZValue):
|
||||
if isinstance(obj, BasicValue):
|
||||
return serialize_basic(obj)
|
||||
elif isinstance(obj, Bitvector):
|
||||
as_integer = sum([obj[i] << i for i in range(len(obj))])
|
||||
return as_integer.to_bytes((len(obj) + 7) // 8, "little")
|
||||
elif isinstance(obj, Bitlist):
|
||||
as_integer = (1 << len(obj)) + sum([obj[i] << i for i in range(len(obj))])
|
||||
return as_integer.to_bytes((as_integer.bit_length() + 7) // 8, "little")
|
||||
elif isinstance(obj, Series):
|
||||
return encode_series(obj)
|
||||
else:
|
||||
|
@ -85,6 +92,12 @@ def encode_series(values: Series):
|
|||
def pack(values: Series):
|
||||
if isinstance(values, bytes): # Bytes and BytesN are already packed
|
||||
return values
|
||||
elif isinstance(values, Bitvector):
|
||||
as_integer = sum([values[i] << i for i in range(len(values))])
|
||||
return as_integer.to_bytes((values.length + 7) // 8, "little")
|
||||
elif isinstance(values, Bitlist):
|
||||
as_integer = sum([values[i] << i for i in range(len(values))])
|
||||
return as_integer.to_bytes((values.length + 7) // 8, "little")
|
||||
return b''.join([serialize_basic(value) for value in values])
|
||||
|
||||
|
||||
|
@ -115,6 +128,8 @@ def item_length(typ: SSZType) -> int:
|
|||
def chunk_count(typ: SSZType) -> int:
|
||||
if isinstance(typ, BasicType):
|
||||
return 1
|
||||
elif issubclass(typ, Bits):
|
||||
return (typ.length + 255) // 256
|
||||
elif issubclass(typ, Elements):
|
||||
return (typ.length * item_length(typ.elem_type) + 31) // 32
|
||||
elif issubclass(typ, Container):
|
||||
|
@ -134,7 +149,7 @@ def hash_tree_root(obj: SSZValue):
|
|||
else:
|
||||
raise Exception(f"Type not supported: {type(obj)}")
|
||||
|
||||
if isinstance(obj, (List, Bytes)):
|
||||
if isinstance(obj, (List, Bytes, Bitlist)):
|
||||
return mix_in_length(merkleize_chunks(leaves, pad_to=chunk_count(obj.type())), len(obj))
|
||||
else:
|
||||
return merkleize_chunks(leaves)
|
||||
|
|
|
@ -31,7 +31,7 @@ class BasicValue(int, SSZValue, metaclass=BasicType):
|
|||
pass
|
||||
|
||||
|
||||
class Bool(BasicValue): # can't subclass bool.
|
||||
class boolean(BasicValue): # can't subclass bool.
|
||||
byte_len = 1
|
||||
|
||||
def __new__(cls, value: int): # int value, but can be any subclass of int (bool, Bit, Bool, etc...)
|
||||
|
@ -48,7 +48,7 @@ class Bool(BasicValue): # can't subclass bool.
|
|||
|
||||
|
||||
# Alias for Bool
|
||||
class Bit(Bool):
|
||||
class bit(boolean):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -233,7 +233,7 @@ class ParamsMeta(SSZType):
|
|||
return f"{self.__name__}~{self.__class__.__name__}"
|
||||
|
||||
def __repr__(self):
|
||||
return self, self.__class__
|
||||
return f"{self.__name__}~{self.__class__.__name__}"
|
||||
|
||||
def attr_from_params(self, p):
|
||||
# single key params are valid too. Wrap them in a tuple.
|
||||
|
@ -310,6 +310,10 @@ class BaseList(list, Elements):
|
|||
cls = self.__class__
|
||||
return f"{cls.__name__}[{cls.elem_type.__name__}, {cls.length}]({', '.join(str(v) for v in self)})"
|
||||
|
||||
def __repr__(self):
|
||||
cls = self.__class__
|
||||
return f"{cls.__name__}[{cls.elem_type.__name__}, {cls.length}]({', '.join(str(v) for v in self)})"
|
||||
|
||||
def __getitem__(self, k) -> SSZValue:
|
||||
if isinstance(k, int): # check if we are just doing a lookup, and not slicing
|
||||
if k < 0:
|
||||
|
@ -320,12 +324,18 @@ class BaseList(list, Elements):
|
|||
return super().__getitem__(k)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
if k < 0:
|
||||
raise IndexError(f"cannot set item in type {self.__class__} at negative index {k} (to {v})")
|
||||
if k > len(self):
|
||||
raise IndexError(f"cannot set item in type {self.__class__}"
|
||||
f" at out of bounds index {k} (to {v}, bound: {len(self)})")
|
||||
super().__setitem__(k, coerce_type_maybe(v, self.__class__.elem_type, strict=True))
|
||||
if type(k) == slice:
|
||||
if (k.start is not None and k.start < 0) or (k.stop is not None and k.stop > len(self)):
|
||||
raise IndexError(f"cannot set item in type {self.__class__}"
|
||||
f" at out of bounds slice {k} (to {v}, bound: {len(self)})")
|
||||
super().__setitem__(k, [coerce_type_maybe(x, self.__class__.elem_type) for x in v])
|
||||
else:
|
||||
if k < 0:
|
||||
raise IndexError(f"cannot set item in type {self.__class__} at negative index {k} (to {v})")
|
||||
if k > len(self):
|
||||
raise IndexError(f"cannot set item in type {self.__class__}"
|
||||
f" at out of bounds index {k} (to {v}, bound: {len(self)})")
|
||||
super().__setitem__(k, coerce_type_maybe(v, self.__class__.elem_type, strict=True))
|
||||
|
||||
def append(self, v):
|
||||
super().append(coerce_type_maybe(v, self.__class__.elem_type, strict=True))
|
||||
|
@ -338,6 +348,48 @@ class BaseList(list, Elements):
|
|||
return self[len(self) - 1]
|
||||
|
||||
|
||||
class BitElementsType(ElementsType):
|
||||
elem_type: SSZType = boolean
|
||||
length: int
|
||||
|
||||
|
||||
class Bits(BaseList, metaclass=BitElementsType):
|
||||
pass
|
||||
|
||||
|
||||
class Bitlist(Bits):
|
||||
@classmethod
|
||||
def is_fixed_size(cls):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls()
|
||||
|
||||
|
||||
class Bitvector(Bits):
|
||||
|
||||
@classmethod
|
||||
def extract_args(cls, *args):
|
||||
if len(args) == 0:
|
||||
return cls.default()
|
||||
else:
|
||||
return super().extract_args(*args)
|
||||
|
||||
@classmethod
|
||||
def value_check(cls, value):
|
||||
# check length limit strictly
|
||||
return len(value) == cls.length and super().value_check(value)
|
||||
|
||||
@classmethod
|
||||
def is_fixed_size(cls):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls(0 for _ in range(cls.length))
|
||||
|
||||
|
||||
class List(BaseList):
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Iterable
|
||||
from .ssz_impl import serialize, hash_tree_root
|
||||
from .ssz_typing import (
|
||||
Bit, Bool, Container, List, Vector, Bytes, BytesN,
|
||||
bit, boolean, Container, List, Vector, Bytes, BytesN,
|
||||
Bitlist, Bitvector,
|
||||
uint8, uint16, uint32, uint64, uint256, byte
|
||||
)
|
||||
from ..hash_function import hash as bytes_hash
|
||||
|
@ -74,10 +75,32 @@ def merge(a: str, branch: Iterable[str]) -> str:
|
|||
|
||||
|
||||
test_data = [
|
||||
("bit F", Bit(False), "00", chunk("00")),
|
||||
("bit T", Bit(True), "01", chunk("01")),
|
||||
("bool F", Bool(False), "00", chunk("00")),
|
||||
("bool T", Bool(True), "01", chunk("01")),
|
||||
("bit F", bit(False), "00", chunk("00")),
|
||||
("bit T", bit(True), "01", chunk("01")),
|
||||
("boolean F", boolean(False), "00", chunk("00")),
|
||||
("boolean T", boolean(True), "01", chunk("01")),
|
||||
("bitvector TTFTFTFF", Bitvector[8](1, 1, 0, 1, 0, 1, 0, 0), "2b", chunk("2b")),
|
||||
("bitlist TTFTFTFF", Bitlist[8](1, 1, 0, 1, 0, 1, 0, 0), "2b01", h(chunk("2b"), chunk("08"))),
|
||||
("bitvector FTFT", Bitvector[4](0, 1, 0, 1), "0a", chunk("0a")),
|
||||
("bitlist FTFT", Bitlist[4](0, 1, 0, 1), "1a", h(chunk("0a"), chunk("04"))),
|
||||
("bitvector FTF", Bitvector[3](0, 1, 0), "02", chunk("02")),
|
||||
("bitlist FTF", Bitlist[3](0, 1, 0), "0a", h(chunk("02"), chunk("03"))),
|
||||
("bitvector TFTFFFTTFT", Bitvector[10](1, 0, 1, 0, 0, 0, 1, 1, 0, 1), "c502", chunk("c502")),
|
||||
("bitlist TFTFFFTTFT", Bitlist[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1), "c506", h(chunk("c502"), chunk("0A"))),
|
||||
("bitvector TFTFFFTTFTFFFFTT", Bitvector[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1),
|
||||
"c5c2", chunk("c5c2")),
|
||||
("bitlist TFTFFFTTFTFFFFTT", Bitlist[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1),
|
||||
"c5c201", h(chunk("c5c2"), chunk("10"))),
|
||||
("long bitvector", Bitvector[512](1 for i in range(512)),
|
||||
"ff" * 64, h("ff" * 32, "ff" * 32)),
|
||||
("long bitlist", Bitlist[512](1),
|
||||
"03", h(h(chunk("01"), chunk("")), chunk("01"))),
|
||||
("long bitlist", Bitlist[512](1 for i in range(512)),
|
||||
"ff" * 64 + "01", h(h("ff" * 32, "ff" * 32), chunk("0002"))),
|
||||
("odd bitvector", Bitvector[513](1 for i in range(513)),
|
||||
"ff" * 64 + "01", h(h("ff" * 32, "ff" * 32), h(chunk("01"), chunk("")))),
|
||||
("odd bitlist", Bitlist[513](1 for i in range(513)),
|
||||
"ff" * 64 + "03", h(h(h("ff" * 32, "ff" * 32), h(chunk("01"), chunk(""))), chunk("0102"))),
|
||||
("uint8 00", uint8(0x00), "00", chunk("00")),
|
||||
("uint8 01", uint8(0x01), "01", chunk("01")),
|
||||
("uint8 ab", uint8(0xab), "ab", chunk("ab")),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from .ssz_typing import (
|
||||
SSZValue, SSZType, BasicValue, BasicType, Series, ElementsType,
|
||||
Elements, Bit, Bool, Container, List, Vector, Bytes, BytesN,
|
||||
Elements, bit, boolean, Container, List, Vector, Bytes, BytesN,
|
||||
byte, uint, uint8, uint16, uint32, uint64, uint128, uint256,
|
||||
Bytes32, Bytes48
|
||||
)
|
||||
|
@ -22,8 +22,8 @@ def test_subclasses():
|
|||
assert issubclass(u, SSZValue)
|
||||
assert isinstance(u, SSZType)
|
||||
assert isinstance(u, BasicType)
|
||||
assert issubclass(Bool, BasicValue)
|
||||
assert isinstance(Bool, BasicType)
|
||||
assert issubclass(boolean, BasicValue)
|
||||
assert isinstance(boolean, BasicType)
|
||||
|
||||
for c in [Container, List, Vector, Bytes, BytesN]:
|
||||
assert issubclass(c, Series)
|
||||
|
@ -45,16 +45,16 @@ def test_basic_instances():
|
|||
assert isinstance(v, BasicValue)
|
||||
assert isinstance(v, SSZValue)
|
||||
|
||||
assert isinstance(Bool(True), BasicValue)
|
||||
assert isinstance(Bool(False), BasicValue)
|
||||
assert isinstance(Bit(True), Bool)
|
||||
assert isinstance(Bit(False), Bool)
|
||||
assert isinstance(boolean(True), BasicValue)
|
||||
assert isinstance(boolean(False), BasicValue)
|
||||
assert isinstance(bit(True), boolean)
|
||||
assert isinstance(bit(False), boolean)
|
||||
|
||||
|
||||
def test_basic_value_bounds():
|
||||
max = {
|
||||
Bool: 2 ** 1,
|
||||
Bit: 2 ** 1,
|
||||
boolean: 2 ** 1,
|
||||
bit: 2 ** 1,
|
||||
uint8: 2 ** (8 * 1),
|
||||
byte: 2 ** (8 * 1),
|
||||
uint16: 2 ** (8 * 2),
|
||||
|
|
Loading…
Reference in New Issue