Merge branch 'dev' into double-operations
This commit is contained in:
commit
b317a7329f
10
README.md
10
README.md
|
@ -9,11 +9,14 @@ This repository hosts the current Eth2 specifications. Discussions about design
|
||||||
|
|
||||||
## Specs
|
## Specs
|
||||||
|
|
||||||
Core specifications for Eth2 clients be found in [specs/](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are:
|
[![GitHub release](https://img.shields.io/github/v/release/ethereum/eth2.0-specs)](https://github.com/ethereum/eth2.0-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec)
|
||||||
|
|
||||||
|
|
||||||
|
Core specifications for Eth2 clients be found in [specs](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are:
|
||||||
|
|
||||||
### Phase 0
|
### Phase 0
|
||||||
* [The Beacon Chain](specs/phase0/beacon-chain.md)
|
* [The Beacon Chain](specs/phase0/beacon-chain.md)
|
||||||
* [Fork Choice](specs/phase0/fork-choice.md)
|
* [Beacon Chain Fork Choice](specs/phase0/fork-choice.md)
|
||||||
* [Deposit Contract](specs/phase0/deposit-contract.md)
|
* [Deposit Contract](specs/phase0/deposit-contract.md)
|
||||||
* [Honest Validator](specs/phase0/validator.md)
|
* [Honest Validator](specs/phase0/validator.md)
|
||||||
* [P2P Networking](specs/phase0/p2p-interface.md)
|
* [P2P Networking](specs/phase0/p2p-interface.md)
|
||||||
|
@ -22,8 +25,9 @@ Core specifications for Eth2 clients be found in [specs/](specs/). These are div
|
||||||
* [From Phase 0 to Phase 1](specs/phase1/phase1-fork.md)
|
* [From Phase 0 to Phase 1](specs/phase1/phase1-fork.md)
|
||||||
* [The Beacon Chain for Shards](specs/phase1/beacon-chain.md)
|
* [The Beacon Chain for Shards](specs/phase1/beacon-chain.md)
|
||||||
* [Custody Game](specs/phase1/custody-game.md)
|
* [Custody Game](specs/phase1/custody-game.md)
|
||||||
* [Shard Transition and Fraud Proofs](specs/phase1/fraud-proofs.md)
|
* [Shard Transition and Fraud Proofs](specs/phase1/shard-transition.md)
|
||||||
* [Light client syncing protocol](specs/phase1/light-client-sync.md)
|
* [Light client syncing protocol](specs/phase1/light-client-sync.md)
|
||||||
|
* [Beacon Chain Fork Choice for Shards](specs/phase1/fork-choice.md)
|
||||||
|
|
||||||
### Phase 2
|
### Phase 2
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,8 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||||
# Phase 1: Upgrade from Phase 0
|
# Phase 1: Upgrade from Phase 0
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
PHASE_1_FORK_VERSION: 0x01000000
|
PHASE_1_FORK_VERSION: 0x01000000
|
||||||
|
# [STUB]
|
||||||
|
PHASE_1_GENESIS_SLOT: 32
|
||||||
INITIAL_ACTIVE_SHARDS: 64
|
INITIAL_ACTIVE_SHARDS: 64
|
||||||
|
|
||||||
# Phase 1: General
|
# Phase 1: General
|
||||||
|
|
|
@ -162,6 +162,8 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# [customized] for testnet distinction
|
# [customized] for testnet distinction
|
||||||
PHASE_1_FORK_VERSION: 0x01000001
|
PHASE_1_FORK_VERSION: 0x01000001
|
||||||
|
# [customized] for testing
|
||||||
|
PHASE_1_GENESIS_SLOT: 8
|
||||||
# [customized] reduced for testing
|
# [customized] reduced for testing
|
||||||
INITIAL_ACTIVE_SHARDS: 4
|
INITIAL_ACTIVE_SHARDS: 4
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -375,7 +375,7 @@ class PySpecCommand(Command):
|
||||||
specs/phase0/fork-choice.md
|
specs/phase0/fork-choice.md
|
||||||
specs/phase1/custody-game.md
|
specs/phase1/custody-game.md
|
||||||
specs/phase1/beacon-chain.md
|
specs/phase1/beacon-chain.md
|
||||||
specs/phase1/fraud-proofs.md
|
specs/phase1/shard-transition.md
|
||||||
specs/phase1/fork-choice.md
|
specs/phase1/fork-choice.md
|
||||||
specs/phase1/phase1-fork.md
|
specs/phase1/phase1-fork.md
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1170,6 +1170,8 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
|
||||||
return state
|
return state
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*Note*: The ETH1 block with `eth1_timestamp` meeting the minimum genesis active validator count criteria can also occur before `MIN_GENESIS_TIME`.
|
||||||
|
|
||||||
### Genesis state
|
### Genesis state
|
||||||
|
|
||||||
Let `genesis_state = candidate_state` whenever `is_valid_genesis_state(candidate_state) is True` for the first time.
|
Let `genesis_state = candidate_state` whenever `is_valid_genesis_state(candidate_state) is True` for the first time.
|
||||||
|
|
|
@ -273,13 +273,12 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None:
|
||||||
current_epoch = compute_epoch_at_slot(get_current_slot(store))
|
current_epoch = compute_epoch_at_slot(get_current_slot(store))
|
||||||
# Use GENESIS_EPOCH for previous when genesis to avoid underflow
|
# Use GENESIS_EPOCH for previous when genesis to avoid underflow
|
||||||
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH
|
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH
|
||||||
|
# If attestation target is from a future epoch, delay consideration until the epoch arrives
|
||||||
assert target.epoch in [current_epoch, previous_epoch]
|
assert target.epoch in [current_epoch, previous_epoch]
|
||||||
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
|
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
|
||||||
|
|
||||||
# Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
|
# Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
|
||||||
assert target.root in store.blocks
|
assert target.root in store.blocks
|
||||||
# Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives
|
|
||||||
assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch)
|
|
||||||
|
|
||||||
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
|
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
|
||||||
assert attestation.data.beacon_block_root in store.blocks
|
assert attestation.data.beacon_block_root in store.blocks
|
||||||
|
|
|
@ -289,7 +289,7 @@ There are two primary global topics used to propagate beacon blocks and aggregat
|
||||||
- The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated.
|
- The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated.
|
||||||
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
|
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
|
||||||
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot).
|
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot).
|
||||||
- The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally).
|
- The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally).
|
||||||
- The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`.
|
- The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`.
|
||||||
- The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
|
- The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
|
||||||
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
|
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
|
||||||
|
|
|
@ -524,6 +524,8 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th
|
||||||
* Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets
|
* Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets
|
||||||
* Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR
|
* Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR
|
||||||
|
|
||||||
|
*Note*: Short lived beacon committee assignments should not be added in into the ENR `attnets` entry.
|
||||||
|
|
||||||
*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements.
|
*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements.
|
||||||
|
|
||||||
## How to avoid slashing
|
## How to avoid slashing
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
- [Extended `AttestationData`](#extended-attestationdata)
|
- [Extended `AttestationData`](#extended-attestationdata)
|
||||||
- [Extended `Attestation`](#extended-attestation)
|
- [Extended `Attestation`](#extended-attestation)
|
||||||
- [Extended `PendingAttestation`](#extended-pendingattestation)
|
- [Extended `PendingAttestation`](#extended-pendingattestation)
|
||||||
- [`IndexedAttestation`](#indexedattestation)
|
- [Extended `IndexedAttestation`](#extended-indexedattestation)
|
||||||
- [Extended `AttesterSlashing`](#extended-attesterslashing)
|
- [Extended `AttesterSlashing`](#extended-attesterslashing)
|
||||||
- [Extended `Validator`](#extended-validator)
|
- [Extended `Validator`](#extended-validator)
|
||||||
- [Extended `BeaconBlockBody`](#extended-beaconblockbody)
|
- [Extended `BeaconBlockBody`](#extended-beaconblockbody)
|
||||||
|
@ -24,35 +24,37 @@
|
||||||
- [Extended `SignedBeaconBlock`](#extended-signedbeaconblock)
|
- [Extended `SignedBeaconBlock`](#extended-signedbeaconblock)
|
||||||
- [Extended `BeaconState`](#extended-beaconstate)
|
- [Extended `BeaconState`](#extended-beaconstate)
|
||||||
- [New containers](#new-containers)
|
- [New containers](#new-containers)
|
||||||
- [`ShardBlockWrapper`](#shardblockwrapper)
|
- [`ShardBlock`](#shardblock)
|
||||||
- [`ShardSignableHeader`](#shardsignableheader)
|
- [`SignedShardBlock`](#signedshardblock)
|
||||||
|
- [`ShardBlockHeader`](#shardblockheader)
|
||||||
- [`ShardState`](#shardstate)
|
- [`ShardState`](#shardstate)
|
||||||
- [`ShardTransition`](#shardtransition)
|
- [`ShardTransition`](#shardtransition)
|
||||||
- [`CompactCommittee`](#compactcommittee)
|
- [`CompactCommittee`](#compactcommittee)
|
||||||
- [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper)
|
- [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper)
|
||||||
- [Helper functions](#helper-functions)
|
- [Helper functions](#helper-functions)
|
||||||
- [Misc](#misc-1)
|
- [Misc](#misc-1)
|
||||||
- [`get_previous_slot`](#get_previous_slot)
|
- [`compute_previous_slot`](#compute_previous_slot)
|
||||||
- [`pack_compact_validator`](#pack_compact_validator)
|
- [`pack_compact_validator`](#pack_compact_validator)
|
||||||
- [`unpack_compact_validator`](#unpack_compact_validator)
|
- [`unpack_compact_validator`](#unpack_compact_validator)
|
||||||
- [`committee_to_compact_committee`](#committee_to_compact_committee)
|
- [`committee_to_compact_committee`](#committee_to_compact_committee)
|
||||||
- [`compute_shard_from_committee_index`](#compute_shard_from_committee_index)
|
- [`compute_shard_from_committee_index`](#compute_shard_from_committee_index)
|
||||||
- [`compute_offset_slots`](#compute_offset_slots)
|
- [`compute_offset_slots`](#compute_offset_slots)
|
||||||
|
- [`compute_updated_gasprice`](#compute_updated_gasprice)
|
||||||
- [Beacon state accessors](#beacon-state-accessors)
|
- [Beacon state accessors](#beacon-state-accessors)
|
||||||
- [`get_active_shard_count`](#get_active_shard_count)
|
- [`get_active_shard_count`](#get_active_shard_count)
|
||||||
- [`get_online_validator_indices`](#get_online_validator_indices)
|
- [`get_online_validator_indices`](#get_online_validator_indices)
|
||||||
- [`get_shard_committee`](#get_shard_committee)
|
- [`get_shard_committee`](#get_shard_committee)
|
||||||
- [`get_shard_proposer_index`](#get_shard_proposer_index)
|
|
||||||
- [`get_light_client_committee`](#get_light_client_committee)
|
- [`get_light_client_committee`](#get_light_client_committee)
|
||||||
|
- [`get_shard_proposer_index`](#get_shard_proposer_index)
|
||||||
- [`get_indexed_attestation`](#get_indexed_attestation)
|
- [`get_indexed_attestation`](#get_indexed_attestation)
|
||||||
- [`get_updated_gasprice`](#get_updated_gasprice)
|
|
||||||
- [`get_start_shard`](#get_start_shard)
|
- [`get_start_shard`](#get_start_shard)
|
||||||
- [`get_shard`](#get_shard)
|
- [`get_shard`](#get_shard)
|
||||||
- [`get_latest_slot_for_shard`](#get_latest_slot_for_shard)
|
- [`get_latest_slot_for_shard`](#get_latest_slot_for_shard)
|
||||||
- [`get_offset_slots`](#get_offset_slots)
|
- [`get_offset_slots`](#get_offset_slots)
|
||||||
- [Predicates](#predicates)
|
- [Predicates](#predicates)
|
||||||
- [`is_winning_attestation`](#is_winning_attestation)
|
|
||||||
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
|
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
|
||||||
|
- [`is_shard_attestation`](#is_shard_attestation)
|
||||||
|
- [`is_winning_attestation`](#is_winning_attestation)
|
||||||
- [Block processing](#block-processing)
|
- [Block processing](#block-processing)
|
||||||
- [Operations](#operations)
|
- [Operations](#operations)
|
||||||
- [New Attestation processing](#new-attestation-processing)
|
- [New Attestation processing](#new-attestation-processing)
|
||||||
|
@ -152,7 +154,7 @@ class PendingAttestation(Container):
|
||||||
crosslink_success: boolean
|
crosslink_success: boolean
|
||||||
```
|
```
|
||||||
|
|
||||||
### `IndexedAttestation`
|
### Extended `IndexedAttestation`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class IndexedAttestation(Container):
|
class IndexedAttestation(Container):
|
||||||
|
@ -160,7 +162,7 @@ class IndexedAttestation(Container):
|
||||||
attestation: Attestation
|
attestation: Attestation
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Extended `AttesterSlashing`
|
### Extended `AttesterSlashing`
|
||||||
|
|
||||||
Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition.
|
Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition.
|
||||||
|
|
||||||
|
@ -291,26 +293,33 @@ class BeaconState(Container):
|
||||||
|
|
||||||
The following containers are new in Phase 1.
|
The following containers are new in Phase 1.
|
||||||
|
|
||||||
### `ShardBlockWrapper`
|
### `ShardBlock`
|
||||||
|
|
||||||
_Wrapper for being broadcasted over the network._
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class ShardBlockWrapper(Container):
|
class ShardBlock(Container):
|
||||||
shard_parent_root: Root
|
shard_parent_root: Root
|
||||||
beacon_parent_root: Root
|
beacon_parent_root: Root
|
||||||
slot: Slot
|
slot: Slot
|
||||||
|
proposer_index: ValidatorIndex
|
||||||
body: ByteList[MAX_SHARD_BLOCK_SIZE]
|
body: ByteList[MAX_SHARD_BLOCK_SIZE]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SignedShardBlock`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SignedShardBlock(Container):
|
||||||
|
message: ShardBlock
|
||||||
signature: BLSSignature
|
signature: BLSSignature
|
||||||
```
|
```
|
||||||
|
|
||||||
### `ShardSignableHeader`
|
### `ShardBlockHeader`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class ShardSignableHeader(Container):
|
class ShardBlockHeader(Container):
|
||||||
shard_parent_root: Root
|
shard_parent_root: Root
|
||||||
beacon_parent_root: Root
|
beacon_parent_root: Root
|
||||||
slot: Slot
|
slot: Slot
|
||||||
|
proposer_index: ValidatorIndex
|
||||||
body_root: Root
|
body_root: Root
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -320,7 +329,7 @@ class ShardSignableHeader(Container):
|
||||||
class ShardState(Container):
|
class ShardState(Container):
|
||||||
slot: Slot
|
slot: Slot
|
||||||
gasprice: Gwei
|
gasprice: Gwei
|
||||||
data: Bytes32
|
transition_digest: Bytes32
|
||||||
latest_block_root: Root
|
latest_block_root: Root
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -361,10 +370,10 @@ class AttestationCustodyBitWrapper(Container):
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
#### `get_previous_slot`
|
#### `compute_previous_slot`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_previous_slot(slot: Slot) -> Slot:
|
def compute_previous_slot(slot: Slot) -> Slot:
|
||||||
if slot > 0:
|
if slot > 0:
|
||||||
return Slot(slot - 1)
|
return Slot(slot - 1)
|
||||||
else:
|
else:
|
||||||
|
@ -431,6 +440,20 @@ def compute_offset_slots(start_slot: Slot, end_slot: Slot) -> Sequence[Slot]:
|
||||||
return [Slot(start_slot + x) for x in SHARD_BLOCK_OFFSETS if start_slot + x < end_slot]
|
return [Slot(start_slot + x) for x in SHARD_BLOCK_OFFSETS if start_slot + x < end_slot]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `compute_updated_gasprice`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compute_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei:
|
||||||
|
if length > TARGET_SHARD_BLOCK_SIZE:
|
||||||
|
delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE)
|
||||||
|
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
|
||||||
|
return min(prev_gasprice + delta, MAX_GASPRICE)
|
||||||
|
else:
|
||||||
|
delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length)
|
||||||
|
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
|
||||||
|
return max(prev_gasprice, MIN_GASPRICE + delta) - delta
|
||||||
|
```
|
||||||
|
|
||||||
### Beacon state accessors
|
### Beacon state accessors
|
||||||
|
|
||||||
#### `get_active_shard_count`
|
#### `get_active_shard_count`
|
||||||
|
@ -457,16 +480,13 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -
|
||||||
source_epoch -= SHARD_COMMITTEE_PERIOD
|
source_epoch -= SHARD_COMMITTEE_PERIOD
|
||||||
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
|
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
|
||||||
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
|
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
|
||||||
return compute_committee(active_validator_indices, seed, shard, get_active_shard_count(beacon_state))
|
active_shard_count = get_active_shard_count(beacon_state)
|
||||||
```
|
return compute_committee(
|
||||||
|
indices=active_validator_indices,
|
||||||
#### `get_shard_proposer_index`
|
seed=seed,
|
||||||
|
index=shard,
|
||||||
```python
|
count=active_shard_count,
|
||||||
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
|
)
|
||||||
committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard)
|
|
||||||
r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8])
|
|
||||||
return committee[r % len(committee)]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_light_client_committee`
|
#### `get_light_client_committee`
|
||||||
|
@ -478,8 +498,21 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque
|
||||||
source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD
|
source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD
|
||||||
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
|
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
|
||||||
seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT)
|
seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT)
|
||||||
active_shards = get_active_shard_count(beacon_state)
|
return compute_committee(
|
||||||
return compute_committee(active_validator_indices, seed, 0, active_shards)[:TARGET_COMMITTEE_SIZE]
|
indices=active_validator_indices,
|
||||||
|
seed=seed,
|
||||||
|
index=0,
|
||||||
|
count=get_active_shard_count(beacon_state),
|
||||||
|
)[:TARGET_COMMITTEE_SIZE]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `get_shard_proposer_index`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
|
||||||
|
committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard)
|
||||||
|
r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8])
|
||||||
|
return committee[r % len(committee)]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_indexed_attestation`
|
#### `get_indexed_attestation`
|
||||||
|
@ -493,20 +526,6 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_updated_gasprice`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei:
|
|
||||||
if length > TARGET_SHARD_BLOCK_SIZE:
|
|
||||||
delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE)
|
|
||||||
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
|
|
||||||
return min(prev_gasprice + delta, MAX_GASPRICE)
|
|
||||||
else:
|
|
||||||
delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length)
|
|
||||||
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
|
|
||||||
return max(prev_gasprice, MIN_GASPRICE + delta) - delta
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `get_start_shard`
|
#### `get_start_shard`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -538,24 +557,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]:
|
||||||
|
|
||||||
### Predicates
|
### Predicates
|
||||||
|
|
||||||
#### `is_winning_attestation`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def is_winning_attestation(state: BeaconState,
|
|
||||||
attestation: PendingAttestation,
|
|
||||||
committee_index: CommitteeIndex,
|
|
||||||
winning_root: Root) -> bool:
|
|
||||||
"""
|
|
||||||
Check if ``attestation`` helped contribute to the successful crosslink of
|
|
||||||
``winning_root`` formed by ``committee_index`` committee at the current slot.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
attestation.slot == state.slot
|
|
||||||
and attestation.data.index == committee_index
|
|
||||||
and attestation.data.shard_transition_root == winning_root
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Updated `is_valid_indexed_attestation`
|
#### Updated `is_valid_indexed_attestation`
|
||||||
|
|
||||||
Note that this replaces the Phase 0 `is_valid_indexed_attestation`.
|
Note that this replaces the Phase 0 `is_valid_indexed_attestation`.
|
||||||
|
@ -598,6 +599,39 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
||||||
return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature)
|
return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `is_shard_attestation`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_shard_attestation(state: BeaconState,
|
||||||
|
attestation: Attestation,
|
||||||
|
committee_index: CommitteeIndex) -> bool:
|
||||||
|
if not (
|
||||||
|
attestation.data.index == committee_index
|
||||||
|
and attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot # Must be on-time attestation
|
||||||
|
# TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `is_winning_attestation`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_winning_attestation(state: BeaconState,
|
||||||
|
attestation: PendingAttestation,
|
||||||
|
committee_index: CommitteeIndex,
|
||||||
|
winning_root: Root) -> bool:
|
||||||
|
"""
|
||||||
|
Check if ``attestation`` helped contribute to the successful crosslink of
|
||||||
|
``winning_root`` formed by ``committee_index`` committee at the current slot.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
attestation.data.slot == state.slot
|
||||||
|
and attestation.data.index == committee_index
|
||||||
|
and attestation.data.shard_transition_root == winning_root
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### Block processing
|
### Block processing
|
||||||
|
|
||||||
|
@ -606,12 +640,11 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||||
process_block_header(state, block)
|
process_block_header(state, block)
|
||||||
process_randao(state, block.body)
|
process_randao(state, block.body)
|
||||||
process_eth1_data(state, block.body)
|
process_eth1_data(state, block.body)
|
||||||
verify_shard_transition_false_positives(state, block.body)
|
|
||||||
process_light_client_signatures(state, block.body)
|
process_light_client_signatures(state, block.body)
|
||||||
process_operations(state, block.body)
|
process_operations(state, block.body)
|
||||||
|
verify_shard_transition_false_positives(state, block.body)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Operations
|
#### Operations
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -668,7 +701,7 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||||
# Correct data root count
|
# Correct data root count
|
||||||
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard))
|
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard))
|
||||||
# Correct parent block root
|
# Correct parent block root
|
||||||
assert data.beacon_block_root == get_block_root_at_slot(state, get_previous_slot(state.slot))
|
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
|
||||||
# Type 2: no shard transition, no custody bits
|
# Type 2: no shard transition, no custody bits
|
||||||
else:
|
else:
|
||||||
# Ensure delayed attestation
|
# Ensure delayed attestation
|
||||||
|
@ -684,6 +717,9 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
|
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
|
||||||
|
# TODO: only need to check it once when phase 1 starts
|
||||||
|
assert state.slot > PHASE_1_GENESIS_SLOT
|
||||||
|
|
||||||
# Correct data root count
|
# Correct data root count
|
||||||
offset_slots = get_offset_slots(state, shard)
|
offset_slots = get_offset_slots(state, shard)
|
||||||
assert (
|
assert (
|
||||||
|
@ -694,28 +730,32 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
|
||||||
)
|
)
|
||||||
assert transition.start_slot == offset_slots[0]
|
assert transition.start_slot == offset_slots[0]
|
||||||
|
|
||||||
# Reconstruct shard headers
|
|
||||||
headers = []
|
headers = []
|
||||||
proposers = []
|
proposers = []
|
||||||
|
prev_gasprice = state.shard_states[shard].gasprice
|
||||||
shard_parent_root = state.shard_states[shard].latest_block_root
|
shard_parent_root = state.shard_states[shard].latest_block_root
|
||||||
for i in range(len(offset_slots)):
|
for i in range(len(offset_slots)):
|
||||||
if any(transition.shard_data_roots):
|
shard_block_length = transition.shard_block_lengths[i]
|
||||||
headers.append(ShardSignableHeader(
|
shard_state = transition.shard_states[i]
|
||||||
|
# Verify correct calculation of gas prices and slots
|
||||||
|
assert shard_state.gasprice == compute_updated_gasprice(prev_gasprice, shard_block_length)
|
||||||
|
assert shard_state.slot == offset_slots[i]
|
||||||
|
# Collect the non-empty proposals result
|
||||||
|
is_empty_proposal = shard_block_length == 0
|
||||||
|
if not is_empty_proposal:
|
||||||
|
proposal_index = get_shard_proposer_index(state, offset_slots[i], shard)
|
||||||
|
# Reconstruct shard headers
|
||||||
|
header = ShardBlockHeader(
|
||||||
shard_parent_root=shard_parent_root,
|
shard_parent_root=shard_parent_root,
|
||||||
parent_hash=get_block_root_at_slot(state, get_previous_slot(state.slot)),
|
beacon_parent_root=get_block_root_at_slot(state, offset_slots[i]),
|
||||||
|
proposer_index=proposal_index,
|
||||||
slot=offset_slots[i],
|
slot=offset_slots[i],
|
||||||
body_root=transition.shard_data_roots[i]
|
body_root=transition.shard_data_roots[i]
|
||||||
))
|
)
|
||||||
proposers.append(get_shard_proposer_index(state, shard, offset_slots[i]))
|
shard_parent_root = hash_tree_root(header)
|
||||||
shard_parent_root = hash_tree_root(headers[-1])
|
headers.append(header)
|
||||||
|
proposers.append(proposal_index)
|
||||||
|
|
||||||
# Verify correct calculation of gas prices and slots
|
|
||||||
prev_gasprice = state.shard_states[shard].gasprice
|
|
||||||
for i in range(len(offset_slots)):
|
|
||||||
shard_state = transition.shard_states[i]
|
|
||||||
block_length = transition.shard_block_lengths[i]
|
|
||||||
assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, block_length)
|
|
||||||
assert shard_state.slot == offset_slots[i]
|
|
||||||
prev_gasprice = shard_state.gasprice
|
prev_gasprice = shard_state.gasprice
|
||||||
|
|
||||||
pubkeys = [state.validators[proposer].pubkey for proposer in proposers]
|
pubkeys = [state.validators[proposer].pubkey for proposer in proposers]
|
||||||
|
@ -727,7 +767,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
|
||||||
assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate)
|
assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate)
|
||||||
|
|
||||||
# Save updated state
|
# Save updated state
|
||||||
state.shard_states[shard] = transition.shard_states[-1]
|
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
|
||||||
state.shard_states[shard].slot = state.slot - 1
|
state.shard_states[shard].slot = state.slot - 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -750,6 +790,9 @@ def process_crosslink_for_shard(state: BeaconState,
|
||||||
for attestation in transition_attestations:
|
for attestation in transition_attestations:
|
||||||
participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||||
transition_participants = transition_participants.union(participants)
|
transition_participants = transition_participants.union(participants)
|
||||||
|
assert attestation.data.head_shard_root == shard_transition.shard_data_roots[
|
||||||
|
len(shard_transition.shard_data_roots) - 1
|
||||||
|
]
|
||||||
|
|
||||||
enough_online_stake = (
|
enough_online_stake = (
|
||||||
get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >=
|
get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >=
|
||||||
|
@ -761,7 +804,6 @@ def process_crosslink_for_shard(state: BeaconState,
|
||||||
|
|
||||||
# Attestation <-> shard transition consistency
|
# Attestation <-> shard transition consistency
|
||||||
assert shard_transition_root == hash_tree_root(shard_transition)
|
assert shard_transition_root == hash_tree_root(shard_transition)
|
||||||
assert attestation.data.head_shard_root == shard_transition.shard_data_roots[-1]
|
|
||||||
|
|
||||||
# Apply transition
|
# Apply transition
|
||||||
apply_shard_transition(state, shard, shard_transition)
|
apply_shard_transition(state, shard, shard_transition)
|
||||||
|
@ -772,11 +814,11 @@ def process_crosslink_for_shard(state: BeaconState,
|
||||||
increase_balance(state, beacon_proposer_index, proposer_reward)
|
increase_balance(state, beacon_proposer_index, proposer_reward)
|
||||||
states_slots_lengths = zip(
|
states_slots_lengths = zip(
|
||||||
shard_transition.shard_states,
|
shard_transition.shard_states,
|
||||||
get_offset_slots(state, get_latest_slot_for_shard(state, shard)),
|
get_offset_slots(state, shard),
|
||||||
shard_transition.shard_block_lengths
|
shard_transition.shard_block_lengths
|
||||||
)
|
)
|
||||||
for shard_state, slot, length in states_slots_lengths:
|
for shard_state, slot, length in states_slots_lengths:
|
||||||
proposer_index = get_shard_proposer_index(state, shard, slot)
|
proposer_index = get_shard_proposer_index(state, slot, shard)
|
||||||
decrease_balance(state, proposer_index, shard_state.gasprice * length)
|
decrease_balance(state, proposer_index, shard_state.gasprice * length)
|
||||||
|
|
||||||
# Return winning transition root
|
# Return winning transition root
|
||||||
|
@ -797,11 +839,12 @@ def process_crosslinks(state: BeaconState,
|
||||||
for committee_index in map(CommitteeIndex, range(committee_count)):
|
for committee_index in map(CommitteeIndex, range(committee_count)):
|
||||||
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
|
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||||
# All attestations in the block for this committee/shard and current slot
|
# All attestations in the block for this committee/shard and current slot
|
||||||
|
shard_transition = shard_transitions[shard]
|
||||||
shard_attestations = [
|
shard_attestations = [
|
||||||
attestation for attestation in attestations
|
attestation for attestation in attestations
|
||||||
if attestation.data.index == committee_index and attestation.data.slot == state.slot
|
if is_shard_attestation(state, attestation, committee_index)
|
||||||
]
|
]
|
||||||
shard_transition = shard_transitions[shard]
|
|
||||||
winning_root = process_crosslink_for_shard(state, committee_index, shard_transition, shard_attestations)
|
winning_root = process_crosslink_for_shard(state, committee_index, shard_transition, shard_attestations)
|
||||||
if winning_root != Root():
|
if winning_root != Root():
|
||||||
# Mark relevant pending attestations as creating a successful crosslink
|
# Mark relevant pending attestations as creating a successful crosslink
|
||||||
|
@ -896,13 +939,17 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB
|
||||||
|
|
||||||
increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT))
|
increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT))
|
||||||
|
|
||||||
slot = get_previous_slot(state.slot)
|
slot = compute_previous_slot(state.slot)
|
||||||
signing_root = compute_signing_root(get_block_root_at_slot(state, slot),
|
signing_root = compute_signing_root(get_block_root_at_slot(state, slot),
|
||||||
get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot)))
|
get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot)))
|
||||||
|
if len(signer_pubkeys) == 0:
|
||||||
|
# TODO: handle the empty light_client_signature case?
|
||||||
|
assert block_body.light_client_signature == BLSSignature()
|
||||||
|
return
|
||||||
|
else:
|
||||||
assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature)
|
assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Epoch transition
|
### Epoch transition
|
||||||
|
|
||||||
This epoch transition overrides the phase0 epoch transition:
|
This epoch transition overrides the phase0 epoch transition:
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
||||||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
|
||||||
|
|
||||||
- [Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs](#ethereum-20-phase-1----shard-transition-and-fraud-proofs)
|
|
||||||
- [Table of contents](#table-of-contents)
|
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [Fraud proofs](#fraud-proofs)
|
|
||||||
- [Shard state transition function](#shard-state-transition-function)
|
|
||||||
- [Honest committee member behavior](#honest-committee-member-behavior)
|
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
||||||
|
|
||||||
# Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs
|
|
||||||
|
|
||||||
**Notice**: This document is a work-in-progress for researchers and implementers.
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
<!-- TOC -->
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
<!-- /TOC -->
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0.
|
|
||||||
|
|
||||||
## Fraud proofs
|
|
||||||
|
|
||||||
TODO. The intent is to have a single universal fraud proof type, which contains the following parts:
|
|
||||||
|
|
||||||
1. An on-time attestation on some `shard` signing a `ShardTransition`
|
|
||||||
2. An index `i` of a particular position to focus on
|
|
||||||
3. The `ShardTransition` itself
|
|
||||||
4. The full body of the block
|
|
||||||
5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing
|
|
||||||
|
|
||||||
The proof verifies that one of the two conditions is false:
|
|
||||||
|
|
||||||
1. `custody_bits[i][j] != generate_custody_bit(subkey, block_contents)` for any `j`
|
|
||||||
2. `execute_state_transition(shard, slot, transition.shard_states[i-1].data, hash_tree_root(parent), get_shard_proposer_index(state, shard, slot), block_contents) != transition.shard_states[i].data` (if `i=0` then instead use `parent.shard_states[shard][-1].data`)
|
|
||||||
|
|
||||||
## Shard state transition function
|
|
||||||
|
|
||||||
```python
|
|
||||||
def shard_state_transition(shard: Shard,
|
|
||||||
slot: Slot,
|
|
||||||
pre_state: Root,
|
|
||||||
previous_beacon_root: Root,
|
|
||||||
proposer_pubkey: BLSPubkey,
|
|
||||||
block_data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> Root:
|
|
||||||
# We will add something more substantive in phase 2
|
|
||||||
return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(block_data))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Honest committee member behavior
|
|
||||||
|
|
||||||
Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure:
|
|
||||||
|
|
||||||
* Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`.
|
|
||||||
* For `slot in get_offset_slots(state, start_slot)`, do the following:
|
|
||||||
* Look for all valid proposals for `slot`; that is, a Bytes `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover.
|
|
||||||
* If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))`
|
|
||||||
* If `len(choices) == 1`, do `proposals.append(choices[0])`
|
|
||||||
* If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`.
|
|
||||||
* If `proposals[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged.
|
|
||||||
|
|
||||||
Make an attestation using `shard_data_roots = [hash_tree_root(proposal) for proposal in proposals]` and `shard_state_roots = shard_states`.
|
|
|
@ -7,7 +7,7 @@
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Fork to Phase 1](#fork-to-phase-1)
|
- [Fork to Phase 1](#fork-to-phase-1)
|
||||||
- [Fork trigger.](#fork-trigger)
|
- [Fork trigger](#fork-trigger)
|
||||||
- [Upgrading the state](#upgrading-the-state)
|
- [Upgrading the state](#upgrading-the-state)
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
@ -35,17 +35,18 @@ Warning: this configuration is not definitive.
|
||||||
| Name | Value |
|
| Name | Value |
|
||||||
| - | - |
|
| - | - |
|
||||||
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
|
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
|
||||||
|
| `PHASE_1_GENESIS_SLOT` | `2**5` **TBD** |
|
||||||
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
|
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
|
||||||
|
|
||||||
## Fork to Phase 1
|
## Fork to Phase 1
|
||||||
|
|
||||||
### Fork trigger.
|
### Fork trigger
|
||||||
|
|
||||||
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork.
|
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at slot `PHASE_1_GENESIS_SLOT`, where `PHASE_1_GENESIS_SLOT % SLOTS_PER_EPOCH == 0`.
|
||||||
|
|
||||||
### Upgrading the state
|
### Upgrading the state
|
||||||
|
|
||||||
After `process_slots` of Phase 0 finishes, but before the first Phase 1 block is processed, an irregular state change is made to upgrade to Phase 1.
|
After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_GENESIS_SLOT`, an irregular state change is made to upgrade to Phase 1.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
||||||
|
@ -102,7 +103,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
||||||
ShardState(
|
ShardState(
|
||||||
slot=pre.slot,
|
slot=pre.slot,
|
||||||
gasprice=MIN_GASPRICE,
|
gasprice=MIN_GASPRICE,
|
||||||
data=Root(),
|
transition_digest=Root(),
|
||||||
latest_block_root=Root(),
|
latest_block_root=Root(),
|
||||||
) for i in range(INITIAL_ACTIVE_SHARDS)
|
) for i in range(INITIAL_ACTIVE_SHARDS)
|
||||||
),
|
),
|
||||||
|
@ -110,7 +111,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
||||||
current_light_committee=CompactCommittee(), # computed after state creation
|
current_light_committee=CompactCommittee(), # computed after state creation
|
||||||
next_light_committee=CompactCommittee(),
|
next_light_committee=CompactCommittee(),
|
||||||
# Custody game
|
# Custody game
|
||||||
custody_challenge_index=0,
|
exposed_derived_secrets=[] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS,
|
||||||
# exposed_derived_secrets will fully default to zeroes
|
# exposed_derived_secrets will fully default to zeroes
|
||||||
)
|
)
|
||||||
next_epoch = Epoch(epoch + 1)
|
next_epoch = Epoch(epoch + 1)
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
# Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs
|
||||||
|
|
||||||
|
**Notice**: This document is a work-in-progress for researchers and implementers.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
|
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||||
|
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Helper functions](#helper-functions)
|
||||||
|
- [Misc](#misc)
|
||||||
|
- [Shard block verification functions](#shard-block-verification-functions)
|
||||||
|
- [Shard state transition](#shard-state-transition)
|
||||||
|
- [Fraud proofs](#fraud-proofs)
|
||||||
|
- [Verifying the proof](#verifying-the-proof)
|
||||||
|
- [Honest committee member behavior](#honest-committee-member-behavior)
|
||||||
|
- [Helper functions](#helper-functions-1)
|
||||||
|
- [Make attestations](#make-attestations)
|
||||||
|
|
||||||
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0.
|
||||||
|
|
||||||
|
## Helper functions
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compute_shard_transition_digest(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
beacon_parent_root: Root,
|
||||||
|
shard_body_root: Root) -> Bytes32:
|
||||||
|
# TODO: use SSZ hash tree root
|
||||||
|
return hash(
|
||||||
|
hash_tree_root(shard_state) + beacon_parent_root + shard_body_root
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shard block verification functions
|
||||||
|
|
||||||
|
```python
|
||||||
|
def verify_shard_block_message(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
block: ShardBlock,
|
||||||
|
slot: Slot,
|
||||||
|
shard: Shard) -> bool:
|
||||||
|
assert block.shard_parent_root == shard_state.latest_block_root
|
||||||
|
assert block.slot == slot
|
||||||
|
assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard)
|
||||||
|
assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def verify_shard_block_signature(beacon_state: BeaconState,
|
||||||
|
signed_block: SignedShardBlock) -> bool:
|
||||||
|
proposer = beacon_state.validators[signed_block.message.proposer_index]
|
||||||
|
domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot))
|
||||||
|
signing_root = compute_signing_root(signed_block.message, domain)
|
||||||
|
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shard state transition
|
||||||
|
|
||||||
|
```python
|
||||||
|
def shard_state_transition(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
block: ShardBlock) -> None:
|
||||||
|
# Update shard state
|
||||||
|
prev_gasprice = shard_state.gasprice
|
||||||
|
if len(block.body) == 0:
|
||||||
|
latest_block_root = shard_state.latest_block_root
|
||||||
|
else:
|
||||||
|
latest_block_root = hash_tree_root(block)
|
||||||
|
|
||||||
|
shard_state.transition_digest = compute_shard_transition_digest(
|
||||||
|
beacon_state,
|
||||||
|
shard_state,
|
||||||
|
block.beacon_parent_root,
|
||||||
|
block.body,
|
||||||
|
)
|
||||||
|
shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body))
|
||||||
|
shard_state.slot = block.slot
|
||||||
|
shard_state.latest_block_root = latest_block_root
|
||||||
|
```
|
||||||
|
|
||||||
|
We have a pure function `get_post_shard_state` for describing the fraud proof verification and honest validator behavior.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_post_shard_state(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
block: ShardBlock) -> ShardState:
|
||||||
|
"""
|
||||||
|
A pure function that returns a new post ShardState instead of modifying the given `shard_state`.
|
||||||
|
"""
|
||||||
|
post_state = shard_state.copy()
|
||||||
|
shard_state_transition(beacon_state, post_state, block)
|
||||||
|
return post_state
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fraud proofs
|
||||||
|
|
||||||
|
### Verifying the proof
|
||||||
|
|
||||||
|
TODO. The intent is to have a single universal fraud proof type, which contains the following parts:
|
||||||
|
|
||||||
|
1. An on-time attestation `attestation` on some shard `shard` signing a `transition: ShardTransition`
|
||||||
|
2. An index `offset_index` of a particular position to focus on
|
||||||
|
3. The `transition: ShardTransition` itself
|
||||||
|
4. The full body of the shard block `shard_block`
|
||||||
|
5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing
|
||||||
|
6. The `subkey` to generate the custody bit
|
||||||
|
|
||||||
|
Call the following function to verify the proof:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def is_valid_fraud_proof(beacon_state: BeaconState,
|
||||||
|
attestation: Attestation,
|
||||||
|
offset_index: uint64,
|
||||||
|
transition: ShardTransition,
|
||||||
|
block: ShardBlock,
|
||||||
|
subkey: BLSPubkey,
|
||||||
|
beacon_parent_block: BeaconBlock) -> bool:
|
||||||
|
# 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`.
|
||||||
|
custody_bits = attestation.custody_bits_blocks
|
||||||
|
for j in range(custody_bits[offset_index]):
|
||||||
|
if custody_bits[offset_index][j] != generate_custody_bit(subkey, block):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 2. Check if the shard state transition result is wrong between
|
||||||
|
# `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`.
|
||||||
|
if offset_index == 0:
|
||||||
|
shard = get_shard(beacon_state, attestation)
|
||||||
|
shard_state = beacon_parent_block.shard_transitions[shard].shard_states[-1]
|
||||||
|
else:
|
||||||
|
shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here.
|
||||||
|
|
||||||
|
shard_state = get_post_shard_state(beacon_state, shard_state, block)
|
||||||
|
if shard_state.transition_digest != transition.shard_states[offset_index].transition_digest:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool:
|
||||||
|
# TODO
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Honest committee member behavior
|
||||||
|
|
||||||
|
### Helper functions
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedShardBlock]) -> SignedShardBlock:
|
||||||
|
# TODO: Let `winning_proposal` be the proposal with the largest number of total attestations from slots in
|
||||||
|
# `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing
|
||||||
|
# the first proposal locally seen. Do `proposals.append(winning_proposal)`.
|
||||||
|
return proposals[-1] # stub
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]:
|
||||||
|
return [hash_tree_root(proposal.message.body) for proposal in proposals]
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_proposal_choices_at_slot(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
slot: Slot,
|
||||||
|
shard: Shard,
|
||||||
|
shard_blocks: Sequence[SignedShardBlock],
|
||||||
|
validate_signature: bool=True) -> Sequence[SignedShardBlock]:
|
||||||
|
"""
|
||||||
|
Return the valid shard blocks at the given ``slot``.
|
||||||
|
Note that this function doesn't change the state.
|
||||||
|
"""
|
||||||
|
choices = []
|
||||||
|
shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot]
|
||||||
|
for block in shard_blocks_at_slot:
|
||||||
|
try:
|
||||||
|
# Verify block message and signature
|
||||||
|
# TODO these validations should have been checked upon receiving shard blocks.
|
||||||
|
assert verify_shard_block_message(beacon_state, shard_state, block.message, slot, shard)
|
||||||
|
if validate_signature:
|
||||||
|
assert verify_shard_block_signature(beacon_state, block)
|
||||||
|
|
||||||
|
shard_state = get_post_shard_state(beacon_state, shard_state, block.message)
|
||||||
|
except Exception:
|
||||||
|
pass # TODO: throw error in the test helper
|
||||||
|
else:
|
||||||
|
choices.append(block)
|
||||||
|
return choices
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_proposal_at_slot(beacon_state: BeaconState,
|
||||||
|
shard_state: ShardState,
|
||||||
|
slot: Shard,
|
||||||
|
shard: Shard,
|
||||||
|
shard_blocks: Sequence[SignedShardBlock],
|
||||||
|
validate_signature: bool=True) -> Tuple[SignedShardBlock, ShardState]:
|
||||||
|
"""
|
||||||
|
Return ``proposal``, ``shard_state`` of the given ``slot``.
|
||||||
|
Note that this function doesn't change the state.
|
||||||
|
"""
|
||||||
|
choices = get_proposal_choices_at_slot(
|
||||||
|
beacon_state=beacon_state,
|
||||||
|
shard_state=shard_state,
|
||||||
|
slot=slot,
|
||||||
|
shard=shard,
|
||||||
|
shard_blocks=shard_blocks,
|
||||||
|
validate_signature=validate_signature,
|
||||||
|
)
|
||||||
|
if len(choices) == 0:
|
||||||
|
block = ShardBlock(slot=slot)
|
||||||
|
proposal = SignedShardBlock(message=block)
|
||||||
|
elif len(choices) == 1:
|
||||||
|
proposal = choices[0]
|
||||||
|
else:
|
||||||
|
proposal = get_winning_proposal(beacon_state, choices)
|
||||||
|
|
||||||
|
# Apply state transition
|
||||||
|
shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message)
|
||||||
|
|
||||||
|
return proposal, shard_state
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_shard_state_transition_result(
|
||||||
|
beacon_state: BeaconState,
|
||||||
|
shard: Shard,
|
||||||
|
shard_blocks: Sequence[SignedShardBlock],
|
||||||
|
validate_signature: bool=True,
|
||||||
|
) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]:
|
||||||
|
proposals = []
|
||||||
|
shard_states = []
|
||||||
|
shard_state = beacon_state.shard_states[shard]
|
||||||
|
for slot in get_offset_slots(beacon_state, shard):
|
||||||
|
proposal, shard_state = get_proposal_at_slot(
|
||||||
|
beacon_state=beacon_state,
|
||||||
|
shard_state=shard_state,
|
||||||
|
slot=slot,
|
||||||
|
shard=shard,
|
||||||
|
shard_blocks=shard_blocks,
|
||||||
|
validate_signature=validate_signature,
|
||||||
|
)
|
||||||
|
shard_states.append(shard_state)
|
||||||
|
proposals.append(proposal)
|
||||||
|
|
||||||
|
shard_data_roots = compute_shard_body_roots(proposals)
|
||||||
|
|
||||||
|
return proposals, shard_states, shard_data_roots
|
||||||
|
```
|
||||||
|
|
||||||
|
### Make attestations
|
||||||
|
|
||||||
|
Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks` since the latest successful crosslink for `shard` into the beacon chain. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_shard_transition(beacon_state: BeaconState,
|
||||||
|
shard: Shard,
|
||||||
|
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
|
||||||
|
offset_slots = get_offset_slots(beacon_state, shard)
|
||||||
|
start_slot = offset_slots[0]
|
||||||
|
proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks)
|
||||||
|
|
||||||
|
assert len(proposals) > 0
|
||||||
|
assert len(shard_data_roots) > 0
|
||||||
|
|
||||||
|
shard_block_lengths = []
|
||||||
|
proposer_signatures = []
|
||||||
|
for proposal in proposals:
|
||||||
|
shard_block_lengths.append(len(proposal.message.body))
|
||||||
|
if proposal.signature != BLSSignature():
|
||||||
|
proposer_signatures.append(proposal.signature)
|
||||||
|
|
||||||
|
proposer_signature_aggregate = bls.Aggregate(proposer_signatures)
|
||||||
|
|
||||||
|
return ShardTransition(
|
||||||
|
start_slot=start_slot,
|
||||||
|
shard_block_lengths=shard_block_lengths,
|
||||||
|
shard_data_roots=shard_data_roots,
|
||||||
|
shard_states=shard_states,
|
||||||
|
proposer_signature_aggregate=proposer_signature_aggregate,
|
||||||
|
)
|
||||||
|
```
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from eth2spec.test.context import expect_assertion_error, PHASE0
|
from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1
|
||||||
from eth2spec.test.helpers.state import state_transition_and_sign_block
|
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, transition_to
|
||||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||||
from eth2spec.test.helpers.keys import privkeys
|
from eth2spec.test.helpers.keys import privkeys
|
||||||
from eth2spec.utils import bls
|
from eth2spec.utils import bls
|
||||||
|
@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
|
||||||
yield 'post', state
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
def build_attestation_data(spec, state, slot, index):
|
def build_attestation_data(spec, state, slot, index, shard_transition=None, on_time=True):
|
||||||
assert state.slot >= slot
|
assert state.slot >= slot
|
||||||
|
|
||||||
if slot == state.slot:
|
if slot == state.slot:
|
||||||
|
@ -66,7 +66,7 @@ def build_attestation_data(spec, state, slot, index):
|
||||||
source_epoch = state.current_justified_checkpoint.epoch
|
source_epoch = state.current_justified_checkpoint.epoch
|
||||||
source_root = state.current_justified_checkpoint.root
|
source_root = state.current_justified_checkpoint.root
|
||||||
|
|
||||||
return spec.AttestationData(
|
attestation_data = spec.AttestationData(
|
||||||
slot=slot,
|
slot=slot,
|
||||||
index=index,
|
index=index,
|
||||||
beacon_block_root=block_root,
|
beacon_block_root=block_root,
|
||||||
|
@ -74,11 +74,34 @@ def build_attestation_data(spec, state, slot, index):
|
||||||
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
|
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if spec.fork == PHASE1:
|
||||||
|
if shard_transition is not None:
|
||||||
|
lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
|
||||||
|
attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
|
||||||
|
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
|
||||||
|
else:
|
||||||
|
# No shard transition
|
||||||
|
shard = spec.get_shard(state, spec.Attestation(data=attestation_data))
|
||||||
|
if on_time:
|
||||||
|
temp_state = state.copy()
|
||||||
|
next_slot(spec, temp_state)
|
||||||
|
shard_transition = spec.get_shard_transition(temp_state, shard, [])
|
||||||
|
lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
|
||||||
|
attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
|
||||||
|
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
|
||||||
|
else:
|
||||||
|
attestation_data.head_shard_root = state.shard_states[shard].transition_digest
|
||||||
|
attestation_data.shard_transition_root = spec.Root()
|
||||||
|
return attestation_data
|
||||||
|
|
||||||
|
|
||||||
def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False):
|
def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False):
|
||||||
shard = spec.get_shard(state, attestation)
|
shard = spec.get_shard(state, attestation)
|
||||||
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
|
offset_slots = spec.compute_offset_slots(
|
||||||
for offset_slot in offset_slots:
|
spec.get_latest_slot_for_shard(state, shard),
|
||||||
|
attestation.data.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY,
|
||||||
|
)
|
||||||
|
for _ in offset_slots:
|
||||||
attestation.custody_bits_blocks.append(
|
attestation.custody_bits_blocks.append(
|
||||||
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
||||||
)
|
)
|
||||||
|
@ -89,7 +112,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False)
|
||||||
return attestation
|
return attestation
|
||||||
|
|
||||||
|
|
||||||
def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False):
|
def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
|
||||||
'''
|
'''
|
||||||
Construct on-time attestation for next slot
|
Construct on-time attestation for next slot
|
||||||
'''
|
'''
|
||||||
|
@ -98,7 +121,15 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True)
|
return get_valid_attestation(
|
||||||
|
spec,
|
||||||
|
state,
|
||||||
|
slot=slot,
|
||||||
|
index=index,
|
||||||
|
shard_transition=shard_transition,
|
||||||
|
signed=signed,
|
||||||
|
on_time=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False):
|
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False):
|
||||||
|
@ -113,13 +144,22 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False)
|
||||||
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False)
|
return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False)
|
||||||
|
|
||||||
|
|
||||||
def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True):
|
def get_valid_attestation(spec,
|
||||||
|
state,
|
||||||
|
slot=None,
|
||||||
|
index=None,
|
||||||
|
shard_transition=None,
|
||||||
|
empty=False,
|
||||||
|
signed=False,
|
||||||
|
on_time=True):
|
||||||
if slot is None:
|
if slot is None:
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
if index is None:
|
if index is None:
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
attestation_data = build_attestation_data(spec, state, slot, index)
|
attestation_data = build_attestation_data(
|
||||||
|
spec, state, slot=slot, index=index, shard_transition=shard_transition, on_time=on_time
|
||||||
|
)
|
||||||
|
|
||||||
beacon_committee = spec.get_beacon_committee(
|
beacon_committee = spec.get_beacon_committee(
|
||||||
state,
|
state,
|
||||||
|
@ -138,7 +178,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe
|
||||||
if signed:
|
if signed:
|
||||||
sign_attestation(spec, state, attestation)
|
sign_attestation(spec, state, attestation)
|
||||||
|
|
||||||
if spec.fork == 'phase1' and on_time:
|
if spec.fork == PHASE1 and on_time:
|
||||||
attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed)
|
attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed)
|
||||||
|
|
||||||
return attestation
|
return attestation
|
||||||
|
@ -210,7 +250,7 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index
|
||||||
|
|
||||||
|
|
||||||
def sign_attestation(spec, state, attestation):
|
def sign_attestation(spec, state, attestation):
|
||||||
if spec.fork == 'phase1' and any(attestation.custody_bits_blocks):
|
if spec.fork == PHASE1 and any(attestation.custody_bits_blocks):
|
||||||
sign_on_time_attestation(spec, state, attestation)
|
sign_on_time_attestation(spec, state, attestation)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -274,7 +314,21 @@ def next_epoch_with_attestations(spec,
|
||||||
spec, post_state, slot_to_attest, index=index, signed=True, on_time=False)
|
spec, post_state, slot_to_attest, index=index, signed=True, on_time=False)
|
||||||
block.body.attestations.append(prev_attestation)
|
block.body.attestations.append(prev_attestation)
|
||||||
|
|
||||||
|
if spec.fork == PHASE1:
|
||||||
|
fill_block_shard_transitions_by_attestations(spec, post_state, block)
|
||||||
|
|
||||||
signed_block = state_transition_and_sign_block(spec, post_state, block)
|
signed_block = state_transition_and_sign_block(spec, post_state, block)
|
||||||
signed_blocks.append(signed_block)
|
signed_blocks.append(signed_block)
|
||||||
|
|
||||||
return state, signed_blocks, post_state
|
return state, signed_blocks, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def fill_block_shard_transitions_by_attestations(spec, state, block):
|
||||||
|
block.body.shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||||
|
for attestation in block.body.attestations:
|
||||||
|
shard = spec.get_shard(state, attestation)
|
||||||
|
if attestation.data.slot == state.slot:
|
||||||
|
temp_state = state.copy()
|
||||||
|
transition_to(spec, temp_state, slot=block.slot)
|
||||||
|
shard_transition = spec.get_shard_transition(temp_state, shard, [])
|
||||||
|
block.body.shard_transitions[shard] = shard_transition
|
||||||
|
|
|
@ -71,24 +71,31 @@ def build_empty_block(spec, state, slot=None):
|
||||||
"""
|
"""
|
||||||
if slot is None:
|
if slot is None:
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
if slot < state.slot:
|
|
||||||
raise Exception("build_empty_block cannot build blocks for past slots")
|
|
||||||
if slot > state.slot:
|
|
||||||
# transition forward in copied state to grab relevant data from state
|
|
||||||
state = state.copy()
|
|
||||||
spec.process_slots(state, slot)
|
|
||||||
|
|
||||||
|
state, parent_block_root = get_state_and_beacon_parent_root_at_slot(spec, state, slot)
|
||||||
empty_block = spec.BeaconBlock()
|
empty_block = spec.BeaconBlock()
|
||||||
empty_block.slot = slot
|
empty_block.slot = slot
|
||||||
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
|
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
|
||||||
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
|
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
|
||||||
previous_block_header = state.latest_block_header.copy()
|
empty_block.parent_root = parent_block_root
|
||||||
if previous_block_header.state_root == spec.Root():
|
|
||||||
previous_block_header.state_root = hash_tree_root(state)
|
|
||||||
empty_block.parent_root = hash_tree_root(previous_block_header)
|
|
||||||
apply_randao_reveal(spec, state, empty_block)
|
apply_randao_reveal(spec, state, empty_block)
|
||||||
return empty_block
|
return empty_block
|
||||||
|
|
||||||
|
|
||||||
def build_empty_block_for_next_slot(spec, state):
|
def build_empty_block_for_next_slot(spec, state):
|
||||||
return build_empty_block(spec, state, state.slot + 1)
|
return build_empty_block(spec, state, state.slot + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_state_and_beacon_parent_root_at_slot(spec, state, slot):
|
||||||
|
if slot < state.slot:
|
||||||
|
raise Exception("Cannot build blocks for past slots")
|
||||||
|
if slot > state.slot:
|
||||||
|
# transition forward in copied state to grab relevant data from state
|
||||||
|
state = state.copy()
|
||||||
|
spec.process_slots(state, slot)
|
||||||
|
|
||||||
|
previous_block_header = state.latest_block_header.copy()
|
||||||
|
if previous_block_header.state_root == spec.Root():
|
||||||
|
previous_block_header.state_root = hash_tree_root(state)
|
||||||
|
beacon_parent_root = hash_tree_root(previous_block_header)
|
||||||
|
return state, beacon_parent_root
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
from eth2spec.test.context import expect_assertion_error
|
||||||
|
|
||||||
|
|
||||||
|
def run_crosslinks_processing(spec, state, shard_transitions, attestations, valid=True):
|
||||||
|
"""
|
||||||
|
Run ``process_attestation``, yielding:
|
||||||
|
- pre-state ('pre')
|
||||||
|
- shard_transitions ('shard_transitions')
|
||||||
|
- attestations ('attestations')
|
||||||
|
- post-state ('post').
|
||||||
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
|
"""
|
||||||
|
# yield pre-state
|
||||||
|
yield 'pre', state
|
||||||
|
yield 'shard_transitions', shard_transitions
|
||||||
|
yield 'attestations', attestations
|
||||||
|
|
||||||
|
# If the attestation is invalid, processing is aborted, and there is no post-state.
|
||||||
|
if not valid:
|
||||||
|
expect_assertion_error(lambda: spec.process_crosslinks(state, shard_transitions, attestations))
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
# process crosslinks
|
||||||
|
spec.process_crosslinks(state, shard_transitions, attestations)
|
||||||
|
|
||||||
|
# yield post-state
|
||||||
|
yield 'post', state
|
|
@ -1,63 +0,0 @@
|
||||||
from eth2spec.utils.ssz.ssz_typing import Bitlist
|
|
||||||
from eth2spec.utils import bls
|
|
||||||
|
|
||||||
from eth2spec.test.helpers.keys import privkeys
|
|
||||||
import eth2spec.test.helpers.attestations as phase0_attestations
|
|
||||||
|
|
||||||
|
|
||||||
def get_valid_on_time_attestation(spec, state, index=None, signed=False):
|
|
||||||
'''
|
|
||||||
Construct on-time attestation for next slot
|
|
||||||
'''
|
|
||||||
if index is None:
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False)
|
|
||||||
shard = spec.get_shard(state, attestation)
|
|
||||||
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
|
|
||||||
|
|
||||||
for _ in offset_slots:
|
|
||||||
attestation.custody_bits_blocks.append(
|
|
||||||
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
|
||||||
)
|
|
||||||
|
|
||||||
if signed:
|
|
||||||
sign_attestation(spec, state, attestation)
|
|
||||||
|
|
||||||
return attestation
|
|
||||||
|
|
||||||
|
|
||||||
def sign_attestation(spec, state, attestation):
|
|
||||||
if not any(attestation.custody_bits_blocks):
|
|
||||||
phase0_attestations.sign_attestation(spec, state, attestation)
|
|
||||||
return
|
|
||||||
|
|
||||||
committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index)
|
|
||||||
signatures = []
|
|
||||||
for block_index, custody_bits in enumerate(attestation.custody_bits_blocks):
|
|
||||||
for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits):
|
|
||||||
if not abit:
|
|
||||||
continue
|
|
||||||
signatures.append(get_attestation_custody_signature(
|
|
||||||
spec,
|
|
||||||
state,
|
|
||||||
attestation.data,
|
|
||||||
block_index,
|
|
||||||
cbit,
|
|
||||||
privkeys[participant]
|
|
||||||
))
|
|
||||||
|
|
||||||
attestation.signature = bls.Aggregate(signatures)
|
|
||||||
|
|
||||||
|
|
||||||
def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey):
|
|
||||||
domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
|
|
||||||
signing_root = spec.compute_signing_root(
|
|
||||||
spec.AttestationCustodyBitWrapper(
|
|
||||||
attestation_data.hash_tree_root(),
|
|
||||||
block_index,
|
|
||||||
bit,
|
|
||||||
),
|
|
||||||
domain,
|
|
||||||
)
|
|
||||||
return bls.Sign(privkey, signing_root)
|
|
|
@ -1,71 +0,0 @@
|
||||||
from eth2spec.test.helpers.keys import privkeys
|
|
||||||
from eth2spec.utils import bls
|
|
||||||
from eth2spec.utils.bls import only_with_bls
|
|
||||||
from eth2spec.utils.ssz.ssz_impl import (
|
|
||||||
hash_tree_root,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .attestations import (
|
|
||||||
sign_shard_attestation,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@only_with_bls()
|
|
||||||
def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None):
|
|
||||||
if proposer_index is None:
|
|
||||||
proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
|
|
||||||
|
|
||||||
privkey = privkeys[proposer_index]
|
|
||||||
domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSER, spec.compute_epoch_of_shard_slot(block.slot))
|
|
||||||
signing_root = spec.compute_signing_root(block, domain)
|
|
||||||
block.signature = bls.Sign(privkey, signing_root)
|
|
||||||
|
|
||||||
|
|
||||||
def build_empty_shard_block(spec,
|
|
||||||
beacon_state,
|
|
||||||
shard_state,
|
|
||||||
slot,
|
|
||||||
signed=False,
|
|
||||||
full_attestation=False):
|
|
||||||
if slot is None:
|
|
||||||
slot = shard_state.slot
|
|
||||||
|
|
||||||
previous_beacon_header = beacon_state.latest_block_header.copy()
|
|
||||||
if previous_beacon_header.state_root == spec.Bytes32():
|
|
||||||
previous_beacon_header.state_root = beacon_state.hash_tree_root()
|
|
||||||
beacon_block_root = hash_tree_root(previous_beacon_header)
|
|
||||||
|
|
||||||
previous_block_header = shard_state.latest_block_header.copy()
|
|
||||||
if previous_block_header.state_root == spec.Bytes32():
|
|
||||||
previous_block_header.state_root = shard_state.hash_tree_root()
|
|
||||||
parent_root = hash_tree_root(previous_block_header)
|
|
||||||
|
|
||||||
block = spec.ShardBlock(
|
|
||||||
shard=shard_state.shard,
|
|
||||||
slot=slot,
|
|
||||||
beacon_block_root=beacon_block_root,
|
|
||||||
parent_root=parent_root,
|
|
||||||
block_size_sum=shard_state.block_size_sum + spec.SHARD_HEADER_SIZE,
|
|
||||||
)
|
|
||||||
|
|
||||||
if full_attestation:
|
|
||||||
shard_committee = spec.get_shard_committee(beacon_state, shard_state.shard, block.slot)
|
|
||||||
block.aggregation_bits = list(
|
|
||||||
(True,) * len(shard_committee) +
|
|
||||||
(False,) * (spec.MAX_PERIOD_COMMITTEE_SIZE * 2 - len(shard_committee))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
shard_committee = []
|
|
||||||
|
|
||||||
block.attestations = sign_shard_attestation(
|
|
||||||
spec,
|
|
||||||
beacon_state,
|
|
||||||
shard_state,
|
|
||||||
block,
|
|
||||||
participants=shard_committee,
|
|
||||||
)
|
|
||||||
|
|
||||||
if signed:
|
|
||||||
sign_shard_block(spec, beacon_state, shard_state, block)
|
|
||||||
|
|
||||||
return block
|
|
|
@ -1,18 +0,0 @@
|
||||||
from eth2spec.test.helpers.phase1.shard_block import sign_shard_block
|
|
||||||
|
|
||||||
|
|
||||||
def configure_shard_state(spec, beacon_state, shard=0):
|
|
||||||
beacon_state.slot = spec.Slot(spec.SHARD_GENESIS_EPOCH * spec.SLOTS_PER_EPOCH)
|
|
||||||
shard_state = spec.get_genesis_shard_state(spec.Shard(shard))
|
|
||||||
shard_state.slot = spec.ShardSlot(spec.SHARD_GENESIS_EPOCH * spec.SHARD_SLOTS_PER_EPOCH)
|
|
||||||
return beacon_state, shard_state
|
|
||||||
|
|
||||||
|
|
||||||
def shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block):
|
|
||||||
"""
|
|
||||||
Shard state transition via the provided ``block``
|
|
||||||
then package the block with the state root and signature.
|
|
||||||
"""
|
|
||||||
spec.shard_state_transition(beacon_state, shard_state, block)
|
|
||||||
block.state_root = shard_state.hash_tree_root()
|
|
||||||
sign_shard_block(spec, beacon_state, shard_state, block)
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
|
||||||
|
from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot
|
||||||
|
from eth2spec.test.helpers.state import transition_to
|
||||||
|
from eth2spec.test.helpers.keys import privkeys
|
||||||
|
from eth2spec.utils import bls
|
||||||
|
from eth2spec.utils.bls import only_with_bls
|
||||||
|
|
||||||
|
|
||||||
|
@only_with_bls()
|
||||||
|
def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None):
|
||||||
|
slot = block.message.slot
|
||||||
|
if proposer_index is None:
|
||||||
|
proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
|
||||||
|
|
||||||
|
privkey = privkeys[proposer_index]
|
||||||
|
domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSAL, spec.compute_epoch_at_slot(slot))
|
||||||
|
signing_root = spec.compute_signing_root(block.message, domain)
|
||||||
|
block.signature = bls.Sign(privkey, signing_root)
|
||||||
|
|
||||||
|
|
||||||
|
def build_shard_block(spec,
|
||||||
|
beacon_state,
|
||||||
|
shard,
|
||||||
|
slot=None,
|
||||||
|
body=None,
|
||||||
|
signed=False):
|
||||||
|
shard_state = beacon_state.shard_states[shard]
|
||||||
|
if slot is None:
|
||||||
|
slot = shard_state.slot + 1
|
||||||
|
|
||||||
|
if body is None:
|
||||||
|
body = b'\x56' * 128
|
||||||
|
|
||||||
|
proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
|
||||||
|
beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot)
|
||||||
|
|
||||||
|
block = spec.ShardBlock(
|
||||||
|
shard_parent_root=shard_state.latest_block_root,
|
||||||
|
beacon_parent_root=beacon_parent_root,
|
||||||
|
slot=slot,
|
||||||
|
proposer_index=proposer_index,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
signed_block = spec.SignedShardBlock(
|
||||||
|
message=block,
|
||||||
|
)
|
||||||
|
|
||||||
|
if signed:
|
||||||
|
sign_shard_block(spec, beacon_state, shard, signed_block, proposer_index=proposer_index)
|
||||||
|
|
||||||
|
return signed_block
|
||||||
|
|
||||||
|
|
||||||
|
def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot):
|
||||||
|
temp_state = state.copy()
|
||||||
|
transition_to(spec, temp_state, on_time_slot)
|
||||||
|
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||||
|
for shard, blocks in shard_blocks.items():
|
||||||
|
offset_slots = spec.get_offset_slots(temp_state, shard)
|
||||||
|
len_offset_slots = len(offset_slots)
|
||||||
|
assert len_offset_slots == on_time_slot - state.shard_states[shard].slot - 1
|
||||||
|
shard_transition = spec.get_shard_transition(temp_state, shard, blocks)
|
||||||
|
if len(blocks) > 0:
|
||||||
|
shard_block_root = blocks[-1].message.hash_tree_root()
|
||||||
|
assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root
|
||||||
|
assert shard_transition.shard_states[len_offset_slots - 1].slot == offset_slots[-1]
|
||||||
|
shard_transitions[shard] = shard_transition
|
||||||
|
|
||||||
|
return shard_transitions
|
||||||
|
|
||||||
|
|
||||||
|
def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition=None):
|
||||||
|
temp_state = state.copy()
|
||||||
|
transition_to(spec, temp_state, on_time_slot - 1)
|
||||||
|
attestation = get_valid_on_time_attestation(
|
||||||
|
spec,
|
||||||
|
temp_state,
|
||||||
|
index=index,
|
||||||
|
shard_transition=shard_transition,
|
||||||
|
signed=True,
|
||||||
|
)
|
||||||
|
assert attestation.data.slot == temp_state.slot
|
||||||
|
if shard_transition is not None:
|
||||||
|
assert attestation.data.shard_transition_root == shard_transition.hash_tree_root()
|
||||||
|
return attestation
|
|
@ -30,6 +30,16 @@ def transition_to(spec, state, slot):
|
||||||
assert state.slot == slot
|
assert state.slot == slot
|
||||||
|
|
||||||
|
|
||||||
|
def transition_to_valid_shard_slot(spec, state):
|
||||||
|
"""
|
||||||
|
Transition to slot `spec.PHASE_1_GENESIS_SLOT + 1` and fork at `spec.PHASE_1_GENESIS_SLOT`.
|
||||||
|
"""
|
||||||
|
transition_to(spec, state, spec.PHASE_1_GENESIS_SLOT)
|
||||||
|
state = spec.upgrade_to_phase1(state) # `upgrade_to_phase1` is a pure function
|
||||||
|
next_slot(spec, state)
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
def next_epoch(spec, state):
|
def next_epoch(spec, state):
|
||||||
"""
|
"""
|
||||||
Transition to the start slot of the next epoch
|
Transition to the start slot of the next epoch
|
||||||
|
|
|
@ -6,10 +6,13 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_e
|
||||||
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
||||||
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants
|
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants
|
||||||
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations
|
||||||
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
||||||
|
|
||||||
from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases
|
from eth2spec.test.context import (
|
||||||
|
spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases,
|
||||||
|
PHASE1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
|
@ -492,12 +495,14 @@ def test_attestation(spec, state):
|
||||||
|
|
||||||
yield 'pre', state
|
yield 'pre', state
|
||||||
|
|
||||||
attestation = get_valid_attestation(spec, state, signed=True)
|
attestation = get_valid_attestation(spec, state, signed=True, on_time=True)
|
||||||
|
|
||||||
# Add to state via block transition
|
# Add to state via block transition
|
||||||
pre_current_attestations_len = len(state.current_epoch_attestations)
|
pre_current_attestations_len = len(state.current_epoch_attestations)
|
||||||
attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
attestation_block.body.attestations.append(attestation)
|
attestation_block.body.attestations.append(attestation)
|
||||||
|
if spec.fork == PHASE1:
|
||||||
|
fill_block_shard_transitions_by_attestations(spec, state, attestation_block)
|
||||||
signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
|
signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block)
|
||||||
|
|
||||||
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
|
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
|
|
@ -0,0 +1,73 @@
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
PHASE0,
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
always_bls,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.crosslinks import run_crosslinks_processing
|
||||||
|
from eth2spec.test.helpers.shard_block import (
|
||||||
|
build_attestation_with_shard_transition,
|
||||||
|
build_shard_block,
|
||||||
|
build_shard_transitions_till_slot,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
|
||||||
|
|
||||||
|
|
||||||
|
def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True):
|
||||||
|
state = transition_to_valid_shard_slot(spec, state)
|
||||||
|
# At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1`
|
||||||
|
slot_x = state.slot
|
||||||
|
committee_index = spec.CommitteeIndex(0)
|
||||||
|
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||||
|
assert state.shard_states[shard].slot == slot_x - 1
|
||||||
|
|
||||||
|
# Create SignedShardBlock
|
||||||
|
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||||
|
shard_block = build_shard_block(spec, state, shard, body=body, signed=True)
|
||||||
|
shard_blocks = [shard_block]
|
||||||
|
# Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot`
|
||||||
|
shard_transitions = build_shard_transitions_till_slot(
|
||||||
|
spec,
|
||||||
|
state,
|
||||||
|
shard_blocks={shard: shard_blocks},
|
||||||
|
on_time_slot=state.slot + target_len_offset_slot,
|
||||||
|
)
|
||||||
|
shard_transition = shard_transitions[shard]
|
||||||
|
# Create an attestation that would be included at beacon block `state.slot + target_len_offset_slot`
|
||||||
|
attestation = build_attestation_with_shard_transition(
|
||||||
|
spec,
|
||||||
|
state,
|
||||||
|
index=committee_index,
|
||||||
|
on_time_slot=state.slot + target_len_offset_slot,
|
||||||
|
shard_transition=shard_transition,
|
||||||
|
)
|
||||||
|
pre_gasprice = state.shard_states[shard].gasprice
|
||||||
|
|
||||||
|
transition_to(spec, state, state.slot + target_len_offset_slot)
|
||||||
|
pre_shard_state = state.shard_states[shard]
|
||||||
|
|
||||||
|
yield from run_crosslinks_processing(spec, state, shard_transitions, [attestation], valid=valid)
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
# After state transition,
|
||||||
|
assert state.slot == slot_x + target_len_offset_slot
|
||||||
|
shard_state = state.shard_states[shard]
|
||||||
|
assert shard_state != pre_shard_state
|
||||||
|
assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
|
||||||
|
|
||||||
|
if target_len_offset_slot == 1:
|
||||||
|
assert shard_state.gasprice > pre_gasprice
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except([PHASE0])
|
||||||
|
@spec_state_test
|
||||||
|
@always_bls
|
||||||
|
def test_basic_crosslinks(spec, state):
|
||||||
|
yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except([PHASE0])
|
||||||
|
@spec_state_test
|
||||||
|
@always_bls
|
||||||
|
def test_multiple_offset_slots(spec, state):
|
||||||
|
yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=3, valid=True)
|
|
@ -0,0 +1,107 @@
|
||||||
|
from typing import Dict, Sequence
|
||||||
|
|
||||||
|
from eth2spec.test.context import (
|
||||||
|
PHASE0,
|
||||||
|
with_all_phases_except,
|
||||||
|
spec_state_test,
|
||||||
|
always_bls,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.block import build_empty_block
|
||||||
|
from eth2spec.test.helpers.shard_block import (
|
||||||
|
build_attestation_with_shard_transition,
|
||||||
|
build_shard_block,
|
||||||
|
build_shard_transitions_till_slot,
|
||||||
|
)
|
||||||
|
from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot
|
||||||
|
|
||||||
|
|
||||||
|
def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index, valid=True):
|
||||||
|
shard_transitions = build_shard_transitions_till_slot(
|
||||||
|
spec, state, shard_blocks, on_time_slot=state.slot + target_len_offset_slot
|
||||||
|
)
|
||||||
|
attestations = [
|
||||||
|
build_attestation_with_shard_transition(
|
||||||
|
spec,
|
||||||
|
state,
|
||||||
|
on_time_slot=state.slot + target_len_offset_slot,
|
||||||
|
index=committee_index,
|
||||||
|
shard_transition=shard_transitions[shard],
|
||||||
|
)
|
||||||
|
for shard in shard_blocks.keys()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Propose beacon block at slot `x + 1`
|
||||||
|
beacon_block = build_empty_block(spec, state, slot=state.slot + target_len_offset_slot)
|
||||||
|
beacon_block.body.attestations = attestations
|
||||||
|
beacon_block.body.shard_transitions = shard_transitions
|
||||||
|
|
||||||
|
pre_shard_states = state.shard_states.copy()
|
||||||
|
yield 'pre', state.copy()
|
||||||
|
yield 'block', beacon_block
|
||||||
|
state_transition_and_sign_block(spec, state, beacon_block)
|
||||||
|
if valid:
|
||||||
|
yield 'post', state
|
||||||
|
else:
|
||||||
|
yield 'post', None
|
||||||
|
return
|
||||||
|
|
||||||
|
for shard in range(spec.get_active_shard_count(state)):
|
||||||
|
post_shard_state = state.shard_states[shard]
|
||||||
|
if shard in shard_blocks:
|
||||||
|
# Shard state has been changed to state_transition result
|
||||||
|
assert post_shard_state == shard_transitions[shard].shard_states[
|
||||||
|
len(shard_transitions[shard].shard_states) - 1
|
||||||
|
]
|
||||||
|
assert beacon_block.slot == shard_transitions[shard].shard_states[0].slot + target_len_offset_slot
|
||||||
|
assert post_shard_state.slot == state.slot - 1
|
||||||
|
if len(shard_blocks[shard]) == 0:
|
||||||
|
# `latest_block_root` is the same
|
||||||
|
assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except([PHASE0])
|
||||||
|
@spec_state_test
|
||||||
|
@always_bls
|
||||||
|
def test_process_beacon_block_with_normal_shard_transition(spec, state):
|
||||||
|
state = transition_to_valid_shard_slot(spec, state)
|
||||||
|
|
||||||
|
target_len_offset_slot = 1
|
||||||
|
committee_index = spec.CommitteeIndex(0)
|
||||||
|
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||||
|
assert state.shard_states[shard].slot == state.slot - 1
|
||||||
|
|
||||||
|
pre_gasprice = state.shard_states[shard].gasprice
|
||||||
|
|
||||||
|
# Create SignedShardBlock at slot `shard_state.slot + 1`
|
||||||
|
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||||
|
shard_block = build_shard_block(spec, state, shard, body=body, signed=True)
|
||||||
|
shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
|
||||||
|
|
||||||
|
yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index)
|
||||||
|
|
||||||
|
shard_state = state.shard_states[shard]
|
||||||
|
|
||||||
|
if target_len_offset_slot == 1 and len(shard_blocks) > 0:
|
||||||
|
assert shard_state.gasprice > pre_gasprice
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases_except([PHASE0])
|
||||||
|
@spec_state_test
|
||||||
|
@always_bls
|
||||||
|
def test_process_beacon_block_with_empty_proposal_transition(spec, state):
|
||||||
|
state = transition_to_valid_shard_slot(spec, state)
|
||||||
|
|
||||||
|
target_len_offset_slot = 1
|
||||||
|
committee_index = spec.CommitteeIndex(0)
|
||||||
|
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||||
|
assert state.shard_states[shard].slot == state.slot - 1
|
||||||
|
|
||||||
|
# No new shard block
|
||||||
|
shard_blocks = {}
|
||||||
|
|
||||||
|
pre_gasprice = state.shard_states[shard].gasprice
|
||||||
|
|
||||||
|
yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index)
|
||||||
|
|
||||||
|
if target_len_offset_slot == 1 and len(shard_blocks) > 0:
|
||||||
|
assert state.shard_states[shard].gasprice > pre_gasprice
|
|
@ -5,7 +5,7 @@ from gen_base import gen_runner, gen_typing
|
||||||
from gen_from_tests.gen import generate_from_tests
|
from gen_from_tests.gen import generate_from_tests
|
||||||
|
|
||||||
from eth2spec.test.context import PHASE0
|
from eth2spec.test.context import PHASE0
|
||||||
from eth2spec.test.sanity import test_blocks, test_slots
|
from eth2spec.test.phase_0.sanity import test_blocks, test_slots
|
||||||
from eth2spec.config import config_util
|
from eth2spec.config import config_util
|
||||||
from eth2spec.phase0 import spec as spec_phase0
|
from eth2spec.phase0 import spec as spec_phase0
|
||||||
from eth2spec.phase1 import spec as spec_phase1
|
from eth2spec.phase1 import spec as spec_phase1
|
||||||
|
|
Loading…
Reference in New Issue