Merge pull request #2462 from mkalinin/merge-transition-with-dynamic-ttd

Merge transition process with computed transition total difficulty
This commit is contained in:
Danny Ryan 2021-06-08 09:55:10 -06:00 committed by GitHub
commit 5d9d786499
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 467 additions and 45 deletions

View File

@ -43,6 +43,7 @@ while the details are in review and may change.
* [ethereum.org](https://ethereum.org) high-level description of the merge [here](https://ethereum.org/en/eth2/docking/)
* Specifications:
* [Beacon Chain changes](specs/merge/beacon-chain.md)
* [Merge fork](specs/merge/fork.md)
* [Fork Choice changes](specs/merge/fork-choice.md)
* [Validator additions](specs/merge/validator.md)

View File

@ -32,7 +32,7 @@ SHARDING_FORK_VERSION: 0x03000000
SHARDING_FORK_EPOCH: 18446744073709551615
# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D.
TRANSITION_TOTAL_DIFFICULTY: 4294967296
MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296
# Time parameters

View File

@ -31,7 +31,7 @@ SHARDING_FORK_VERSION: 0x03000001
SHARDING_FORK_EPOCH: 18446744073709551615
# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D.
TRANSITION_TOTAL_DIFFICULTY: 4294967296
MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296
# Time parameters

View File

@ -510,7 +510,7 @@ ExecutionState = Any
def get_pow_block(hash: Bytes32) -> PowBlock:
return PowBlock(block_hash=hash, is_valid=True, is_processed=True,
total_difficulty=config.TRANSITION_TOTAL_DIFFICULTY)
total_difficulty=uint256(0), difficulty=uint256(0))
def get_execution_state(execution_state_root: Bytes32) -> ExecutionState:
@ -867,6 +867,7 @@ class PySpecCommand(Command):
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
specs/merge/beacon-chain.md
specs/merge/fork.md
specs/merge/fork-choice.md
specs/merge/validator.md
"""

View File

@ -14,8 +14,6 @@
- [Custom types](#custom-types)
- [Constants](#constants)
- [Execution](#execution)
- [Configuration](#configuration)
- [Transition](#transition)
- [Containers](#containers)
- [Extended containers](#extended-containers)
- [`BeaconBlockBody`](#beaconblockbody)
@ -35,6 +33,7 @@
- [Block processing](#block-processing)
- [Execution payload processing](#execution-payload-processing)
- [`process_execution_payload`](#process_execution_payload)
- [Initialize state for pure Merge testnets and test vectors](#initialize-state-for-pure-merge-testnets-and-test-vectors)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -62,18 +61,6 @@ We define the following Python custom types for type hinting and readability:
| `MAX_EXECUTION_TRANSACTIONS` | `uint64(2**14)` (= 16,384) |
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |
## Configuration
Warning: this configuration is not definitive.
### Transition
| Name | Value |
| - | - |
| `MERGE_FORK_VERSION` | `Version('0x02000000')` |
| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
| `TRANSITION_TOTAL_DIFFICULTY` | **TBD** |
## Containers
### Extended containers
@ -247,3 +234,63 @@ def process_execution_payload(state: BeaconState,
transactions_root=hash_tree_root(execution_payload.transactions),
)
```
## Initialize state for pure Merge testnets and test vectors
This helper function is only for initializing the state for pure Merge testnets and tests.
*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `MERGE_FORK_VERSION` as the current fork version, (2) utilizing the Merge `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) adding initial `latest_execution_payload_header`.
```python
def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
eth1_timestamp: uint64,
deposits: Sequence[Deposit]) -> BeaconState:
fork = Fork(
previous_version=GENESIS_FORK_VERSION,
current_version=MERGE_FORK_VERSION, # [Modified in Merge]
epoch=GENESIS_EPOCH,
)
state = BeaconState(
genesis_time=eth1_timestamp + GENESIS_DELAY,
fork=fork,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy
)
# Process deposits
leaves = list(map(lambda deposit: deposit.data, deposits))
for index, deposit in enumerate(deposits):
deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1])
state.eth1_data.deposit_root = hash_tree_root(deposit_data_list)
process_deposit(state, deposit)
# Process activations
for index, validator in enumerate(state.validators):
balance = state.balances[index]
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = hash_tree_root(state.validators)
# [New in Merge] Construct execution payload header
# Note: initialized with zero block height
state.latest_execution_payload_header = ExecutionPayloadHeader(
block_hash=eth1_block_hash,
parent_hash=Hash32(),
coinbase=Bytes20(),
state_root=Bytes32(),
number=uint64(0),
gas_limit=uint64(0),
gas_used=uint64(0),
timestamp=eth1_timestamp,
receipt_root=Bytes32(),
logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](),
transactions_root=Root(),
)
return state
```

View File

@ -12,13 +12,13 @@
- [`ExecutionEngine`](#executionengine)
- [`set_head`](#set_head)
- [`finalize_block`](#finalize_block)
- [Containers](#containers)
- [`PowBlock`](#powblock)
- [Helper functions](#helper-functions)
- [`get_pow_block`](#get_pow_block)
- [`is_valid_transition_block`](#is_valid_transition_block)
- [Helpers](#helpers)
- [`TransitionStore`](#transitionstore)
- [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block)
- [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block)
- [Updated fork-choice handlers](#updated-fork-choice-handlers)
- [`on_block`](#on_block)
- [`on_block`](#on_block)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -66,44 +66,52 @@ def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool:
...
```
## Containers
## Helpers
#### `PowBlock`
### `TransitionStore`
```python
class PowBlock(Container):
@dataclass
class TransitionStore(object):
transition_total_difficulty: uint256
```
### `PowBlock`
```python
@dataclass
class PowBlock(object):
block_hash: Hash32
is_processed: boolean
is_valid: boolean
total_difficulty: uint256
difficulty: uint256
```
## Helper functions
#### `get_pow_block`
### `get_pow_block`
Let `get_pow_block(block_hash: Hash32) -> PowBlock` be the function that given the hash of the PoW block returns its data.
*Note*: The `eth_getBlockByHash` JSON-RPC method does not distinguish invalid blocks from blocks that haven't been processed yet. Either extending this existing method or implementing a new one is required.
#### `is_valid_transition_block`
### `is_valid_terminal_pow_block`
Used by fork-choice handler, `on_block`.
```python
def is_valid_transition_block(block: PowBlock) -> bool:
is_total_difficulty_reached = block.total_difficulty >= TRANSITION_TOTAL_DIFFICULTY
def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock) -> bool:
is_total_difficulty_reached = block.total_difficulty >= transition_store.transition_total_difficulty
return block.is_valid and is_total_difficulty_reached
```
## Updated fork-choice handlers
#### `on_block`
### `on_block`
*Note*: The only modification is the addition of the verification of transition block conditions.
```python
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: TransitionStore=None) -> None:
block = signed_block.message
# Parent block must be known
assert block.parent_root in store.block_states
@ -119,11 +127,11 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# [New in Merge]
if is_transition_block(pre_state, block):
if (transition_store is not None) and is_transition_block(pre_state, block):
# Delay consideration of block until PoW block is processed by the PoW node
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
assert pow_block.is_processed
assert is_valid_transition_block(pow_block)
assert is_valid_terminal_pow_block(transition_store, pow_block)
# Check the block is valid and compute the post-state
state = pre_state.copy()

121
specs/merge/fork.md Normal file
View File

@ -0,0 +1,121 @@
# Ethereum 2.0 The Merge
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Merge](#fork-to-merge)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
- [Initializing transition store](#initializing-transition-store)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This document describes the process of the Merge upgrade.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `MERGE_FORK_VERSION` | `Version('0x02000000')` |
| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
| `MIN_ANCHOR_POW_BLOCK_DIFFICULTY` | **TBD** |
| `TARGET_SECONDS_TO_MERGE` | `uint64(7 * 86400)` = (604,800) |
## Fork to Merge
### Fork trigger
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `MERGE_FORK_EPOCH`.
Since the Merge transition process relies on `Eth1Data` in the beacon state we do want to make sure that this data is fresh. This is achieved by forcing `MERGE_FORK_EPOCH` to point to eth1 voting period boundary, i.e. `MERGE_FORK_EPOCH` should satisfy the following condition `MERGE_FORK_EPOCH % EPOCHS_PER_ETH1_VOTING_PERIOD == 0`.
Note that for the pure Merge networks, we don't apply `upgrade_to_merge` since it starts with Merge version logic.
### Upgrading the state
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, an irregular state change is made to upgrade to Merge.
The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `MERGE_FORK_EPOCH * SLOTS_PER_EPOCH`.
Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document.
In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead the logic must be within `process_slots`.
```python
def upgrade_to_merge(pre: phase0.BeaconState) -> BeaconState:
epoch = phase0.get_current_epoch(pre)
post = BeaconState(
# Versioning
genesis_time=pre.genesis_time,
genesis_validators_root=pre.genesis_validators_root,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=MERGE_FORK_VERSION,
epoch=epoch,
),
# History
latest_block_header=pre.latest_block_header,
block_roots=pre.block_roots,
state_roots=pre.state_roots,
historical_roots=pre.historical_roots,
# Eth1
eth1_data=pre.eth1_data,
eth1_data_votes=pre.eth1_data_votes,
eth1_deposit_index=pre.eth1_deposit_index,
# Registry
validators=pre.validators,
balances=pre.balances,
# Randomness
randao_mixes=pre.randao_mixes,
# Slashings
slashings=pre.slashings,
# Attestations
previous_epoch_attestations=pre.previous_epoch_attestations,
current_epoch_attestations=pre.current_epoch_attestations,
# Finality
justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
# Execution-layer
latest_execution_payload_header=ExecutionPayloadHeader(),
)
return post
```
### Initializing transition store
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, a transition store is initialized to be further utilized by the transition process of the Merge.
Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function.
```python
def compute_transition_total_difficulty(anchor_pow_block: PowBlock) -> uint256:
seconds_per_voting_period = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH * SECONDS_PER_SLOT
pow_blocks_per_voting_period = seconds_per_voting_period // SECONDS_PER_ETH1_BLOCK
pow_blocks_to_merge = TARGET_SECONDS_TO_MERGE // SECONDS_PER_ETH1_BLOCK
pow_blocks_after_anchor_block = ETH1_FOLLOW_DISTANCE + pow_blocks_per_voting_period + pow_blocks_to_merge
anchor_difficulty = max(MIN_ANCHOR_POW_BLOCK_DIFFICULTY, anchor_pow_block.difficulty)
return anchor_pow_block.total_difficulty + anchor_difficulty * pow_blocks_after_anchor_block
def get_transition_store(anchor_pow_block: PowBlock) -> TransitionStore:
transition_total_difficulty = compute_transition_total_difficulty(anchor_pow_block)
return TransitionStore(transition_total_difficulty=transition_total_difficulty)
def initialize_transition_store(state: BeaconState) -> TransitionStore:
pow_block = get_pow_block(state.eth1_data.block_hash)
return get_transition_store(pow_block)
```

View File

@ -20,7 +20,6 @@
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Execution Payload](#execution-payload)
- [`get_pow_chain_head`](#get_pow_chain_head)
- [`produce_execution_payload`](#produce_execution_payload)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -68,18 +67,15 @@ All validator responsibilities remain unchanged other than those noted below. Na
Let `get_pow_chain_head() -> PowBlock` be the function that returns the head of the PoW chain. The body of the function is implementation specific.
###### `produce_execution_payload`
Let `produce_execution_payload(parent_hash: Hash32, timestamp: uint64) -> ExecutionPayload` be the function that produces new instance of execution payload.
The `ExecutionEngine` protocol is used for the implementation specific part of execution payload proposals.
* Set `block.body.execution_payload = get_execution_payload(state)` where:
* Set `block.body.execution_payload = get_execution_payload(state, transition_store, execution_engine)` where:
```python
def get_execution_payload(state: BeaconState, execution_engine: ExecutionEngine) -> ExecutionPayload:
def get_execution_payload(state: BeaconState,
transition_store: TransitionStore,
execution_engine: ExecutionEngine) -> ExecutionPayload:
if not is_transition_completed(state):
pow_block = get_pow_chain_head()
if not is_valid_transition_block(pow_block):
if not is_valid_terminal_pow_block(transition_store, pow_block):
# Pre-merge, empty payload
return ExecutionPayload()
else:

View File

@ -0,0 +1,45 @@
MERGE_FORK_TEST_META_TAGS = {
'fork': 'merge',
}
def run_fork_test(post_spec, pre_state):
# Clean up state to be more realistic
pre_state.current_epoch_attestations = []
yield 'pre', pre_state
post_state = post_spec.upgrade_to_merge(pre_state)
# Stable fields
stable_fields = [
'genesis_time', 'genesis_validators_root', 'slot',
# History
'latest_block_header', 'block_roots', 'state_roots', 'historical_roots',
# Eth1
'eth1_data', 'eth1_data_votes', 'eth1_deposit_index',
# Registry
'validators', 'balances',
# Randomness
'randao_mixes',
# Slashings
'slashings',
# Attestations
'previous_epoch_attestations', 'current_epoch_attestations',
# Finality
'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint',
]
for field in stable_fields:
assert getattr(pre_state, field) == getattr(post_state, field)
# Modified fields
modified_fields = ['fork']
for field in modified_fields:
assert getattr(pre_state, field) != getattr(post_state, field)
assert pre_state.fork.current_version == post_state.fork.previous_version
assert post_state.fork.current_version == post_spec.config.MERGE_FORK_VERSION
assert post_state.fork.epoch == post_spec.get_current_epoch(post_state)
assert post_state.latest_execution_payload_header == post_spec.ExecutionPayloadHeader()
yield 'post', post_state

View File

@ -0,0 +1,82 @@
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
PHASE0, MERGE,
MINIMAL,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,
)
from eth2spec.test.helpers.merge.fork import (
MERGE_FORK_TEST_META_TAGS,
run_fork_test,
)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_base_state(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_next_epoch(spec, phases, state):
next_epoch(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_next_epoch_with_block(spec, phases, state):
next_epoch_via_block(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_many_next_epoch(spec, phases, state):
for _ in range(3):
next_epoch(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_low_balances(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_misc_balances(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_large_validator_set(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)

View File

@ -0,0 +1,120 @@
from random import Random
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
PHASE0, MERGE,
MINIMAL,
)
from eth2spec.test.helpers.merge.fork import (
MERGE_FORK_TEST_META_TAGS,
run_fork_test,
)
from eth2spec.test.helpers.random import (
randomize_state,
randomize_attestation_participation,
)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_0(spec, phases, state):
randomize_state(spec, state, rng=Random(1010))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_1(spec, phases, state):
randomize_state(spec, state, rng=Random(2020))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_2(spec, phases, state):
randomize_state(spec, state, rng=Random(3030))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_3(spec, phases, state):
randomize_state(spec, state, rng=Random(4040))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_duplicate_attestations(spec, phases, state):
randomize_state(spec, state, rng=Random(1111))
# Note: `run_fork_test` empties `current_epoch_attestations`
state.previous_epoch_attestations = state.previous_epoch_attestations + state.previous_epoch_attestations
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_mismatched_attestations(spec, phases, state):
# Create a random state
randomize_state(spec, state, rng=Random(2222))
# Now make two copies
state_0 = state.copy()
state_1 = state.copy()
# Randomize attestation participation of both
randomize_attestation_participation(spec, state_0, rng=Random(3333))
randomize_attestation_participation(spec, state_1, rng=Random(4444))
# Note: `run_fork_test` empties `current_epoch_attestations`
# Use pending attestations from both random states in a single state for testing
state_0.previous_epoch_attestations = state_0.previous_epoch_attestations + state_1.previous_epoch_attestations
yield from run_fork_test(phases[MERGE], state_0)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_low_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(5050))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_misc_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(6060))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_large_validator_set(spec, phases, state):
randomize_state(spec, state, rng=Random(7070))
yield from run_fork_test(phases[MERGE], state)

View File

@ -23,6 +23,7 @@ Key of valid `fork` strings that might be found in `meta.yaml`
| String ID | Pre-fork | Post-fork | Function |
| - | - | - | - |
| `altair` | Phase 0 | Altair | `upgrade_to_altair` |
| `merge` | Phase 0 | Merge | `upgrade_to_merge` |
### `pre.ssz_snappy`