Merge branch 'dev' into eip7002
This commit is contained in:
commit
d4483e4ca7
8
setup.py
8
setup.py
|
@ -384,7 +384,7 @@ from typing import (
|
||||||
|
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
|
||||||
from eth2spec.utils.ssz.ssz_typing import (
|
from eth2spec.utils.ssz.ssz_typing import (
|
||||||
View, boolean, Container, List, Vector, uint8, uint32, uint64,
|
View, boolean, Container, List, Vector, uint8, uint32, uint64, uint256,
|
||||||
Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist)
|
Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist)
|
||||||
from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401
|
from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401
|
||||||
from eth2spec.utils import bls
|
from eth2spec.utils import bls
|
||||||
|
@ -552,7 +552,7 @@ class BellatrixSpecBuilder(AltairSpecBuilder):
|
||||||
return super().imports(preset_name) + f'''
|
return super().imports(preset_name) + f'''
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
from eth2spec.altair import {preset_name} as altair
|
from eth2spec.altair import {preset_name} as altair
|
||||||
from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256
|
from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -589,7 +589,7 @@ class NoopExecutionEngine(ExecutionEngine):
|
||||||
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
|
payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
|
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
raise NotImplementedError("no default block production")
|
raise NotImplementedError("no default block production")
|
||||||
|
|
||||||
|
@ -1207,7 +1207,7 @@ setup(
|
||||||
extras_require={
|
extras_require={
|
||||||
"test": ["pytest>=4.4", "pytest-cov", "pytest-xdist"],
|
"test": ["pytest>=4.4", "pytest-cov", "pytest-xdist"],
|
||||||
"lint": ["flake8==5.0.4", "mypy==0.981", "pylint==2.15.3"],
|
"lint": ["flake8==5.0.4", "mypy==0.981", "pylint==2.15.3"],
|
||||||
"generator": ["python-snappy==0.6.1", "filelock"],
|
"generator": ["python-snappy==0.6.1", "filelock", "pathos==0.3.0"],
|
||||||
"docs": ["mkdocs==1.4.2", "mkdocs-material==9.1.5", "mdx-truly-sane-lists==1.3", "mkdocs-awesome-pages-plugin==2.8.0"]
|
"docs": ["mkdocs==1.4.2", "mkdocs-material==9.1.5", "mdx-truly-sane-lists==1.3", "mkdocs-awesome-pages-plugin==2.8.0"]
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
- [Protocols](#protocols)
|
- [Protocols](#protocols)
|
||||||
- [`ExecutionEngine`](#executionengine)
|
- [`ExecutionEngine`](#executionengine)
|
||||||
- [`get_payload`](#get_payload)
|
- [Modified `get_payload`](#modified-get_payload)
|
||||||
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
||||||
- [Block proposal](#block-proposal)
|
- [Block proposal](#block-proposal)
|
||||||
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
||||||
|
@ -40,7 +40,7 @@ Please see related Beacon Chain doc before continuing and use them as a referenc
|
||||||
|
|
||||||
### `ExecutionEngine`
|
### `ExecutionEngine`
|
||||||
|
|
||||||
#### `get_payload`
|
#### Modified `get_payload`
|
||||||
|
|
||||||
`get_payload` returns the upgraded EIP-4788 `ExecutionPayload` type.
|
`get_payload` returns the upgraded EIP-4788 `ExecutionPayload` type.
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
|
- [`GetPayloadResponse`](#getpayloadresponse)
|
||||||
- [`get_pow_block_at_terminal_total_difficulty`](#get_pow_block_at_terminal_total_difficulty)
|
- [`get_pow_block_at_terminal_total_difficulty`](#get_pow_block_at_terminal_total_difficulty)
|
||||||
- [`get_terminal_pow_block`](#get_terminal_pow_block)
|
- [`get_terminal_pow_block`](#get_terminal_pow_block)
|
||||||
- [Protocols](#protocols)
|
- [Protocols](#protocols)
|
||||||
|
@ -36,6 +37,14 @@ Please see related Beacon Chain doc before continuing and use them as a referenc
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
|
### `GetPayloadResponse`
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GetPayloadResponse(object):
|
||||||
|
execution_payload: ExecutionPayload
|
||||||
|
```
|
||||||
|
|
||||||
### `get_pow_block_at_terminal_total_difficulty`
|
### `get_pow_block_at_terminal_total_difficulty`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -83,13 +92,13 @@ The Engine API may be used to implement it with an external execution engine.
|
||||||
|
|
||||||
#### `get_payload`
|
#### `get_payload`
|
||||||
|
|
||||||
Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that
|
Given the `payload_id`, `get_payload` returns `GetPayloadResponse` with the most recent version of
|
||||||
has been built since the corresponding call to `notify_forkchoice_updated` method.
|
the execution payload that has been built since the corresponding call to `notify_forkchoice_updated` method.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
|
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
|
||||||
"""
|
"""
|
||||||
Return ``execution_payload`` object.
|
Return ``GetPayloadResponse`` object.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
@ -162,7 +171,7 @@ def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: Exe
|
||||||
# Pre-merge, empty payload
|
# Pre-merge, empty payload
|
||||||
return ExecutionPayload()
|
return ExecutionPayload()
|
||||||
else:
|
else:
|
||||||
return execution_engine.get_payload(payload_id)
|
return execution_engine.get_payload(payload_id).execution_payload
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note*: It is recommended for a validator to call `prepare_execution_payload` as soon as input parameters become known,
|
*Note*: It is recommended for a validator to call `prepare_execution_payload` as soon as input parameters become known,
|
||||||
|
|
|
@ -11,9 +11,10 @@
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
|
- [Modified `GetPayloadResponse`](#modified-getpayloadresponse)
|
||||||
- [Protocols](#protocols)
|
- [Protocols](#protocols)
|
||||||
- [`ExecutionEngine`](#executionengine)
|
- [`ExecutionEngine`](#executionengine)
|
||||||
- [`get_payload`](#get_payload)
|
- [Modified `get_payload`](#modified-get_payload)
|
||||||
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
||||||
- [Block proposal](#block-proposal)
|
- [Block proposal](#block-proposal)
|
||||||
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
||||||
|
@ -39,11 +40,20 @@ Please see related Beacon Chain doc before continuing and use them as a referenc
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
|
### Modified `GetPayloadResponse`
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GetPayloadResponse(object):
|
||||||
|
execution_payload: ExecutionPayload
|
||||||
|
block_value: uint256
|
||||||
|
```
|
||||||
|
|
||||||
## Protocols
|
## Protocols
|
||||||
|
|
||||||
### `ExecutionEngine`
|
### `ExecutionEngine`
|
||||||
|
|
||||||
#### `get_payload`
|
#### Modified `get_payload`
|
||||||
|
|
||||||
`get_payload` returns the upgraded Capella `ExecutionPayload` type.
|
`get_payload` returns the upgraded Capella `ExecutionPayload` type.
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,7 @@ This is the modification of the fork choice accompanying the Deneb upgrade.
|
||||||
def validate_blobs(expected_kzg_commitments: Sequence[KZGCommitment],
|
def validate_blobs(expected_kzg_commitments: Sequence[KZGCommitment],
|
||||||
blobs: Sequence[Blob],
|
blobs: Sequence[Blob],
|
||||||
proofs: Sequence[KZGProof]) -> None:
|
proofs: Sequence[KZGProof]) -> None:
|
||||||
assert len(expected_kzg_commitments) == len(blobs)
|
assert len(expected_kzg_commitments) == len(blobs) == len(proofs)
|
||||||
assert len(blobs) == len(proofs)
|
|
||||||
|
|
||||||
assert verify_blob_kzg_proof_batch(blobs, expected_kzg_commitments, proofs)
|
assert verify_blob_kzg_proof_batch(blobs, expected_kzg_commitments, proofs)
|
||||||
```
|
```
|
||||||
|
|
|
@ -22,7 +22,7 @@ The specification of these changes continues in the same format as the network s
|
||||||
- [Topics and messages](#topics-and-messages)
|
- [Topics and messages](#topics-and-messages)
|
||||||
- [Global topics](#global-topics)
|
- [Global topics](#global-topics)
|
||||||
- [`beacon_block`](#beacon_block)
|
- [`beacon_block`](#beacon_block)
|
||||||
- [`blob_sidecar_{index}`](#blob_sidecar_index)
|
- [`blob_sidecar_{subnet_id}`](#blob_sidecar_subnet_id)
|
||||||
- [Transitioning the gossip](#transitioning-the-gossip)
|
- [Transitioning the gossip](#transitioning-the-gossip)
|
||||||
- [The Req/Resp domain](#the-reqresp-domain)
|
- [The Req/Resp domain](#the-reqresp-domain)
|
||||||
- [Messages](#messages)
|
- [Messages](#messages)
|
||||||
|
@ -107,7 +107,7 @@ The new topics along with the type of the `data` field of a gossipsub message ar
|
||||||
|
|
||||||
| Name | Message Type |
|
| Name | Message Type |
|
||||||
| - | - |
|
| - | - |
|
||||||
| `blob_sidecar_{index}` | `SignedBlobSidecar` (new) |
|
| `blob_sidecar_{subnet_id}` | `SignedBlobSidecar` (new) |
|
||||||
|
|
||||||
##### Global topics
|
##### Global topics
|
||||||
|
|
||||||
|
@ -117,13 +117,13 @@ Deneb introduces new global topics for blob sidecars.
|
||||||
|
|
||||||
The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in deneb.
|
The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in deneb.
|
||||||
|
|
||||||
###### `blob_sidecar_{index}`
|
###### `blob_sidecar_{subnet_id}`
|
||||||
|
|
||||||
This topic is used to propagate signed blob sidecars, one for each sidecar index. The number of indices is defined by `MAX_BLOBS_PER_BLOCK`.
|
This topic is used to propagate signed blob sidecars, where each blob index maps to some `subnet_id`.
|
||||||
|
|
||||||
The following validations MUST pass before forwarding the `signed_blob_sidecar` on the network, assuming the alias `sidecar = signed_blob_sidecar.message`:
|
The following validations MUST pass before forwarding the `signed_blob_sidecar` on the network, assuming the alias `sidecar = signed_blob_sidecar.message`:
|
||||||
|
|
||||||
- _[REJECT]_ The sidecar is for the correct topic -- i.e. `sidecar.index` matches the topic `{index}`.
|
- _[REJECT]_ The sidecar is for the correct subnet -- i.e. `compute_subnet_for_blob_sidecar(sidecar.index) == subnet_id`.
|
||||||
- _[IGNORE]_ The sidecar is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `sidecar.slot <= current_slot` (a client MAY queue future sidecars for processing at the appropriate slot).
|
- _[IGNORE]_ The sidecar is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `sidecar.slot <= current_slot` (a client MAY queue future sidecars for processing at the appropriate slot).
|
||||||
- _[IGNORE]_ The sidecar is from a slot greater than the latest finalized slot -- i.e. validate that `sidecar.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)`
|
- _[IGNORE]_ The sidecar is from a slot greater than the latest finalized slot -- i.e. validate that `sidecar.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)`
|
||||||
- _[IGNORE]_ The sidecar's block's parent (defined by `sidecar.block_parent_root`) has been seen (via both gossip and non-gossip sources) (a client MAY queue sidecars for processing once the parent block is retrieved).
|
- _[IGNORE]_ The sidecar's block's parent (defined by `sidecar.block_parent_root`) has been seen (via both gossip and non-gossip sources) (a client MAY queue sidecars for processing once the parent block is retrieved).
|
||||||
|
@ -226,7 +226,7 @@ No more than `MAX_REQUEST_BLOB_SIDECARS` may be requested at a time.
|
||||||
The response MUST consist of zero or more `response_chunk`.
|
The response MUST consist of zero or more `response_chunk`.
|
||||||
Each _successful_ `response_chunk` MUST contain a single `BlobSidecar` payload.
|
Each _successful_ `response_chunk` MUST contain a single `BlobSidecar` payload.
|
||||||
|
|
||||||
Clients MUST support requesting sidecars since `minimum_request_epoch`, where `minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH)`. If any root in the request content references a block earlier than `minimum_request_epoch`, peers MAY respond with error code `3: ResourceUnavailable` or not include the blob in the response.
|
Clients MUST support requesting sidecars since `minimum_request_epoch`, where `minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH)`. If any root in the request content references a block earlier than `minimum_request_epoch`, peers MAY respond with error code `3: ResourceUnavailable` or not include the blob sidecar in the response.
|
||||||
|
|
||||||
Clients MUST respond with at least one sidecar, if they have it.
|
Clients MUST respond with at least one sidecar, if they have it.
|
||||||
Clients MAY limit the number of blocks and sidecars in the response.
|
Clients MAY limit the number of blocks and sidecars in the response.
|
||||||
|
|
|
@ -566,7 +566,7 @@ def verify_blob_kzg_proof_batch(blobs: Sequence[Blob],
|
||||||
proofs_bytes: Sequence[Bytes48]) -> bool:
|
proofs_bytes: Sequence[Bytes48]) -> bool:
|
||||||
"""
|
"""
|
||||||
Given a list of blobs and blob KZG proofs, verify that they correspond to the provided commitments.
|
Given a list of blobs and blob KZG proofs, verify that they correspond to the provided commitments.
|
||||||
|
Will return True if there are zero blobs/commitments/proofs.
|
||||||
Public method.
|
Public method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,14 @@
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Constants](#constants)
|
||||||
|
- [Misc](#misc)
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
- [`get_blobs_and_kzg_commitments`](#get_blobs_and_kzg_commitments)
|
- [`BlobsBundle`](#blobsbundle)
|
||||||
|
- [Modified `GetPayloadResponse`](#modified-getpayloadresponse)
|
||||||
|
- [Protocol](#protocol)
|
||||||
|
- [`ExecutionEngine`](#executionengine)
|
||||||
|
- [Modified `get_payload`](#modified-get_payload)
|
||||||
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
|
||||||
- [Block and sidecar proposal](#block-and-sidecar-proposal)
|
- [Block and sidecar proposal](#block-and-sidecar-proposal)
|
||||||
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
|
||||||
|
@ -34,19 +40,50 @@ All behaviors and definitions defined in this document, and documents it extends
|
||||||
All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of Deneb](./beacon-chain.md) are requisite for this document and used throughout.
|
All terminology, constants, functions, and protocol mechanics defined in the updated [Beacon Chain doc of Deneb](./beacon-chain.md) are requisite for this document and used throughout.
|
||||||
Please see related Beacon Chain doc before continuing and use them as a reference throughout.
|
Please see related Beacon Chain doc before continuing and use them as a reference throughout.
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
| Name | Value | Unit |
|
||||||
|
| - | - | :-: |
|
||||||
|
| `BLOB_SIDECAR_SUBNET_COUNT` | `4` | The number of blob sidecar subnets used in the gossipsub protocol. |
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
### `get_blobs_and_kzg_commitments`
|
### `BlobsBundle`
|
||||||
|
|
||||||
The interface to retrieve blobs and corresponding kzg commitments.
|
|
||||||
|
|
||||||
Note: This API is *unstable*. `get_blobs_and_kzg_commitments` and `get_payload` may be unified.
|
|
||||||
Implementers may also retrieve blobs individually per transaction.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_blobs_and_kzg_commitments(
|
@dataclass
|
||||||
payload_id: PayloadId
|
class BlobsBundle(object):
|
||||||
) -> Tuple[Sequence[Blob], Sequence[KZGCommitment], Sequence[KZGProof]]:
|
commitments: Sequence[KZGCommitment]
|
||||||
|
proofs: Sequence[KZGProof]
|
||||||
|
blobs: Sequence[Blob]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modified `GetPayloadResponse`
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GetPayloadResponse(object):
|
||||||
|
execution_payload: ExecutionPayload
|
||||||
|
block_value: uint256
|
||||||
|
blobs_bundle: BlobsBundle
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
### `ExecutionEngine`
|
||||||
|
|
||||||
|
#### Modified `get_payload`
|
||||||
|
|
||||||
|
Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that
|
||||||
|
has been built since the corresponding call to `notify_forkchoice_updated` method.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse:
|
||||||
|
"""
|
||||||
|
Return ExecutionPayload, uint256, BlobsBundle objects.
|
||||||
|
"""
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
@ -62,7 +99,8 @@ All validator responsibilities remain unchanged other than those noted below.
|
||||||
##### Blob KZG commitments
|
##### Blob KZG commitments
|
||||||
|
|
||||||
1. After retrieving the execution payload from the execution engine as specified in Capella,
|
1. After retrieving the execution payload from the execution engine as specified in Capella,
|
||||||
use the `payload_id` to retrieve `blobs` and `blob_kzg_commitments` via `get_blobs_and_kzg_commitments(payload_id)`.
|
use the `payload_id` to retrieve `blobs`, `blob_kzg_commitments`, and `blob_kzg_proofs`
|
||||||
|
via `get_payload(payload_id).blobs_bundle`.
|
||||||
2. Validate `blobs` and `blob_kzg_commitments`:
|
2. Validate `blobs` and `blob_kzg_commitments`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -108,7 +146,7 @@ def get_blob_sidecars(block: BeaconBlock,
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then for each sidecar, `signed_sidecar = SignedBlobSidecar(message=sidecar, signature=signature)` is constructed and published to the `blob_sidecar_{index}` topics according to its index.
|
Then for each sidecar, `signed_sidecar = SignedBlobSidecar(message=sidecar, signature=signature)` is constructed and published to the associated sidecar topic, the `blob_sidecar_{subnet_id}` pubsub topic.
|
||||||
|
|
||||||
`signature` is obtained from:
|
`signature` is obtained from:
|
||||||
|
|
||||||
|
@ -121,6 +159,15 @@ def get_blob_sidecar_signature(state: BeaconState,
|
||||||
return bls.Sign(privkey, signing_root)
|
return bls.Sign(privkey, signing_root)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `subnet_id` for the `signed_sidecar` is calculated with:
|
||||||
|
- Let `blob_index = signed_sidecar.message.index`.
|
||||||
|
- Let `subnet_id = compute_subnet_for_blob_sidecar(blob_index)`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compute_subnet_for_blob_sidecar(blob_index: BlobIndex) -> SubnetID:
|
||||||
|
return SubnetID(blob_index % BLOB_SIDECAR_SUBNET_COUNT)
|
||||||
|
```
|
||||||
|
|
||||||
After publishing the peers on the network may request the sidecar through sync-requests, or a local user may be interested.
|
After publishing the peers on the network may request the sidecar through sync-requests, or a local user may be interested.
|
||||||
|
|
||||||
The validator MUST hold on to sidecars for `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` epochs and serve when capable,
|
The validator MUST hold on to sidecars for `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` epochs and serve when capable,
|
||||||
|
|
|
@ -10,6 +10,7 @@ This is an accompanying document to [Phase 0 -- The Beacon Chain](./beacon-chain
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Custom types](#custom-types)
|
||||||
- [Constants](#constants)
|
- [Constants](#constants)
|
||||||
- [Misc](#misc)
|
- [Misc](#misc)
|
||||||
- [Containers](#containers)
|
- [Containers](#containers)
|
||||||
|
@ -82,16 +83,28 @@ A validator is an entity that participates in the consensus of the Ethereum proo
|
||||||
|
|
||||||
All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Deposit Contract](./deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout.
|
All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Deposit Contract](./deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout.
|
||||||
|
|
||||||
|
## Custom types
|
||||||
|
|
||||||
|
We define the following Python custom types for type hinting and readability:
|
||||||
|
|
||||||
|
| Name | SSZ equivalent | Description |
|
||||||
|
| - | - | - |
|
||||||
|
| `NodeID` | `uint256` | node identifier |
|
||||||
|
| `SubnetID` | `uint64` | subnet identifier |
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
| Name | Value | Unit | Duration |
|
| Name | Value | Unit | Duration |
|
||||||
| - | - | :-: | :-: |
|
| - | - | :-: | :-: |
|
||||||
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | |
|
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators |
|
||||||
| `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | |
|
| `EPOCHS_PER_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
|
||||||
| `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
|
|
||||||
| `ATTESTATION_SUBNET_COUNT` | `64` | The number of attestation subnets used in the gossipsub protocol. |
|
| `ATTESTATION_SUBNET_COUNT` | `64` | The number of attestation subnets used in the gossipsub protocol. |
|
||||||
|
| `ATTESTATION_SUBNET_EXTRA_BITS` | `0` | The number of extra bits of a NodeId to use when mapping to a subscribed subnet |
|
||||||
|
| `SUBNETS_PER_NODE` | `2` | The number of long-lived subnets a beacon node should be subscribed to. |
|
||||||
|
| `ATTESTATION_SUBNET_PREFIX_BITS` | `(ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS)` | |
|
||||||
|
| `NODE_ID_BITS` | `256` | The bit length of uint256 is 256 |
|
||||||
|
|
||||||
## Containers
|
## Containers
|
||||||
|
|
||||||
|
@ -513,7 +526,9 @@ The `subnet_id` for the `attestation` is calculated with:
|
||||||
- Let `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)`.
|
- Let `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64:
|
def compute_subnet_for_attestation(committees_per_slot: uint64,
|
||||||
|
slot: Slot,
|
||||||
|
committee_index: CommitteeIndex) -> SubnetID:
|
||||||
"""
|
"""
|
||||||
Compute the correct subnet for an attestation for Phase 0.
|
Compute the correct subnet for an attestation for Phase 0.
|
||||||
Note, this mimics expected future behavior where attestations will be mapped to their shard subnet.
|
Note, this mimics expected future behavior where attestations will be mapped to their shard subnet.
|
||||||
|
@ -521,7 +536,7 @@ def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, comm
|
||||||
slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH)
|
slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH)
|
||||||
committees_since_epoch_start = committees_per_slot * slots_since_epoch_start
|
committees_since_epoch_start = committees_per_slot * slots_since_epoch_start
|
||||||
|
|
||||||
return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT)
|
return SubnetID((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Attestation aggregation
|
### Attestation aggregation
|
||||||
|
@ -606,15 +621,31 @@ def get_aggregate_and_proof_signature(state: BeaconState,
|
||||||
|
|
||||||
## Phase 0 attestation subnet stability
|
## Phase 0 attestation subnet stability
|
||||||
|
|
||||||
Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each validator must:
|
Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each beacon node should:
|
||||||
|
|
||||||
* Randomly select and remain subscribed to `RANDOM_SUBNETS_PER_VALIDATOR` attestation subnets
|
* Remain subscribed to `SUBNETS_PER_NODE` for `EPOCHS_PER_SUBNET_SUBSCRIPTION` epochs.
|
||||||
* Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets
|
* Maintain advertisement of the selected subnets in their node's ENR `attnets` entry by setting the selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets.
|
||||||
* Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR
|
* Select these subnets based on their node-id as specified by the following `compute_subscribed_subnets(node_id, epoch)` function.
|
||||||
|
|
||||||
*Note*: Short lived beacon committee assignments should not be added in into the ENR `attnets` entry.
|
```python
|
||||||
|
def compute_subscribed_subnet(node_id: NodeID, epoch: Epoch, index: int) -> SubnetID:
|
||||||
|
node_id_prefix = node_id >> (NODE_ID_BITS - int(ATTESTATION_SUBNET_PREFIX_BITS))
|
||||||
|
node_offset = node_id % EPOCHS_PER_SUBNET_SUBSCRIPTION
|
||||||
|
permutation_seed = hash(uint_to_bytes(uint64((epoch + node_offset) // EPOCHS_PER_SUBNET_SUBSCRIPTION)))
|
||||||
|
permutated_prefix = compute_shuffled_index(
|
||||||
|
node_id_prefix,
|
||||||
|
1 << int(ATTESTATION_SUBNET_PREFIX_BITS),
|
||||||
|
permutation_seed,
|
||||||
|
)
|
||||||
|
return SubnetID((permutated_prefix + index) % ATTESTATION_SUBNET_COUNT)
|
||||||
|
```
|
||||||
|
|
||||||
*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements.
|
```python
|
||||||
|
def compute_subscribed_subnets(node_id: NodeID, epoch: Epoch) -> Sequence[SubnetID]:
|
||||||
|
return [compute_subscribed_subnet(node_id, epoch, index) for index in range(SUBNETS_PER_NODE)]
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note*: When preparing for a hard fork, a validator must select and subscribe to subnets of the future fork versioning at least `EPOCHS_PER_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements.
|
||||||
|
|
||||||
## How to avoid slashing
|
## How to avoid slashing
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from eth_utils import encode_hex
|
from dataclasses import (
|
||||||
|
dataclass,
|
||||||
|
field,
|
||||||
|
)
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -8,24 +11,80 @@ import sys
|
||||||
import json
|
import json
|
||||||
from typing import Iterable, AnyStr, Any, Callable
|
from typing import Iterable, AnyStr, Any, Callable
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from ruamel.yaml import (
|
from ruamel.yaml import (
|
||||||
YAML,
|
YAML,
|
||||||
)
|
)
|
||||||
|
|
||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
from snappy import compress
|
from snappy import compress
|
||||||
|
from pathos.multiprocessing import ProcessingPool as Pool
|
||||||
|
|
||||||
|
from eth_utils import encode_hex
|
||||||
|
|
||||||
from eth2spec.test import context
|
from eth2spec.test import context
|
||||||
from eth2spec.test.exceptions import SkippedTest
|
from eth2spec.test.exceptions import SkippedTest
|
||||||
|
|
||||||
from .gen_typing import TestProvider
|
from .gen_typing import TestProvider
|
||||||
|
from .settings import (
|
||||||
|
GENERATOR_MODE,
|
||||||
|
MODE_MULTIPROCESSING,
|
||||||
|
MODE_SINGLE_PROCESS,
|
||||||
|
NUM_PROCESS,
|
||||||
|
TIME_THRESHOLD_TO_PRINT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Flag that the runner does NOT run test via pytest
|
# Flag that the runner does NOT run test via pytest
|
||||||
context.is_pytest = False
|
context.is_pytest = False
|
||||||
|
|
||||||
|
|
||||||
TIME_THRESHOLD_TO_PRINT = 1.0 # seconds
|
@dataclass
|
||||||
|
class Diagnostics(object):
|
||||||
|
collected_test_count: int = 0
|
||||||
|
generated_test_count: int = 0
|
||||||
|
skipped_test_count: int = 0
|
||||||
|
test_identifiers: list = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
TestCaseParams = namedtuple(
|
||||||
|
'TestCaseParams', [
|
||||||
|
'test_case', 'case_dir', 'log_file', 'file_mode',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def worker_function(item):
|
||||||
|
return generate_test_vector(*item)
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_yaml():
|
||||||
|
yaml = YAML(pure=True)
|
||||||
|
yaml.default_flow_style = None
|
||||||
|
|
||||||
|
def _represent_none(self, _):
|
||||||
|
return self.represent_scalar('tag:yaml.org,2002:null', 'null')
|
||||||
|
|
||||||
|
yaml.representer.add_representer(type(None), _represent_none)
|
||||||
|
|
||||||
|
return yaml
|
||||||
|
|
||||||
|
|
||||||
|
def get_cfg_yaml():
|
||||||
|
# Spec config is using a YAML subset
|
||||||
|
cfg_yaml = YAML(pure=True)
|
||||||
|
cfg_yaml.default_flow_style = False # Emit separate line for each key
|
||||||
|
|
||||||
|
def cfg_represent_bytes(self, data):
|
||||||
|
return self.represent_int(encode_hex(data))
|
||||||
|
|
||||||
|
cfg_yaml.representer.add_representer(bytes, cfg_represent_bytes)
|
||||||
|
|
||||||
|
def cfg_represent_quoted_str(self, data):
|
||||||
|
return self.represent_scalar(u'tag:yaml.org,2002:str', data, style="'")
|
||||||
|
|
||||||
|
cfg_yaml.representer.add_representer(context.quoted_str, cfg_represent_quoted_str)
|
||||||
|
return cfg_yaml
|
||||||
|
|
||||||
|
|
||||||
def validate_output_dir(path_str):
|
def validate_output_dir(path_str):
|
||||||
|
@ -40,6 +99,47 @@ def validate_output_dir(path_str):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_case_dir(test_case, output_dir):
|
||||||
|
return (
|
||||||
|
Path(output_dir) / Path(test_case.preset_name) / Path(test_case.fork_name)
|
||||||
|
/ Path(test_case.runner_name) / Path(test_case.handler_name)
|
||||||
|
/ Path(test_case.suite_name) / Path(test_case.case_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_identifier(test_case):
|
||||||
|
return "::".join([
|
||||||
|
test_case.preset_name,
|
||||||
|
test_case.fork_name,
|
||||||
|
test_case.runner_name,
|
||||||
|
test_case.handler_name,
|
||||||
|
test_case.suite_name,
|
||||||
|
test_case.case_name
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def get_incomplete_tag_file(case_dir):
|
||||||
|
return case_dir / "INCOMPLETE"
|
||||||
|
|
||||||
|
|
||||||
|
def should_skip_case_dir(case_dir, is_force, diagnostics_obj):
|
||||||
|
is_skip = False
|
||||||
|
incomplete_tag_file = get_incomplete_tag_file(case_dir)
|
||||||
|
|
||||||
|
if case_dir.exists():
|
||||||
|
if not is_force and not incomplete_tag_file.exists():
|
||||||
|
diagnostics_obj.skipped_test_count += 1
|
||||||
|
print(f'Skipping already existing test: {case_dir}')
|
||||||
|
is_skip = True
|
||||||
|
else:
|
||||||
|
print(f'Warning, output directory {case_dir} already exist,'
|
||||||
|
' old files will be deleted and it will generate test vector files with the latest version')
|
||||||
|
# Clear the existing case_dir folder
|
||||||
|
shutil.rmtree(case_dir)
|
||||||
|
|
||||||
|
return is_skip, diagnostics_obj
|
||||||
|
|
||||||
|
|
||||||
def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
"""
|
"""
|
||||||
Implementation for a general test generator.
|
Implementation for a general test generator.
|
||||||
|
@ -94,28 +194,6 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
else:
|
else:
|
||||||
file_mode = "w"
|
file_mode = "w"
|
||||||
|
|
||||||
yaml = YAML(pure=True)
|
|
||||||
yaml.default_flow_style = None
|
|
||||||
|
|
||||||
def _represent_none(self, _):
|
|
||||||
return self.represent_scalar('tag:yaml.org,2002:null', 'null')
|
|
||||||
|
|
||||||
yaml.representer.add_representer(type(None), _represent_none)
|
|
||||||
|
|
||||||
# Spec config is using a YAML subset
|
|
||||||
cfg_yaml = YAML(pure=True)
|
|
||||||
cfg_yaml.default_flow_style = False # Emit separate line for each key
|
|
||||||
|
|
||||||
def cfg_represent_bytes(self, data):
|
|
||||||
return self.represent_int(encode_hex(data))
|
|
||||||
|
|
||||||
cfg_yaml.representer.add_representer(bytes, cfg_represent_bytes)
|
|
||||||
|
|
||||||
def cfg_represent_quoted_str(self, data):
|
|
||||||
return self.represent_scalar(u'tag:yaml.org,2002:str', data, style="'")
|
|
||||||
|
|
||||||
cfg_yaml.representer.add_representer(context.quoted_str, cfg_represent_quoted_str)
|
|
||||||
|
|
||||||
log_file = Path(output_dir) / 'testgen_error_log.txt'
|
log_file = Path(output_dir) / 'testgen_error_log.txt'
|
||||||
|
|
||||||
print(f"Generating tests into {output_dir}")
|
print(f"Generating tests into {output_dir}")
|
||||||
|
@ -129,12 +207,13 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}")
|
print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}")
|
||||||
|
|
||||||
collect_only = args.collect_only
|
collect_only = args.collect_only
|
||||||
collected_test_count = 0
|
|
||||||
generated_test_count = 0
|
|
||||||
skipped_test_count = 0
|
|
||||||
test_identifiers = []
|
|
||||||
|
|
||||||
|
diagnostics_obj = Diagnostics()
|
||||||
provider_start = time.time()
|
provider_start = time.time()
|
||||||
|
|
||||||
|
if GENERATOR_MODE == MODE_MULTIPROCESSING:
|
||||||
|
all_test_case_params = []
|
||||||
|
|
||||||
for tprov in test_providers:
|
for tprov in test_providers:
|
||||||
if not collect_only:
|
if not collect_only:
|
||||||
# runs anything that we don't want to repeat for every test case.
|
# runs anything that we don't want to repeat for every test case.
|
||||||
|
@ -145,106 +224,113 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
if len(presets) != 0 and test_case.preset_name not in presets:
|
if len(presets) != 0 and test_case.preset_name not in presets:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case_dir = (
|
case_dir = get_test_case_dir(test_case, output_dir)
|
||||||
Path(output_dir) / Path(test_case.preset_name) / Path(test_case.fork_name)
|
|
||||||
/ Path(test_case.runner_name) / Path(test_case.handler_name)
|
|
||||||
/ Path(test_case.suite_name) / Path(test_case.case_name)
|
|
||||||
)
|
|
||||||
collected_test_count += 1
|
|
||||||
print(f"Collected test at: {case_dir}")
|
print(f"Collected test at: {case_dir}")
|
||||||
|
diagnostics_obj.collected_test_count += 1
|
||||||
|
|
||||||
incomplete_tag_file = case_dir / "INCOMPLETE"
|
is_skip, diagnostics_obj = should_skip_case_dir(case_dir, args.force, diagnostics_obj)
|
||||||
|
if is_skip:
|
||||||
if case_dir.exists():
|
|
||||||
if not args.force and not incomplete_tag_file.exists():
|
|
||||||
skipped_test_count += 1
|
|
||||||
print(f'Skipping already existing test: {case_dir}')
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if GENERATOR_MODE == MODE_SINGLE_PROCESS:
|
||||||
|
result = generate_test_vector(test_case, case_dir, log_file, file_mode)
|
||||||
|
write_result_into_diagnostics_obj(result, diagnostics_obj)
|
||||||
|
elif GENERATOR_MODE == MODE_MULTIPROCESSING:
|
||||||
|
item = TestCaseParams(test_case, case_dir, log_file, file_mode)
|
||||||
|
all_test_case_params.append(item)
|
||||||
|
|
||||||
|
if GENERATOR_MODE == MODE_MULTIPROCESSING:
|
||||||
|
with Pool(processes=NUM_PROCESS) as pool:
|
||||||
|
results = pool.map(worker_function, iter(all_test_case_params))
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
write_result_into_diagnostics_obj(result, diagnostics_obj)
|
||||||
|
|
||||||
|
provider_end = time.time()
|
||||||
|
span = round(provider_end - provider_start, 2)
|
||||||
|
|
||||||
|
if collect_only:
|
||||||
|
print(f"Collected {diagnostics_obj.collected_test_count} tests in total")
|
||||||
else:
|
else:
|
||||||
print(f'Warning, output directory {case_dir} already exist,'
|
summary_message = f"completed generation of {generator_name} with {diagnostics_obj.generated_test_count} tests"
|
||||||
f' old files will be deleted and it will generate test vector files with the latest version')
|
summary_message += f" ({diagnostics_obj.skipped_test_count} skipped tests)"
|
||||||
# Clear the existing case_dir folder
|
if span > TIME_THRESHOLD_TO_PRINT:
|
||||||
shutil.rmtree(case_dir)
|
summary_message += f" in {span} seconds"
|
||||||
|
print(summary_message)
|
||||||
|
|
||||||
|
diagnostics_output = {
|
||||||
|
"collected_test_count": diagnostics_obj.collected_test_count,
|
||||||
|
"generated_test_count": diagnostics_obj.generated_test_count,
|
||||||
|
"skipped_test_count": diagnostics_obj.skipped_test_count,
|
||||||
|
"test_identifiers": diagnostics_obj.test_identifiers,
|
||||||
|
"durations": [f"{span} seconds"],
|
||||||
|
}
|
||||||
|
diagnostics_path = Path(os.path.join(output_dir, "diagnostics_obj.json"))
|
||||||
|
diagnostics_lock = FileLock(os.path.join(output_dir, "diagnostics_obj.json.lock"))
|
||||||
|
with diagnostics_lock:
|
||||||
|
diagnostics_path.touch(exist_ok=True)
|
||||||
|
if os.path.getsize(diagnostics_path) == 0:
|
||||||
|
with open(diagnostics_path, "w+") as f:
|
||||||
|
json.dump(diagnostics_output, f)
|
||||||
|
else:
|
||||||
|
with open(diagnostics_path, "r+") as f:
|
||||||
|
existing_diagnostics = json.load(f)
|
||||||
|
for k, v in diagnostics_output.items():
|
||||||
|
existing_diagnostics[k] += v
|
||||||
|
with open(diagnostics_path, "w+") as f:
|
||||||
|
json.dump(existing_diagnostics, f)
|
||||||
|
print(f"wrote diagnostics_obj to {diagnostics_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_test_vector(test_case, case_dir, log_file, file_mode):
|
||||||
|
cfg_yaml = get_cfg_yaml()
|
||||||
|
yaml = get_default_yaml()
|
||||||
|
|
||||||
|
written_part = False
|
||||||
|
|
||||||
print(f'Generating test: {case_dir}')
|
print(f'Generating test: {case_dir}')
|
||||||
test_start = time.time()
|
test_start = time.time()
|
||||||
|
|
||||||
written_part = False
|
|
||||||
|
|
||||||
# Add `INCOMPLETE` tag file to indicate that the test generation has not completed.
|
# Add `INCOMPLETE` tag file to indicate that the test generation has not completed.
|
||||||
|
incomplete_tag_file = get_incomplete_tag_file(case_dir)
|
||||||
case_dir.mkdir(parents=True, exist_ok=True)
|
case_dir.mkdir(parents=True, exist_ok=True)
|
||||||
with incomplete_tag_file.open("w") as f:
|
with incomplete_tag_file.open("w") as f:
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
|
result = None
|
||||||
try:
|
try:
|
||||||
def output_part(out_kind: str, name: str, fn: Callable[[Path, ], None]):
|
|
||||||
# make sure the test case directory is created before any test part is written.
|
|
||||||
case_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
try:
|
|
||||||
fn(case_dir)
|
|
||||||
except IOError as e:
|
|
||||||
error_message = (
|
|
||||||
f'[Error] error when dumping test "{case_dir}", part "{name}", kind "{out_kind}": {e}'
|
|
||||||
)
|
|
||||||
# Write to error log file
|
|
||||||
with log_file.open("a+") as f:
|
|
||||||
f.write(error_message)
|
|
||||||
traceback.print_exc(file=f)
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
sys.exit(error_message)
|
|
||||||
|
|
||||||
meta = dict()
|
meta = dict()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for (name, out_kind, data) in test_case.case_fn():
|
written_part, meta = execute_test(test_case, case_dir, meta, log_file, file_mode, cfg_yaml, yaml)
|
||||||
written_part = True
|
|
||||||
if out_kind == "meta":
|
|
||||||
meta[name] = data
|
|
||||||
elif out_kind == "cfg":
|
|
||||||
output_part(out_kind, name, dump_yaml_fn(data, name, file_mode, cfg_yaml))
|
|
||||||
elif out_kind == "data":
|
|
||||||
output_part(out_kind, name, dump_yaml_fn(data, name, file_mode, yaml))
|
|
||||||
elif out_kind == "ssz":
|
|
||||||
output_part(out_kind, name, dump_ssz_fn(data, name, file_mode))
|
|
||||||
else:
|
|
||||||
assert False # Unknown kind
|
|
||||||
except SkippedTest as e:
|
except SkippedTest as e:
|
||||||
|
result = 0 # 0 means skipped
|
||||||
print(e)
|
print(e)
|
||||||
skipped_test_count += 1
|
|
||||||
shutil.rmtree(case_dir)
|
shutil.rmtree(case_dir)
|
||||||
continue
|
return result
|
||||||
|
|
||||||
# Once all meta data is collected (if any), write it to a meta data file.
|
# Once all meta data is collected (if any), write it to a meta data file.
|
||||||
if len(meta) != 0:
|
if len(meta) != 0:
|
||||||
written_part = True
|
written_part = True
|
||||||
output_part("data", "meta", dump_yaml_fn(meta, "meta", file_mode, yaml))
|
output_part(case_dir, log_file, "data", "meta", dump_yaml_fn(meta, "meta", file_mode, yaml))
|
||||||
|
|
||||||
if not written_part:
|
|
||||||
print(f"test case {case_dir} did not produce any test case parts")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
result = -1 # -1 means error
|
||||||
error_message = f"[ERROR] failed to generate vector(s) for test {case_dir}: {e}"
|
error_message = f"[ERROR] failed to generate vector(s) for test {case_dir}: {e}"
|
||||||
# Write to error log file
|
# Write to error log file
|
||||||
with log_file.open("a+") as f:
|
with log_file.open("a+") as f:
|
||||||
f.write(error_message)
|
f.write(error_message)
|
||||||
traceback.print_exc(file=f)
|
traceback.print_exc(file=f)
|
||||||
f.write('\n')
|
f.write('\n')
|
||||||
|
print(error_message)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
# If no written_part, the only file was incomplete_tag_file. Clear the existing case_dir folder.
|
# If no written_part, the only file was incomplete_tag_file. Clear the existing case_dir folder.
|
||||||
if not written_part:
|
if not written_part:
|
||||||
|
print(f"[Error] test case {case_dir} did not produce any written_part")
|
||||||
shutil.rmtree(case_dir)
|
shutil.rmtree(case_dir)
|
||||||
|
result = -1
|
||||||
else:
|
else:
|
||||||
generated_test_count += 1
|
result = get_test_identifier(test_case)
|
||||||
test_identifier = "::".join([
|
|
||||||
test_case.preset_name,
|
|
||||||
test_case.fork_name,
|
|
||||||
test_case.runner_name,
|
|
||||||
test_case.handler_name,
|
|
||||||
test_case.suite_name,
|
|
||||||
test_case.case_name
|
|
||||||
])
|
|
||||||
test_identifiers.append(test_identifier)
|
|
||||||
# Only remove `INCOMPLETE` tag file
|
# Only remove `INCOMPLETE` tag file
|
||||||
os.remove(incomplete_tag_file)
|
os.remove(incomplete_tag_file)
|
||||||
test_end = time.time()
|
test_end = time.time()
|
||||||
|
@ -252,39 +338,19 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
|
||||||
if span > TIME_THRESHOLD_TO_PRINT:
|
if span > TIME_THRESHOLD_TO_PRINT:
|
||||||
print(f' - generated in {span} seconds')
|
print(f' - generated in {span} seconds')
|
||||||
|
|
||||||
provider_end = time.time()
|
return result
|
||||||
span = round(provider_end - provider_start, 2)
|
|
||||||
|
|
||||||
if collect_only:
|
|
||||||
print(f"Collected {collected_test_count} tests in total")
|
def write_result_into_diagnostics_obj(result, diagnostics_obj):
|
||||||
|
if result == -1: # error
|
||||||
|
pass
|
||||||
|
elif result == 0:
|
||||||
|
diagnostics_obj.skipped_test_count += 1
|
||||||
|
elif result is not None:
|
||||||
|
diagnostics_obj.generated_test_count += 1
|
||||||
|
diagnostics_obj.test_identifiers.append(result)
|
||||||
else:
|
else:
|
||||||
summary_message = f"completed generation of {generator_name} with {generated_test_count} tests"
|
raise Exception(f"Unexpected result: {result}")
|
||||||
summary_message += f" ({skipped_test_count} skipped tests)"
|
|
||||||
if span > TIME_THRESHOLD_TO_PRINT:
|
|
||||||
summary_message += f" in {span} seconds"
|
|
||||||
print(summary_message)
|
|
||||||
diagnostics = {
|
|
||||||
"collected_test_count": collected_test_count,
|
|
||||||
"generated_test_count": generated_test_count,
|
|
||||||
"skipped_test_count": skipped_test_count,
|
|
||||||
"test_identifiers": test_identifiers,
|
|
||||||
"durations": [f"{span} seconds"],
|
|
||||||
}
|
|
||||||
diagnostics_path = Path(os.path.join(output_dir, "diagnostics.json"))
|
|
||||||
diagnostics_lock = FileLock(os.path.join(output_dir, "diagnostics.json.lock"))
|
|
||||||
with diagnostics_lock:
|
|
||||||
diagnostics_path.touch(exist_ok=True)
|
|
||||||
if os.path.getsize(diagnostics_path) == 0:
|
|
||||||
with open(diagnostics_path, "w+") as f:
|
|
||||||
json.dump(diagnostics, f)
|
|
||||||
else:
|
|
||||||
with open(diagnostics_path, "r+") as f:
|
|
||||||
existing_diagnostics = json.load(f)
|
|
||||||
for k, v in diagnostics.items():
|
|
||||||
existing_diagnostics[k] += v
|
|
||||||
with open(diagnostics_path, "w+") as f:
|
|
||||||
json.dump(existing_diagnostics, f)
|
|
||||||
print(f"wrote diagnostics to {diagnostics_path}")
|
|
||||||
|
|
||||||
|
|
||||||
def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML):
|
def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML):
|
||||||
|
@ -292,9 +358,45 @@ def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML):
|
||||||
out_path = case_path / Path(name + '.yaml')
|
out_path = case_path / Path(name + '.yaml')
|
||||||
with out_path.open(file_mode) as f:
|
with out_path.open(file_mode) as f:
|
||||||
yaml_encoder.dump(data, f)
|
yaml_encoder.dump(data, f)
|
||||||
|
f.close()
|
||||||
return dump
|
return dump
|
||||||
|
|
||||||
|
|
||||||
|
def output_part(case_dir, log_file, out_kind: str, name: str, fn: Callable[[Path, ], None]):
|
||||||
|
# make sure the test case directory is created before any test part is written.
|
||||||
|
case_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
fn(case_dir)
|
||||||
|
except (IOError, ValueError) as e:
|
||||||
|
error_message = f'[Error] error when dumping test "{case_dir}", part "{name}", kind "{out_kind}": {e}'
|
||||||
|
# Write to error log file
|
||||||
|
with log_file.open("a+") as f:
|
||||||
|
f.write(error_message)
|
||||||
|
traceback.print_exc(file=f)
|
||||||
|
f.write('\n')
|
||||||
|
print(error_message)
|
||||||
|
sys.exit(error_message)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_test(test_case, case_dir, meta, log_file, file_mode, cfg_yaml, yaml):
|
||||||
|
result = test_case.case_fn()
|
||||||
|
written_part = False
|
||||||
|
for (name, out_kind, data) in result:
|
||||||
|
written_part = True
|
||||||
|
if out_kind == "meta":
|
||||||
|
meta[name] = data
|
||||||
|
elif out_kind == "cfg":
|
||||||
|
output_part(case_dir, log_file, out_kind, name, dump_yaml_fn(data, name, file_mode, cfg_yaml))
|
||||||
|
elif out_kind == "data":
|
||||||
|
output_part(case_dir, log_file, out_kind, name, dump_yaml_fn(data, name, file_mode, yaml))
|
||||||
|
elif out_kind == "ssz":
|
||||||
|
output_part(case_dir, log_file, out_kind, name, dump_ssz_fn(data, name, file_mode))
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown out_kind %s" % out_kind)
|
||||||
|
|
||||||
|
return written_part, meta
|
||||||
|
|
||||||
|
|
||||||
def dump_ssz_fn(data: AnyStr, name: str, file_mode: str):
|
def dump_ssz_fn(data: AnyStr, name: str, file_mode: str):
|
||||||
def dump(case_path: Path):
|
def dump(case_path: Path):
|
||||||
out_path = case_path / Path(name + '.ssz_snappy')
|
out_path = case_path / Path(name + '.ssz_snappy')
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
|
||||||
|
# Generator mode setting
|
||||||
|
MODE_SINGLE_PROCESS = 'MODE_SINGLE_PROCESS'
|
||||||
|
MODE_MULTIPROCESSING = 'MODE_MULTIPROCESSING'
|
||||||
|
# Test generator mode
|
||||||
|
GENERATOR_MODE = MODE_MULTIPROCESSING
|
||||||
|
# Number of subprocesses when using MODE_MULTIPROCESSING
|
||||||
|
NUM_PROCESS = multiprocessing.cpu_count() // 2 - 1
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
TIME_THRESHOLD_TO_PRINT = 1.0 # seconds
|
|
@ -564,7 +564,7 @@ def _get_basic_dict(ssz_dict: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _get_copy_of_spec(spec):
|
def get_copy_of_spec(spec):
|
||||||
fork = spec.fork
|
fork = spec.fork
|
||||||
preset = spec.config.PRESET_BASE
|
preset = spec.config.PRESET_BASE
|
||||||
module_path = f"eth2spec.{fork}.{preset}"
|
module_path = f"eth2spec.{fork}.{preset}"
|
||||||
|
@ -605,14 +605,14 @@ def with_config_overrides(config_overrides, emitted_fork=None, emit=True):
|
||||||
def decorator(fn):
|
def decorator(fn):
|
||||||
def wrapper(*args, spec: Spec, **kw):
|
def wrapper(*args, spec: Spec, **kw):
|
||||||
# Apply config overrides to spec
|
# Apply config overrides to spec
|
||||||
spec, output_config = spec_with_config_overrides(_get_copy_of_spec(spec), config_overrides)
|
spec, output_config = spec_with_config_overrides(get_copy_of_spec(spec), config_overrides)
|
||||||
|
|
||||||
# Apply config overrides to additional phases, if present
|
# Apply config overrides to additional phases, if present
|
||||||
if 'phases' in kw:
|
if 'phases' in kw:
|
||||||
phases = {}
|
phases = {}
|
||||||
for fork in kw['phases']:
|
for fork in kw['phases']:
|
||||||
phases[fork], output = spec_with_config_overrides(
|
phases[fork], output = spec_with_config_overrides(
|
||||||
_get_copy_of_spec(kw['phases'][fork]), config_overrides)
|
get_copy_of_spec(kw['phases'][fork]), config_overrides)
|
||||||
if emitted_fork == fork:
|
if emitted_fork == fork:
|
||||||
output_config = output
|
output_config = output
|
||||||
kw['phases'] = phases
|
kw['phases'] = phases
|
||||||
|
|
|
@ -150,3 +150,23 @@ def test_incorrect_block_hash(spec, state):
|
||||||
|
|
||||||
yield 'blocks', [signed_block]
|
yield 'blocks', [signed_block]
|
||||||
yield 'post', state
|
yield 'post', state
|
||||||
|
|
||||||
|
|
||||||
|
@with_deneb_and_later
|
||||||
|
@spec_state_test
|
||||||
|
def test_zeroed_commitment(spec, state):
|
||||||
|
"""
|
||||||
|
The blob is invalid, but the commitment is in correct form.
|
||||||
|
"""
|
||||||
|
yield 'pre', state
|
||||||
|
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
opaque_tx, _, blob_kzg_commitments, _ = get_sample_opaque_tx(spec, blob_count=1, is_valid_blob=False)
|
||||||
|
assert all(commitment == b'\x00' * 48 for commitment in blob_kzg_commitments)
|
||||||
|
block.body.blob_kzg_commitments = blob_kzg_commitments
|
||||||
|
block.body.execution_payload.transactions = [opaque_tx]
|
||||||
|
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
||||||
|
yield 'blocks', [signed_block]
|
||||||
|
yield 'post', state
|
||||||
|
|
|
@ -50,12 +50,9 @@ class SignedBlobTransaction(Container):
|
||||||
signature: ECDSASignature
|
signature: ECDSASignature
|
||||||
|
|
||||||
|
|
||||||
def get_sample_blob(spec, rng=None):
|
def get_sample_blob(spec, rng=random.Random(5566), is_valid_blob=True):
|
||||||
if rng is None:
|
|
||||||
rng = random.Random(5566)
|
|
||||||
|
|
||||||
values = [
|
values = [
|
||||||
rng.randint(0, spec.BLS_MODULUS - 1)
|
rng.randint(0, spec.BLS_MODULUS - 1) if is_valid_blob else spec.BLS_MODULUS
|
||||||
for _ in range(spec.FIELD_ELEMENTS_PER_BLOB)
|
for _ in range(spec.FIELD_ELEMENTS_PER_BLOB)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -98,15 +95,19 @@ def get_poly_in_both_forms(spec, rng=None):
|
||||||
return coeffs, evals
|
return coeffs, evals
|
||||||
|
|
||||||
|
|
||||||
def get_sample_opaque_tx(spec, blob_count=1, rng=None):
|
def get_sample_opaque_tx(spec, blob_count=1, rng=random.Random(5566), is_valid_blob=True):
|
||||||
blobs = []
|
blobs = []
|
||||||
blob_kzg_commitments = []
|
blob_kzg_commitments = []
|
||||||
blob_kzg_proofs = []
|
blob_kzg_proofs = []
|
||||||
blob_versioned_hashes = []
|
blob_versioned_hashes = []
|
||||||
for _ in range(blob_count):
|
for _ in range(blob_count):
|
||||||
blob = get_sample_blob(spec, rng)
|
blob = get_sample_blob(spec, rng, is_valid_blob=is_valid_blob)
|
||||||
|
if is_valid_blob:
|
||||||
blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob))
|
blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob))
|
||||||
blob_kzg_proof = spec.compute_blob_kzg_proof(blob, blob_commitment)
|
blob_kzg_proof = spec.compute_blob_kzg_proof(blob, blob_commitment)
|
||||||
|
else:
|
||||||
|
blob_commitment = spec.KZGCommitment()
|
||||||
|
blob_kzg_proof = spec.KZGProof()
|
||||||
blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment)
|
blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment)
|
||||||
blobs.append(blob)
|
blobs.append(blob)
|
||||||
blob_kzg_commitments.append(blob_commitment)
|
blob_kzg_commitments.append(blob_commitment)
|
||||||
|
|
|
@ -75,7 +75,9 @@ def test_time(spec, state):
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_networking(spec, state):
|
def test_networking(spec, state):
|
||||||
assert spec.RANDOM_SUBNETS_PER_VALIDATOR <= spec.ATTESTATION_SUBNET_COUNT
|
assert spec.SUBNETS_PER_NODE <= spec.ATTESTATION_SUBNET_COUNT
|
||||||
|
node_id_length = spec.NodeID(1).type_byte_length() # in bytes
|
||||||
|
assert node_id_length * 8 == spec.NODE_ID_BITS # in bits
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
import random
|
||||||
|
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
|
single_phase,
|
||||||
spec_state_test,
|
spec_state_test,
|
||||||
always_bls, with_phases, with_all_phases,
|
spec_test,
|
||||||
|
always_bls,
|
||||||
|
with_phases,
|
||||||
|
with_all_phases,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.constants import PHASE0
|
from eth2spec.test.helpers.constants import PHASE0
|
||||||
from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation
|
from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation
|
||||||
|
@ -476,3 +482,34 @@ def test_get_aggregate_and_proof_signature(spec, state):
|
||||||
privkey=privkey,
|
privkey=privkey,
|
||||||
pubkey=pubkey,
|
pubkey=pubkey,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_compute_subscribed_subnets_arguments(spec, rng=random.Random(1111)):
|
||||||
|
node_id = rng.randint(0, 2**40 - 1) # try VALIDATOR_REGISTRY_LIMIT
|
||||||
|
epoch = rng.randint(0, 2**64 - 1)
|
||||||
|
subnets = spec.compute_subscribed_subnets(node_id, epoch)
|
||||||
|
assert len(subnets) == spec.SUBNETS_PER_NODE
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_test
|
||||||
|
@single_phase
|
||||||
|
def test_compute_subscribed_subnets_random_1(spec):
|
||||||
|
rng = random.Random(1111)
|
||||||
|
run_compute_subscribed_subnets_arguments(spec, rng)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_test
|
||||||
|
@single_phase
|
||||||
|
def test_compute_subscribed_subnets_random_2(spec):
|
||||||
|
rng = random.Random(2222)
|
||||||
|
run_compute_subscribed_subnets_arguments(spec, rng)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_test
|
||||||
|
@single_phase
|
||||||
|
def test_compute_subscribed_subnets_random_3(spec):
|
||||||
|
rng = random.Random(3333)
|
||||||
|
run_compute_subscribed_subnets_arguments(spec, rng)
|
||||||
|
|
|
@ -10,20 +10,13 @@ from typing import (
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from eth_utils import encode_hex
|
from eth_utils import encode_hex
|
||||||
from py_ecc.optimized_bls12_381 import ( # noqa: F401
|
|
||||||
G1,
|
|
||||||
G2,
|
|
||||||
Z1,
|
|
||||||
Z2,
|
|
||||||
curve_order as BLS_MODULUS,
|
|
||||||
add,
|
|
||||||
multiply,
|
|
||||||
neg,
|
|
||||||
)
|
|
||||||
from py_ecc.typing import (
|
from py_ecc.typing import (
|
||||||
Optimized_Point3D,
|
Optimized_Point3D,
|
||||||
)
|
)
|
||||||
from eth2spec.utils import bls
|
from eth2spec.utils import bls
|
||||||
|
from eth2spec.utils.bls import (
|
||||||
|
BLS_MODULUS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
PRIMITIVE_ROOT_OF_UNITY = 7
|
PRIMITIVE_ROOT_OF_UNITY = 7
|
||||||
|
@ -35,7 +28,7 @@ def generate_setup(generator: Optimized_Point3D, secret: int, length: int) -> Tu
|
||||||
"""
|
"""
|
||||||
result = [generator]
|
result = [generator]
|
||||||
for _ in range(1, length):
|
for _ in range(1, length):
|
||||||
result.append(multiply(result[-1], secret))
|
result.append(bls.multiply(result[-1], secret))
|
||||||
return tuple(result)
|
return tuple(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,9 +42,9 @@ def fft(vals: Sequence[Optimized_Point3D], modulus: int, domain: int) -> Sequenc
|
||||||
R = fft(vals[1::2], modulus, domain[::2])
|
R = fft(vals[1::2], modulus, domain[::2])
|
||||||
o = [0] * len(vals)
|
o = [0] * len(vals)
|
||||||
for i, (x, y) in enumerate(zip(L, R)):
|
for i, (x, y) in enumerate(zip(L, R)):
|
||||||
y_times_root = multiply(y, domain[i])
|
y_times_root = bls.multiply(y, domain[i])
|
||||||
o[i] = add(x, y_times_root)
|
o[i] = bls.add(x, y_times_root)
|
||||||
o[i + len(L)] = add(x, neg(y_times_root))
|
o[i + len(L)] = bls.add(x, bls.neg(y_times_root))
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,12 +83,14 @@ def get_lagrange(setup: Sequence[Optimized_Point3D]) -> Tuple[bytes]:
|
||||||
# TODO: introduce an IFFT function for simplicity
|
# TODO: introduce an IFFT function for simplicity
|
||||||
fft_output = fft(setup, BLS_MODULUS, domain)
|
fft_output = fft(setup, BLS_MODULUS, domain)
|
||||||
inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS)
|
inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS)
|
||||||
return tuple(bls.G1_to_bytes48(multiply(fft_output[-i], inv_length)) for i in range(len(fft_output)))
|
return tuple(bls.G1_to_bytes48(bls.multiply(fft_output[-i], inv_length)) for i in range(len(fft_output)))
|
||||||
|
|
||||||
|
|
||||||
def dump_kzg_trusted_setup_files(secret: int, g1_length: int, g2_length: int, output_dir: str) -> None:
|
def dump_kzg_trusted_setup_files(secret: int, g1_length: int, g2_length: int, output_dir: str) -> None:
|
||||||
setup_g1 = generate_setup(bls.G1, secret, g1_length)
|
bls.use_fastest()
|
||||||
setup_g2 = generate_setup(bls.G2, secret, g2_length)
|
|
||||||
|
setup_g1 = generate_setup(bls.G1(), secret, g1_length)
|
||||||
|
setup_g2 = generate_setup(bls.G2(), secret, g2_length)
|
||||||
setup_g1_lagrange = get_lagrange(setup_g1)
|
setup_g1_lagrange = get_lagrange(setup_g1)
|
||||||
roots_of_unity = compute_roots_of_unity(g1_length)
|
roots_of_unity = compute_roots_of_unity(g1_length)
|
||||||
|
|
||||||
|
|
|
@ -114,8 +114,8 @@ Optional step for optimistic sync tests.
|
||||||
|
|
||||||
This step sets the [`payloadStatus`](https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#PayloadStatusV1)
|
This step sets the [`payloadStatus`](https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#PayloadStatusV1)
|
||||||
value that Execution Layer client mock returns in responses to the following Engine API calls:
|
value that Execution Layer client mock returns in responses to the following Engine API calls:
|
||||||
* [`engine_newPayloadV1(payload)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_newpayloadv1) if `payload.blockHash == payload_info.block_hash`
|
* [`engine_newPayloadV1(payload)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_newpayloadv1) if `payload.blockHash == payload_info.block_hash`
|
||||||
* [`engine_forkchoiceUpdatedV1(forkchoiceState, ...)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_forkchoiceupdatedv1) if `forkchoiceState.headBlockHash == payload_info.block_hash`
|
* [`engine_forkchoiceUpdatedV1(forkchoiceState, ...)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_forkchoiceupdatedv1) if `forkchoiceState.headBlockHash == payload_info.block_hash`
|
||||||
|
|
||||||
*Note:* Status of a payload must be *initialized* via `on_payload_info` before the corresponding `on_block` execution step.
|
*Note:* Status of a payload must be *initialized* via `on_payload_info` before the corresponding `on_block` execution step.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue