Merge pull request #1686 from ethereum/v011x
Release `v0.11.1` to master
This commit is contained in:
commit
e69f24f29b
4
Makefile
4
Makefile
|
@ -77,6 +77,10 @@ test: pyspec
|
||||||
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
||||||
python -m pytest -n 4 --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
|
python -m pytest -n 4 --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
|
||||||
|
|
||||||
|
find_test: pyspec
|
||||||
|
. venv/bin/activate; cd $(PY_SPEC_DIR); \
|
||||||
|
python -m pytest -k=$(K) --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
|
||||||
|
|
||||||
citest: pyspec
|
citest: pyspec
|
||||||
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
|
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
|
||||||
python -m pytest -n 4 --junitxml=eth2spec/test_results.xml eth2spec
|
python -m pytest -n 4 --junitxml=eth2spec/test_results.xml eth2spec
|
||||||
|
|
40
setup.py
40
setup.py
|
@ -92,6 +92,8 @@ from dataclasses import (
|
||||||
field,
|
field,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from lru import LRU
|
||||||
|
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
from eth2spec.utils.ssz.ssz_typing import (
|
from eth2spec.utils.ssz.ssz_typing import (
|
||||||
View, boolean, Container, List, Vector, uint64,
|
View, boolean, Container, List, Vector, uint64,
|
||||||
|
@ -114,6 +116,8 @@ from dataclasses import (
|
||||||
field,
|
field,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from lru import LRU
|
||||||
|
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
from eth2spec.utils.ssz.ssz_typing import (
|
from eth2spec.utils.ssz.ssz_typing import (
|
||||||
View, boolean, Container, List, Vector, uint64, uint8, bit,
|
View, boolean, Container, List, Vector, uint64, uint8, bit,
|
||||||
|
@ -152,8 +156,8 @@ def hash(x: bytes) -> Bytes32: # type: ignore
|
||||||
return hash_cache[x]
|
return hash_cache[x]
|
||||||
|
|
||||||
|
|
||||||
def cache_this(key_fn, value_fn): # type: ignore
|
def cache_this(key_fn, value_fn, lru_size): # type: ignore
|
||||||
cache_dict = {} # type: ignore
|
cache_dict = LRU(size=lru_size)
|
||||||
|
|
||||||
def wrapper(*args, **kw): # type: ignore
|
def wrapper(*args, **kw): # type: ignore
|
||||||
key = key_fn(*args, **kw)
|
key = key_fn(*args, **kw)
|
||||||
|
@ -164,35 +168,50 @@ def cache_this(key_fn, value_fn): # type: ignore
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
_compute_shuffled_index = compute_shuffled_index
|
||||||
|
compute_shuffled_index = cache_this(
|
||||||
|
lambda index, index_count, seed: (index, index_count, seed),
|
||||||
|
_compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3)
|
||||||
|
|
||||||
|
_get_total_active_balance = get_total_active_balance
|
||||||
|
get_total_active_balance = cache_this(
|
||||||
|
lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)),
|
||||||
|
_get_total_active_balance, lru_size=10)
|
||||||
|
|
||||||
_get_base_reward = get_base_reward
|
_get_base_reward = get_base_reward
|
||||||
get_base_reward = cache_this(
|
get_base_reward = cache_this(
|
||||||
lambda state, index: (state.validators.hash_tree_root(), state.slot),
|
lambda state, index: (state.validators.hash_tree_root(), state.slot, index),
|
||||||
_get_base_reward)
|
_get_base_reward, lru_size=2048)
|
||||||
|
|
||||||
_get_committee_count_at_slot = get_committee_count_at_slot
|
_get_committee_count_at_slot = get_committee_count_at_slot
|
||||||
get_committee_count_at_slot = cache_this(
|
get_committee_count_at_slot = cache_this(
|
||||||
lambda state, epoch: (state.validators.hash_tree_root(), epoch),
|
lambda state, epoch: (state.validators.hash_tree_root(), epoch),
|
||||||
_get_committee_count_at_slot)
|
_get_committee_count_at_slot, lru_size=SLOTS_PER_EPOCH * 3)
|
||||||
|
|
||||||
_get_active_validator_indices = get_active_validator_indices
|
_get_active_validator_indices = get_active_validator_indices
|
||||||
get_active_validator_indices = cache_this(
|
get_active_validator_indices = cache_this(
|
||||||
lambda state, epoch: (state.validators.hash_tree_root(), epoch),
|
lambda state, epoch: (state.validators.hash_tree_root(), epoch),
|
||||||
_get_active_validator_indices)
|
_get_active_validator_indices, lru_size=3)
|
||||||
|
|
||||||
_get_beacon_committee = get_beacon_committee
|
_get_beacon_committee = get_beacon_committee
|
||||||
get_beacon_committee = cache_this(
|
get_beacon_committee = cache_this(
|
||||||
lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index),
|
lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index),
|
||||||
_get_beacon_committee)
|
_get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)
|
||||||
|
|
||||||
_get_matching_target_attestations = get_matching_target_attestations
|
_get_matching_target_attestations = get_matching_target_attestations
|
||||||
get_matching_target_attestations = cache_this(
|
get_matching_target_attestations = cache_this(
|
||||||
lambda state, epoch: (state.hash_tree_root(), epoch),
|
lambda state, epoch: (state.hash_tree_root(), epoch),
|
||||||
_get_matching_target_attestations)
|
_get_matching_target_attestations, lru_size=10)
|
||||||
|
|
||||||
_get_matching_head_attestations = get_matching_head_attestations
|
_get_matching_head_attestations = get_matching_head_attestations
|
||||||
get_matching_head_attestations = cache_this(
|
get_matching_head_attestations = cache_this(
|
||||||
lambda state, epoch: (state.hash_tree_root(), epoch),
|
lambda state, epoch: (state.hash_tree_root(), epoch),
|
||||||
_get_matching_head_attestations)'''
|
_get_matching_head_attestations, lru_size=10)
|
||||||
|
|
||||||
|
_get_attesting_indices = get_attesting_indices
|
||||||
|
get_attesting_indices = cache_this(
|
||||||
|
lambda state, data, bits: (state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root()),
|
||||||
|
_get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
|
||||||
|
|
||||||
|
|
||||||
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
|
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
|
||||||
|
@ -481,6 +500,7 @@ setup(
|
||||||
"py_ecc==2.0.0",
|
"py_ecc==2.0.0",
|
||||||
"dataclasses==0.6",
|
"dataclasses==0.6",
|
||||||
"remerkleable==0.1.12",
|
"remerkleable==0.1.12",
|
||||||
"ruamel.yaml==0.16.5"
|
"ruamel.yaml==0.16.5",
|
||||||
|
"lru-dict==1.1.6"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -996,10 +996,11 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
||||||
```python
|
```python
|
||||||
def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
|
def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
|
||||||
"""
|
"""
|
||||||
Return the combined effective balance of the ``indices``. (1 Gwei minimum to avoid divisions by zero.)
|
Return the combined effective balance of the ``indices``.
|
||||||
|
``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
||||||
Math safe up to ~10B ETH, afterwhich this overflows uint64.
|
Math safe up to ~10B ETH, afterwhich this overflows uint64.
|
||||||
"""
|
"""
|
||||||
return Gwei(max(1, sum([state.validators[index].effective_balance for index in indices])))
|
return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices])))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_total_active_balance`
|
#### `get_total_active_balance`
|
||||||
|
@ -1008,6 +1009,7 @@ def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
|
||||||
def get_total_active_balance(state: BeaconState) -> Gwei:
|
def get_total_active_balance(state: BeaconState) -> Gwei:
|
||||||
"""
|
"""
|
||||||
Return the combined effective balance of the active validators.
|
Return the combined effective balance of the active validators.
|
||||||
|
Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
||||||
"""
|
"""
|
||||||
return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state))))
|
return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state))))
|
||||||
```
|
```
|
||||||
|
@ -1289,6 +1291,10 @@ def get_unslashed_attesting_indices(state: BeaconState,
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei:
|
def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei:
|
||||||
|
"""
|
||||||
|
Return the combined effective balance of the set of unslashed validators participating in ``attestations``.
|
||||||
|
Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
|
||||||
|
"""
|
||||||
return get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
return get_total_balance(state, get_unslashed_attesting_indices(state, attestations))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1366,7 +1372,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
|
||||||
if index in unslashed_attesting_indices:
|
if index in unslashed_attesting_indices:
|
||||||
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
|
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
|
||||||
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
|
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
|
||||||
rewards[index] = reward_numerator // (total_balance // increment)
|
rewards[index] += reward_numerator // (total_balance // increment)
|
||||||
else:
|
else:
|
||||||
penalties[index] += get_base_reward(state, index)
|
penalties[index] += get_base_reward(state, index)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ It consists of four main sections:
|
||||||
- [Multiplexing](#multiplexing)
|
- [Multiplexing](#multiplexing)
|
||||||
- [Eth2 network interaction domains](#eth2-network-interaction-domains)
|
- [Eth2 network interaction domains](#eth2-network-interaction-domains)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
|
- [MetaData](#metadata)
|
||||||
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
|
- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub)
|
||||||
- [Topics and messages](#topics-and-messages)
|
- [Topics and messages](#topics-and-messages)
|
||||||
- [Global topics](#global-topics)
|
- [Global topics](#global-topics)
|
||||||
|
@ -49,6 +50,8 @@ It consists of four main sections:
|
||||||
- [Goodbye](#goodbye)
|
- [Goodbye](#goodbye)
|
||||||
- [BeaconBlocksByRange](#beaconblocksbyrange)
|
- [BeaconBlocksByRange](#beaconblocksbyrange)
|
||||||
- [BeaconBlocksByRoot](#beaconblocksbyroot)
|
- [BeaconBlocksByRoot](#beaconblocksbyroot)
|
||||||
|
- [Ping](#ping)
|
||||||
|
- [GetMetaData](#getmetadata)
|
||||||
- [The discovery domain: discv5](#the-discovery-domain-discv5)
|
- [The discovery domain: discv5](#the-discovery-domain-discv5)
|
||||||
- [Integration into libp2p stacks](#integration-into-libp2p-stacks)
|
- [Integration into libp2p stacks](#integration-into-libp2p-stacks)
|
||||||
- [ENR structure](#enr-structure)
|
- [ENR structure](#enr-structure)
|
||||||
|
@ -157,8 +160,7 @@ The following SecIO parameters MUST be supported by all stacks:
|
||||||
The [Libp2p-noise](https://github.com/libp2p/specs/tree/master/noise) secure
|
The [Libp2p-noise](https://github.com/libp2p/specs/tree/master/noise) secure
|
||||||
channel handshake with `secp256k1` identities will be used for mainnet.
|
channel handshake with `secp256k1` identities will be used for mainnet.
|
||||||
|
|
||||||
As specified in the libp2p specification, clients MUST support the `XX` handshake pattern and
|
As specified in the libp2p specification, clients MUST support the `XX` handshake pattern.
|
||||||
can optionally implement the `IK` and `XXfallback` patterns for optimistic 0-RTT.
|
|
||||||
|
|
||||||
## Protocol Negotiation
|
## Protocol Negotiation
|
||||||
|
|
||||||
|
@ -196,6 +198,24 @@ This section outlines constants that are used in this spec.
|
||||||
| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. |
|
| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. |
|
||||||
| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. |
|
| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. |
|
||||||
|
|
||||||
|
## MetaData
|
||||||
|
|
||||||
|
Clients MUST locally store the following `MetaData`:
|
||||||
|
|
||||||
|
```
|
||||||
|
(
|
||||||
|
seq_number: uint64
|
||||||
|
attnets: Bitvector[ATTESTATION_SUBNET_COUNT]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where
|
||||||
|
|
||||||
|
- `seq_number` is a `uint64` starting at `0` used to version the node's metadata. If any other field in the local `MetaData` changes, the node MUST increment `seq_number` by 1.
|
||||||
|
- `attnets` is a `Bitvector` representing the node's persistent attestation subnet subscriptions.
|
||||||
|
|
||||||
|
*Note*: `MetaData.seq_number` is used for versioning of the node's metadata, is entirely independent of the ENR sequence number, and will in most cases be out of sync with the ENR sequence number.
|
||||||
|
|
||||||
## The gossip domain: gossipsub
|
## The gossip domain: gossipsub
|
||||||
|
|
||||||
Clients MUST support the [gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p protocol.
|
Clients MUST support the [gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p protocol.
|
||||||
|
@ -260,9 +280,10 @@ There are two primary global topics used to propagate beacon blocks and aggregat
|
||||||
|
|
||||||
- `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network
|
- `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network
|
||||||
- The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
|
- The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
|
||||||
- The block is from a slot greater than the latest finalized slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc).
|
- The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc).
|
||||||
- The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`.
|
- The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`.
|
||||||
- The proposer signature, `signed_beacon_block.signature`, is valid.
|
- The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey.
|
||||||
|
- The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated.
|
||||||
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
|
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`)
|
||||||
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot).
|
- `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot).
|
||||||
- The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
|
- The aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
|
||||||
|
@ -601,6 +622,60 @@ Clients MUST support requesting blocks since the latest finalized epoch.
|
||||||
|
|
||||||
Clients MUST respond with at least one block, if they have it. Clients MAY limit the number of blocks in the response.
|
Clients MUST respond with at least one block, if they have it. Clients MAY limit the number of blocks in the response.
|
||||||
|
|
||||||
|
#### Ping
|
||||||
|
|
||||||
|
**Protocol ID:** `/eth2/beacon_chain/req/ping/1/`
|
||||||
|
|
||||||
|
Request Content:
|
||||||
|
|
||||||
|
```
|
||||||
|
(
|
||||||
|
uint64
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Response Content:
|
||||||
|
|
||||||
|
```
|
||||||
|
(
|
||||||
|
uint64
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sent intermittently, the `Ping` protocol checks liveness of connected peers.
|
||||||
|
Peers request and respond with their local metadata sequence number (`MetaData.seq_number`).
|
||||||
|
|
||||||
|
If the peer does not respond to the `Ping` request, the client MAY disconnect from the peer.
|
||||||
|
|
||||||
|
A client can then determine if their local record of a peer's MetaData is up to date
|
||||||
|
and MAY request an updated version via the `MetaData` RPC method if not.
|
||||||
|
|
||||||
|
The request MUST be encoded as an SSZ-field.
|
||||||
|
|
||||||
|
The response MUST consist of a single `response_chunk`.
|
||||||
|
|
||||||
|
#### GetMetaData
|
||||||
|
|
||||||
|
**Protocol ID:** `/eth2/beacon_chain/req/metadata/1/`
|
||||||
|
|
||||||
|
No Request Content.
|
||||||
|
|
||||||
|
Response Content:
|
||||||
|
|
||||||
|
```
|
||||||
|
(
|
||||||
|
MetaData
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Requests the MetaData of a peer. The request opens and negotiates the stream without
|
||||||
|
sending any request content. Once established the receiving peer responds with
|
||||||
|
it's local most up-to-date MetaData.
|
||||||
|
|
||||||
|
The response MUST be encoded as an SSZ-container.
|
||||||
|
|
||||||
|
The response MUST consist of a single `response_chunk`.
|
||||||
|
|
||||||
## The discovery domain: discv5
|
## The discovery domain: discv5
|
||||||
|
|
||||||
Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery, both in the interoperability testnet and mainnet.
|
Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery, both in the interoperability testnet and mainnet.
|
||||||
|
@ -622,6 +697,9 @@ This integration enables the libp2p stack to subsequently form connections and s
|
||||||
The Ethereum Node Record (ENR) for an Ethereum 2.0 client MUST contain the following entries (exclusive of the sequence number and signature, which MUST be present in an ENR):
|
The Ethereum Node Record (ENR) for an Ethereum 2.0 client MUST contain the following entries (exclusive of the sequence number and signature, which MUST be present in an ENR):
|
||||||
|
|
||||||
- The compressed secp256k1 publickey, 33 bytes (`secp256k1` field).
|
- The compressed secp256k1 publickey, 33 bytes (`secp256k1` field).
|
||||||
|
|
||||||
|
The ENR MAY contain the following entries:
|
||||||
|
|
||||||
- An IPv4 address (`ip` field) and/or IPv6 address (`ip6` field).
|
- An IPv4 address (`ip` field) and/or IPv6 address (`ip6` field).
|
||||||
- A TCP port (`tcp` field) representing the local libp2p listening port.
|
- A TCP port (`tcp` field) representing the local libp2p listening port.
|
||||||
- A UDP port (`udp` field) representing the local discv5 listening port.
|
- A UDP port (`udp` field) representing the local discv5 listening port.
|
||||||
|
@ -630,12 +708,16 @@ Specifications of these parameters can be found in the [ENR Specification](http:
|
||||||
|
|
||||||
#### Attestation subnet bitfield
|
#### Attestation subnet bitfield
|
||||||
|
|
||||||
The ENR MAY contain an entry (`attnets`) signifying the attestation subnet bitfield with the following form to more easily discover peers participating in particular attestation gossip subnets.
|
The ENR `attnets` entry signifies the attestation subnet bitfield with the following form to more easily discover peers participating in particular attestation gossip subnets.
|
||||||
|
|
||||||
| Key | Value |
|
| Key | Value |
|
||||||
|:-------------|:-------------------------------------------------|
|
|:-------------|:-------------------------------------------------|
|
||||||
| `attnets` | SSZ `Bitvector[ATTESTATION_SUBNET_COUNT]` |
|
| `attnets` | SSZ `Bitvector[ATTESTATION_SUBNET_COUNT]` |
|
||||||
|
|
||||||
|
If a node's `MetaData.attnets` has any non-zero bit, the ENR MUST include the `attnets` entry with the same value as `MetaData.attnets`.
|
||||||
|
|
||||||
|
If a node's `MetaData.attnets` is composed of all zeros, the ENR MAY optionally include the `attnets` entry or leave it out entirely.
|
||||||
|
|
||||||
#### Interop
|
#### Interop
|
||||||
|
|
||||||
In the interoperability testnet, all peers will support all capabilities defined in this document (gossip, full Req/Resp suite, discovery protocol), therefore the ENR record does not need to carry Eth2 capability information, as it would be superfluous.
|
In the interoperability testnet, all peers will support all capabilities defined in this document (gossip, full Req/Resp suite, discovery protocol), therefore the ENR record does not need to carry Eth2 capability information, as it would be superfluous.
|
||||||
|
|
|
@ -200,9 +200,11 @@ The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahe
|
||||||
|
|
||||||
Specifically a validator should:
|
Specifically a validator should:
|
||||||
* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
|
* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
|
||||||
* Join the pubsub topic -- `committee_index{committee_index % ATTESTATION_SUBNET_COUNT}_beacon_attestation`.
|
* Find peers of the pubsub topic `committee_index{committee_index % ATTESTATION_SUBNET_COUNT}_beacon_attestation`.
|
||||||
* For any current peer subscribed to the topic, the validator simply sends a `subscribe` message for the new topic.
|
* 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.
|
||||||
* 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`.
|
* 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.
|
||||||
|
|
||||||
## Beacon chain responsibilities
|
## Beacon chain responsibilities
|
||||||
|
|
||||||
|
@ -404,12 +406,12 @@ Set `attestation.data = attestation_data` where `attestation_data` is the `Attes
|
||||||
|
|
||||||
##### Aggregate signature
|
##### Aggregate signature
|
||||||
|
|
||||||
Set `attestation.signature = signed_attestation_data` where `signed_attestation_data` is obtained from:
|
Set `attestation.signature = attestation_signature` where `attestation_signature` is obtained from:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_signed_attestation_data(state: BeaconState, attestation: IndexedAttestation, privkey: int) -> BLSSignature:
|
def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature:
|
||||||
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
|
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
|
||||||
signing_root = compute_signing_root(attestation.data, domain)
|
signing_root = compute_signing_root(attestation_data, domain)
|
||||||
return bls.Sign(privkey, signing_root)
|
return bls.Sign(privkey, signing_root)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from .helpers.genesis import create_genesis_state
|
||||||
|
|
||||||
from .utils import vector_test, with_meta_tags
|
from .utils import vector_test, with_meta_tags
|
||||||
|
|
||||||
|
from random import Random
|
||||||
from typing import Any, Callable, Sequence, TypedDict, Protocol
|
from typing import Any, Callable, Sequence, TypedDict, Protocol
|
||||||
|
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
@ -71,6 +72,14 @@ def default_activation_threshold(spec):
|
||||||
return spec.MAX_EFFECTIVE_BALANCE
|
return spec.MAX_EFFECTIVE_BALANCE
|
||||||
|
|
||||||
|
|
||||||
|
def zero_activation_threshold(spec):
|
||||||
|
"""
|
||||||
|
Helper method to use 0 gwei as the activation threshold for state creation for tests.
|
||||||
|
Usage: `@with_custom_state(threshold_fn=zero_activation_threshold, ...)`
|
||||||
|
"""
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def default_balances(spec):
|
def default_balances(spec):
|
||||||
"""
|
"""
|
||||||
Helper method to create a series of default balances.
|
Helper method to create a series of default balances.
|
||||||
|
@ -100,8 +109,18 @@ def misc_balances(spec):
|
||||||
Usage: `@with_custom_state(balances_fn=misc_balances, ...)`
|
Usage: `@with_custom_state(balances_fn=misc_balances, ...)`
|
||||||
"""
|
"""
|
||||||
num_validators = spec.SLOTS_PER_EPOCH * 8
|
num_validators = spec.SLOTS_PER_EPOCH * 8
|
||||||
num_misc_validators = spec.SLOTS_PER_EPOCH
|
balances = [spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators for i in range(num_validators)]
|
||||||
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + [spec.MIN_DEPOSIT_AMOUNT] * num_misc_validators
|
rng = Random(1234)
|
||||||
|
rng.shuffle(balances)
|
||||||
|
return balances
|
||||||
|
|
||||||
|
|
||||||
|
def low_single_balance(spec):
|
||||||
|
"""
|
||||||
|
Helper method to create a single of balance of 1 Gwei.
|
||||||
|
Usage: `@with_custom_state(balances_fn=low_single_balance, ...)`
|
||||||
|
"""
|
||||||
|
return [1]
|
||||||
|
|
||||||
|
|
||||||
def single_phase(fn):
|
def single_phase(fn):
|
||||||
|
|
|
@ -39,7 +39,7 @@ def build_attestation_data(spec, state, slot, index):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_valid_attestation(spec, state, slot=None, index=None, signed=False):
|
def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False):
|
||||||
if slot is None:
|
if slot is None:
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
if index is None:
|
if index is None:
|
||||||
|
@ -59,6 +59,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, signed=False):
|
||||||
aggregation_bits=aggregation_bits,
|
aggregation_bits=aggregation_bits,
|
||||||
data=attestation_data,
|
data=attestation_data,
|
||||||
)
|
)
|
||||||
|
if not empty:
|
||||||
fill_aggregate_attestation(spec, state, attestation)
|
fill_aggregate_attestation(spec, state, attestation)
|
||||||
if signed:
|
if signed:
|
||||||
sign_attestation(spec, state, attestation)
|
sign_attestation(spec, state, attestation)
|
||||||
|
|
|
@ -292,14 +292,12 @@ def test_bad_source_root(spec, state):
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_empty_aggregation_bits(spec, state):
|
def test_empty_aggregation_bits(spec, state):
|
||||||
attestation = get_valid_attestation(spec, state)
|
attestation = get_valid_attestation(spec, state, empty=True)
|
||||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](
|
assert attestation.aggregation_bits == Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](
|
||||||
*([0b0] * len(attestation.aggregation_bits)))
|
*([0b0] * len(attestation.aggregation_bits)))
|
||||||
|
|
||||||
sign_attestation(spec, state, attestation)
|
|
||||||
|
|
||||||
yield from run_attestation_processing(spec, state, attestation)
|
yield from run_attestation_processing(spec, state, attestation)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
spec_state_test, with_all_phases, spec_test,
|
spec_state_test, with_all_phases, spec_test,
|
||||||
misc_balances, with_custom_state, default_activation_threshold,
|
misc_balances, with_custom_state,
|
||||||
|
low_single_balance, zero_activation_threshold,
|
||||||
single_phase,
|
single_phase,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.state import (
|
from eth2spec.test.helpers.state import (
|
||||||
|
@ -24,7 +23,7 @@ def run_process_rewards_and_penalties(spec, state):
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_genesis_epoch_no_attestations_no_penalties(spec, state):
|
def test_genesis_epoch_no_attestations_no_penalties(spec, state):
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH
|
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state):
|
||||||
# ensure has not cross the epoch boundary
|
# ensure has not cross the epoch boundary
|
||||||
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH
|
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH
|
||||||
|
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
yield from run_process_rewards_and_penalties(spec, state)
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
@ -60,12 +59,12 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state):
|
||||||
assert state.balances[index] == pre_state.balances[index]
|
assert state.balances[index] == pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
def prepare_state_with_full_attestations(spec, state):
|
def prepare_state_with_full_attestations(spec, state, empty=False):
|
||||||
attestations = []
|
attestations = []
|
||||||
for slot in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
for slot in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
||||||
# create an attestation for each slot in epoch
|
# create an attestation for each slot in epoch
|
||||||
if slot < spec.SLOTS_PER_EPOCH:
|
if slot < spec.SLOTS_PER_EPOCH:
|
||||||
attestation = get_valid_attestation(spec, state, signed=True)
|
attestation = get_valid_attestation(spec, state, empty=empty, signed=True)
|
||||||
attestations.append(attestation)
|
attestations.append(attestation)
|
||||||
# fill each created slot in state after inclusion delay
|
# fill each created slot in state after inclusion delay
|
||||||
if slot - spec.MIN_ATTESTATION_INCLUSION_DELAY >= 0:
|
if slot - spec.MIN_ATTESTATION_INCLUSION_DELAY >= 0:
|
||||||
|
@ -84,7 +83,7 @@ def prepare_state_with_full_attestations(spec, state):
|
||||||
def test_full_attestations(spec, state):
|
def test_full_attestations(spec, state):
|
||||||
attestations = prepare_state_with_full_attestations(spec, state)
|
attestations = prepare_state_with_full_attestations(spec, state)
|
||||||
|
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
yield from run_process_rewards_and_penalties(spec, state)
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
@ -122,18 +121,19 @@ def test_full_attestations_random_incorrect_fields(spec, state):
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_test
|
@spec_test
|
||||||
@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold)
|
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.MAX_EFFECTIVE_BALANCE // 2)
|
||||||
@single_phase
|
@single_phase
|
||||||
def test_full_attestations_misc_balances(spec, state):
|
def test_full_attestations_misc_balances(spec, state):
|
||||||
attestations = prepare_state_with_full_attestations(spec, state)
|
attestations = prepare_state_with_full_attestations(spec, state)
|
||||||
|
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
yield from run_process_rewards_and_penalties(spec, state)
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
||||||
assert len(attesting_indices) > 0
|
assert len(attesting_indices) > 0
|
||||||
assert len(attesting_indices) != len(pre_state.validators)
|
assert len(attesting_indices) != len(pre_state.validators)
|
||||||
|
assert any(v.effective_balance != spec.MAX_EFFECTIVE_BALANCE for v in state.validators)
|
||||||
for index in range(len(pre_state.validators)):
|
for index in range(len(pre_state.validators)):
|
||||||
if index in attesting_indices:
|
if index in attesting_indices:
|
||||||
assert state.balances[index] > pre_state.balances[index]
|
assert state.balances[index] > pre_state.balances[index]
|
||||||
|
@ -141,13 +141,35 @@ def test_full_attestations_misc_balances(spec, state):
|
||||||
assert state.balances[index] < pre_state.balances[index]
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
else:
|
else:
|
||||||
assert state.balances[index] == pre_state.balances[index]
|
assert state.balances[index] == pre_state.balances[index]
|
||||||
|
# Check if base rewards are consistent with effective balance.
|
||||||
|
brs = {}
|
||||||
|
for index in attesting_indices:
|
||||||
|
br = spec.get_base_reward(state, index)
|
||||||
|
if br in brs:
|
||||||
|
assert brs[br] == state.validators[index].effective_balance
|
||||||
|
else:
|
||||||
|
brs[br] = state.validators[index].effective_balance
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_test
|
||||||
|
@with_custom_state(balances_fn=low_single_balance, threshold_fn=zero_activation_threshold)
|
||||||
|
@single_phase
|
||||||
|
def test_full_attestations_one_validaor_one_gwei(spec, state):
|
||||||
|
attestations = prepare_state_with_full_attestations(spec, state)
|
||||||
|
|
||||||
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
# Few assertions. Mainly to check that this extreme case can run without exception
|
||||||
|
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
||||||
|
assert len(attesting_indices) == 1
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_no_attestations_all_penalties(spec, state):
|
def test_no_attestations_all_penalties(spec, state):
|
||||||
next_epoch(spec, state)
|
next_epoch(spec, state)
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH + 1
|
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH + 1
|
||||||
|
|
||||||
|
@ -157,6 +179,22 @@ def test_no_attestations_all_penalties(spec, state):
|
||||||
assert state.balances[index] < pre_state.balances[index]
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_empty_attestations(spec, state):
|
||||||
|
attestations = prepare_state_with_full_attestations(spec, state, empty=True)
|
||||||
|
|
||||||
|
pre_state = state.copy()
|
||||||
|
|
||||||
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
||||||
|
assert len(attesting_indices) == 0
|
||||||
|
|
||||||
|
for index in range(len(pre_state.validators)):
|
||||||
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_duplicate_attestation(spec, state):
|
def test_duplicate_attestation(spec, state):
|
||||||
|
@ -173,8 +211,8 @@ def test_duplicate_attestation(spec, state):
|
||||||
|
|
||||||
assert len(participants) > 0
|
assert len(participants) > 0
|
||||||
|
|
||||||
single_state = deepcopy(state)
|
single_state = state.copy()
|
||||||
dup_state = deepcopy(state)
|
dup_state = state.copy()
|
||||||
|
|
||||||
inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY
|
inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
add_attestations_to_state(spec, single_state, [attestation], inclusion_slot)
|
add_attestations_to_state(spec, single_state, [attestation], inclusion_slot)
|
||||||
|
@ -220,7 +258,7 @@ def test_attestations_some_slashed(spec, state):
|
||||||
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH + 1
|
assert spec.compute_epoch_at_slot(state.slot) == spec.GENESIS_EPOCH + 1
|
||||||
assert len(state.previous_epoch_attestations) == spec.SLOTS_PER_EPOCH
|
assert len(state.previous_epoch_attestations) == spec.SLOTS_PER_EPOCH
|
||||||
|
|
||||||
pre_state = deepcopy(state)
|
pre_state = state.copy()
|
||||||
|
|
||||||
yield from run_process_rewards_and_penalties(spec, state)
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue