diff --git a/.circleci/config.yml b/.circleci/config.yml index bdb3f5bc6..94065d0bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -142,7 +142,19 @@ jobs: command: make citest fork=capella - store_test_results: path: tests/core/pyspec/test-reports - + test-eip4844: + docker: + - image: circleci/python:3.8 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_pyspec_cached_venv + - run: + name: Run py-tests + command: make citest fork=eip4844 + - store_test_results: + path: tests/core/pyspec/test-reports table_of_contents: docker: - image: circleci/node:10.16.3 @@ -260,6 +272,9 @@ workflows: - test-capella: requires: - install_pyspec_test + - test-eip4844: + requires: + - install_pyspec_test - table_of_contents - codespell - lint: diff --git a/.gitignore b/.gitignore index 101cb0b08..219251599 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/altair/ tests/core/pyspec/eth2spec/bellatrix/ tests/core/pyspec/eth2spec/capella/ +tests/core/pyspec/eth2spec/eip4844/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index a1c2c80cf..6afe42993 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \ - && pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix \ + && pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix ./eth2spec/capella \ && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix -p eth2spec.capella lint_generators: pyspec diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 1c0a12d4e..e86f5b5c6 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -47,9 +47,9 @@ BELLATRIX_FORK_EPOCH: 18446744073709551615 # Capella CAPELLA_FORK_VERSION: 0x03000000 CAPELLA_FORK_EPOCH: 18446744073709551615 -# Sharding -SHARDING_FORK_VERSION: 0x04000000 -SHARDING_FORK_EPOCH: 18446744073709551615 +# EIP4844 +EIP4844_FORK_VERSION: 0x04000000 +EIP4844_FORK_EPOCH: 18446744073709551615 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index e619ee931..0917cfb57 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -46,9 +46,9 @@ BELLATRIX_FORK_EPOCH: 18446744073709551615 # Capella CAPELLA_FORK_VERSION: 0x03000001 CAPELLA_FORK_EPOCH: 18446744073709551615 -# Sharding -SHARDING_FORK_VERSION: 0x04000001 -SHARDING_FORK_EPOCH: 18446744073709551615 +# EIP4844 +EIP4844_FORK_VERSION: 0x04000001 +EIP4844_FORK_EPOCH: 18446744073709551615 # Time parameters diff --git a/presets/mainnet/eip4844.yaml b/presets/mainnet/eip4844.yaml new file mode 100644 index 000000000..dac58a9f0 --- /dev/null +++ b/presets/mainnet/eip4844.yaml @@ -0,0 +1,8 @@ +# Mainnet preset - Phase0 + +# Misc +# --------------------------------------------------------------- +# `uint64(4096)` +FIELD_ELEMENTS_PER_BLOB: 4096 +# `uint64(2**4)` (= 16) +MAX_BLOBS_PER_BLOCK: 16 \ No newline at end of file diff --git a/presets/minimal/eip4844.yaml b/presets/minimal/eip4844.yaml new file mode 100644 index 000000000..fbb676819 --- /dev/null +++ b/presets/minimal/eip4844.yaml @@ -0,0 +1,8 @@ +# Minimal preset - Phase0 + +# Misc +# --------------------------------------------------------------- +# [customized] +FIELD_ELEMENTS_PER_BLOB: 4 +# `uint64(2**4)` (= 16) +MAX_BLOBS_PER_BLOCK: 16 diff --git a/setup.py b/setup.py index d83931850..36400e2ab 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ PHASE0 = 'phase0' ALTAIR = 'altair' BELLATRIX = 'bellatrix' CAPELLA = 'capella' +EIP4844 = 'eip4844' # The helper functions that are used when defining constants @@ -208,6 +209,9 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> elif source.startswith("class"): class_name, parent_class = _get_class_info_from_source(source) # check consistency with spec + if class_name != current_name: + print('class_name', class_name, 'current_name', current_name) + assert class_name == current_name if parent_class: assert parent_class == "Container" @@ -230,7 +234,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> if not _is_constant_id(name): # Check for short type declarations - if value.startswith(("uint", "Bytes", "ByteList", "Union")): + if value.startswith(("uint", "Bytes", "ByteList", "Union", "Vector", "List")): custom_types[name] = value continue @@ -304,7 +308,7 @@ class SpecBuilder(ABC): @classmethod @abstractmethod - def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]: # TODO + def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]: # TODO """ The constants that are required for custom types. """ @@ -432,7 +436,7 @@ get_attesting_indices = cache_this( return {} @classmethod - def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]: + def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]: return {} @classmethod @@ -547,11 +551,11 @@ EXECUTION_ENGINE = NoopExecutionEngine()""" @classmethod - def hardcoded_custom_type_dep_constants(cls) -> str: + def hardcoded_custom_type_dep_constants(cls, spec_object) -> str: constants = { - 'MAX_BYTES_PER_TRANSACTION': 'uint64(2**30)', + 'MAX_BYTES_PER_TRANSACTION': spec_object.preset_vars['MAX_BYTES_PER_TRANSACTION'].value, } - return {**super().hardcoded_custom_type_dep_constants(), **constants} + return {**super().hardcoded_custom_type_dep_constants(spec_object), **constants} # @@ -567,14 +571,57 @@ from eth2spec.bellatrix import {preset_name} as bellatrix ''' +# +# EIP4844SpecBuilder +# +class EIP4844SpecBuilder(BellatrixSpecBuilder): + fork: str = EIP4844 + + @classmethod + def imports(cls, preset_name: str): + return super().imports(preset_name) + f''' +from eth2spec.utils import kzg +from eth2spec.bellatrix import {preset_name} as bellatrix +''' + + @classmethod + def sundry_functions(cls) -> str: + return super().sundry_functions() + ''' +# TODO: for mainnet, load pre-generated trusted setup file to reduce building time. +# TESTING_FIELD_ELEMENTS_PER_BLOB is hardcoded copy from minimal presets +TESTING_FIELD_ELEMENTS_PER_BLOB = 4 +TESTING_SECRET = 1337 +TESTING_KZG_SETUP_G1 = kzg.generate_setup(bls.G1, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB) +TESTING_KZG_SETUP_G2 = kzg.generate_setup(bls.G2, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB) +TESTING_KZG_SETUP_LAGRANGE = kzg.get_lagrange(TESTING_KZG_SETUP_G1) + +KZG_SETUP_G1 = [bls.G1_to_bytes48(p) for p in TESTING_KZG_SETUP_G1] +KZG_SETUP_G2 = [bls.G2_to_bytes96(p) for p in TESTING_KZG_SETUP_G2] +KZG_SETUP_LAGRANGE = TESTING_KZG_SETUP_LAGRANGE +ROOTS_OF_UNITY = kzg.compute_roots_of_unity(TESTING_FIELD_ELEMENTS_PER_BLOB) + + +def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> BlobsSidecar: + pass''' + + @classmethod + def hardcoded_custom_type_dep_constants(cls, spec_object) -> str: + constants = { + 'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value, + 'MAX_BLOBS_PER_BLOCK': spec_object.preset_vars['MAX_BLOBS_PER_BLOCK'].value, + } + return {**super().hardcoded_custom_type_dep_constants(spec_object), **constants} + + + spec_builders = { builder.fork: builder - for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder) + for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, EIP4844SpecBuilder) } def is_spec_defined_type(value: str) -> bool: - return value.startswith('ByteList') or value.startswith('Union') + return value.startswith(('ByteList', 'Union', 'Vector', 'List')) def objects_to_spec(preset_name: str, @@ -652,7 +699,7 @@ def objects_to_spec(preset_name: str, ordered_class_objects_spec = '\n\n\n'.join(ordered_class_objects.values()) ssz_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_ssz_dep_constants()[x]), builder.hardcoded_ssz_dep_constants())) ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), builder.hardcoded_ssz_dep_constants())) - custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_custom_type_dep_constants()[x]), builder.hardcoded_custom_type_dep_constants())) + custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_custom_type_dep_constants(spec_object)[x]), builder.hardcoded_custom_type_dep_constants(spec_object))) spec = ( builder.imports(preset_name) + builder.preparations() @@ -869,14 +916,14 @@ class PySpecCommand(Command): if len(self.md_doc_paths) == 0: print("no paths were specified, using default markdown file paths for pyspec" " build (spec fork: %s)" % self.spec_fork) - if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA): + if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844): self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md specs/phase0/validator.md specs/phase0/weak-subjectivity.md """ - if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA): + if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA, EIP4844): self.md_doc_paths += """ specs/altair/beacon-chain.md specs/altair/bls.md @@ -885,7 +932,7 @@ class PySpecCommand(Command): specs/altair/p2p-interface.md specs/altair/sync-protocol.md """ - if self.spec_fork in (BELLATRIX, CAPELLA): + if self.spec_fork in (BELLATRIX, CAPELLA, EIP4844): self.md_doc_paths += """ specs/bellatrix/beacon-chain.md specs/bellatrix/fork.md @@ -902,6 +949,14 @@ class PySpecCommand(Command): specs/capella/validator.md specs/capella/p2p-interface.md """ + if self.spec_fork == EIP4844: + self.md_doc_paths += """ + specs/eip4844/beacon-chain.md + specs/eip4844/fork.md + specs/eip4844/polynomial-commitments.md + specs/eip4844/p2p-interface.md + specs/eip4844/validator.md + """ if len(self.md_doc_paths) == 0: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index b3c250c11..a5f19e20c 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -51,7 +51,7 @@ def pytest_addoption(parser): def _validate_fork_name(forks): for fork in forks: - if fork not in ALL_PHASES: + if fork not in set(ALL_PHASES): raise ValueError( f'The given --fork argument "{fork}" is not an available fork.' f' The available forks: {ALL_PHASES}' diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 06313c195..ec3032b28 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -7,13 +7,14 @@ from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phas from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spec_bellatrix_minimal from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal +from eth2spec.eip4844 import mainnet as spec_eip4844_mainnet, minimal as spec_eip4844_minimal from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( - PHASE0, ALTAIR, BELLATRIX, CAPELLA, + PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844, MINIMAL, MAINNET, - ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA, + ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, ALL_FORK_UPGRADES, ) from .helpers.typing import SpecForkName, PresetBaseName @@ -76,12 +77,14 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { ALTAIR: spec_altair_minimal, BELLATRIX: spec_bellatrix_minimal, CAPELLA: spec_capella_minimal, + EIP4844: spec_eip4844_minimal, }, MAINNET: { PHASE0: spec_phase0_mainnet, ALTAIR: spec_altair_mainnet, BELLATRIX: spec_bellatrix_mainnet, CAPELLA: spec_capella_mainnet, + EIP4844: spec_eip4844_mainnet }, } @@ -576,12 +579,13 @@ def is_post_bellatrix(spec): def is_post_capella(spec): - return spec.fork not in FORKS_BEFORE_CAPELLA + return spec.fork == CAPELLA with_altair_and_later = with_all_phases_except([PHASE0]) with_bellatrix_and_later = with_all_phases_except([PHASE0, ALTAIR]) -with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX]) +with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, EIP4844]) +with_eip4844_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, CAPELLA]) def only_generator(reason): diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 0bc6b2e08..b1463b97b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -14,9 +14,15 @@ CAPELLA = SpecForkName('capella') SHARDING = SpecForkName('sharding') CUSTODY_GAME = SpecForkName('custody_game') DAS = SpecForkName('das') +EIP4844 = SpecForkName('eip4844') -# The forks that pytest runs with. -ALL_PHASES = (PHASE0, ALTAIR, BELLATRIX, CAPELLA) +# The forks that pytest can run with. +ALL_PHASES = ( + # Formal forks + PHASE0, ALTAIR, BELLATRIX, CAPELLA, + # Experimental patches + EIP4844, +) # The forks that output to the test vectors. TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 30f7a0394..5225d4efe 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import FORKS_BEFORE_CAPELLA +from eth2spec.test.context import is_post_capella def build_empty_execution_payload(spec, state, randao_mix=None): @@ -28,7 +28,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None): block_hash=spec.Hash32(), transactions=empty_txs, ) - if spec.fork not in FORKS_BEFORE_CAPELLA: + if is_post_capella(spec): num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue)) payload.withdrawals = state.withdrawal_queue[:num_withdrawals] @@ -55,7 +55,7 @@ def get_execution_payload_header(spec, execution_payload): block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) - if spec.fork not in FORKS_BEFORE_CAPELLA: + if is_post_capella(spec): payload_header.withdrawals_root = spec.hash_tree_root(execution_payload.withdrawals) return payload_header diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index ca248d8a5..0280bc7fb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -13,6 +13,7 @@ from eth2spec.test.helpers.constants import ( ALTAIR, BELLATRIX, CAPELLA, + EIP4844, ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, @@ -150,6 +151,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict= state = post_spec.upgrade_to_bellatrix(state) elif post_spec.fork == CAPELLA: state = post_spec.upgrade_to_capella(state) + elif post_spec.fork == EIP4844: + state = post_spec.upgrade_to_eip4844(state) assert state.fork.epoch == fork_epoch diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 83994c409..67ff2d4a8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.constants import ( - ALTAIR, BELLATRIX, - FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA, + ALTAIR, BELLATRIX, CAPELLA, + FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, ) from eth2spec.test.helpers.keys import pubkeys @@ -20,7 +20,7 @@ def build_mock_validator(spec, i: int, balance: int): effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE) ) - if spec.fork not in FORKS_BEFORE_CAPELLA: + if spec.fork in (CAPELLA): validator.fully_withdrawn_epoch = spec.FAR_FUTURE_EPOCH return validator diff --git a/tests/core/pyspec/eth2spec/test/helpers/sharding.py b/tests/core/pyspec/eth2spec/test/helpers/sharding.py new file mode 100644 index 000000000..6c90153fc --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/sharding.py @@ -0,0 +1,81 @@ +import random +from eth2spec.utils.ssz.ssz_typing import ( + Container, + Bytes20, Bytes32, + ByteList, + List, + Union, + boolean, + uint256, uint64, +) +from eth2spec.utils.ssz.ssz_impl import serialize + + +# +# Containers from EIP-4844 +# +MAX_CALLDATA_SIZE = 2**24 +MAX_VERSIONED_HASHES_LIST_SIZE = 2**24 +MAX_ACCESS_LIST_STORAGE_KEYS = 2**24 +MAX_ACCESS_LIST_SIZE = 2**24 + + +class AccessTuple(Container): + address: Bytes20 # Address = Bytes20 + storage_keys: List[Bytes32, MAX_ACCESS_LIST_STORAGE_KEYS] + + +class ECDSASignature(Container): + y_parity: boolean + r: uint256 + s: uint256 + + +class BlobTransaction(Container): + chain_id: uint256 + nonce: uint64 + priority_fee_per_gas: uint256 + max_basefee_per_gas: uint256 + gas: uint64 + to: Union[None, Bytes20] # Address = Bytes20 + value: uint256 + data: ByteList[MAX_CALLDATA_SIZE] + access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] + blob_versioned_hashes: List[Bytes32, MAX_VERSIONED_HASHES_LIST_SIZE] + + +class SignedBlobTransaction(Container): + message: BlobTransaction + signature: ECDSASignature + + +def get_sample_blob(spec, rng=None): + if rng is None: + rng = random.Random(5566) + + return spec.Blob([ + rng.randint(0, spec.BLS_MODULUS - 1) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ]) + + +def get_sample_opaque_tx(spec, blob_count=1, rng=None): + blobs = [] + blob_kzg_commitments = [] + blob_versioned_hashes = [] + for _ in range(blob_count): + blob = get_sample_blob(spec, rng) + blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob)) + blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment) + blobs.append(blob) + blob_kzg_commitments.append(blob_commitment) + blob_versioned_hashes.append(blob_versioned_hash) + + signed_blob_tx = SignedBlobTransaction( + message=BlobTransaction( + blob_versioned_hashes=blob_versioned_hashes, + ) + ) + serialized_tx = serialize(signed_blob_tx) + opaque_tx = spec.uint_to_bytes(spec.BLOB_TX_TYPE) + serialized_tx + return opaque_tx, blobs, blob_kzg_commitments diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 44d663422..907e73b8d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -45,6 +45,7 @@ from eth2spec.test.context import ( @with_all_phases @spec_state_test def test_prev_slot_block_transition(spec, state): + print('spec.fork', spec.fork) # Go to clean slot spec.process_slots(state, state.slot + 1) # Make a block for it diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index 4e8d4bbaa..6e545cef7 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -1,7 +1,7 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA +from eth2spec.test.helpers.constants import ALL_PHASES from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store @@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): spec.on_attestation(store, attestation) sample_index = indexed_attestation.attesting_indices[0] - if spec.fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA): + if spec.fork in ALL_PHASES: latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 9211e0ff0..e33017ade 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -1,5 +1,25 @@ from py_ecc.bls import G2ProofOfPossession as py_ecc_bls from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_to_G2 +from py_ecc.optimized_bls12_381 import ( # noqa: F401 + G1, + G2, + Z1, + Z2, + add, + multiply, + neg, + pairing, + final_exponentiate, + FQ12 +) +from py_ecc.bls.g2_primitives import ( # noqa: F401 + G1_to_pubkey as G1_to_bytes48, + pubkey_to_G1 as bytes48_to_G1, + G2_to_signature as G2_to_bytes96, + signature_to_G2 as bytes96_to_G2, +) + + import milagro_bls_binding as milagro_bls # noqa: F401 for BLS switching option # Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing. @@ -109,3 +129,12 @@ def SkToPk(SK): return bls.SkToPk(SK) else: return bls.SkToPk(SK.to_bytes(32, 'big')) + + +def pairing_check(values): + p_q_1, p_q_2 = values + final_exponentiation = final_exponentiate( + pairing(p_q_1[1], p_q_1[0], final_exponentiate=False) + * pairing(p_q_2[1], p_q_2[0], final_exponentiate=False) + ) + return final_exponentiation == FQ12.one() diff --git a/tests/core/pyspec/eth2spec/utils/kzg.py b/tests/core/pyspec/eth2spec/utils/kzg.py new file mode 100644 index 000000000..e174e69ab --- /dev/null +++ b/tests/core/pyspec/eth2spec/utils/kzg.py @@ -0,0 +1,80 @@ +# Ref: +# - https://github.com/ethereum/research/blob/8f084630528ba33d92b2bc05edf5338dd193c6f1/trusted_setup/trusted_setup.py +# - https://github.com/asn-d6/kzgverify +from py_ecc.optimized_bls12_381 import ( # noqa: F401 + G1, + G2, + Z1, + Z2, + curve_order as BLS_MODULUS, + add, + multiply, + neg, +) +from eth2spec.utils import bls + + +PRIMITIVE_ROOT_OF_UNITY = 7 + + +def generate_setup(generator, secret, length): + """ + Generate trusted setup of ``generator`` in ``length``. + """ + result = [generator] + for _ in range(1, length): + result.append(multiply(result[-1], secret)) + return tuple(result) + + +def fft(vals, modulus, domain): + """ + FFT for group elements + """ + if len(vals) == 1: + return vals + L = fft(vals[::2], modulus, domain[::2]) + R = fft(vals[1::2], modulus, domain[::2]) + o = [0] * len(vals) + for i, (x, y) in enumerate(zip(L, R)): + y_times_root = multiply(y, domain[i]) + o[i] = add(x, y_times_root) + o[i + len(L)] = add(x, neg(y_times_root)) + return o + + +def compute_root_of_unity(length) -> int: + """ + Generate a w such that ``w**length = 1``. + """ + assert (BLS_MODULUS - 1) % length == 0 + return pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // length, BLS_MODULUS) + + +def compute_roots_of_unity(field_elements_per_blob): + """ + Compute a list of roots of unity for a given order. + The order must divide the BLS multiplicative group order, i.e. BLS_MODULUS - 1 + """ + assert (BLS_MODULUS - 1) % field_elements_per_blob == 0 + root_of_unity = compute_root_of_unity(length=field_elements_per_blob) + + roots = [] + current_root_of_unity = 1 + for _ in range(field_elements_per_blob): + roots.append(current_root_of_unity) + current_root_of_unity = current_root_of_unity * root_of_unity % BLS_MODULUS + return roots + + +def get_lagrange(setup): + """ + Convert a G1 or G2 portion of a setup into the Lagrange basis. + """ + root_of_unity = compute_root_of_unity(len(setup)) + assert pow(root_of_unity, len(setup), BLS_MODULUS) == 1 + domain = [pow(root_of_unity, i, BLS_MODULUS) for i in range(len(setup))] + # TODO: introduce an IFFT function for simplicity + fft_output = fft(setup, BLS_MODULUS, domain) + inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS) + return [bls.G1_to_bytes48(multiply(fft_output[-i], inv_length)) for i in range(len(fft_output))]