Merge remote-tracking branch 'EF/dev' into fix/no-committee-for-shard-case

# Conflicts:
#	specs/sharding/p2p-interface.md
This commit is contained in:
Anton Nashatyrev 2021-06-04 18:43:12 +03:00
commit 116c1c0f3a
10 changed files with 92 additions and 71 deletions

View File

@ -596,7 +596,7 @@ def objects_to_spec(preset_name: str,
def format_config_var(name: str, vardef: VariableDefinition) -> str:
if vardef.type_name is None:
out = f'{name}={vardef.value}'
out = f'{name}={vardef.value},'
else:
out = f'{name}={vardef.type_name}({vardef.value}),'
if vardef.comment is not None:
@ -1017,7 +1017,7 @@ setup(
"py_ecc==5.2.0",
"milagro_bls_binding==1.6.3",
"dataclasses==0.6",
"remerkleable==0.1.19",
"remerkleable==0.1.20",
RUAMEL_YAML_VERSION,
"lru-dict==1.1.6",
MARKO_VERSION,

View File

@ -137,8 +137,8 @@ This patch updates a few configuration values to move penalty parameters closer
| Name | Value | Description |
| - | - | - |
| `INACTIVITY_SCORE_BIAS` | `uint64(4)` | score points per inactive epoch |
| `INACTIVITY_SCORE_RECOVERY_RATE` | `uint64(16)` | score points per recovering epoch |
| `INACTIVITY_SCORE_BIAS` | `uint64(2**2)` (= 4) | score points per inactive epoch |
| `INACTIVITY_SCORE_RECOVERY_RATE` | `uint64(2**4)` (= 16) | score points per leak-free epoch |
## Containers
@ -157,8 +157,7 @@ class BeaconBlockBody(Container):
attestations: List[Attestation, MAX_ATTESTATIONS]
deposits: List[Deposit, MAX_DEPOSITS]
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
# [New in Altair]
sync_aggregate: SyncAggregate
sync_aggregate: SyncAggregate # [New in Altair]
```
#### `BeaconState`
@ -266,10 +265,7 @@ def has_flag(flags: ParticipationFlags, flag_index: int) -> bool:
```python
def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
"""
Return the sequence of sync committee indices (which may include duplicate indices)
for the next sync committee, given a ``state`` at a sync committee period boundary.
Note: Committee can contain duplicate indices for small validator sets (< SYNC_COMMITTEE_SIZE + 128)
Return the sync committee indices, with possible duplicates, for the next sync committee.
"""
epoch = Epoch(get_current_epoch(state) + 1)
@ -292,21 +288,12 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd
#### `get_next_sync_committee`
*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries.
```python
def get_next_sync_committee(state: BeaconState) -> SyncCommittee:
"""
Return the *next* sync committee for a given ``state``.
``SyncCommittee`` contains an aggregate pubkey that enables
resource-constrained clients to save some computation when verifying
the sync committee's signature.
``SyncCommittee`` can also contain duplicate pubkeys, when ``get_next_sync_committee_indices``
returns duplicate indices. Implementations must take care when handling
optimizations relating to aggregation and verification in the presence of duplicates.
Note: This function should only be called at sync committee period boundaries by ``process_sync_committee_updates``
as ``get_next_sync_committee_indices`` is not stable within a given period.
Return the next sync committee, with possible pubkey duplicates.
"""
indices = get_next_sync_committee_indices(state)
pubkeys = [state.validators[index].pubkey for index in indices]
@ -325,14 +312,12 @@ def get_base_reward_per_increment(state: BeaconState) -> Gwei:
*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH` and the use of increment based accounting.
*Note*: On average an optimally performing validator earns one base reward per epoch.
```python
def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
"""
Return the base reward for the validator defined by ``index`` with respect to the current ``state``.
Note: An optimally performing validator can earn one base reward per epoch over a long time horizon.
This takes into account both per-epoch (e.g. attestation) and intermittent duties (e.g. block proposal
and sync committees).
"""
increments = state.validators[index].effective_balance // EFFECTIVE_BALANCE_INCREMENT
return Gwei(increments * get_base_reward_per_increment(state))
@ -559,6 +544,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
#### Sync committee processing
*Note*: The function `process_sync_committee` is new.
```python
def process_sync_committee(state: BeaconState, aggregate: SyncAggregate) -> None:
# Verify sync committee aggregate signature signing over the previous slot block root
@ -627,17 +614,17 @@ def process_justification_and_finalization(state: BeaconState) -> None:
```python
def process_inactivity_updates(state: BeaconState) -> None:
# Score updates based on previous epoch participation, skip genesis epoch
# Skip the genesis epoch as score updates are based on the previous epoch participation
if get_current_epoch(state) == GENESIS_EPOCH:
return
for index in get_eligible_validator_indices(state):
# Increase inactivity score of inactive validators
# Increase the inactivity score of inactive validators
if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)):
state.inactivity_scores[index] -= min(1, state.inactivity_scores[index])
else:
state.inactivity_scores[index] += INACTIVITY_SCORE_BIAS
# Decrease the score of all validators for forgiveness when not during a leak
# Decrease the inactivity score of all eligible validators during a leak-free epoch
if not is_in_inactivity_leak(state):
state.inactivity_scores[index] -= min(INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index])
```

View File

@ -120,7 +120,7 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64
return sync_committee.pubkeys[i:i + sync_subcommittee_size]
```
- _[IGNORE]_ The contribution's slot is for the current slot, i.e. `contribution.slot == current_slot`.
- _[IGNORE]_ The contribution's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. `contribution.slot == current_slot`.
- _[IGNORE]_ The block being signed over (`contribution.beacon_block_root`) has been seen (via both gossip and non-gossip sources).
- _[REJECT]_ The subcommittee index is in the allowed range, i.e. `contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT`.
- _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index` for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index`.
@ -141,7 +141,7 @@ The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync
The following validations MUST pass before forwarding the `sync_committee_signature` on the network:
- _[IGNORE]_ The signature's slot is for the current slot, i.e. `sync_committee_signature.slot == current_slot`.
- _[IGNORE]_ The signature's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. `sync_committee_signature.slot == current_slot`.
- _[IGNORE]_ The block being signed over (`sync_committee_signature.beacon_block_root`) has been seen (via both gossip and non-gossip sources).
- _[IGNORE]_ There has been no other valid sync committee signature for the declared `slot` for the validator referenced by `sync_committee_signature.validator_index`.
- _[REJECT]_ The `subnet_id` is valid for the given validator, i.e. `subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index)`.

View File

@ -282,7 +282,12 @@ def get_sync_committee_signature(state: BeaconState,
signing_root = compute_signing_root(block_root, domain)
signature = bls.Sign(privkey, signing_root)
return SyncCommitteeSignature(slot=state.slot, validator_index=validator_index, signature=signature)
return SyncCommitteeSignature(
slot=state.slot,
beacon_block_root=block_root,
validator_index=validator_index,
signature=signature,
)
```
##### Broadcast sync committee signature

View File

@ -216,6 +216,7 @@ class ShardBlobHeader(Container):
# Slot and shard that this header is intended for
slot: Slot
shard: Shard
# SSZ-summary of ShardBlobBody
body_summary: ShardBlobBodySummary
# Proposer of the shard-blob
proposer_index: ValidatorIndex
@ -253,7 +254,7 @@ class ShardBlobReference(Container):
# Slot and shard that this reference is intended for
slot: Slot
shard: Shard
# Hash-tree-root of commitment data
# Hash-tree-root of ShardBlobBody
body_root: Root
# Proposer of the shard-blob
proposer_index: ValidatorIndex

View File

@ -17,9 +17,11 @@
- [SignedShardBlob](#signedshardblob)
- [Gossip domain](#gossip-domain)
- [Topics and messages](#topics-and-messages)
- [Shard blobs: `shard_blob_{subnet_id}`](#shard-blobs-shard_blob_subnet_id)
- [Shard header: `shard_header`](#shard-header-shard_header)
- [Shard proposer slashing: `shard_proposer_slashing`](#shard-proposer-slashing-shard_proposer_slashing)
- [Shard blob subnets](#shard-blob-subnets)
- [`shard_blob_{subnet_id}`](#shard_blob_subnet_id)
- [Global topics](#global-topics)
- [`shard_header`](#shard_header)
- [`shard_proposer_slashing`](#shard_proposer_slashing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -28,7 +30,7 @@
## Introduction
The specification of these changes continues in the same format as the [Phase0](../phase0/p2p-interface.md) and
[Altair](../altair/p2p-interface.md) network specifications, and assumes them as pre-requisite.
[Altair](../altair/p2p-interface.md) network specifications, and assumes them as pre-requisite.
The adjustments and additions for Shards are outlined in this document.
## Constants
@ -64,6 +66,7 @@ class ShardBlob(Container):
# Slot and shard that this blob is intended for
slot: Slot
shard: Shard
# Shard data with related commitments and beacon anchor
body: ShardBlobBody
# Proposer of the shard-blob
proposer_index: ValidatorIndex
@ -88,12 +91,16 @@ Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.
| Name | Message Type |
|----------------------------------|---------------------------|
| `shard_blob_{subnet_id}` | `SignedShardBlob` |
| `shard_header` | `SignedShardHeader` |
| `shard_header` | `SignedShardBlobHeader` |
| `shard_proposer_slashing` | `ShardProposerSlashing` |
The [DAS network specification](./das-p2p.md) defines additional topics.
#### Shard blobs: `shard_blob_{subnet_id}`
#### Shard blob subnets
Shard blob subnets are used to propagate shard blobs to subsections of the network.
##### `shard_blob_{subnet_id}`
Shard block data, in the form of a `SignedShardBlob` is published to the `shard_blob_{subnet_id}` subnets.
@ -120,7 +127,7 @@ The following validations MUST pass before forwarding the `signed_blob` (with in
- _[REJECT]_ The shard should have a committee at slot --
i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard) is not None`
- _[REJECT]_ The shard blob is for the correct subnet --
i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id`
i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id`
- _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination.
- _[REJECT]_ As already limited by the SSZ list-limit, it is important the blob is well-formatted and not too large.
- _[REJECT]_ The `blob.body.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid.
@ -131,12 +138,16 @@ The following validations MUST pass before forwarding the `signed_blob` (with in
the block MAY be queued for later processing while proposers for the blob's branch are calculated --
in such a case _do not_ `REJECT`, instead `IGNORE` this message.
#### Global topics
#### Shard header: `shard_header`
There are two additional global topics for Sharding, one is used to propagate shard blob headers (`shard_header`) to
all nodes on the network. Another one is used to propagate validator message (`shard_proposer_slashing`).
##### `shard_header`
Shard header data, in the form of a `SignedShardBlobHeader` is published to the global `shard_header` subnet.
The following validations MUST pass before forwarding the `signed_shard_header` (with inner `message` as `header`) on the network.
The following validations MUST pass before forwarding the `signed_shard_blob_header` (with inner `message` as `header`) on the network.
- _[IGNORE]_ The `header` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. validate that `header.slot <= current_slot`
(a client MAY queue future headers for processing at the appropriate slot).
@ -145,7 +156,7 @@ The following validations MUST pass before forwarding the `signed_shard_header`
- _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination.
- _[REJECT]_ The shard should have a committee at slot --
i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard) is not None`
- _[REJECT]_ The proposer signature, `signed_shard_header.signature`, is valid with respect to the `proposer_index` pubkey.
- _[REJECT]_ The proposer signature, `signed_shard_blob_header.signature`, is valid with respect to the `proposer_index` pubkey.
- _[REJECT]_ The header is proposed by the expected `proposer_index` for the block's slot
in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`).
If the `proposer_index` cannot immediately be verified against the expected shuffling,
@ -153,7 +164,7 @@ The following validations MUST pass before forwarding the `signed_shard_header`
in such a case _do not_ `REJECT`, instead `IGNORE` this message.
#### Shard proposer slashing: `shard_proposer_slashing`
##### `shard_proposer_slashing`
Shard proposer slashings, in the form of `ShardProposerSlashing`, are published to the global `shard_proposer_slashing` topic.

View File

@ -17,10 +17,10 @@
- [Serialization](#serialization)
- [`uintN`](#uintn)
- [`boolean`](#boolean)
- [`null`](#null)
- [`Bitvector[N]`](#bitvectorn)
- [`Bitlist[N]`](#bitlistn)
- [Vectors, containers, lists, unions](#vectors-containers-lists-unions)
- [Vectors, containers, lists](#vectors-containers-lists)
- [Union](#union)
- [Deserialization](#deserialization)
- [Merkleization](#merkleization)
- [Summaries and expansions](#summaries-and-expansions)
@ -61,7 +61,7 @@
* **bitlist**: ordered variable-length collection of `boolean` values, limited to `N` bits
* notation `Bitlist[N]`
* **union**: union type containing one of the given subtypes
* notation `Union[type_0, type_1, ...]`, e.g. `union[null, uint64]`
* notation `Union[type_0, type_1, ...]`, e.g. `union[None, uint64, uint32]`
*Note*: Both `Vector[boolean, N]` and `Bitvector[N]` are valid, yet distinct due to their different serialization requirements. Similarly, both `List[boolean, N]` and `Bitlist[N]` are valid, yet distinct. Generally `Bitvector[N]`/`Bitlist[N]` are preferred because of their serialization efficiencies.
@ -77,7 +77,6 @@ For convenience we alias:
* `byte` to `uint8` (this is a basic type)
* `BytesN` and `ByteVector[N]` to `Vector[byte, N]` (this is *not* a basic type)
* `ByteList[N]` to `List[byte, N]`
* `null`: `{}`
### Default values
Assuming a helper function `default(type)` which returns the default value for `type`, we can recursively define the default value for all types.
@ -101,7 +100,7 @@ An SSZ object is called zeroed (and thus, `is_zero(object)` returns true) if it
- Empty vector types (`Vector[type, 0]`, `Bitvector[0]`) are illegal.
- Containers with no fields are illegal.
- The `null` type is only legal as the first type in a union subtype (i.e. with type index zero).
- The `None` type option in a `Union` type is only legal as the first option (i.e. with index zero).
## Serialization
@ -123,12 +122,6 @@ assert value in (True, False)
return b"\x01" if value is True else b"\x00"
```
### `null`
```python
return b""
```
### `Bitvector[N]`
```python
@ -150,7 +143,7 @@ array[len(value) // 8] |= 1 << (len(value) % 8)
return bytes(array)
```
### Vectors, containers, lists, unions
### Vectors, containers, lists
```python
# Recursively serialize
@ -170,19 +163,31 @@ fixed_parts = [part if part != None else variable_offsets[i] for i, part in enum
return b"".join(fixed_parts + variable_parts)
```
If `value` is a union type:
### Union
Define value as an object that has properties `value.value` with the contained value, and `value.type_index` which indexes the type.
A `value` as `Union[T...]` type has properties `value.value` with the contained value, and `value.selector` which indexes the selected `Union` type option `T`.
A `Union`:
- May have multiple selectors with the same type.
- Should not use selectors above 127 (i.e. highest bit is set), these are reserved for backwards compatible extensions.
- Must have at least 1 type option.
- May have `None` as first type option, i.e. `selector == 0`
- Must have at least 2 type options if the first is `None`
- Is always considered a variable-length type, even if all type options have an equal fixed-length.
```python
serialized_bytes = serialize(value.value)
serialized_type_index = value.type_index.to_bytes(BYTES_PER_LENGTH_OFFSET, "little")
return serialized_type_index + serialized_bytes
if value.value is None:
assert value.selector == 0
return b"\x00"
else:
serialized_bytes = serialize(value.value)
serialized_selector_index = value.selector.to_bytes(1, "little")
return serialized_selector_index + serialized_bytes
```
## Deserialization
Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to.
Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to.
Deserialization can be implemented using a recursive algorithm. The deserialization of basic objects is easy, and from there we can find a simple recursive algorithm for all fixed-size objects. For variable-size objects we have to do one of the following depending on what kind of object it is:
@ -191,12 +196,14 @@ Deserialization can be implemented using a recursive algorithm. The deserializat
* The size of each object in the vector/list can be inferred from the difference of two offsets. To get the size of the last object, the total number of bytes has to be known (it is not generally possible to deserialize an SSZ object of unknown length)
* Containers follow the same principles as vectors, with the difference that there may be fixed-size objects in a container as well. This means the `fixed_parts` data will contain offsets as well as fixed-size objects.
* In the case of bitlists, the length in bits cannot be uniquely inferred from the number of bytes in the object. Because of this, they have a bit at the end that is always set. This bit has to be used to infer the size of the bitlist in bits.
* In the case of unions, the first byte of the deserialization scope is deserialized as type selector, the remainder of the scope is deserialized as the selected type.
Note that deserialization requires hardening against invalid inputs. A non-exhaustive list:
- Offsets: out of order, out of range, mismatching minimum element size.
- Scope: Extra unused bytes, not aligned with element size.
- More elements than a list limit allows. Part of enforcing consensus.
- An out-of-bounds selected index in an `Union`
Efficient algorithms for computing this object can be found in [the implementations](#implementations).
@ -227,7 +234,7 @@ We first define helper functions:
- If `1` chunk: the root is the chunk itself.
- If `> 1` chunks: merkleize as binary tree.
* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`.
* `mix_in_type`: Given a Merkle root `root` and a type_index `type_index` (`"uint256"` little-endian serialization) return `hash(root + type_index)`.
* `mix_in_selector`: Given a Merkle root `root` and a type selector `selector` (`"uint256"` little-endian serialization) return `hash(root + selector)`.
We now define Merkleization `hash_tree_root(value)` of an object `value` recursively:
@ -237,7 +244,8 @@ We now define Merkleization `hash_tree_root(value)` of an object `value` recursi
* `mix_in_length(merkleize(pack_bits(value), limit=chunk_count(type)), len(value))` if `value` is a bitlist.
* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container.
* `mix_in_length(merkleize([hash_tree_root(element) for element in value], limit=chunk_count(type)), len(value))` if `value` is a list of composite objects.
* `mix_in_type(merkleize(value.value), value.type_index)` if `value` is of union type.
* `mix_in_selector(hash_tree_root(value.value), value.selector)` if `value` is of union type, and `value.value` is not `None`
* `mix_in_selector(Bytes32(), 0)` if `value` is of union type, and `value.value` is `None`
## Summaries and expansions

View File

@ -1,7 +1,7 @@
import random
from eth2spec.test.context import fork_transition_test
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_block
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_signed_block
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
from eth2spec.test.helpers.attestations import next_slots_with_attestations
@ -261,12 +261,12 @@ def _run_transition_test_with_attestations(state,
assert current_epoch == spec.GENESIS_EPOCH
# skip genesis epoch to avoid dealing with some edge cases...
block = next_epoch_via_block(spec, state)
block = next_epoch_via_signed_block(spec, state)
# regular state transition until fork:
fill_cur_epoch = False
fill_prev_epoch = True
blocks = [pre_tag(sign_block(spec, state, block))]
blocks = [pre_tag(block)]
current_epoch = spec.get_current_epoch(state)
for _ in range(current_epoch, fork_epoch - 1):
_, blocks_in_epoch, state = next_slots_with_attestations(
@ -414,8 +414,8 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe
# continue regular state transition but add attestations
# for enough epochs to finalize the ``fork_epoch``
block = next_epoch_via_block(post_spec, state)
blocks.append(post_tag(sign_block(post_spec, state, block)))
block = next_epoch_via_signed_block(post_spec, state)
blocks.append(post_tag(block))
for _ in range(4):
_, blocks_in_epoch, state = next_slots_with_attestations(
post_spec,

View File

@ -58,11 +58,19 @@ def next_epoch(spec, state):
spec.process_slots(state, slot)
def next_epoch_via_block(spec, state):
def next_epoch_via_block(spec, state, insert_state_root=False):
"""
Transition to the start slot of the next epoch via a full block transition
"""
return apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)
block = apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)
if insert_state_root:
block.state_root = state.hash_tree_root()
return block
def next_epoch_via_signed_block(spec, state):
block = next_epoch_via_block(spec, state, insert_state_root=True)
return sign_block(spec, state, block)
def get_state_root(spec, state, slot) -> bytes:

View File

@ -2,6 +2,7 @@
# Ignore linter: This module makes importing SSZ types easy, and hides away the underlying library from the spec.
from remerkleable.complex import Container, Vector, List
from remerkleable.union import Union
from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256
from remerkleable.bitfields import Bitvector, Bitlist
from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList