Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Generate Hive Test Fixtures #3374

Draft
wants to merge 11 commits into
base: dev
Choose a base branch
from
95 changes: 95 additions & 0 deletions configs/hive.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Mainnet config

# Extends the mainnet preset
PRESET_BASE: 'mainnet'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Mainnet config
# Extends the mainnet preset
PRESET_BASE: 'mainnet'
# Hive config
# Extends the hive preset
PRESET_BASE: 'hive'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a small modification in setup.py for this change to work: It was failing because there is no hive kzg setup, so I added EQUIVALENT_KZG_SETUPS map to re-utilize the existing mainnet kzg setup.


# Free-form short name of the network that this configuration applies to - known
# canonical network names include:
# * 'mainnet' - there can be only one
# * 'prater' - testnet
# Must match the regex: [a-z0-9\-]
CONFIG_NAME: 'hive'

# Transition
# ---------------------------------------------------------------
# Estimated on Sept 15, 2022
TERMINAL_TOTAL_DIFFICULTY: 58750000000000000000000
# By default, don't use these params
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615



# Genesis
# ---------------------------------------------------------------
# `2**14` (= 16,384)
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
# Dec 1, 2020, 12pm UTC
MIN_GENESIS_TIME: 1606824000
# Mainnet initial fork version, recommend altering for testnets
GENESIS_FORK_VERSION: 0x0000000a
# 604800 seconds (7 days)
GENESIS_DELAY: 604800


# Forking
# ---------------------------------------------------------------
# Some forks are disabled for now:
# - These may be re-assigned to another fork-version later
# - Temporarily set to max uint64 value: 2**64 - 1

# Altair
ALTAIR_FORK_VERSION: 0x0100000a
ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Bellatrix
BELLATRIX_FORK_VERSION: 0x0200000a
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
# Capella
CAPELLA_FORK_VERSION: 0x0300000a
CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC
# Deneb
DENEB_FORK_VERSION: 0x0400000a
DENEB_FORK_EPOCH: 18446744073709551615




# Time parameters
# ---------------------------------------------------------------
# 12 seconds
SECONDS_PER_SLOT: 12
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# 2**8 (= 256) epochs ~27 hours
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**8 (= 256) epochs ~27 hours
SHARD_COMMITTEE_PERIOD: 256
# 2**11 (= 2,048) Eth1 blocks ~8 hours
ETH1_FOLLOW_DISTANCE: 2048


# Validator cycle
# ---------------------------------------------------------------
# 2**2 (= 4)
INACTIVITY_SCORE_BIAS: 4
# 2**4 (= 16)
INACTIVITY_SCORE_RECOVERY_RATE: 16
# 2**4 * 10**9 (= 16,000,000,000) Gwei
EJECTION_BALANCE: 16000000000
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
CHURN_LIMIT_QUOTIENT: 65536


# Fork choice
# ---------------------------------------------------------------
# 40%
PROPOSER_SCORE_BOOST: 40

# Deposit contract
# ---------------------------------------------------------------
# Ethereum PoW Mainnet
DEPOSIT_CHAIN_ID: 1
DEPOSIT_NETWORK_ID: 1
DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa
15 changes: 11 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr
ssz_objects: Dict[str, str] = {}
dataclasses: Dict[str, str] = {}
custom_types: Dict[str, str] = {}
preset_base = config.get('PRESET_BASE')
if preset_base is not None:
preset_base = preset_base.strip("'")

with open(file_name) as source_file:
document = gfm.parse(source_file.read())
Expand Down Expand Up @@ -291,7 +294,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr

# Load KZG trusted setup from files
if any('KZG_SETUP' in name for name in constant_vars):
_update_constant_vars_with_kzg_setups(constant_vars, preset_name)
_update_constant_vars_with_kzg_setups(constant_vars, preset_name if preset_base is None else preset_base)

return SpecObject(
functions=functions,
Expand Down Expand Up @@ -692,6 +695,7 @@ def is_byte_vector(value: str) -> bool:


def objects_to_spec(preset_name: str,
preset_base: Optional[str],
spec_object: SpecObject,
builder: SpecBuilder,
ordered_class_objects: Dict[str, str]) -> str:
Expand Down Expand Up @@ -744,7 +748,7 @@ def format_config_var(name: str, vardef: VariableDefinition) -> str:
config_spec += '\n'.join(f' {k}: {v.type_name if v.type_name is not None else "int"}'
for k, v in spec_object.config_vars.items())
config_spec += '\n\n\nconfig = Configuration(\n'
config_spec += f' PRESET_BASE="{preset_name}",\n'
config_spec += f' PRESET_BASE="{preset_name if preset_base is None else preset_base}",\n'
config_spec += '\n'.join(' ' + format_config_var(k, v) for k, v in spec_object.config_vars.items())
config_spec += '\n)\n'

Expand Down Expand Up @@ -936,8 +940,10 @@ def _build_spec(preset_name: str, fork: str,
while OrderedDict(new_objects) != OrderedDict(class_objects):
new_objects = copy.deepcopy(class_objects)
dependency_order_class_objects(class_objects, spec_object.custom_types)

return objects_to_spec(preset_name, spec_object, spec_builders[fork], class_objects)
preset_base = config.get('PRESET_BASE')
if preset_base is not None:
preset_base = preset_base.strip("'")
return objects_to_spec(preset_name, preset_base, spec_object, spec_builders[fork], class_objects)


class BuildTarget(NamedTuple):
Expand Down Expand Up @@ -975,6 +981,7 @@ def initialize_options(self):
self.build_targets = """
minimal:presets/minimal:configs/minimal.yaml
mainnet:presets/mainnet:configs/mainnet.yaml
hive:presets/mainnet:configs/hive.yaml
"""

def finalize_options(self):
Expand Down
Empty file.
71 changes: 71 additions & 0 deletions tests/core/pyspec/eth2spec/test/capella/api/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from eth2spec.test.helpers.hive import StateID, VerifyBeaconStateV2
from eth2spec.test.context import (
with_capella_and_later,
spec_state_test,
hive_state,
)
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
next_slot,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.withdrawals import (
prepare_expected_withdrawals,
)


def _perform_valid_withdrawal(spec, state):
fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals(
spec, state, num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2,
num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2)

next_slot(spec, state)
pre_next_withdrawal_index = state.next_withdrawal_index

expected_withdrawals = spec.get_expected_withdrawals(state)

pre_state = state.copy()

# Block 1
block = build_empty_block_for_next_slot(spec, state)
signed_block_1 = state_transition_and_sign_block(spec, state, block)

withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals]
fully_withdrawable_indices = list(set(fully_withdrawable_indices).difference(set(withdrawn_indices)))
partial_withdrawals_indices = list(set(partial_withdrawals_indices).difference(set(withdrawn_indices)))
assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD

withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals]
fully_withdrawable_indices = list(set(fully_withdrawable_indices).difference(set(withdrawn_indices)))
partial_withdrawals_indices = list(set(partial_withdrawals_indices).difference(set(withdrawn_indices)))
assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD

return pre_state, signed_block_1, pre_next_withdrawal_index


@with_capella_and_later
@spec_state_test
@hive_state
def test_debug_beacon_state_v2(spec, state):
_, signed_block_1, pre_next_withdrawal_index = (_perform_valid_withdrawal(spec, state))

# Block 2
block = build_empty_block_for_next_slot(spec, state)
signed_block_2 = state_transition_and_sign_block(spec, state, block)

assert state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2
yield 'blocks', [signed_block_1, signed_block_2]
yield 'post', state

yield 'hive', [
(
VerifyBeaconStateV2(id=StateID.Head()).
state_root(signed_block_2.message.state_root)
),
(
VerifyBeaconStateV2(id=StateID.Slot(signed_block_2.message.slot)).
state_root(signed_block_2.message.state_root)
),
]
44 changes: 38 additions & 6 deletions tests/core/pyspec/eth2spec/test/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
from dataclasses import dataclass
import importlib

from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal
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.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal
from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal, hive as spec_phase0_hive
from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal, hive as spec_altair_hive
from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spec_bellatrix_minimal, hive as spec_bellatrix_hive
from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal, hive as spec_capella_hive
from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal, hive as spec_deneb_hive
from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal
from eth2spec.utils import bls

from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
EIP6110,
MINIMAL, MAINNET,
MINIMAL, MAINNET, HIVE,
ALL_PHASES,
ALL_FORK_UPGRADES,
)
Expand Down Expand Up @@ -91,6 +91,13 @@ class ForkMeta:
DENEB: spec_deneb_mainnet,
EIP6110: spec_eip6110_mainnet,
},
HIVE: {
PHASE0: spec_phase0_hive,
ALTAIR: spec_altair_hive,
BELLATRIX: spec_bellatrix_hive,
CAPELLA: spec_capella_hive,
DENEB: spec_deneb_hive,
},
}


Expand Down Expand Up @@ -744,3 +751,28 @@ def post_tag(obj):

return wrapper
return decorator


#
# Hive state modifiers
#

def hive_state(fn):
"""
Makes necessary changes to the state in order for the client to accept it in hive mode.
"""
def wrapper(*args, **kwargs):
if 'state' not in kwargs or 'spec' not in kwargs:
raise Exception("hive_state decorator requires state and spec")
state = kwargs['state']
spec = kwargs['spec']

# Increase genesis time to min genesis time
state.genesis_time = spec.config.MIN_GENESIS_TIME
kwargs['state'] = state

res = fn(*args, **kwargs)
if res is not None:
yield 'genesis', state
yield from res
return wrapper
3 changes: 2 additions & 1 deletion tests/core/pyspec/eth2spec/test/helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@
#
MAINNET = PresetBaseName('mainnet')
MINIMAL = PresetBaseName('minimal')
HIVE = PresetBaseName('hive')

ALL_PRESETS = (MINIMAL, MAINNET)
ALL_PRESETS = (MINIMAL, MAINNET, HIVE)


#
Expand Down
48 changes: 48 additions & 0 deletions tests/core/pyspec/eth2spec/test/helpers/hive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from ruamel.yaml import YAML
from dataclasses import dataclass, field
from typing import Dict
yaml = YAML()


class StateID(str):

@classmethod
def Root(cls, root):
return cls(f"root:{root.hex()}")

@classmethod
def Slot(cls, slot):
return cls(f"slot:{slot}")

@classmethod
def Head(cls):
return cls("head")

@classmethod
def Genesis(cls):
return cls("genesis")

@classmethod
def Finalized(cls):
return cls("finalized")

@classmethod
def Justified(cls):
return cls("justified")


@dataclass(kw_only=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kw_only is new in Python3.10 but eth2spec's minimum requirement is Python3.9.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! :)

Also removed the method key since the YAML library tags the type of each exported class, and I could use this tag in hive to detect the object type, so it worked out quite nicely.

class VerifyBeaconStateV2:
method: str = "BeaconStateV2"
id: StateID
fields: Dict = field(default_factory=dict)

def __post_init__(self):
self.id = str(self.id)

def state_root(self, state_root):
self.fields["state_root"] = state_root.hex()
return self


yaml.register_class(VerifyBeaconStateV2)
7 changes: 7 additions & 0 deletions tests/formats/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Sanity tests

The aim of the sanity tests is to set a base-line on what really needs to pass, i.e. the essentials.

There are two handlers, documented individually:
- [`slots`](./slots.md): transitions of one or more slots (and epoch transitions within)
- [`blocks`](./blocks.md): transitions triggered by one or more blocks
Loading