Merge branch 'dev' into dankrad-custody-256bit
This commit is contained in:
commit
41cfa7fdf6
|
@ -76,8 +76,8 @@ BLS_WITHDRAWAL_PREFIX: 0x00
|
|||
|
||||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
# 86400 seconds (1 day)
|
||||
MIN_GENESIS_DELAY: 86400
|
||||
# 172800 seconds (2 days)
|
||||
GENESIS_DELAY: 172800
|
||||
# 12 seconds
|
||||
SECONDS_PER_SLOT: 12
|
||||
# 2**0 (= 1) slots 12 seconds
|
||||
|
|
|
@ -77,7 +77,7 @@ BLS_WITHDRAWAL_PREFIX: 0x00
|
|||
# Time parameters
|
||||
# ---------------------------------------------------------------
|
||||
# [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis
|
||||
MIN_GENESIS_DELAY: 300
|
||||
GENESIS_DELAY: 300
|
||||
# [customized] Faster for testing purposes
|
||||
SECONDS_PER_SLOT: 6
|
||||
# 2**0 (= 1) slots 6 seconds
|
||||
|
@ -165,7 +165,7 @@ PHASE_1_FORK_VERSION: 0x01000001
|
|||
# [customized] for testing
|
||||
PHASE_1_GENESIS_SLOT: 8
|
||||
# [customized] reduced for testing
|
||||
INITIAL_ACTIVE_SHARDS: 4
|
||||
INITIAL_ACTIVE_SHARDS: 2
|
||||
|
||||
|
||||
# Phase 1: General
|
||||
|
|
16
setup.py
16
setup.py
|
@ -140,7 +140,7 @@ SUNDRY_CONSTANTS_FUNCTIONS = '''
|
|||
def ceillog2(x: uint64) -> int:
|
||||
return (x - 1).bit_length()
|
||||
'''
|
||||
SUNDRY_FUNCTIONS = '''
|
||||
PHASE0_SUNDRY_FUNCTIONS = '''
|
||||
# Monkey patch hash cache
|
||||
_hash = hash
|
||||
hash_cache: Dict[bytes, Bytes32] = {}
|
||||
|
@ -220,6 +220,13 @@ get_attesting_indices = cache_this(
|
|||
_get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
|
||||
|
||||
|
||||
PHASE1_SUNDRY_FUNCTIONS = '''
|
||||
_get_start_shard = get_start_shard
|
||||
get_start_shard = cache_this(
|
||||
lambda state, slot: (state.validators.hash_tree_root(), slot),
|
||||
_get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)'''
|
||||
|
||||
|
||||
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
|
||||
"""
|
||||
Given all the objects that constitute a spec, combine them into a single pyfile.
|
||||
|
@ -250,9 +257,11 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
|
|||
+ '\n\n' + CONFIG_LOADER
|
||||
+ '\n\n' + ssz_objects_instantiation_spec
|
||||
+ '\n\n' + functions_spec
|
||||
+ '\n' + SUNDRY_FUNCTIONS
|
||||
+ '\n'
|
||||
+ '\n' + PHASE0_SUNDRY_FUNCTIONS
|
||||
)
|
||||
if fork == 'phase1':
|
||||
spec += '\n' + PHASE1_SUNDRY_FUNCTIONS
|
||||
spec += '\n'
|
||||
return spec
|
||||
|
||||
|
||||
|
@ -385,6 +394,7 @@ class PySpecCommand(Command):
|
|||
specs/phase1/shard-transition.md
|
||||
specs/phase1/fork-choice.md
|
||||
specs/phase1/phase1-fork.md
|
||||
specs/phase1/shard-fork-choice.md
|
||||
"""
|
||||
else:
|
||||
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)
|
||||
|
|
|
@ -218,7 +218,7 @@ The following values are (non-configurable) constants used throughout the specif
|
|||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `MIN_GENESIS_DELAY` | `86400` | seconds | 1 day |
|
||||
| `GENESIS_DELAY` | `172800` | seconds | 2 days |
|
||||
| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds |
|
||||
| `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | 14 seconds |
|
||||
| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds |
|
||||
|
@ -1137,7 +1137,7 @@ Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 b
|
|||
- `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash`
|
||||
- `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash`
|
||||
|
||||
Eth1 blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `MIN_GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case.
|
||||
Eth1 blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case.
|
||||
|
||||
```python
|
||||
def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
|
||||
|
@ -1149,7 +1149,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
|
|||
epoch=GENESIS_EPOCH,
|
||||
)
|
||||
state = BeaconState(
|
||||
genesis_time=eth1_timestamp - eth1_timestamp % MIN_GENESIS_DELAY + 2 * MIN_GENESIS_DELAY,
|
||||
genesis_time=eth1_timestamp + GENESIS_DELAY,
|
||||
fork=fork,
|
||||
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
|
||||
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
|
||||
|
|
|
@ -285,7 +285,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None:
|
|||
# Attestations must not be for blocks in the future. If not, the attestation should not be considered
|
||||
assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot
|
||||
|
||||
# FFG and LMD vote must be consistent with each other
|
||||
# LMD vote must be consistent with FFG vote target
|
||||
target_slot = compute_start_slot_at_epoch(target.epoch)
|
||||
assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot)
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ This section outlines constants that are used in this spec.
|
|||
| Name | Value | Description |
|
||||
|---|---|---|
|
||||
| `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. |
|
||||
| `MAX_REQUEST_BLOCKS` | `2**10` (= 1024) | Maximum number of blocks in a single request |
|
||||
| `MAX_CHUNK_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. |
|
||||
| `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). |
|
||||
| `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. |
|
||||
|
@ -262,7 +263,7 @@ Additional global topics are used to propagate lower frequency validator message
|
|||
- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`.
|
||||
- _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation.
|
||||
- `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. The following validations MUST pass before forwarding the `proposer_slashing` on to the network
|
||||
- _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.index`.
|
||||
- _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`.
|
||||
- _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation.
|
||||
- `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network.
|
||||
- _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`).
|
||||
|
@ -274,7 +275,7 @@ Additional global topics are used to propagate lower frequency validator message
|
|||
Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are:
|
||||
|
||||
- `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet.
|
||||
- _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation) == subnet_id`).
|
||||
- _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index) == subnet_id`).
|
||||
- _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot).
|
||||
- _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1`).
|
||||
- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index.
|
||||
|
@ -285,7 +286,7 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio
|
|||
|
||||
Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. `beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1.
|
||||
|
||||
Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation)}` as `Attestation`s.
|
||||
Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index)}` as `Attestation`s.
|
||||
|
||||
Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s.
|
||||
|
||||
|
@ -391,11 +392,11 @@ The `ErrorMessage` schema is:
|
|||
|
||||
```
|
||||
(
|
||||
error_message: String
|
||||
error_message: List[byte, 256]
|
||||
)
|
||||
```
|
||||
|
||||
*Note*: The String type is encoded as UTF-8 bytes without NULL terminator when SSZ-encoded. As the `ErrorMessage` is not an SSZ-container, only the UTF-8 bytes will be sent when SSZ-encoded.
|
||||
*Note*: By convention, the `error_message` is a sequence of bytes that MAY be interpreted as a UTF-8 string (for debugging purposes). Clients MUST treat as valid any byte sequences.
|
||||
|
||||
### Encoding strategies
|
||||
|
||||
|
@ -443,9 +444,9 @@ In case of an invalid input (header or payload), a reader MUST:
|
|||
|
||||
All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container.
|
||||
|
||||
Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their
|
||||
Responses that are SSZ-lists (for example `List[SignedBeaconBlock, ...]`) send their
|
||||
constituents individually as `response_chunk`s. For example, the
|
||||
`[]SignedBeaconBlock` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload.
|
||||
`List[SignedBeaconBlock, ...]` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload.
|
||||
|
||||
### Messages
|
||||
|
||||
|
@ -468,9 +469,9 @@ The fields are, as seen by the client at the time of sending the message:
|
|||
- `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where
|
||||
- `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync)
|
||||
- `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root`
|
||||
- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block.
|
||||
- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint).
|
||||
- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block.
|
||||
- `head_root`: The hash_tree_root root of the current head block.
|
||||
- `head_root`: The `hash_tree_root` root of the current head block (`BeaconBlock`).
|
||||
- `head_slot`: The slot of the block corresponding to the `head_root`.
|
||||
|
||||
The dialing client MUST send a `Status` request upon connection.
|
||||
|
@ -528,7 +529,7 @@ Request Content:
|
|||
Response Content:
|
||||
```
|
||||
(
|
||||
[]SignedBeaconBlock
|
||||
List[SignedBeaconBlock, MAX_REQUEST_BLOCKS]
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -545,7 +546,7 @@ The response MUST consist of zero or more `response_chunk`. Each _successful_ `r
|
|||
|
||||
Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period and MUST support serving requests of blocks up to their own `head_block_root`.
|
||||
|
||||
Clients MUST respond with at least the first block that exists in the range, if they have it.
|
||||
Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks.
|
||||
|
||||
The following blocks, where they exist, MUST be send in consecutive order.
|
||||
|
||||
|
@ -568,7 +569,7 @@ Request Content:
|
|||
|
||||
```
|
||||
(
|
||||
[]Root
|
||||
List[Root, MAX_REQUEST_BLOCKS]
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -576,12 +577,14 @@ Response Content:
|
|||
|
||||
```
|
||||
(
|
||||
[]SignedBeaconBlock
|
||||
List[SignedBeaconBlock, MAX_REQUEST_BLOCKS]
|
||||
)
|
||||
```
|
||||
|
||||
Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). The response is a list of `SignedBeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks.
|
||||
|
||||
No more than `MAX_REQUEST_BLOCKS` may be requested at a time.
|
||||
|
||||
`BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown).
|
||||
|
||||
The request MUST be encoded as an SSZ-field.
|
||||
|
@ -1052,7 +1055,7 @@ discv5 uses ENRs and we will presumably need to:
|
|||
|
||||
Although client software might very well be running locally prior to the solidification of the eth2 genesis state and block, clients cannot form valid ENRs prior to this point. ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. Once genesis data is known, we can then form ENRs and safely find peers.
|
||||
|
||||
When using an eth1 deposit contract for deposits, `fork_digest` will be known at least `MIN_GENESIS_DELAY` (24 hours in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis.
|
||||
When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis.
|
||||
|
||||
## Compression/Encoding
|
||||
|
||||
|
|
|
@ -199,8 +199,8 @@ The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahe
|
|||
|
||||
Specifically a validator should:
|
||||
* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
|
||||
* Find peers of the pubsub topic `committee_index{committee_index % ATTESTATION_SUBNET_COUNT}_beacon_attestation`.
|
||||
* If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][committee_index % ATTESTATION_SUBNET_COUNT] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field.
|
||||
* Find peers of the pubsub topic `beacon_attestation_{compute_subnet_for_attestation(state, slot, committee_index)}`.
|
||||
* If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][compute_subnet_for_attestation(state, slot, committee_index)] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field.
|
||||
* If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`), then subscribe to the topic.
|
||||
|
||||
*Note*: If the validator is _not_ assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to _subscribe_ and listen to all messages on the topic.
|
||||
|
@ -425,18 +425,18 @@ def get_attestation_signature(state: BeaconState, attestation_data: AttestationD
|
|||
|
||||
#### Broadcast attestation
|
||||
|
||||
Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation)}` pubsub topic.
|
||||
Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.committee_index)}` pubsub topic.
|
||||
|
||||
```python
|
||||
def compute_subnet_for_attestation(state: BeaconState, attestation: Attestation) -> uint64:
|
||||
def compute_subnet_for_attestation(state: BeaconState, slot: Slot, committee_index: CommitteeIndex) -> uint64:
|
||||
"""
|
||||
Compute the correct subnet for an attestation for Phase 0.
|
||||
Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet.
|
||||
"""
|
||||
slots_since_epoch_start = attestation.data.slot % SLOTS_PER_EPOCH
|
||||
committees_since_epoch_start = get_committee_count_at_slot(state, attestation.data.slot) * slots_since_epoch_start
|
||||
slots_since_epoch_start = slot % SLOTS_PER_EPOCH
|
||||
committees_since_epoch_start = get_committee_count_at_slot(state, slot) * slots_since_epoch_start
|
||||
|
||||
return (committees_since_epoch_start + attestation.data.index) % ATTESTATION_SUBNET_COUNT
|
||||
return (committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT
|
||||
```
|
||||
|
||||
### Attestation aggregation
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
- [`get_light_client_committee`](#get_light_client_committee)
|
||||
- [`get_shard_proposer_index`](#get_shard_proposer_index)
|
||||
- [`get_indexed_attestation`](#get_indexed_attestation)
|
||||
- [`get_committee_count_delta`](#get_committee_count_delta)
|
||||
- [`get_start_shard`](#get_start_shard)
|
||||
- [`get_shard`](#get_shard)
|
||||
- [`get_latest_slot_for_shard`](#get_latest_slot_for_shard)
|
||||
|
@ -74,6 +75,7 @@
|
|||
- [New Attester slashing processing](#new-attester-slashing-processing)
|
||||
- [Light client processing](#light-client-processing)
|
||||
- [Epoch transition](#epoch-transition)
|
||||
- [Phase 1 final updates](#phase-1-final-updates)
|
||||
- [Custody game updates](#custody-game-updates)
|
||||
- [Online-tracking](#online-tracking)
|
||||
- [Light client committee updates](#light-client-committee-updates)
|
||||
|
@ -293,6 +295,7 @@ class BeaconState(Container):
|
|||
current_justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
# Phase 1
|
||||
current_epoch_start_shard: Shard
|
||||
shard_states: List[ShardState, MAX_SHARDS]
|
||||
online_countdown: List[OnlineEpochs, VALIDATOR_REGISTRY_LIMIT] # not a raw byte array, considered its large size.
|
||||
current_light_committee: CompactCommittee
|
||||
|
@ -545,8 +548,13 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque
|
|||
|
||||
```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 the proposer's index of shard block at ``slot``.
|
||||
"""
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
committee = get_shard_committee(beacon_state, epoch, shard)
|
||||
seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + int_to_bytes(slot, length=8))
|
||||
r = bytes_to_int(seed[:8])
|
||||
return committee[r % len(committee)]
|
||||
```
|
||||
|
||||
|
@ -561,18 +569,49 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation)
|
|||
)
|
||||
```
|
||||
|
||||
#### `get_committee_count_delta`
|
||||
|
||||
```python
|
||||
def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: Slot) -> uint64:
|
||||
"""
|
||||
Return the sum of committee counts in range ``[start_slot, stop_slot)``.
|
||||
"""
|
||||
return sum(get_committee_count_at_slot(state, Slot(slot)) for slot in range(start_slot, stop_slot))
|
||||
```
|
||||
|
||||
#### `get_start_shard`
|
||||
|
||||
```python
|
||||
def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
|
||||
# TODO: implement start shard logic
|
||||
return Shard(0)
|
||||
"""
|
||||
Return the start shard at ``slot``.
|
||||
"""
|
||||
current_epoch_start_slot = compute_start_slot_at_epoch(get_current_epoch(state))
|
||||
active_shard_count = get_active_shard_count(state)
|
||||
if current_epoch_start_slot == slot:
|
||||
return state.current_epoch_start_shard
|
||||
elif slot > current_epoch_start_slot:
|
||||
# Current epoch or the next epoch lookahead
|
||||
shard_delta = get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot)
|
||||
return Shard((state.current_epoch_start_shard + shard_delta) % active_shard_count)
|
||||
else:
|
||||
# Previous epoch
|
||||
shard_delta = get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
|
||||
max_committees_per_epoch = MAX_COMMITTEES_PER_SLOT * SLOTS_PER_EPOCH
|
||||
return Shard(
|
||||
# Ensure positive
|
||||
(state.current_epoch_start_shard + max_committees_per_epoch * active_shard_count - shard_delta)
|
||||
% active_shard_count
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_shard`
|
||||
|
||||
```python
|
||||
def get_shard(state: BeaconState, attestation: Attestation) -> Shard:
|
||||
"""
|
||||
Return the shard that the given ``attestation`` is attesting.
|
||||
"""
|
||||
return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
|
||||
```
|
||||
|
||||
|
@ -580,6 +619,9 @@ def get_shard(state: BeaconState, attestation: Attestation) -> Shard:
|
|||
|
||||
```python
|
||||
def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot:
|
||||
"""
|
||||
Return the latest slot number of the given ``shard``.
|
||||
"""
|
||||
return state.shard_states[shard].slot
|
||||
```
|
||||
|
||||
|
@ -588,7 +630,8 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot:
|
|||
```python
|
||||
def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]:
|
||||
"""
|
||||
Return the offset slots of the given ``shard`` between that latest included slot and current slot.
|
||||
Return the offset slots of the given ``shard``.
|
||||
The offset slot are after the latest slot and before current slot.
|
||||
"""
|
||||
return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot)
|
||||
```
|
||||
|
@ -662,8 +705,7 @@ def is_on_time_attestation(state: BeaconState,
|
|||
"""
|
||||
Check if the given attestation is on-time.
|
||||
"""
|
||||
# TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1
|
||||
return attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot
|
||||
return attestation.data.slot == compute_previous_slot(state.slot)
|
||||
```
|
||||
|
||||
#### `is_winning_attestation`
|
||||
|
@ -772,20 +814,19 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
else:
|
||||
assert attestation.data.source == state.previous_justified_checkpoint
|
||||
|
||||
shard = get_shard(state, attestation)
|
||||
|
||||
# Type 1: on-time attestations, the custody bits should be non-empty.
|
||||
if attestation.custody_bits_blocks != []:
|
||||
# Ensure on-time attestation
|
||||
assert is_on_time_attestation(state, attestation)
|
||||
# Correct data root count
|
||||
shard = get_shard(state, attestation)
|
||||
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard))
|
||||
# Correct parent block root
|
||||
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
|
||||
# Type 2: no shard transition, no custody bits
|
||||
else:
|
||||
# Ensure delayed attestation
|
||||
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY < state.slot
|
||||
assert data.slot < compute_previous_slot(state.slot)
|
||||
# Late attestations cannot have a shard transition root
|
||||
assert data.shard_transition_root == Root()
|
||||
|
||||
|
@ -880,9 +921,10 @@ def process_crosslink_for_shard(state: BeaconState,
|
|||
committee_index: CommitteeIndex,
|
||||
shard_transition: ShardTransition,
|
||||
attestations: Sequence[Attestation]) -> Root:
|
||||
committee = get_beacon_committee(state, state.slot, committee_index)
|
||||
on_time_attestation_slot = compute_previous_slot(state.slot)
|
||||
committee = get_beacon_committee(state, on_time_attestation_slot, committee_index)
|
||||
online_indices = get_online_validator_indices(state)
|
||||
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||
shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot)
|
||||
|
||||
# Loop over all shard transition roots
|
||||
shard_transition_roots = set([a.data.shard_transition_root for a in attestations])
|
||||
|
@ -937,15 +979,15 @@ def process_crosslink_for_shard(state: BeaconState,
|
|||
def process_crosslinks(state: BeaconState,
|
||||
shard_transitions: Sequence[ShardTransition],
|
||||
attestations: Sequence[Attestation]) -> None:
|
||||
committee_count = get_committee_count_at_slot(state, state.slot)
|
||||
on_time_attestation_slot = compute_previous_slot(state.slot)
|
||||
committee_count = get_committee_count_at_slot(state, on_time_attestation_slot)
|
||||
for committee_index in map(CommitteeIndex, range(committee_count)):
|
||||
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||
# All attestations in the block for this committee/shard and current slot
|
||||
shard_attestations = [
|
||||
attestation for attestation in attestations
|
||||
if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index
|
||||
]
|
||||
|
||||
shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot)
|
||||
winning_root = process_crosslink_for_shard(state, committee_index, shard_transitions[shard], shard_attestations)
|
||||
if winning_root != Root():
|
||||
# Mark relevant pending attestations as creating a successful crosslink
|
||||
|
@ -1077,10 +1119,20 @@ def process_epoch(state: BeaconState) -> None:
|
|||
process_reveal_deadlines(state)
|
||||
process_challenge_deadlines(state)
|
||||
process_slashings(state)
|
||||
process_final_updates(state)
|
||||
process_final_updates(state) # phase 0 final updates
|
||||
process_phase_1_final_updates(state)
|
||||
```
|
||||
|
||||
#### Phase 1 final updates
|
||||
|
||||
```python
|
||||
def process_phase_1_final_updates(state: BeaconState) -> None:
|
||||
process_custody_final_updates(state)
|
||||
process_online_tracking(state)
|
||||
process_light_client_committee_updates(state)
|
||||
|
||||
# Update current_epoch_start_shard
|
||||
state.current_epoch_start_shard = get_start_shard(state, Slot(state.slot + 1))
|
||||
```
|
||||
|
||||
#### Custody game updates
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
|
||||
- [Introduction](#introduction)
|
||||
- [Fork choice](#fork-choice)
|
||||
- [Helpers](#helpers)
|
||||
- [Extended `LatestMessage`](#extended-latestmessage)
|
||||
- [Updated `update_latest_messages`](#updated-update_latest_messages)
|
||||
- [Handlers](#handlers)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
@ -25,6 +28,33 @@ Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_atte
|
|||
|
||||
The rest of the fork choice remains stable.
|
||||
|
||||
### Helpers
|
||||
|
||||
#### Extended `LatestMessage`
|
||||
|
||||
```python
|
||||
@dataclass(eq=True, frozen=True)
|
||||
class LatestMessage(object):
|
||||
epoch: Epoch
|
||||
root: Root
|
||||
shard: Shard
|
||||
shard_root: Root
|
||||
```
|
||||
|
||||
#### Updated `update_latest_messages`
|
||||
|
||||
```python
|
||||
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
|
||||
target = attestation.data.target
|
||||
beacon_block_root = attestation.data.beacon_block_root
|
||||
shard = get_shard(store.block_states[beacon_block_root], attestation)
|
||||
for i in attesting_indices:
|
||||
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
|
||||
store.latest_messages[i] = LatestMessage(
|
||||
epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root
|
||||
)
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
```python
|
||||
|
@ -49,4 +79,4 @@ def on_attestation(store: Store, attestation: Attestation) -> None:
|
|||
if attestation.aggregation_bits[i]
|
||||
]
|
||||
update_latest_messages(store, attesting_indices, attestation)
|
||||
```
|
||||
```
|
||||
|
|
|
@ -99,6 +99,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
|
|||
current_justified_checkpoint=pre.current_justified_checkpoint,
|
||||
finalized_checkpoint=pre.finalized_checkpoint,
|
||||
# Phase 1
|
||||
current_epoch_start_shard=Shard(0),
|
||||
shard_states=List[ShardState, MAX_SHARDS](
|
||||
ShardState(
|
||||
slot=pre.slot,
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
# Ethereum 2.0 Phase 1 -- Beacon Chain + Shard Chain Fork Choice
|
||||
|
||||
**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 -->
|
||||
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Fork choice](#fork-choice)
|
||||
- [Helpers](#helpers)
|
||||
- [`ShardStore`](#shardstore)
|
||||
- [`get_forkchoice_shard_store`](#get_forkchoice_shard_store)
|
||||
- [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance)
|
||||
- [`get_shard_head`](#get_shard_head)
|
||||
- [`get_shard_ancestor`](#get_shard_ancestor)
|
||||
- [`get_pending_shard_blocks`](#get_pending_shard_blocks)
|
||||
- [Handlers](#handlers)
|
||||
- [`on_shard_block`](#on_shard_block)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. It assumes the [beacon chain fork choice spec](./fork-choice.md).
|
||||
|
||||
## Fork choice
|
||||
|
||||
### Helpers
|
||||
|
||||
#### `ShardStore`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ShardStore:
|
||||
shard: Shard
|
||||
blocks: Dict[Root, ShardBlock] = field(default_factory=dict)
|
||||
block_states: Dict[Root, ShardState] = field(default_factory=dict)
|
||||
```
|
||||
|
||||
#### `get_forkchoice_shard_store`
|
||||
|
||||
```python
|
||||
def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore:
|
||||
return ShardStore(
|
||||
shard=shard,
|
||||
blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot, shard=shard)},
|
||||
block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]},
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_shard_latest_attesting_balance`
|
||||
|
||||
```python
|
||||
def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, root: Root) -> Gwei:
|
||||
state = store.checkpoint_states[store.justified_checkpoint]
|
||||
active_indices = get_active_validator_indices(state, get_current_epoch(state))
|
||||
return Gwei(sum(
|
||||
state.validators[i].effective_balance for i in active_indices
|
||||
if (
|
||||
i in store.latest_messages
|
||||
# TODO: check the latest message logic: currently, validator's previous vote of another shard
|
||||
# would be ignored once their newer vote is accepted. Check if it makes sense.
|
||||
and store.latest_messages[i].shard == shard_store.shard
|
||||
and get_shard_ancestor(
|
||||
store, shard_store, store.latest_messages[i].shard_root, shard_store.blocks[root].slot
|
||||
) == root
|
||||
)
|
||||
))
|
||||
```
|
||||
|
||||
#### `get_shard_head`
|
||||
|
||||
```python
|
||||
def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
|
||||
# Execute the LMD-GHOST fork choice
|
||||
beacon_head_root = get_head(store)
|
||||
shard_head_state = store.block_states[beacon_head_root].shard_states[shard_store.shard]
|
||||
shard_head_root = shard_head_state.latest_block_root
|
||||
shard_blocks = {
|
||||
root: shard_block for root, shard_block in shard_store.blocks.items()
|
||||
if shard_block.slot > shard_head_state.slot
|
||||
}
|
||||
while True:
|
||||
# Find the valid child block roots
|
||||
children = [
|
||||
root for root, shard_block in shard_blocks.items()
|
||||
if shard_block.shard_parent_root == shard_head_root
|
||||
]
|
||||
if len(children) == 0:
|
||||
return shard_head_root
|
||||
# Sort by latest attesting balance with ties broken lexicographically
|
||||
shard_head_root = max(
|
||||
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard_store, root), root)
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_shard_ancestor`
|
||||
|
||||
```python
|
||||
def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root:
|
||||
block = shard_store.blocks[root]
|
||||
if block.slot > slot:
|
||||
return get_shard_ancestor(store, shard_store, block.shard_parent_root, slot)
|
||||
elif block.slot == slot:
|
||||
return root
|
||||
else:
|
||||
# root is older than queried slot, thus a skip slot. Return most recent root prior to slot
|
||||
return root
|
||||
```
|
||||
|
||||
#### `get_pending_shard_blocks`
|
||||
|
||||
```python
|
||||
def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]:
|
||||
"""
|
||||
Return the canonical shard block branch that has not yet been crosslinked.
|
||||
"""
|
||||
shard = shard_store.shard
|
||||
|
||||
beacon_head_root = get_head(store)
|
||||
beacon_head_state = store.block_states[beacon_head_root]
|
||||
latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root
|
||||
|
||||
shard_head_root = get_shard_head(store, shard_store)
|
||||
root = shard_head_root
|
||||
shard_blocks = []
|
||||
while root != latest_shard_block_root:
|
||||
shard_block = shard_store.blocks[root]
|
||||
shard_blocks.append(shard_block)
|
||||
root = shard_block.shard_parent_root
|
||||
|
||||
shard_blocks.reverse()
|
||||
return shard_blocks
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
#### `on_shard_block`
|
||||
|
||||
```python
|
||||
def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None:
|
||||
shard_block = signed_shard_block.message
|
||||
shard = shard_store.shard
|
||||
|
||||
# Check shard
|
||||
# TODO: check it in networking spec
|
||||
assert shard_block.shard == shard
|
||||
|
||||
# Check shard parent exists
|
||||
assert shard_block.shard_parent_root in shard_store.block_states
|
||||
shard_parent_state = shard_store.block_states[shard_block.shard_parent_root]
|
||||
|
||||
# Check beacon parent exists
|
||||
assert shard_block.beacon_parent_root in store.block_states
|
||||
beacon_parent_state = store.block_states[shard_block.beacon_parent_root]
|
||||
|
||||
# Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor)
|
||||
finalized_beacon_state = store.block_states[store.finalized_checkpoint.root]
|
||||
finalized_shard_state = finalized_beacon_state.shard_states[shard]
|
||||
assert shard_block.slot > finalized_shard_state.slot
|
||||
|
||||
# Check block is a descendant of the finalized block at the checkpoint finalized slot
|
||||
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
|
||||
assert (
|
||||
get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root
|
||||
)
|
||||
|
||||
# Check the block is valid and compute the post-state
|
||||
assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block)
|
||||
assert verify_shard_block_signature(beacon_parent_state, signed_shard_block)
|
||||
|
||||
post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block)
|
||||
|
||||
# Add new block to the store
|
||||
shard_store.blocks[hash_tree_root(shard_block)] = shard_block
|
||||
|
||||
# Add new state for this block to the store
|
||||
shard_store.block_states[hash_tree_root(shard_block)] = post_state
|
||||
```
|
|
@ -30,7 +30,7 @@ This document describes the shard transition function and fraud proofs as part o
|
|||
### Misc
|
||||
|
||||
```python
|
||||
def compute_shard_transition_digest(beacon_state: BeaconState,
|
||||
def compute_shard_transition_digest(beacon_parent_state: BeaconState,
|
||||
shard_state: ShardState,
|
||||
beacon_parent_root: Root,
|
||||
shard_body_root: Root) -> Bytes32:
|
||||
|
@ -43,15 +43,27 @@ def compute_shard_transition_digest(beacon_state: BeaconState,
|
|||
### 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
|
||||
def verify_shard_block_message(beacon_parent_state: BeaconState,
|
||||
shard_parent_state: ShardState,
|
||||
block: ShardBlock) -> bool:
|
||||
# Check `shard_parent_root` field
|
||||
assert block.shard_parent_root == shard_parent_state.latest_block_root
|
||||
# Check `beacon_parent_root` field
|
||||
beacon_parent_block_header = beacon_parent_state.latest_block_header.copy()
|
||||
if beacon_parent_block_header.state_root == Root():
|
||||
beacon_parent_block_header.state_root = hash_tree_root(beacon_parent_state)
|
||||
beacon_parent_root = hash_tree_root(beacon_parent_block_header)
|
||||
assert block.beacon_parent_root == beacon_parent_root
|
||||
# Check `slot` field
|
||||
shard = block.shard
|
||||
next_slot = Slot(block.slot + 1)
|
||||
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot)
|
||||
assert block.slot in offset_slots
|
||||
# Check `shard` field
|
||||
assert block.shard == shard
|
||||
assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard)
|
||||
# Check `proposer_index` field
|
||||
assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard)
|
||||
# Check `body` field
|
||||
assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE
|
||||
return True
|
||||
```
|
||||
|
@ -71,22 +83,23 @@ def verify_shard_block_signature(beacon_state: BeaconState,
|
|||
def shard_state_transition(beacon_state: BeaconState,
|
||||
shard_state: ShardState,
|
||||
block: ShardBlock) -> None:
|
||||
# Update shard state
|
||||
"""
|
||||
Update ``shard_state`` with shard ``block`` and ``beacon_state`.
|
||||
"""
|
||||
shard_state.slot = block.slot
|
||||
prev_gasprice = shard_state.gasprice
|
||||
shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body))
|
||||
if len(block.body) == 0:
|
||||
latest_block_root = shard_state.latest_block_root
|
||||
else:
|
||||
latest_block_root = hash_tree_root(block)
|
||||
|
||||
shard_state.latest_block_root = latest_block_root
|
||||
shard_state.transition_digest = compute_shard_transition_digest(
|
||||
beacon_state,
|
||||
shard_state,
|
||||
block.beacon_parent_root,
|
||||
hash_tree_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.
|
||||
|
@ -171,38 +184,9 @@ def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[
|
|||
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,
|
||||
shard_parent_state: ShardState,
|
||||
slot: Shard,
|
||||
shard: Shard,
|
||||
shard_blocks: Sequence[SignedShardBlock],
|
||||
|
@ -211,24 +195,17 @@ def get_proposal_at_slot(beacon_state: BeaconState,
|
|||
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)
|
||||
shard_blocks = [block for block in shard_blocks if block.message.slot == slot]
|
||||
if len(shard_blocks) == 0:
|
||||
block = ShardBlock(slot=slot, shard=shard)
|
||||
proposal = SignedShardBlock(message=block)
|
||||
elif len(choices) == 1:
|
||||
proposal = choices[0]
|
||||
elif len(shard_blocks) == 1:
|
||||
proposal = shard_blocks[0]
|
||||
else:
|
||||
proposal = get_winning_proposal(beacon_state, choices)
|
||||
proposal = get_winning_proposal(beacon_state, shard_blocks)
|
||||
|
||||
# Apply state transition
|
||||
shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message)
|
||||
shard_state = get_post_shard_state(beacon_state, shard_parent_state, proposal.message)
|
||||
|
||||
return proposal, shard_state
|
||||
```
|
||||
|
@ -243,10 +220,11 @@ def get_shard_state_transition_result(
|
|||
proposals = []
|
||||
shard_states = []
|
||||
shard_state = beacon_state.shard_states[shard]
|
||||
for slot in get_offset_slots(beacon_state, shard):
|
||||
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
|
||||
for slot in offset_slots:
|
||||
proposal, shard_state = get_proposal_at_slot(
|
||||
beacon_state=beacon_state,
|
||||
shard_state=shard_state,
|
||||
shard_parent_state=shard_state,
|
||||
slot=slot,
|
||||
shard=shard,
|
||||
shard_blocks=shard_blocks,
|
||||
|
@ -268,7 +246,7 @@ Suppose you are a committee member on shard `shard` at slot `current_slot` and y
|
|||
def get_shard_transition(beacon_state: BeaconState,
|
||||
shard: Shard,
|
||||
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
|
||||
offset_slots = get_offset_slots(beacon_state, shard)
|
||||
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
|
||||
proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks)
|
||||
|
||||
shard_block_lengths = []
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.12.0
|
||||
0.12.1
|
|
@ -1,41 +1,13 @@
|
|||
from eth2spec.test.context import with_all_phases, spec_state_test
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.fork_choice import add_attestation_to_store, add_block_to_store, get_anchor_root
|
||||
from eth2spec.test.helpers.state import (
|
||||
next_epoch,
|
||||
state_transition_and_sign_block,
|
||||
)
|
||||
|
||||
|
||||
def add_block_to_store(spec, store, signed_block):
|
||||
pre_state = store.block_states[signed_block.message.parent_root]
|
||||
block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < block_time:
|
||||
spec.on_tick(store, block_time)
|
||||
|
||||
spec.on_block(store, signed_block)
|
||||
|
||||
|
||||
def add_attestation_to_store(spec, store, attestation):
|
||||
parent_block = store.blocks[attestation.data.beacon_block_root]
|
||||
pre_state = store.block_states[spec.hash_tree_root(parent_block)]
|
||||
block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
|
||||
next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < next_epoch_time:
|
||||
spec.on_tick(store, next_epoch_time)
|
||||
|
||||
spec.on_attestation(store, attestation)
|
||||
|
||||
|
||||
def get_anchor_root(spec, state):
|
||||
anchor_block_header = state.latest_block_header.copy()
|
||||
if anchor_block_header.state_root == spec.Bytes32():
|
||||
anchor_block_header.state_root = spec.hash_tree_root(state)
|
||||
return spec.hash_tree_root(anchor_block_header)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_genesis(spec, state):
|
||||
|
|
|
@ -18,18 +18,25 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
|
|||
|
||||
if spec.fork == PHASE0:
|
||||
sample_index = indexed_attestation.attesting_indices[0]
|
||||
latest_message = spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
)
|
||||
else:
|
||||
attesting_indices = [
|
||||
index for i, index in enumerate(indexed_attestation.committee)
|
||||
if attestation.aggregation_bits[i]
|
||||
]
|
||||
sample_index = attesting_indices[0]
|
||||
assert (
|
||||
store.latest_messages[sample_index] ==
|
||||
spec.LatestMessage(
|
||||
latest_message = spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
shard=spec.get_shard(state, attestation),
|
||||
shard_root=attestation.data.shard_head_root,
|
||||
)
|
||||
|
||||
assert (
|
||||
store.latest_messages[sample_index] == latest_message
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
|
||||
from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls
|
||||
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
|
||||
from eth2spec.test.helpers.shard_block import (
|
||||
build_shard_block,
|
||||
get_shard_transitions,
|
||||
get_committee_index_of_shard,
|
||||
)
|
||||
from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block
|
||||
from eth2spec.test.helpers.block import build_empty_block
|
||||
|
||||
|
||||
def run_on_shard_block(spec, store, shard_store, signed_block, valid=True):
|
||||
if not valid:
|
||||
try:
|
||||
spec.on_shard_block(store, shard_store, signed_block)
|
||||
except AssertionError:
|
||||
return
|
||||
else:
|
||||
assert False
|
||||
|
||||
spec.on_shard_block(store, shard_store, signed_block)
|
||||
assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message
|
||||
|
||||
|
||||
def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer):
|
||||
shard = shard_store.shard
|
||||
body = b'\x56' * 4
|
||||
shard_head_root = spec.get_shard_head(store, shard_store)
|
||||
shard_parent_state = shard_store.block_states[shard_head_root]
|
||||
assert shard_parent_state.slot != beacon_parent_state.slot
|
||||
shard_block = build_shard_block(
|
||||
spec, beacon_parent_state, shard,
|
||||
shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
|
||||
)
|
||||
shard_blocks_buffer.append(shard_block)
|
||||
run_on_shard_block(spec, store, shard_store, shard_block)
|
||||
assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root()
|
||||
|
||||
|
||||
def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer):
|
||||
pending_shard_blocks = [
|
||||
spec.SignedShardBlock(message=b)
|
||||
for b in spec.get_pending_shard_blocks(store, shard_store)
|
||||
]
|
||||
assert pending_shard_blocks == shard_blocks_buffer
|
||||
|
||||
|
||||
def is_in_offset_sets(spec, beacon_head_state, shard):
|
||||
offset_slots = spec.compute_offset_slots(
|
||||
beacon_head_state.shard_states[shard].slot, beacon_head_state.slot + 1
|
||||
)
|
||||
return beacon_head_state.slot in offset_slots
|
||||
|
||||
|
||||
def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer):
|
||||
store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
shard = shard_store.shard
|
||||
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
|
||||
has_shard_committee = committee_index is not None # has committee of `shard` at this slot
|
||||
|
||||
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
|
||||
|
||||
# If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block
|
||||
if has_shard_committee and len(shard_blocks_buffer) > 0:
|
||||
# Sanity check `get_pending_shard_blocks` function
|
||||
check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer)
|
||||
# Use temporary next state to get ShardTransition of shard block
|
||||
shard_transitions = get_shard_transitions(
|
||||
spec,
|
||||
state,
|
||||
shard_blocks={shard: shard_blocks_buffer},
|
||||
)
|
||||
shard_transition = shard_transitions[shard]
|
||||
attestation = get_valid_on_time_attestation(
|
||||
spec,
|
||||
state,
|
||||
index=committee_index,
|
||||
shard_transition=shard_transition,
|
||||
signed=False,
|
||||
)
|
||||
assert spec.get_shard(state, attestation) == shard
|
||||
beacon_block.body.attestations = [attestation]
|
||||
beacon_block.body.shard_transitions = shard_transitions
|
||||
|
||||
# Clear buffer
|
||||
shard_blocks_buffer.clear()
|
||||
|
||||
signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) # transition!
|
||||
add_block_to_store(spec, store, signed_beacon_block)
|
||||
assert spec.get_head(store) == beacon_block.hash_tree_root()
|
||||
|
||||
# On shard block at transitioned `state.slot`
|
||||
if is_in_offset_sets(spec, state, shard):
|
||||
# The created shard block would be appended to `shard_blocks_buffer`
|
||||
apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer)
|
||||
|
||||
return has_shard_committee
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
@never_bls # Set to never_bls for testing `check_pending_shard_blocks`
|
||||
def test_basic(spec, state):
|
||||
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
|
||||
state = spec.upgrade_to_phase1(state)
|
||||
shard = spec.Shard(1)
|
||||
|
||||
# Initialization
|
||||
store = spec.get_forkchoice_store(state)
|
||||
anchor_root = get_anchor_root(spec, state)
|
||||
assert spec.get_head(store) == anchor_root
|
||||
|
||||
shard_store = spec.get_forkchoice_shard_store(state, shard)
|
||||
shard_head_root = spec.get_shard_head(store, shard_store)
|
||||
assert shard_head_root == state.shard_states[shard].latest_block_root
|
||||
assert shard_store.block_states[shard_head_root].slot == 1
|
||||
assert shard_store.block_states[shard_head_root] == state.shard_states[shard]
|
||||
|
||||
# For mainnet config, it's possible that only one committee of `shard` per epoch.
|
||||
# we set this counter to test more rounds.
|
||||
shard_committee_counter = 2
|
||||
shard_blocks_buffer = []
|
||||
while shard_committee_counter > 0:
|
||||
has_shard_committee = apply_shard_and_beacon(
|
||||
spec, state, store, shard_store, shard_blocks_buffer
|
||||
)
|
||||
if has_shard_committee:
|
||||
shard_committee_counter -= 1
|
|
@ -21,7 +21,7 @@ def test_initialize_beacon_state_from_eth1(spec):
|
|||
# initialize beacon_state
|
||||
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
|
||||
|
||||
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
|
||||
assert state.genesis_time == eth1_timestamp + spec.GENESIS_DELAY
|
||||
assert len(state.validators) == deposit_count
|
||||
assert state.eth1_data.deposit_root == deposit_root
|
||||
assert state.eth1_data.deposit_count == deposit_count
|
||||
|
@ -57,7 +57,7 @@ def test_initialize_beacon_state_some_small_balances(spec):
|
|||
# initialize beacon_state
|
||||
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
|
||||
|
||||
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
|
||||
assert state.genesis_time == eth1_timestamp + spec.GENESIS_DELAY
|
||||
assert len(state.validators) == small_deposit_count
|
||||
assert state.eth1_data.deposit_root == deposit_root
|
||||
assert state.eth1_data.deposit_count == len(deposits)
|
||||
|
|
|
@ -3,8 +3,9 @@ from lru import LRU
|
|||
from typing import List
|
||||
|
||||
from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot, transition_to
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist
|
||||
|
@ -83,12 +84,10 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t
|
|||
attestation_data.shard_head_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
|
||||
# No shard transition -> no shard block
|
||||
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, [])
|
||||
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
|
||||
lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1
|
||||
attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index]
|
||||
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
|
||||
|
@ -98,8 +97,8 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t
|
|||
return attestation_data
|
||||
|
||||
|
||||
def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None,
|
||||
valid_custody_bits=None):
|
||||
def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition,
|
||||
signed=False, valid_custody_bits=None):
|
||||
shard = spec.get_shard(state, attestation)
|
||||
offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1)
|
||||
|
||||
|
@ -117,7 +116,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False,
|
|||
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
|
||||
custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root)
|
||||
|
||||
for i, offset_slot in enumerate(offset_slots):
|
||||
for i in range(len(offset_slots)):
|
||||
attestation.custody_bits_blocks.append(
|
||||
Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits])
|
||||
)
|
||||
|
@ -207,7 +206,7 @@ def get_valid_attestation(spec,
|
|||
if spec.fork == PHASE1 and on_time:
|
||||
attestation = convert_to_valid_on_time_attestation(
|
||||
spec, state, attestation,
|
||||
shard_transition=shard_transition,
|
||||
shard_transition,
|
||||
valid_custody_bits=valid_custody_bits,
|
||||
signed=signed,
|
||||
)
|
||||
|
@ -347,7 +346,17 @@ def next_epoch_with_attestations(spec,
|
|||
committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest)
|
||||
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)):
|
||||
for index in range(committees_per_slot):
|
||||
cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True)
|
||||
if spec.fork == PHASE1:
|
||||
shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest)
|
||||
shard_transition = get_shard_transition_of_committee(spec, post_state, index)
|
||||
block.body.shard_transitions[shard] = shard_transition
|
||||
else:
|
||||
shard_transition = None
|
||||
|
||||
cur_attestation = get_valid_attestation(
|
||||
spec, post_state, slot_to_attest,
|
||||
shard_transition=shard_transition, index=index, signed=True, on_time=True
|
||||
)
|
||||
block.body.attestations.append(cur_attestation)
|
||||
|
||||
if fill_prev_epoch:
|
||||
|
@ -358,9 +367,6 @@ def next_epoch_with_attestations(spec,
|
|||
spec, post_state, slot_to_attest, index=index, signed=True, on_time=False)
|
||||
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_blocks.append(signed_block)
|
||||
|
||||
|
@ -426,14 +432,3 @@ def cached_prepare_state_with_attestations(spec, state):
|
|||
|
||||
# Put the LRU cache result into the state view, as if we transitioned the original view
|
||||
state.set_backing(_prep_state_cache_dict[key])
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
def get_anchor_root(spec, state):
|
||||
anchor_block_header = state.latest_block_header.copy()
|
||||
if anchor_block_header.state_root == spec.Bytes32():
|
||||
anchor_block_header.state_root = spec.hash_tree_root(state)
|
||||
return spec.hash_tree_root(anchor_block_header)
|
||||
|
||||
|
||||
def add_block_to_store(spec, store, signed_block):
|
||||
pre_state = store.block_states[signed_block.message.parent_root]
|
||||
block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < block_time:
|
||||
spec.on_tick(store, block_time)
|
||||
|
||||
spec.on_block(store, signed_block)
|
||||
|
||||
|
||||
def add_attestation_to_store(spec, store, attestation):
|
||||
parent_block = store.blocks[attestation.data.beacon_block_root]
|
||||
pre_state = store.block_states[spec.hash_tree_root(parent_block)]
|
||||
block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
|
||||
next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
|
||||
|
||||
if store.time < next_epoch_time:
|
||||
spec.on_tick(store, next_epoch_time)
|
||||
|
||||
spec.on_attestation(store, attestation)
|
|
@ -1,6 +1,4 @@
|
|||
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
|
||||
|
@ -23,19 +21,21 @@ def build_shard_block(spec,
|
|||
shard,
|
||||
slot=None,
|
||||
body=None,
|
||||
shard_parent_state=None,
|
||||
signed=False):
|
||||
shard_state = beacon_state.shard_states[shard]
|
||||
if shard_parent_state is None:
|
||||
shard_parent_state = beacon_state.shard_states[shard]
|
||||
|
||||
if slot is None:
|
||||
slot = shard_state.slot + 1
|
||||
slot = shard_parent_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)
|
||||
|
||||
proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard)
|
||||
block = spec.ShardBlock(
|
||||
shard_parent_root=shard_state.latest_block_root,
|
||||
shard_parent_root=shard_parent_state.latest_block_root,
|
||||
beacon_parent_root=beacon_parent_root,
|
||||
slot=slot,
|
||||
shard=shard,
|
||||
|
@ -52,15 +52,17 @@ def build_shard_block(spec,
|
|||
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)
|
||||
def get_shard_transitions(spec, parent_beacon_state, shard_blocks):
|
||||
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||
on_time_slot = parent_beacon_state.slot + 1
|
||||
for shard, blocks in shard_blocks.items():
|
||||
offset_slots = spec.get_offset_slots(temp_state, shard)
|
||||
offset_slots = spec.compute_offset_slots(
|
||||
spec.get_latest_slot_for_shard(parent_beacon_state, shard),
|
||||
on_time_slot,
|
||||
)
|
||||
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)
|
||||
shard_transition = spec.get_shard_transition(parent_beacon_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
|
||||
|
@ -70,17 +72,11 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot):
|
|||
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
|
||||
def get_committee_index_of_shard(spec, state, slot, shard): # Optional[CommitteeIndex]
|
||||
active_shard_count = spec.get_active_shard_count(state)
|
||||
committee_count = spec.get_committee_count_at_slot(state, slot)
|
||||
start_shard = spec.get_start_shard(state, slot)
|
||||
for committee_index in range(committee_count):
|
||||
if (start_shard + committee_index) % active_shard_count == shard:
|
||||
return committee_index
|
||||
return None
|
||||
|
|
|
@ -26,3 +26,12 @@ def run_shard_transitions_processing(spec, state, shard_transitions, attestation
|
|||
|
||||
# yield post-state
|
||||
yield 'post', state
|
||||
|
||||
|
||||
def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks=None):
|
||||
if shard_blocks is None:
|
||||
shard_blocks = []
|
||||
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks)
|
||||
return shard_transition
|
||||
|
|
|
@ -16,8 +16,9 @@ from eth2spec.test.helpers.attester_slashings import (
|
|||
get_indexed_attestation_participants,
|
||||
)
|
||||
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
||||
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
|
||||
|
||||
from eth2spec.test.context import (
|
||||
spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases,
|
||||
|
@ -687,14 +688,23 @@ def test_attestation(spec, state):
|
|||
|
||||
yield 'pre', state
|
||||
|
||||
attestation = get_valid_attestation(spec, state, signed=True, on_time=True)
|
||||
attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||
|
||||
index = 0
|
||||
if spec.fork == PHASE1:
|
||||
shard = spec.compute_shard_from_committee_index(state, index, state.slot)
|
||||
shard_transition = get_shard_transition_of_committee(spec, state, index)
|
||||
attestation_block.body.shard_transitions[shard] = shard_transition
|
||||
else:
|
||||
shard_transition = None
|
||||
|
||||
attestation = get_valid_attestation(
|
||||
spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True
|
||||
)
|
||||
|
||||
# Add to state via block transition
|
||||
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.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)
|
||||
|
||||
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
|
||||
|
|
|
@ -2,72 +2,64 @@ from eth2spec.test.context import (
|
|||
PHASE0,
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
always_bls,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
|
||||
from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing
|
||||
from eth2spec.test.helpers.shard_block import (
|
||||
build_attestation_with_shard_transition,
|
||||
build_shard_block,
|
||||
build_shard_transitions_till_slot,
|
||||
get_shard_transitions,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
|
||||
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_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
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
|
||||
assert state.shard_states[shard].slot == state.slot - 1
|
||||
transition_to(spec, state, state.slot + target_len_offset_slot)
|
||||
assert state.shard_states[shard].slot == state.slot - target_len_offset_slot - 1
|
||||
|
||||
# Create SignedShardBlock
|
||||
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||
shard_block = build_shard_block(spec, state, shard, body=body, signed=True)
|
||||
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, 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(
|
||||
shard_transitions = get_shard_transitions(
|
||||
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(
|
||||
attestation = get_valid_on_time_attestation(
|
||||
spec,
|
||||
state,
|
||||
index=committee_index,
|
||||
on_time_slot=state.slot + target_len_offset_slot,
|
||||
shard_transition=shard_transition,
|
||||
signed=False,
|
||||
)
|
||||
next_slot(spec, state)
|
||||
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_shard_transitions_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]
|
||||
|
||||
assert shard_state.latest_block_root == shard_block.message.hash_tree_root()
|
||||
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):
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
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)
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=2, valid=True)
|
||||
|
|
|
@ -4,37 +4,40 @@ from eth2spec.test.context import (
|
|||
PHASE0,
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
always_bls,
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
|
||||
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,
|
||||
get_shard_transitions,
|
||||
)
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True):
|
||||
transition_to(spec, state, state.slot + target_len_offset_slot)
|
||||
|
||||
body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE
|
||||
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
|
||||
shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
|
||||
|
||||
shard_transitions = get_shard_transitions(spec, state, shard_blocks)
|
||||
attestations = [
|
||||
build_attestation_with_shard_transition(
|
||||
get_valid_on_time_attestation(
|
||||
spec,
|
||||
state,
|
||||
on_time_slot=state.slot + target_len_offset_slot,
|
||||
index=committee_index,
|
||||
shard_transition=shard_transitions[shard],
|
||||
signed=True,
|
||||
)
|
||||
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 = build_empty_block(spec, state, slot=state.slot + 1)
|
||||
beacon_block.body.attestations = attestations
|
||||
beacon_block.body.shard_transitions = shard_transitions
|
||||
|
||||
pre_gasprice = state.shard_states[shard].gasprice
|
||||
pre_shard_states = state.shard_states.copy()
|
||||
yield 'pre', state.copy()
|
||||
yield 'block', beacon_block
|
||||
|
@ -52,56 +55,37 @@ def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_off
|
|||
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
|
||||
if target_len_offset_slot == 1 and len(shard_blocks) > 0:
|
||||
assert post_shard_state.gasprice > pre_gasprice
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_process_beacon_block_with_normal_shard_transition(spec, state):
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
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)
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
|
||||
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
|
||||
yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_process_beacon_block_with_empty_proposal_transition(spec, state):
|
||||
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
|
||||
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)
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
|
||||
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
|
||||
yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
from eth2spec.test.context import (
|
||||
PHASE0,
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
)
|
||||
from eth2spec.test.helpers.state import next_epoch
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_get_committee_count_delta(spec, state):
|
||||
assert spec.get_committee_count_delta(state, 0, 0) == 0
|
||||
assert spec.get_committee_count_at_slot(state, 0) != 0
|
||||
assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_at_slot(state, 0)
|
||||
assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_at_slot(state, 1)
|
||||
assert spec.get_committee_count_delta(state, 0, 2) == (
|
||||
spec.get_committee_count_at_slot(state, 0) + spec.get_committee_count_at_slot(state, 1)
|
||||
)
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_get_start_shard_current_epoch_start(spec, state):
|
||||
assert state.current_epoch_start_shard == 0
|
||||
next_epoch(spec, state)
|
||||
active_shard_count = spec.get_active_shard_count(state)
|
||||
assert state.current_epoch_start_shard == (
|
||||
spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) % active_shard_count
|
||||
)
|
||||
current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
|
||||
|
||||
slot = current_epoch_start_slot
|
||||
start_shard = spec.get_start_shard(state, slot)
|
||||
assert start_shard == state.current_epoch_start_shard
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_get_start_shard_next_slot(spec, state):
|
||||
next_epoch(spec, state)
|
||||
active_shard_count = spec.get_active_shard_count(state)
|
||||
current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
|
||||
|
||||
slot = current_epoch_start_slot + 1
|
||||
start_shard = spec.get_start_shard(state, slot)
|
||||
|
||||
current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
|
||||
expected_start_shard = (
|
||||
state.current_epoch_start_shard
|
||||
+ spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot)
|
||||
) % active_shard_count
|
||||
assert start_shard == expected_start_shard
|
||||
|
||||
|
||||
@with_all_phases_except([PHASE0])
|
||||
@spec_state_test
|
||||
def test_get_start_shard_previous_slot(spec, state):
|
||||
next_epoch(spec, state)
|
||||
active_shard_count = spec.get_active_shard_count(state)
|
||||
current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
|
||||
|
||||
slot = current_epoch_start_slot - 1
|
||||
start_shard = spec.get_start_shard(state, slot)
|
||||
|
||||
current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state))
|
||||
expected_start_shard = (
|
||||
state.current_epoch_start_shard
|
||||
+ spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH * active_shard_count
|
||||
- spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
|
||||
) % active_shard_count
|
||||
assert start_shard == expected_start_shard
|
Loading…
Reference in New Issue