Add whisk tests
This commit is contained in:
parent
f1aabcd718
commit
4ce2b02b44
|
@ -168,6 +168,19 @@ jobs:
|
|||
command: make citest fork=eip6110
|
||||
- store_test_results:
|
||||
path: tests/core/pyspec/test-reports
|
||||
test-whisk:
|
||||
docker:
|
||||
- image: circleci/python:3.9
|
||||
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=whisk
|
||||
- store_test_results:
|
||||
path: tests/core/pyspec/test-reports
|
||||
table_of_contents:
|
||||
docker:
|
||||
- image: circleci/node:10.16.3
|
||||
|
@ -291,6 +304,9 @@ workflows:
|
|||
- test-eip6110:
|
||||
requires:
|
||||
- install_pyspec_test
|
||||
- test-whisk:
|
||||
requires:
|
||||
- install_pyspec_test
|
||||
- table_of_contents
|
||||
- codespell
|
||||
- lint:
|
||||
|
|
|
@ -71,7 +71,7 @@ jobs:
|
|||
needs: [preclear,lint,codespell,table_of_contents]
|
||||
strategy:
|
||||
matrix:
|
||||
version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110"]
|
||||
version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110", "whisk"]
|
||||
steps:
|
||||
- name: Checkout this repo
|
||||
uses: actions/checkout@v3.2.0
|
||||
|
|
7
setup.py
7
setup.py
|
@ -257,6 +257,11 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr
|
|||
if any('KZG_SETUP' in name for name in constant_vars):
|
||||
_update_constant_vars_with_kzg_setups(constant_vars, preset_name)
|
||||
|
||||
if any('CURDLEPROOFS_CRS' in name for name in constant_vars):
|
||||
# TODO: Use actual CRS derived from a fixed string like 'nankokita_no_kakurenbo'
|
||||
crs_len = int(preset_vars['WHISK_VALIDATORS_PER_SHUFFLE'].value) + int(preset_vars['CURDLEPROOFS_N_BLINDERS'].value) + 3
|
||||
constant_vars['CURDLEPROOFS_CRS_G1'] = VariableDefinition(constant_vars['CURDLEPROOFS_CRS_G1'].value, str(ALL_KZG_SETUPS['mainnet'][0][0:crs_len]), "noqa: E501", None)
|
||||
|
||||
return SpecObject(
|
||||
functions=functions,
|
||||
protocols=protocols,
|
||||
|
@ -519,6 +524,6 @@ setup(
|
|||
"lru-dict==1.2.0",
|
||||
MARKO_VERSION,
|
||||
"py_arkworks_bls12381==0.3.4",
|
||||
"curdleproofs @ git+https://github.com/nalinbhardwaj/curdleproofs.pie@805d06785b6ff35fde7148762277dd1ae678beeb#egg=curdleproofs&subdirectory=curdleproofs",
|
||||
"curdleproofs @ git+https://github.com/nalinbhardwaj/curdleproofs.pie@5fe661b7183454655ff1e47690bb28e01e66ea66#egg=curdleproofs&subdirectory=curdleproofs",
|
||||
]
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
This document details the beacon chain additions and changes of to support the Whisk SSLE,
|
||||
|
||||
*Note:* This specification is built upon [Capella](../../capella/beacon-chain.md) and is under active development.
|
||||
*Note:* This specification is built upon [capella](../../capella/beacon-chain.md) and is under active development.
|
||||
|
||||
## Constants
|
||||
|
||||
|
@ -83,14 +83,15 @@ def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement:
|
|||
TODO: Deneb will introduces this helper too. Should delete it once it's rebased to post-Deneb.
|
||||
"""
|
||||
field_element = int.from_bytes(b, ENDIANNESS)
|
||||
assert field_element < BLS_MODULUS
|
||||
return BLSFieldElement(field_element)
|
||||
return BLSFieldElement(field_element % BLS_MODULUS)
|
||||
```
|
||||
|
||||
| Name | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------- |
|
||||
| `BLS_G1_GENERATOR` | `bls.G1_to_bytes48(bls.G1)` |
|
||||
| `BLS_G1_GENERATOR` | `Bytes48('0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb')` |
|
||||
| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` |
|
||||
| `CURDLEPROOFS_CRS_G1` | `Vector[G1Point, WHISK_VALIDATORS_PER_SHUFFLE + CURDLEPROOFS_N_BLINDERS + 3]`, contents TBD
|
||||
| `CURDLEPROOFS_CRS` | `curdleproofs.CurdleproofsCrs.from_points_compressed(WHISK_VALIDATORS_PER_SHUFFLE, CURDLEPROOFS_N_BLINDERS, CURDLEPROOFS_CRS_G1)` |
|
||||
|
||||
### Curdleproofs and opening proofs
|
||||
|
||||
|
@ -105,8 +106,16 @@ def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker],
|
|||
Verify `post_shuffle_trackers` is a permutation of `pre_shuffle_trackers`.
|
||||
Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/tree/verifier-only.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
return True
|
||||
try:
|
||||
return curdleproofs.IsValidWhiskShuffleProof(
|
||||
CURDLEPROOFS_CRS,
|
||||
pre_shuffle_trackers,
|
||||
post_shuffle_trackers,
|
||||
M,
|
||||
shuffle_proof,
|
||||
)
|
||||
except:
|
||||
return False
|
||||
```
|
||||
|
||||
```python
|
||||
|
@ -117,8 +126,11 @@ def IsValidWhiskOpeningProof(tracker: WhiskTracker,
|
|||
Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`.
|
||||
Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/tree/verifier-only.
|
||||
"""
|
||||
# pylint: disable=unused-argument
|
||||
return True
|
||||
try:
|
||||
return curdleproofs.IsValidWhiskOpeningProof(tracker, k_commitment, tracker_proof)
|
||||
except:
|
||||
return False
|
||||
|
||||
```
|
||||
|
||||
## Epoch processing
|
||||
|
@ -276,7 +288,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
|||
#### `BeaconBlockBody`
|
||||
|
||||
```python
|
||||
class BeaconBlockBody(capella.BeaconBlockBody):
|
||||
class BeaconBlockBody(Container):
|
||||
randao_reveal: BLSSignature
|
||||
eth1_data: Eth1Data # Eth1 data vote
|
||||
graffiti: Bytes32 # Arbitrary data
|
||||
|
@ -289,7 +301,6 @@ class BeaconBlockBody(capella.BeaconBlockBody):
|
|||
sync_aggregate: SyncAggregate
|
||||
# Execution
|
||||
execution_payload: ExecutionPayload
|
||||
# Capella operations
|
||||
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
|
||||
# Whisk
|
||||
whisk_opening_proof: WhiskTrackerProof # [New in Whisk]
|
||||
|
@ -350,10 +361,7 @@ def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool
|
|||
```
|
||||
|
||||
```python
|
||||
def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
process_shuffled_trackers(state, body)
|
||||
|
||||
# Overwrite all validator Whisk fields (first Whisk proposal) or just the permutation commitment (next proposals)
|
||||
def process_whisk_registration(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
proposer = state.validators[get_beacon_proposer_index(state)]
|
||||
if proposer.whisk_tracker.r_G == BLS_G1_GENERATOR: # first Whisk proposal
|
||||
assert body.whisk_tracker.r_G != BLS_G1_GENERATOR
|
||||
|
@ -369,20 +377,19 @@ def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None:
|
|||
assert body.whisk_registration_proof == WhiskTrackerProof()
|
||||
assert body.whisk_tracker == WhiskTracker()
|
||||
assert body.whisk_k_commitment == BLSG1Point()
|
||||
assert body.whisk_shuffle_proof_M_commitment == BLSG1Point()
|
||||
```
|
||||
|
||||
```python
|
||||
def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_block_header(state, block)
|
||||
if is_execution_enabled(state, block.body):
|
||||
process_withdrawals(state, block.body.execution_payload)
|
||||
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
|
||||
process_withdrawals(state, block.body.execution_payload)
|
||||
process_execution_payload(state, block.body, EXECUTION_ENGINE)
|
||||
process_randao(state, block.body)
|
||||
process_eth1_data(state, block.body)
|
||||
process_operations(state, block.body)
|
||||
process_sync_aggregate(state, block.body.sync_aggregate)
|
||||
process_whisk(state, block.body) # [New in Whisk]
|
||||
process_shuffled_trackers(state, block.body) # [New in Whisk]
|
||||
process_whisk_registration(state, block.body) # [New in Whisk]
|
||||
```
|
||||
|
||||
### Deposits
|
||||
|
|
|
@ -9,12 +9,14 @@ from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spe
|
|||
from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal
|
||||
from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal
|
||||
from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal
|
||||
from eth2spec.whisk import mainnet as spec_whisk_mainnet, minimal as spec_whisk_minimal
|
||||
from eth2spec.utils import bls
|
||||
|
||||
from .exceptions import SkippedTest
|
||||
from .helpers.constants import (
|
||||
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
|
||||
EIP6110,
|
||||
WHISK,
|
||||
MINIMAL, MAINNET,
|
||||
ALL_PHASES,
|
||||
ALL_FORK_UPGRADES,
|
||||
|
@ -83,6 +85,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
|
|||
CAPELLA: spec_capella_minimal,
|
||||
DENEB: spec_deneb_minimal,
|
||||
EIP6110: spec_eip6110_minimal,
|
||||
WHISK: spec_whisk_minimal,
|
||||
},
|
||||
MAINNET: {
|
||||
PHASE0: spec_phase0_mainnet,
|
||||
|
@ -91,6 +94,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
|
|||
CAPELLA: spec_capella_mainnet,
|
||||
DENEB: spec_deneb_mainnet,
|
||||
EIP6110: spec_eip6110_mainnet,
|
||||
WHISK: spec_whisk_mainnet,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -541,6 +545,7 @@ with_bellatrix_and_later = with_all_phases_from(BELLATRIX)
|
|||
with_capella_and_later = with_all_phases_from(CAPELLA)
|
||||
with_deneb_and_later = with_all_phases_from(DENEB)
|
||||
with_eip6110_and_later = with_all_phases_from(EIP6110)
|
||||
with_whisk_and_later = with_all_phases_from(WHISK)
|
||||
|
||||
|
||||
class quoted_str(str):
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
from eth2spec.test.helpers.execution_payload import build_empty_execution_payload
|
||||
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
|
||||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.test.helpers.forks import is_post_whisk, is_post_altair, is_post_bellatrix
|
||||
from eth2spec.test.helpers.keys import privkeys, whisk_ks_initial, whisk_ks_final
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.bls import only_with_bls
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from curdleproofs import (
|
||||
GenerateWhiskTrackerProof,
|
||||
WhiskTracker,
|
||||
IsValidWhiskOpeningProof,
|
||||
GenerateWhiskShuffleProof,
|
||||
IsValidWhiskShuffleProof
|
||||
)
|
||||
from py_ecc.optimized_bls12_381.optimized_curve import G1, multiply
|
||||
from py_ecc.typing import Optimized_Field, Optimized_Point3D
|
||||
from py_ecc.bls.g2_primitives import (
|
||||
G1_to_pubkey as py_ecc_G1_to_bytes48,
|
||||
pubkey_to_G1 as py_ecc_bytes48_to_G1,
|
||||
)
|
||||
from eth2spec.test.helpers.whisk import get_whisk_tracker_and_commitment
|
||||
|
||||
PointProjective = Optimized_Point3D[Optimized_Field]
|
||||
|
||||
def get_proposer_index_maybe(spec, state, slot, proposer_index=None):
|
||||
if proposer_index is None:
|
||||
|
@ -24,10 +39,9 @@ def get_proposer_index_maybe(spec, state, slot, proposer_index=None):
|
|||
|
||||
|
||||
@only_with_bls()
|
||||
def apply_randao_reveal(spec, state, block, proposer_index=None):
|
||||
def apply_randao_reveal(spec, state, block, proposer_index):
|
||||
assert state.slot <= block.slot
|
||||
|
||||
proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
|
||||
privkey = privkeys[proposer_index]
|
||||
|
||||
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, spec.compute_epoch_at_slot(block.slot))
|
||||
|
@ -72,7 +86,7 @@ def apply_empty_block(spec, state, slot=None):
|
|||
return transition_unsigned_block(spec, state, block)
|
||||
|
||||
|
||||
def build_empty_block(spec, state, slot=None):
|
||||
def build_empty_block(spec, state, slot=None, proposer_index=None):
|
||||
"""
|
||||
Build empty block for ``slot``, built upon the latest block header seen by ``state``.
|
||||
Slot must be greater than or equal to the current slot in ``state``.
|
||||
|
@ -87,13 +101,14 @@ def build_empty_block(spec, state, slot=None):
|
|||
spec.process_slots(state, slot)
|
||||
|
||||
state, parent_block_root = get_state_and_beacon_parent_root_at_slot(spec, state, slot)
|
||||
proposer_index = get_beacon_proposer_to_build(spec, state, proposer_index)
|
||||
empty_block = spec.BeaconBlock()
|
||||
empty_block.slot = slot
|
||||
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
|
||||
empty_block.proposer_index = proposer_index
|
||||
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
|
||||
empty_block.parent_root = parent_block_root
|
||||
|
||||
apply_randao_reveal(spec, state, empty_block)
|
||||
apply_randao_reveal(spec, state, empty_block, proposer_index)
|
||||
|
||||
if is_post_altair(spec):
|
||||
empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY
|
||||
|
@ -101,11 +116,112 @@ def build_empty_block(spec, state, slot=None):
|
|||
if is_post_bellatrix(spec):
|
||||
empty_block.body.execution_payload = build_empty_execution_payload(spec, state)
|
||||
|
||||
if is_post_whisk(spec):
|
||||
# Whisk opening proof
|
||||
#######
|
||||
|
||||
# Create valid whisk opening proof
|
||||
# TODO: Use k_initial or k_final to handle first and subsequent proposals
|
||||
k_initial = whisk_ks_initial[proposer_index]
|
||||
|
||||
# Sanity check proposer is correct
|
||||
proposer_k_commitment = state.validators[proposer_index].whisk_k_commitment
|
||||
k_commitment = py_ecc_G1_to_bytes48(multiply(G1, int(k_initial)))
|
||||
if proposer_k_commitment != k_commitment:
|
||||
raise Exception("k proposer_index does not match proposer_k_commitment", proposer_k_commitment, k_commitment)
|
||||
|
||||
proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT]
|
||||
if not is_whisk_proposer(proposer_tracker, k_initial):
|
||||
raise Exception("k proposer_index does not match proposer_tracker")
|
||||
|
||||
empty_block.body.whisk_opening_proof = GenerateWhiskTrackerProof(proposer_tracker, k_initial)
|
||||
if not IsValidWhiskOpeningProof(proposer_tracker, proposer_k_commitment, empty_block.body.whisk_opening_proof):
|
||||
raise Exception(
|
||||
"produced opening proof is not valid",
|
||||
proposer_tracker,
|
||||
proposer_k_commitment,
|
||||
empty_block.body.whisk_opening_proof
|
||||
)
|
||||
|
||||
# Whisk shuffle proof
|
||||
#######
|
||||
|
||||
shuffle_indices = spec.get_shuffle_indices(empty_block.body.randao_reveal)
|
||||
pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices]
|
||||
|
||||
post_trackers, m, shuffle_proof = GenerateWhiskShuffleProof(spec.CURDLEPROOFS_CRS, pre_shuffle_trackers)
|
||||
empty_block.body.whisk_post_shuffle_trackers = post_trackers
|
||||
empty_block.body.whisk_shuffle_proof = shuffle_proof
|
||||
empty_block.body.whisk_shuffle_proof_M_commitment = m
|
||||
|
||||
if not IsValidWhiskShuffleProof(
|
||||
spec.CURDLEPROOFS_CRS,
|
||||
pre_shuffle_trackers,
|
||||
empty_block.body.whisk_post_shuffle_trackers,
|
||||
empty_block.body.whisk_shuffle_proof_M_commitment,
|
||||
empty_block.body.whisk_shuffle_proof,
|
||||
):
|
||||
raise Exception(
|
||||
"produced shuffle proof is not valid",
|
||||
pre_shuffle_trackers,
|
||||
empty_block.body.whisk_post_shuffle_trackers,
|
||||
empty_block.body.whisk_shuffle_proof_M_commitment,
|
||||
empty_block.body.whisk_shuffle_proof,
|
||||
)
|
||||
|
||||
# Whisk registration proof
|
||||
#######
|
||||
|
||||
# Branching logic depending if first proposal or not
|
||||
is_first_proposal = state.validators[proposer_index].whisk_tracker.r_G == spec.BLS_G1_GENERATOR
|
||||
if is_first_proposal:
|
||||
# Register new tracker
|
||||
k_final = whisk_ks_final[proposer_index]
|
||||
# TODO: Actual logic should pick a random r, but we may want to do something fancy to locate trackers quickly
|
||||
r = 2
|
||||
tracker, k_commitment = get_whisk_tracker_and_commitment(k_final, r)
|
||||
empty_block.body.whisk_registration_proof = GenerateWhiskTrackerProof(tracker, k_final)
|
||||
empty_block.body.whisk_tracker = tracker
|
||||
empty_block.body.whisk_k_commitment = k_commitment
|
||||
|
||||
else:
|
||||
# Subsequent proposals, just leave empty
|
||||
empty_block.body.whisk_registration_proof = spec.WhiskTrackerProof()
|
||||
empty_block.body.whisk_tracker = spec.WhiskTracker()
|
||||
empty_block.body.whisk_k_commitment = spec.BLSG1Point()
|
||||
|
||||
return empty_block
|
||||
|
||||
|
||||
def build_empty_block_for_next_slot(spec, state):
|
||||
return build_empty_block(spec, state, state.slot + 1)
|
||||
def is_whisk_proposer(tracker: WhiskTracker, k: int) -> bool:
|
||||
return py_ecc_G1_to_bytes48(multiply(py_ecc_bytes48_to_G1(tracker.r_G), k)) == tracker.k_r_G
|
||||
|
||||
|
||||
def get_beacon_proposer_to_build(spec, state, proposer_index=None):
|
||||
if is_post_whisk(spec):
|
||||
if proposer_index is None:
|
||||
return find_whisk_proposer(spec, state)
|
||||
else:
|
||||
return proposer_index
|
||||
else:
|
||||
return spec.get_beacon_proposer_index(state)
|
||||
|
||||
|
||||
def find_whisk_proposer(spec, state):
|
||||
raise Exception("proposer not known without heavy math")
|
||||
proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT]
|
||||
# First attempt direct equality with trackers
|
||||
for i, validator in enumerate(state.validators):
|
||||
# # This is insanely slow
|
||||
# if validator.whisk_tracker == proposer_tracker:
|
||||
if True:
|
||||
return i
|
||||
# In Whisk where to get proposer from?
|
||||
raise Exception("proposer_tracker not matched")
|
||||
|
||||
|
||||
def build_empty_block_for_next_slot(spec, state, proposer_index=None):
|
||||
return build_empty_block(spec, state, state.slot + 1, proposer_index)
|
||||
|
||||
|
||||
def get_state_and_beacon_parent_root_at_slot(spec, state, slot):
|
||||
|
|
|
@ -17,6 +17,7 @@ SHARDING = SpecForkName('sharding')
|
|||
CUSTODY_GAME = SpecForkName('custody_game')
|
||||
DAS = SpecForkName('das')
|
||||
EIP6110 = SpecForkName('eip6110')
|
||||
WHISK = SpecForkName('whisk')
|
||||
|
||||
#
|
||||
# SpecFork settings
|
||||
|
@ -32,11 +33,12 @@ ALL_PHASES = (
|
|||
DENEB,
|
||||
# Experimental patches
|
||||
EIP6110,
|
||||
WHISK,
|
||||
)
|
||||
# The forks that have light client specs
|
||||
LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], DENEB)
|
||||
# The forks that output to the test vectors.
|
||||
TESTGEN_FORKS = (*MAINNET_FORKS, DENEB, EIP6110)
|
||||
TESTGEN_FORKS = (*MAINNET_FORKS, DENEB, EIP6110, WHISK)
|
||||
|
||||
ALL_FORK_UPGRADES = {
|
||||
# pre_fork_name: post_fork_name
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from .constants import (
|
||||
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
|
||||
EIP6110,
|
||||
WHISK,
|
||||
)
|
||||
|
||||
|
||||
def is_post_fork(a, b):
|
||||
if a == WHISK:
|
||||
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, WHISK]
|
||||
if a == EIP6110:
|
||||
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110]
|
||||
if a == DENEB:
|
||||
|
@ -38,3 +41,7 @@ def is_post_deneb(spec):
|
|||
|
||||
def is_post_eip6110(spec):
|
||||
return is_post_fork(spec.fork, EIP6110)
|
||||
|
||||
|
||||
def is_post_whisk(spec):
|
||||
return is_post_fork(spec.fork, WHISK)
|
|
@ -1,13 +1,13 @@
|
|||
from eth2spec.test.helpers.constants import (
|
||||
ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110,
|
||||
ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, WHISK,
|
||||
)
|
||||
from eth2spec.test.helpers.execution_payload import (
|
||||
compute_el_header_block_hash,
|
||||
)
|
||||
from eth2spec.test.helpers.forks import (
|
||||
is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110,
|
||||
is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110, is_post_whisk,
|
||||
)
|
||||
from eth2spec.test.helpers.keys import pubkeys
|
||||
from eth2spec.test.helpers.keys import pubkeys, whisk_ks_initial
|
||||
|
||||
|
||||
def build_mock_validator(spec, i: int, balance: int):
|
||||
|
@ -86,6 +86,9 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
|
|||
elif spec.fork == EIP6110:
|
||||
previous_version = spec.config.DENEB_FORK_VERSION
|
||||
current_version = spec.config.EIP6110_FORK_VERSION
|
||||
elif spec.fork == WHISK:
|
||||
previous_version = spec.config.CAPELLA_FORK_VERSION
|
||||
current_version = spec.config.WHISK_FORK_VERSION
|
||||
|
||||
state = spec.BeaconState(
|
||||
genesis_time=0,
|
||||
|
|
|
@ -4,3 +4,13 @@ from py_ecc.bls import G2ProofOfPossession as bls
|
|||
privkeys = [i + 1 for i in range(32 * 256)]
|
||||
pubkeys = [bls.SkToPk(privkey) for privkey in privkeys]
|
||||
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
|
||||
|
||||
MAX_KEYS = 32 * 256
|
||||
whisk_ks_initial = [i for i in range(MAX_KEYS)]
|
||||
# Must be unique among the set `whisk_ks_initial + whisk_ks_final`
|
||||
whisk_ks_final = [MAX_KEYS + i for i in range(MAX_KEYS)]
|
||||
|
||||
known_whisk_trackers = {}
|
||||
|
||||
def register_known_whisk_tracker(k_r_G: bytes, index: int):
|
||||
known_whisk_trackers[k_r_G] = index
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
from typing import Tuple
|
||||
from eth_typing import BLSPubkey
|
||||
from py_ecc.optimized_bls12_381.optimized_curve import G1, multiply
|
||||
from py_ecc.bls.g2_primitives import (
|
||||
G1_to_pubkey as py_ecc_G1_to_bytes48,
|
||||
pubkey_to_G1 as py_ecc_bytes48_to_G1,
|
||||
)
|
||||
from curdleproofs import (
|
||||
GenerateWhiskTrackerProof,
|
||||
WhiskTracker,
|
||||
IsValidWhiskOpeningProof,
|
||||
GenerateWhiskShuffleProof,
|
||||
IsValidWhiskShuffleProof
|
||||
)
|
||||
|
||||
def get_whisk_k_commitment(k: int) -> BLSPubkey:
|
||||
return py_ecc_G1_to_bytes48(multiply(G1, int(k)))
|
||||
|
||||
def get_whisk_tracker(k: int, r: int) -> WhiskTracker:
|
||||
r_G = multiply(G1, int(r))
|
||||
k_r_G = multiply(r_G, int(k))
|
||||
return WhiskTracker(py_ecc_G1_to_bytes48(r_G), py_ecc_G1_to_bytes48(k_r_G))
|
||||
|
||||
def get_whisk_tracker_and_commitment(k: int, r: int) -> Tuple[WhiskTracker, BLSPubkey]:
|
||||
k_G = multiply(G1, int(k))
|
||||
r_G = multiply(G1, int(r))
|
||||
k_r_G = multiply(r_G, int(k))
|
||||
tracker = WhiskTracker(py_ecc_G1_to_bytes48(r_G), py_ecc_G1_to_bytes48(k_r_G))
|
||||
commitment = py_ecc_G1_to_bytes48(k_G)
|
||||
return tracker, commitment
|
||||
|
||||
# Trigger condition for first proposal
|
||||
def set_as_first_proposal(spec, state, proposer_index: int):
|
||||
# Ensure tracker is empty to prevent breaking it
|
||||
assert state.validators[proposer_index].whisk_tracker.r_G == spec.BLSG1Point()
|
||||
state.validators[proposer_index].whisk_tracker.r_G = spec.BLS_G1_GENERATOR
|
||||
|
||||
|
||||
def is_first_proposal(spec, state, proposer_index: int) -> bool:
|
||||
return state.validators[proposer_index].whisk_tracker.r_G == spec.BLS_G1_GENERATOR
|
||||
|
||||
def set_registration(body, k: int, r: int):
|
||||
tracker, k_commitment = get_whisk_tracker_and_commitment(k, r)
|
||||
body.whisk_registration_proof = GenerateWhiskTrackerProof(tracker, k)
|
||||
body.whisk_tracker = tracker
|
||||
body.whisk_k_commitment = k_commitment
|
||||
|
||||
def set_opening_proof(spec, state, block, proposer_index: int, k: int, r: int):
|
||||
tracker, k_commitment = get_whisk_tracker_and_commitment(k, r)
|
||||
state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT] = tracker
|
||||
state.validators[proposer_index].whisk_k_commitment = k_commitment
|
||||
block.proposer_index = proposer_index
|
||||
block.body.whisk_opening_proof = GenerateWhiskTrackerProof(tracker, k)
|
|
@ -0,0 +1,148 @@
|
|||
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
|
||||
from eth2spec.test.helpers.keys import whisk_ks_initial
|
||||
from eth2spec.test.helpers.whisk import get_whisk_tracker
|
||||
from curdleproofs import GenerateWhiskShuffleProof
|
||||
|
||||
def set_correct_shuffle_proofs(spec, state, body):
|
||||
pre_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body)
|
||||
|
||||
post_trackers, m, shuffle_proof = GenerateWhiskShuffleProof(spec.CURDLEPROOFS_CRS, pre_shuffle_trackers)
|
||||
body.whisk_post_shuffle_trackers = post_trackers
|
||||
body.whisk_shuffle_proof = shuffle_proof
|
||||
body.whisk_shuffle_proof_M_commitment = m
|
||||
|
||||
def get_and_populate_pre_shuffle_trackers(spec, state, body):
|
||||
shuffle_indices = spec.get_shuffle_indices(body.randao_reveal)
|
||||
pre_shuffle_trackers = []
|
||||
for i in shuffle_indices:
|
||||
# Set r to some value > 1 ( = 2+i)
|
||||
tracker = get_whisk_tracker(whisk_ks_initial[i], 2 + i)
|
||||
state.whisk_candidate_trackers[i] = tracker
|
||||
pre_shuffle_trackers.append(tracker)
|
||||
return pre_shuffle_trackers
|
||||
|
||||
def get_pre_shuffle_trackers(spec, state, body):
|
||||
return [state.whisk_candidate_trackers[i] for i in spec.get_shuffle_indices(body.randao_reveal)]
|
||||
|
||||
def set_state_epoch(spec, state, epoch):
|
||||
state.slot = epoch * spec.SLOTS_PER_EPOCH
|
||||
|
||||
def set_state_epoch_selection_gap(spec, state):
|
||||
set_state_epoch(spec, state, spec.WHISK_EPOCHS_PER_SHUFFLING_PHASE - 1)
|
||||
|
||||
def empty_block_body(spec):
|
||||
return spec.BeaconBlockBody()
|
||||
|
||||
def run_process_shuffled_trackers(spec, state, body, valid=True):
|
||||
yield 'pre', state
|
||||
yield 'body', body
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_shuffled_trackers(state, body))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
spec.process_shuffled_trackers(state, body)
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_shuffle_trackers(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_correct_shuffle_proofs(spec, state, body)
|
||||
yield from run_process_shuffled_trackers(spec, state, body)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_no_shuffle_minus_selection_gap(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_state_epoch_selection_gap(spec, state)
|
||||
yield from run_process_shuffled_trackers(spec, state, body)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_no_shuffle_minus_one_and_selection_gap(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_state_epoch(spec, state, spec.WHISK_EPOCHS_PER_SHUFFLING_PHASE - spec.WHISK_PROPOSER_SELECTION_GAP - 1)
|
||||
yield from run_process_shuffled_trackers(spec, state, body)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_shuffle_during_selection_gap(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_correct_shuffle_proofs(spec, state, body)
|
||||
set_state_epoch_selection_gap(spec, state)
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
# Invalid cases on shuffle
|
||||
# - wrong m
|
||||
# - wrong proof
|
||||
# - wrong post shuffle
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_shuffle_bad_m(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_correct_shuffle_proofs(spec, state, body)
|
||||
body.whisk_shuffle_proof_M_commitment = spec.BLSG1Point()
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_shuffle_bad_proof(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_correct_shuffle_proofs(spec, state, body)
|
||||
body.whisk_shuffle_proof = spec.WhiskShuffleProof()
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_shuffle_bad_trackers_zero(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_correct_shuffle_proofs(spec, state, body)
|
||||
body.whisk_post_shuffle_trackers[0] = spec.WhiskTracker()
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
# TODO: This test does not fail
|
||||
# @with_whisk_and_later
|
||||
# @spec_state_test
|
||||
# def test_invalid_shuffle_bad_trackers_swap(spec, state):
|
||||
# body = empty_block_body(spec)
|
||||
# set_correct_shuffle_proofs(spec, state, body)
|
||||
# assert body.whisk_post_shuffle_trackers[0] != body.whisk_post_shuffle_trackers[1]
|
||||
# tracker_0 = body.whisk_post_shuffle_trackers[0]
|
||||
# body.whisk_post_shuffle_trackers[0] = body.whisk_post_shuffle_trackers[1]
|
||||
# body.whisk_post_shuffle_trackers[1] = tracker_0
|
||||
# yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
# Invalid things on gap
|
||||
# - not empty shuffle trackers
|
||||
# - not empty m
|
||||
# - not empty proof
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_gap_non_zero_m(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
body.whisk_shuffle_proof_M_commitment = spec.BLSG1Point('0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
|
||||
set_state_epoch_selection_gap(spec, state)
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_gap_non_zero_proof(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
body.whisk_shuffle_proof = spec.WhiskShuffleProof('0xff')
|
||||
set_state_epoch_selection_gap(spec, state)
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_invalid_gap_non_zero_trackers(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
body.whisk_post_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body)
|
||||
set_state_epoch_selection_gap(spec, state)
|
||||
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
|
||||
from eth2spec.test.helpers.whisk import (
|
||||
get_whisk_k_commitment,
|
||||
get_whisk_tracker,
|
||||
set_opening_proof
|
||||
)
|
||||
|
||||
def empty_block(spec):
|
||||
return spec.BeaconBlock()
|
||||
|
||||
def run_process_whisk_opening_proof(spec, state, block, valid=True):
|
||||
yield 'pre', state
|
||||
yield 'block', block
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_whisk_opening_proof(state, block))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
spec.process_whisk_opening_proof(state, block)
|
||||
|
||||
yield 'post', state
|
||||
|
||||
PROPOSER_INDEX = 0
|
||||
K_OK = 2
|
||||
K_WRONG = 3
|
||||
R_OK = 2
|
||||
R_WRONG = 3
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_valid_proof(spec, state):
|
||||
block = empty_block(spec)
|
||||
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
|
||||
run_process_whisk_opening_proof(spec, state, block)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_wrong_commitment(spec, state):
|
||||
block = empty_block(spec)
|
||||
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
|
||||
state.validators[PROPOSER_INDEX].whisk_k_commitment = get_whisk_k_commitment(K_WRONG)
|
||||
run_process_whisk_opening_proof(spec, state, block, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_wrong_tracker_r(spec, state):
|
||||
block = empty_block(spec)
|
||||
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
|
||||
state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT] = get_whisk_tracker(K_OK, R_WRONG)
|
||||
run_process_whisk_opening_proof(spec, state, block, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_wrong_proof(spec, state):
|
||||
block = empty_block(spec)
|
||||
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
|
||||
block.body.whisk_opening_proof = spec.WhiskTrackerProof()
|
||||
run_process_whisk_opening_proof(spec, state, block, valid=False)
|
|
@ -0,0 +1,90 @@
|
|||
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
|
||||
from eth2spec.test.helpers.keys import whisk_ks_initial, whisk_ks_final
|
||||
from eth2spec.test.helpers.whisk import get_whisk_tracker, set_as_first_proposal, get_whisk_k_commitment, set_registration
|
||||
from curdleproofs import GenerateWhiskShuffleProof
|
||||
|
||||
def empty_block_body(spec):
|
||||
return spec.BeaconBlockBody()
|
||||
|
||||
def set_as_first_proposal_and_proposer(spec, state, proposer_index):
|
||||
state.latest_block_header.proposer_index = proposer_index
|
||||
set_as_first_proposal(spec, state, proposer_index)
|
||||
|
||||
def run_process_whisk_registration(spec, state, body, valid=True):
|
||||
yield 'pre', state
|
||||
yield 'body', body
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_whisk_registration(state, body))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
spec.process_whisk_registration(state, body)
|
||||
|
||||
yield 'post', state
|
||||
|
||||
IDENTITY_R = 1
|
||||
OTHER_R = 2
|
||||
PROPOSER_INDEX = 0
|
||||
OTHER_INDEX = 1
|
||||
OTHER_K = 2
|
||||
|
||||
# First proposal
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_first_proposal_ok(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
|
||||
set_registration(body, OTHER_K, OTHER_R)
|
||||
yield from run_process_whisk_registration(spec, state, body)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_first_proposal_indentity_tracker(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
|
||||
set_registration(body, OTHER_K, IDENTITY_R)
|
||||
yield from run_process_whisk_registration(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_first_proposal_non_unique_k_other(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
|
||||
state.validators[OTHER_INDEX].whisk_k_commitment = get_whisk_k_commitment(OTHER_K)
|
||||
set_registration(body, OTHER_K, OTHER_R)
|
||||
yield from run_process_whisk_registration(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_first_proposal_non_unique_k_self(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
|
||||
state.validators[PROPOSER_INDEX].whisk_k_commitment = get_whisk_k_commitment(OTHER_K)
|
||||
set_registration(body, OTHER_K, OTHER_R)
|
||||
yield from run_process_whisk_registration(spec, state, body, valid=False)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_first_proposal_invalid_proof(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
|
||||
set_registration(body, OTHER_K, OTHER_R)
|
||||
body.whisk_tracker.k_r_G = spec.BLSG1Point()
|
||||
yield from run_process_whisk_registration(spec, state, body, valid=False)
|
||||
|
||||
# Second proposal
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_second_proposal_ok(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
yield from run_process_whisk_registration(spec, state, body)
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_second_proposal_not_zero(spec, state):
|
||||
body = empty_block_body(spec)
|
||||
set_registration(body, OTHER_K, OTHER_R)
|
||||
yield from run_process_whisk_registration(spec, state, body, valid=False)
|
|
@ -0,0 +1 @@
|
|||
from .test_whisk import * # noqa: F401 F403
|
|
@ -0,0 +1,58 @@
|
|||
from eth2spec.test.helpers.block import (
|
||||
build_empty_block_for_next_slot, build_empty_block
|
||||
)
|
||||
from eth2spec.test.context import (
|
||||
spec_state_test,
|
||||
with_whisk_and_later,
|
||||
WHISK,
|
||||
)
|
||||
from eth2spec.test.helpers.keys import privkeys, pubkeys, whisk_ks_initial
|
||||
from eth2spec.test.helpers.state import (
|
||||
state_transition_and_sign_block
|
||||
)
|
||||
from curdleproofs import IsValidWhiskShuffleProof
|
||||
from eth2spec.test.helpers.whisk import is_first_proposal, get_whisk_tracker_and_commitment, set_as_first_proposal
|
||||
from curdleproofs import WhiskTracker
|
||||
|
||||
known_whisk_trackers = {}
|
||||
|
||||
|
||||
def assign_proposer_at_slot(state, slot: int):
|
||||
state
|
||||
|
||||
|
||||
def initialize_whisk_full(spec, state):
|
||||
# TODO: De-duplicate code from whisk/fork.md
|
||||
for index, validator in enumerate(state.validators):
|
||||
whisk_k_commitment, whisk_tracker = spec.get_initial_commitments(whisk_ks_initial[index])
|
||||
validator.whisk_k_commitment = whisk_k_commitment
|
||||
validator.whisk_tracker = whisk_tracker
|
||||
|
||||
# Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day
|
||||
# Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection
|
||||
spec.select_whisk_candidate_trackers(state, spec.Epoch(0))
|
||||
spec.select_whisk_proposer_trackers(state, spec.Epoch(0))
|
||||
|
||||
# Fill candidate trackers with the same tracker so shuffling does not break
|
||||
def fill_candidate_trackers(spec, state, tracker: WhiskTracker):
|
||||
for i in range(spec.WHISK_CANDIDATE_TRACKERS_COUNT):
|
||||
state.whisk_candidate_trackers[i] = tracker
|
||||
|
||||
@with_whisk_and_later
|
||||
@spec_state_test
|
||||
def test_whisk__process_block_single_initial(spec, state):
|
||||
assert state.slot == 0
|
||||
proposer_slot_1 = 0
|
||||
tracker_slot_1, k_commitment = get_whisk_tracker_and_commitment(whisk_ks_initial[proposer_slot_1], 1)
|
||||
state.validators[proposer_slot_1].whisk_k_commitment = k_commitment
|
||||
state.whisk_proposer_trackers[1] = tracker_slot_1
|
||||
fill_candidate_trackers(spec, state, tracker_slot_1)
|
||||
|
||||
# Produce and process a whisk block
|
||||
yield 'pre', state
|
||||
|
||||
block = build_empty_block(spec, state, 1, proposer_slot_1)
|
||||
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
yield 'blocks', [signed_block]
|
||||
yield 'post', state
|
Loading…
Reference in New Issue