Merge pull request #989 from ethereum/finality-testing
Finality testing
This commit is contained in:
commit
129e468ca3
|
@ -158,7 +158,6 @@ def build_attestation_data(state, slot, shard):
|
||||||
|
|
||||||
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
||||||
if slot < current_epoch_start_slot:
|
if slot < current_epoch_start_slot:
|
||||||
print(slot)
|
|
||||||
epoch_boundary_root = get_block_root(state, get_previous_epoch(state))
|
epoch_boundary_root = get_block_root(state, get_previous_epoch(state))
|
||||||
elif slot == current_epoch_start_slot:
|
elif slot == current_epoch_start_slot:
|
||||||
epoch_boundary_root = block_root
|
epoch_boundary_root = block_root
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import eth2spec.phase0.spec as spec
|
||||||
|
|
||||||
|
from eth2spec.phase0.state_transition import (
|
||||||
|
state_transition,
|
||||||
|
)
|
||||||
|
from .helpers import (
|
||||||
|
build_empty_block_for_next_slot,
|
||||||
|
fill_aggregate_attestation,
|
||||||
|
get_current_epoch,
|
||||||
|
get_epoch_start_slot,
|
||||||
|
get_valid_attestation,
|
||||||
|
next_epoch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# mark entire file as 'state'
|
||||||
|
pytestmark = pytest.mark.state
|
||||||
|
|
||||||
|
|
||||||
|
def check_finality(state,
|
||||||
|
prev_state,
|
||||||
|
current_justified_changed,
|
||||||
|
previous_justified_changed,
|
||||||
|
finalized_changed):
|
||||||
|
if current_justified_changed:
|
||||||
|
assert state.current_justified_epoch > prev_state.current_justified_epoch
|
||||||
|
assert state.current_justified_root != prev_state.current_justified_root
|
||||||
|
else:
|
||||||
|
assert state.current_justified_epoch == prev_state.current_justified_epoch
|
||||||
|
assert state.current_justified_root == prev_state.current_justified_root
|
||||||
|
|
||||||
|
if previous_justified_changed:
|
||||||
|
assert state.previous_justified_epoch > prev_state.previous_justified_epoch
|
||||||
|
assert state.previous_justified_root != prev_state.previous_justified_root
|
||||||
|
else:
|
||||||
|
assert state.previous_justified_epoch == prev_state.previous_justified_epoch
|
||||||
|
assert state.previous_justified_root == prev_state.previous_justified_root
|
||||||
|
|
||||||
|
if finalized_changed:
|
||||||
|
assert state.finalized_epoch > prev_state.finalized_epoch
|
||||||
|
assert state.finalized_root != prev_state.finalized_root
|
||||||
|
else:
|
||||||
|
assert state.finalized_epoch == prev_state.finalized_epoch
|
||||||
|
assert state.finalized_root == prev_state.finalized_root
|
||||||
|
|
||||||
|
|
||||||
|
def next_epoch_with_attestations(state,
|
||||||
|
fill_cur_epoch,
|
||||||
|
fill_prev_epoch):
|
||||||
|
post_state = deepcopy(state)
|
||||||
|
blocks = []
|
||||||
|
for _ in range(spec.SLOTS_PER_EPOCH):
|
||||||
|
block = build_empty_block_for_next_slot(post_state)
|
||||||
|
if fill_cur_epoch:
|
||||||
|
slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
|
||||||
|
if slot_to_attest >= get_epoch_start_slot(get_current_epoch(post_state)):
|
||||||
|
cur_attestation = get_valid_attestation(post_state, slot_to_attest)
|
||||||
|
fill_aggregate_attestation(post_state, cur_attestation)
|
||||||
|
block.body.attestations.append(cur_attestation)
|
||||||
|
|
||||||
|
if fill_prev_epoch:
|
||||||
|
slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
|
||||||
|
prev_attestation = get_valid_attestation(post_state, slot_to_attest)
|
||||||
|
fill_aggregate_attestation(post_state, prev_attestation)
|
||||||
|
block.body.attestations.append(prev_attestation)
|
||||||
|
|
||||||
|
state_transition(post_state, block)
|
||||||
|
blocks.append(block)
|
||||||
|
|
||||||
|
return state, blocks, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_finality_rule_4(state):
|
||||||
|
test_state = deepcopy(state)
|
||||||
|
|
||||||
|
blocks = []
|
||||||
|
for epoch in range(4):
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
|
||||||
|
blocks += new_blocks
|
||||||
|
|
||||||
|
# justification/finalization skipped at GENESIS_EPOCH
|
||||||
|
if epoch == 0:
|
||||||
|
check_finality(test_state, prev_state, False, False, False)
|
||||||
|
# justification/finalization skipped at GENESIS_EPOCH + 1
|
||||||
|
elif epoch == 1:
|
||||||
|
check_finality(test_state, prev_state, False, False, False)
|
||||||
|
elif epoch == 2:
|
||||||
|
check_finality(test_state, prev_state, True, False, False)
|
||||||
|
elif epoch >= 3:
|
||||||
|
# rule 4 of finality
|
||||||
|
check_finality(test_state, prev_state, True, True, True)
|
||||||
|
assert test_state.finalized_epoch == prev_state.current_justified_epoch
|
||||||
|
assert test_state.finalized_root == prev_state.current_justified_root
|
||||||
|
|
||||||
|
return state, blocks, test_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_finality_rule_1(state):
|
||||||
|
# get past first two epochs that finality does not run on
|
||||||
|
next_epoch(state)
|
||||||
|
next_epoch(state)
|
||||||
|
|
||||||
|
pre_state = deepcopy(state)
|
||||||
|
test_state = deepcopy(state)
|
||||||
|
|
||||||
|
blocks = []
|
||||||
|
for epoch in range(3):
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
|
||||||
|
blocks += new_blocks
|
||||||
|
|
||||||
|
if epoch == 0:
|
||||||
|
check_finality(test_state, prev_state, True, False, False)
|
||||||
|
elif epoch == 1:
|
||||||
|
check_finality(test_state, prev_state, True, True, False)
|
||||||
|
elif epoch == 2:
|
||||||
|
# finalized by rule 1
|
||||||
|
check_finality(test_state, prev_state, True, True, True)
|
||||||
|
assert test_state.finalized_epoch == prev_state.previous_justified_epoch
|
||||||
|
assert test_state.finalized_root == prev_state.previous_justified_root
|
||||||
|
|
||||||
|
return pre_state, blocks, test_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_finality_rule_2(state):
|
||||||
|
# get past first two epochs that finality does not run on
|
||||||
|
next_epoch(state)
|
||||||
|
next_epoch(state)
|
||||||
|
|
||||||
|
pre_state = deepcopy(state)
|
||||||
|
test_state = deepcopy(state)
|
||||||
|
|
||||||
|
blocks = []
|
||||||
|
for epoch in range(3):
|
||||||
|
if epoch == 0:
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
|
||||||
|
check_finality(test_state, prev_state, True, False, False)
|
||||||
|
elif epoch == 1:
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
|
||||||
|
check_finality(test_state, prev_state, False, True, False)
|
||||||
|
elif epoch == 2:
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
|
||||||
|
# finalized by rule 2
|
||||||
|
check_finality(test_state, prev_state, True, False, True)
|
||||||
|
assert test_state.finalized_epoch == prev_state.previous_justified_epoch
|
||||||
|
assert test_state.finalized_root == prev_state.previous_justified_root
|
||||||
|
|
||||||
|
blocks += new_blocks
|
||||||
|
|
||||||
|
return pre_state, blocks, test_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_finality_rule_3(state):
|
||||||
|
"""
|
||||||
|
Test scenario described here
|
||||||
|
https://github.com/ethereum/eth2.0-specs/issues/611#issuecomment-463612892
|
||||||
|
"""
|
||||||
|
|
||||||
|
# get past first two epochs that finality does not run on
|
||||||
|
next_epoch(state)
|
||||||
|
next_epoch(state)
|
||||||
|
|
||||||
|
pre_state = deepcopy(state)
|
||||||
|
test_state = deepcopy(state)
|
||||||
|
|
||||||
|
blocks = []
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
|
||||||
|
blocks += new_blocks
|
||||||
|
check_finality(test_state, prev_state, True, False, False)
|
||||||
|
|
||||||
|
# In epoch N, JE is set to N, prev JE is set to N-1
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False)
|
||||||
|
blocks += new_blocks
|
||||||
|
check_finality(test_state, prev_state, True, True, True)
|
||||||
|
|
||||||
|
# In epoch N+1, JE is N, prev JE is N-1, and not enough messages get in to do anything
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False)
|
||||||
|
blocks += new_blocks
|
||||||
|
check_finality(test_state, prev_state, False, True, False)
|
||||||
|
|
||||||
|
# In epoch N+2, JE is N, prev JE is N, and enough messages from the previous epoch get in to justify N+1.
|
||||||
|
# N+1 now becomes the JE. Not enough messages from epoch N+2 itself get in to justify N+2
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True)
|
||||||
|
blocks += new_blocks
|
||||||
|
# rule 2
|
||||||
|
check_finality(test_state, prev_state, True, False, True)
|
||||||
|
|
||||||
|
# In epoch N+3, LJE is N+1, prev LJE is N, and enough messages get in to justify epochs N+2 and N+3.
|
||||||
|
prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, True)
|
||||||
|
blocks += new_blocks
|
||||||
|
# rule 3
|
||||||
|
check_finality(test_state, prev_state, True, True, True)
|
||||||
|
assert test_state.finalized_epoch == prev_state.current_justified_epoch
|
||||||
|
assert test_state.finalized_root == prev_state.current_justified_root
|
||||||
|
|
||||||
|
return pre_state, blocks, test_state
|
|
@ -50,32 +50,6 @@ from .helpers import (
|
||||||
# mark entire file as 'sanity'
|
# mark entire file as 'sanity'
|
||||||
pytestmark = pytest.mark.sanity
|
pytestmark = pytest.mark.sanity
|
||||||
|
|
||||||
def check_finality(state,
|
|
||||||
prev_state,
|
|
||||||
current_justified_changed,
|
|
||||||
previous_justified_changed,
|
|
||||||
finalized_changed):
|
|
||||||
if current_justified_changed:
|
|
||||||
assert state.current_justified_epoch > prev_state.current_justified_epoch
|
|
||||||
assert state.current_justified_root != prev_state.current_justified_root
|
|
||||||
else:
|
|
||||||
assert state.current_justified_epoch == prev_state.current_justified_epoch
|
|
||||||
assert state.current_justified_root == prev_state.current_justified_root
|
|
||||||
|
|
||||||
if previous_justified_changed:
|
|
||||||
assert state.previous_justified_epoch > prev_state.previous_justified_epoch
|
|
||||||
assert state.previous_justified_root != prev_state.previous_justified_root
|
|
||||||
else:
|
|
||||||
assert state.previous_justified_epoch == prev_state.previous_justified_epoch
|
|
||||||
assert state.previous_justified_root == prev_state.previous_justified_root
|
|
||||||
|
|
||||||
if finalized_changed:
|
|
||||||
assert state.finalized_epoch > prev_state.finalized_epoch
|
|
||||||
assert state.finalized_root != prev_state.finalized_root
|
|
||||||
else:
|
|
||||||
assert state.finalized_epoch == prev_state.finalized_epoch
|
|
||||||
assert state.finalized_root == prev_state.finalized_root
|
|
||||||
|
|
||||||
|
|
||||||
def test_slot_transition(state):
|
def test_slot_transition(state):
|
||||||
test_state = deepcopy(state)
|
test_state = deepcopy(state)
|
||||||
|
@ -141,33 +115,6 @@ def test_empty_epoch_transition_not_finalizing(state):
|
||||||
return state, [block], test_state
|
return state, [block], test_state
|
||||||
|
|
||||||
|
|
||||||
def test_full_attestations_finalizing(state):
|
|
||||||
test_state = deepcopy(state)
|
|
||||||
|
|
||||||
for slot in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
|
||||||
next_slot(test_state)
|
|
||||||
|
|
||||||
for epoch in range(5):
|
|
||||||
for slot in range(spec.SLOTS_PER_EPOCH):
|
|
||||||
print(test_state.slot)
|
|
||||||
attestation = get_valid_attestation(test_state, test_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
|
||||||
fill_aggregate_attestation(test_state, attestation)
|
|
||||||
block = build_empty_block_for_next_slot(test_state)
|
|
||||||
block.body.attestations.append(attestation)
|
|
||||||
state_transition(test_state, block)
|
|
||||||
|
|
||||||
if epoch == 0:
|
|
||||||
check_finality(test_state, state, False, False, False)
|
|
||||||
elif epoch == 1:
|
|
||||||
check_finality(test_state, state, False, False, False)
|
|
||||||
elif epoch == 2:
|
|
||||||
check_finality(test_state, state, True, False, False)
|
|
||||||
elif epoch == 3:
|
|
||||||
check_finality(test_state, state, True, True, False)
|
|
||||||
elif epoch == 4:
|
|
||||||
check_finality(test_state, state, True, True, True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_proposer_slashing(state):
|
def test_proposer_slashing(state):
|
||||||
test_state = deepcopy(state)
|
test_state = deepcopy(state)
|
||||||
proposer_slashing = get_valid_proposer_slashing(state)
|
proposer_slashing = get_valid_proposer_slashing(state)
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
from copy import deepcopy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
from build.phase0.spec import (
|
|
||||||
get_beacon_proposer_index,
|
|
||||||
cache_state,
|
|
||||||
advance_slot,
|
|
||||||
process_block_header,
|
|
||||||
)
|
|
||||||
from tests.phase0.helpers import (
|
|
||||||
build_empty_block_for_next_slot,
|
|
||||||
)
|
|
||||||
|
|
||||||
# mark entire file as 'header'
|
|
||||||
pytestmark = pytest.mark.header
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_state_for_header_processing(state):
|
|
||||||
cache_state(state)
|
|
||||||
advance_slot(state)
|
|
||||||
|
|
||||||
|
|
||||||
def run_block_header_processing(state, block, valid=True):
|
|
||||||
"""
|
|
||||||
Run ``process_block_header`` returning the pre and post state.
|
|
||||||
If ``valid == False``, run expecting ``AssertionError``
|
|
||||||
"""
|
|
||||||
prepare_state_for_header_processing(state)
|
|
||||||
post_state = deepcopy(state)
|
|
||||||
|
|
||||||
if not valid:
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_block_header(post_state, block)
|
|
||||||
return state, None
|
|
||||||
|
|
||||||
process_block_header(post_state, block)
|
|
||||||
return state, post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_success(state):
|
|
||||||
block = build_empty_block_for_next_slot(state)
|
|
||||||
pre_state, post_state = run_block_header_processing(state, block)
|
|
||||||
return state, block, post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_slot(state):
|
|
||||||
block = build_empty_block_for_next_slot(state)
|
|
||||||
block.slot = state.slot + 2 # invalid slot
|
|
||||||
|
|
||||||
pre_state, post_state = run_block_header_processing(state, block, valid=False)
|
|
||||||
return pre_state, block, None
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_previous_block_root(state):
|
|
||||||
block = build_empty_block_for_next_slot(state)
|
|
||||||
block.previous_block_root = b'\12'*32 # invalid prev root
|
|
||||||
|
|
||||||
pre_state, post_state = run_block_header_processing(state, block, valid=False)
|
|
||||||
return pre_state, block, None
|
|
|
@ -1,140 +0,0 @@
|
||||||
from copy import deepcopy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import build.phase0.spec as spec
|
|
||||||
|
|
||||||
from build.phase0.spec import (
|
|
||||||
ZERO_HASH,
|
|
||||||
process_deposit,
|
|
||||||
)
|
|
||||||
from tests.phase0.helpers import (
|
|
||||||
build_deposit,
|
|
||||||
privkeys,
|
|
||||||
pubkeys,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# mark entire file as 'voluntary_exits'
|
|
||||||
pytestmark = pytest.mark.voluntary_exits
|
|
||||||
|
|
||||||
|
|
||||||
def test_success(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
# fill previous deposits with zero-hash
|
|
||||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
|
||||||
|
|
||||||
index = len(deposit_data_leaves)
|
|
||||||
pubkey = pubkeys[index]
|
|
||||||
privkey = privkeys[index]
|
|
||||||
deposit, root, deposit_data_leaves = build_deposit(
|
|
||||||
pre_state,
|
|
||||||
deposit_data_leaves,
|
|
||||||
pubkey,
|
|
||||||
privkey,
|
|
||||||
spec.MAX_DEPOSIT_AMOUNT,
|
|
||||||
)
|
|
||||||
|
|
||||||
pre_state.latest_eth1_data.deposit_root = root
|
|
||||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
process_deposit(post_state, deposit)
|
|
||||||
|
|
||||||
assert len(post_state.validator_registry) == len(state.validator_registry) + 1
|
|
||||||
assert len(post_state.validator_balances) == len(state.validator_balances) + 1
|
|
||||||
assert post_state.validator_registry[index].pubkey == pubkeys[index]
|
|
||||||
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
|
|
||||||
|
|
||||||
return pre_state, deposit, post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_success_top_up(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
|
||||||
|
|
||||||
validator_index = 0
|
|
||||||
amount = spec.MAX_DEPOSIT_AMOUNT // 4
|
|
||||||
pubkey = pubkeys[validator_index]
|
|
||||||
privkey = privkeys[validator_index]
|
|
||||||
deposit, root, deposit_data_leaves = build_deposit(
|
|
||||||
pre_state,
|
|
||||||
deposit_data_leaves,
|
|
||||||
pubkey,
|
|
||||||
privkey,
|
|
||||||
amount,
|
|
||||||
)
|
|
||||||
|
|
||||||
pre_state.latest_eth1_data.deposit_root = root
|
|
||||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
|
||||||
pre_balance = pre_state.validator_balances[validator_index]
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
process_deposit(post_state, deposit)
|
|
||||||
|
|
||||||
assert len(post_state.validator_registry) == len(state.validator_registry)
|
|
||||||
assert len(post_state.validator_balances) == len(state.validator_balances)
|
|
||||||
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
|
|
||||||
assert post_state.validator_balances[validator_index] == pre_balance + amount
|
|
||||||
|
|
||||||
return pre_state, deposit, post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_wrong_index(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
|
||||||
|
|
||||||
|
|
||||||
index = len(deposit_data_leaves)
|
|
||||||
pubkey = pubkeys[index]
|
|
||||||
privkey = privkeys[index]
|
|
||||||
deposit, root, deposit_data_leaves = build_deposit(
|
|
||||||
pre_state,
|
|
||||||
deposit_data_leaves,
|
|
||||||
pubkey,
|
|
||||||
privkey,
|
|
||||||
spec.MAX_DEPOSIT_AMOUNT,
|
|
||||||
)
|
|
||||||
|
|
||||||
# mess up deposit_index
|
|
||||||
deposit.index = pre_state.deposit_index + 1
|
|
||||||
|
|
||||||
pre_state.latest_eth1_data.deposit_root = root
|
|
||||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_deposit(post_state, deposit)
|
|
||||||
|
|
||||||
return pre_state, deposit, None
|
|
||||||
|
|
||||||
|
|
||||||
def test_bad_merkle_proof(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
|
||||||
|
|
||||||
index = len(deposit_data_leaves)
|
|
||||||
pubkey = pubkeys[index]
|
|
||||||
privkey = privkeys[index]
|
|
||||||
deposit, root, deposit_data_leaves = build_deposit(
|
|
||||||
pre_state,
|
|
||||||
deposit_data_leaves,
|
|
||||||
pubkey,
|
|
||||||
privkey,
|
|
||||||
spec.MAX_DEPOSIT_AMOUNT,
|
|
||||||
)
|
|
||||||
|
|
||||||
# mess up merkle branch
|
|
||||||
deposit.proof[-1] = spec.ZERO_HASH
|
|
||||||
|
|
||||||
pre_state.latest_eth1_data.deposit_root = root
|
|
||||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_deposit(post_state, deposit)
|
|
||||||
|
|
||||||
return pre_state, deposit, None
|
|
|
@ -1,175 +0,0 @@
|
||||||
from copy import deepcopy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import build.phase0.spec as spec
|
|
||||||
|
|
||||||
from build.phase0.spec import (
|
|
||||||
get_active_validator_indices,
|
|
||||||
get_current_epoch,
|
|
||||||
process_voluntary_exit,
|
|
||||||
)
|
|
||||||
from tests.phase0.helpers import (
|
|
||||||
build_voluntary_exit,
|
|
||||||
pubkey_to_privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# mark entire file as 'voluntary_exits'
|
|
||||||
pytestmark = pytest.mark.voluntary_exits
|
|
||||||
|
|
||||||
|
|
||||||
def test_success(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
|
|
||||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
|
||||||
|
|
||||||
#
|
|
||||||
# build voluntary exit
|
|
||||||
#
|
|
||||||
current_epoch = get_current_epoch(pre_state)
|
|
||||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
|
||||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
|
||||||
|
|
||||||
voluntary_exit = build_voluntary_exit(
|
|
||||||
pre_state,
|
|
||||||
current_epoch,
|
|
||||||
validator_index,
|
|
||||||
privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
#
|
|
||||||
# test valid exit
|
|
||||||
#
|
|
||||||
process_voluntary_exit(post_state, voluntary_exit)
|
|
||||||
|
|
||||||
assert not pre_state.validator_registry[validator_index].initiated_exit
|
|
||||||
assert post_state.validator_registry[validator_index].initiated_exit
|
|
||||||
|
|
||||||
return pre_state, voluntary_exit, post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_validator_not_active(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
current_epoch = get_current_epoch(pre_state)
|
|
||||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
|
||||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
|
||||||
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
pre_state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
|
|
||||||
|
|
||||||
#
|
|
||||||
# build and test voluntary exit
|
|
||||||
#
|
|
||||||
voluntary_exit = build_voluntary_exit(
|
|
||||||
pre_state,
|
|
||||||
current_epoch,
|
|
||||||
validator_index,
|
|
||||||
privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_voluntary_exit(pre_state, voluntary_exit)
|
|
||||||
|
|
||||||
return pre_state, voluntary_exit, None
|
|
||||||
|
|
||||||
|
|
||||||
def test_validator_already_exited(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
|
|
||||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
|
||||||
|
|
||||||
current_epoch = get_current_epoch(pre_state)
|
|
||||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
|
||||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
|
||||||
|
|
||||||
# but validator already has exited
|
|
||||||
pre_state.validator_registry[validator_index].exit_epoch = current_epoch + 2
|
|
||||||
|
|
||||||
#
|
|
||||||
# build voluntary exit
|
|
||||||
#
|
|
||||||
voluntary_exit = build_voluntary_exit(
|
|
||||||
pre_state,
|
|
||||||
current_epoch,
|
|
||||||
validator_index,
|
|
||||||
privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_voluntary_exit(pre_state, voluntary_exit)
|
|
||||||
|
|
||||||
return pre_state, voluntary_exit, None
|
|
||||||
|
|
||||||
|
|
||||||
def test_validator_already_initiated_exit(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
|
|
||||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
|
||||||
|
|
||||||
current_epoch = get_current_epoch(pre_state)
|
|
||||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
|
||||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
|
||||||
|
|
||||||
# but validator already has initiated exit
|
|
||||||
pre_state.validator_registry[validator_index].initiated_exit = True
|
|
||||||
|
|
||||||
#
|
|
||||||
# build voluntary exit
|
|
||||||
#
|
|
||||||
voluntary_exit = build_voluntary_exit(
|
|
||||||
pre_state,
|
|
||||||
current_epoch,
|
|
||||||
validator_index,
|
|
||||||
privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_voluntary_exit(pre_state, voluntary_exit)
|
|
||||||
|
|
||||||
return pre_state, voluntary_exit, None
|
|
||||||
|
|
||||||
|
|
||||||
def test_validator_not_active_long_enough(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
current_epoch = get_current_epoch(pre_state)
|
|
||||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
|
||||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
|
||||||
|
|
||||||
# but validator already has initiated exit
|
|
||||||
pre_state.validator_registry[validator_index].initiated_exit = True
|
|
||||||
|
|
||||||
#
|
|
||||||
# build voluntary exit
|
|
||||||
#
|
|
||||||
voluntary_exit = build_voluntary_exit(
|
|
||||||
pre_state,
|
|
||||||
current_epoch,
|
|
||||||
validator_index,
|
|
||||||
privkey,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
current_epoch - pre_state.validator_registry[validator_index].activation_epoch <
|
|
||||||
spec.PERSISTENT_COMMITTEE_PERIOD
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
|
||||||
process_voluntary_exit(pre_state, voluntary_exit)
|
|
||||||
|
|
||||||
return pre_state, voluntary_exit, None
|
|
|
@ -1,204 +0,0 @@
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from py_ecc import bls
|
|
||||||
|
|
||||||
import build.phase0.spec as spec
|
|
||||||
from build.phase0.utils.minimal_ssz import signed_root
|
|
||||||
from build.phase0.spec import (
|
|
||||||
# constants
|
|
||||||
EMPTY_SIGNATURE,
|
|
||||||
# SSZ
|
|
||||||
AttestationData,
|
|
||||||
Deposit,
|
|
||||||
DepositInput,
|
|
||||||
DepositData,
|
|
||||||
Eth1Data,
|
|
||||||
VoluntaryExit,
|
|
||||||
# functions
|
|
||||||
get_block_root,
|
|
||||||
get_block_root_at_slot,
|
|
||||||
get_current_epoch,
|
|
||||||
get_domain,
|
|
||||||
get_empty_block,
|
|
||||||
get_epoch_start_slot,
|
|
||||||
get_genesis_beacon_state,
|
|
||||||
verify_merkle_branch,
|
|
||||||
hash,
|
|
||||||
)
|
|
||||||
from build.phase0.utils.merkle_minimal import (
|
|
||||||
calc_merkle_tree_from_leaves,
|
|
||||||
get_merkle_proof,
|
|
||||||
get_merkle_root,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
privkeys = [i + 1 for i in range(1000)]
|
|
||||||
pubkeys = [bls.privtopub(privkey) for privkey in privkeys]
|
|
||||||
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
|
|
||||||
|
|
||||||
|
|
||||||
def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None):
|
|
||||||
if not deposit_data_leaves:
|
|
||||||
deposit_data_leaves = []
|
|
||||||
deposit_timestamp = 0
|
|
||||||
proof_of_possession = b'\x33' * 96
|
|
||||||
|
|
||||||
deposit_data_list = []
|
|
||||||
for i in range(num_validators):
|
|
||||||
pubkey = pubkeys[i]
|
|
||||||
deposit_data = DepositData(
|
|
||||||
amount=spec.MAX_DEPOSIT_AMOUNT,
|
|
||||||
timestamp=deposit_timestamp,
|
|
||||||
deposit_input=DepositInput(
|
|
||||||
pubkey=pubkey,
|
|
||||||
# insecurely use pubkey as withdrawal key as well
|
|
||||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
|
|
||||||
proof_of_possession=proof_of_possession,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
item = hash(deposit_data.serialize())
|
|
||||||
deposit_data_leaves.append(item)
|
|
||||||
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
|
|
||||||
root = get_merkle_root((tuple(deposit_data_leaves)))
|
|
||||||
proof = list(get_merkle_proof(tree, item_index=i))
|
|
||||||
assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, i, root)
|
|
||||||
deposit_data_list.append(deposit_data)
|
|
||||||
|
|
||||||
genesis_validator_deposits = []
|
|
||||||
for i in range(num_validators):
|
|
||||||
genesis_validator_deposits.append(Deposit(
|
|
||||||
proof=list(get_merkle_proof(tree, item_index=i)),
|
|
||||||
index=i,
|
|
||||||
deposit_data=deposit_data_list[i]
|
|
||||||
))
|
|
||||||
return genesis_validator_deposits, root
|
|
||||||
|
|
||||||
|
|
||||||
def create_genesis_state(num_validators, deposit_data_leaves=None):
|
|
||||||
initial_deposits, deposit_root = create_mock_genesis_validator_deposits(
|
|
||||||
num_validators,
|
|
||||||
deposit_data_leaves,
|
|
||||||
)
|
|
||||||
return get_genesis_beacon_state(
|
|
||||||
initial_deposits,
|
|
||||||
genesis_time=0,
|
|
||||||
genesis_eth1_data=Eth1Data(
|
|
||||||
deposit_root=deposit_root,
|
|
||||||
deposit_count=len(initial_deposits),
|
|
||||||
block_hash=spec.ZERO_HASH,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def force_registry_change_at_next_epoch(state):
|
|
||||||
# artificially trigger registry update at next epoch transition
|
|
||||||
state.finalized_epoch = get_current_epoch(state) - 1
|
|
||||||
for crosslink in state.latest_crosslinks:
|
|
||||||
crosslink.epoch = state.finalized_epoch
|
|
||||||
state.validator_registry_update_epoch = state.finalized_epoch - 1
|
|
||||||
|
|
||||||
|
|
||||||
def build_empty_block_for_next_slot(state):
|
|
||||||
empty_block = get_empty_block()
|
|
||||||
empty_block.slot = state.slot + 1
|
|
||||||
previous_block_header = deepcopy(state.latest_block_header)
|
|
||||||
if previous_block_header.state_root == spec.ZERO_HASH:
|
|
||||||
previous_block_header.state_root = state.hash_tree_root()
|
|
||||||
empty_block.previous_block_root = signed_root(previous_block_header)
|
|
||||||
return empty_block
|
|
||||||
|
|
||||||
|
|
||||||
def build_deposit_data(state, pubkey, privkey, amount):
|
|
||||||
deposit_input = DepositInput(
|
|
||||||
pubkey=pubkey,
|
|
||||||
# insecurely use pubkey as withdrawal key as well
|
|
||||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
|
|
||||||
proof_of_possession=EMPTY_SIGNATURE,
|
|
||||||
)
|
|
||||||
proof_of_possession = bls.sign(
|
|
||||||
message_hash=signed_root(deposit_input),
|
|
||||||
privkey=privkey,
|
|
||||||
domain=get_domain(
|
|
||||||
state.fork,
|
|
||||||
get_current_epoch(state),
|
|
||||||
spec.DOMAIN_DEPOSIT,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
deposit_input.proof_of_possession = proof_of_possession
|
|
||||||
deposit_data = DepositData(
|
|
||||||
amount=amount,
|
|
||||||
timestamp=0,
|
|
||||||
deposit_input=deposit_input,
|
|
||||||
)
|
|
||||||
return deposit_data
|
|
||||||
|
|
||||||
|
|
||||||
def build_attestation_data(state, slot, shard):
|
|
||||||
assert state.slot >= slot
|
|
||||||
|
|
||||||
block_root = build_empty_block_for_next_slot(state).previous_block_root
|
|
||||||
|
|
||||||
epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
|
||||||
if epoch_start_slot == slot:
|
|
||||||
epoch_boundary_root = block_root
|
|
||||||
else:
|
|
||||||
epoch_boundary_root = get_block_root(state, get_current_epoch(state))
|
|
||||||
|
|
||||||
if slot < epoch_start_slot:
|
|
||||||
justified_block_root = state.previous_justified_root
|
|
||||||
else:
|
|
||||||
justified_block_root = state.current_justified_root
|
|
||||||
|
|
||||||
return AttestationData(
|
|
||||||
slot=slot,
|
|
||||||
shard=shard,
|
|
||||||
beacon_block_root=block_root,
|
|
||||||
source_epoch=state.current_justified_epoch,
|
|
||||||
source_root=justified_block_root,
|
|
||||||
target_root=epoch_boundary_root,
|
|
||||||
crosslink_data_root=spec.ZERO_HASH,
|
|
||||||
previous_crosslink=deepcopy(state.latest_crosslinks[shard]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_voluntary_exit(state, epoch, validator_index, privkey):
|
|
||||||
voluntary_exit = VoluntaryExit(
|
|
||||||
epoch=epoch,
|
|
||||||
validator_index=validator_index,
|
|
||||||
signature=EMPTY_SIGNATURE,
|
|
||||||
)
|
|
||||||
voluntary_exit.signature = bls.sign(
|
|
||||||
message_hash=signed_root(voluntary_exit),
|
|
||||||
privkey=privkey,
|
|
||||||
domain=get_domain(
|
|
||||||
fork=state.fork,
|
|
||||||
epoch=epoch,
|
|
||||||
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return voluntary_exit
|
|
||||||
|
|
||||||
|
|
||||||
def build_deposit(state,
|
|
||||||
deposit_data_leaves,
|
|
||||||
pubkey,
|
|
||||||
privkey,
|
|
||||||
amount):
|
|
||||||
deposit_data = build_deposit_data(state, pubkey, privkey, amount)
|
|
||||||
|
|
||||||
item = hash(deposit_data.serialize())
|
|
||||||
index = len(deposit_data_leaves)
|
|
||||||
deposit_data_leaves.append(item)
|
|
||||||
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
|
|
||||||
root = get_merkle_root((tuple(deposit_data_leaves)))
|
|
||||||
proof = list(get_merkle_proof(tree, item_index=index))
|
|
||||||
assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
|
|
||||||
|
|
||||||
deposit = Deposit(
|
|
||||||
proof=list(proof),
|
|
||||||
index=index,
|
|
||||||
deposit_data=deposit_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
return deposit, root, deposit_data_leaves
|
|
|
@ -1,52 +0,0 @@
|
||||||
from .minimal_ssz import hash_tree_root
|
|
||||||
|
|
||||||
|
|
||||||
def jsonize(value, typ, include_hash_tree_roots=False):
|
|
||||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
|
||||||
return value
|
|
||||||
elif typ == 'bool':
|
|
||||||
assert value in (True, False)
|
|
||||||
return value
|
|
||||||
elif isinstance(typ, list):
|
|
||||||
return [jsonize(element, typ[0], include_hash_tree_roots) for element in value]
|
|
||||||
elif isinstance(typ, str) and typ[:4] == 'byte':
|
|
||||||
return '0x' + value.hex()
|
|
||||||
elif hasattr(typ, 'fields'):
|
|
||||||
ret = {}
|
|
||||||
for field, subtype in typ.fields.items():
|
|
||||||
ret[field] = jsonize(getattr(value, field), subtype, include_hash_tree_roots)
|
|
||||||
if include_hash_tree_roots:
|
|
||||||
ret[field + "_hash_tree_root"] = '0x' + hash_tree_root(getattr(value, field), subtype).hex()
|
|
||||||
if include_hash_tree_roots:
|
|
||||||
ret["hash_tree_root"] = '0x' + hash_tree_root(value, typ).hex()
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
print(value, typ)
|
|
||||||
raise Exception("Type not recognized")
|
|
||||||
|
|
||||||
|
|
||||||
def dejsonize(json, typ):
|
|
||||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
|
||||||
return json
|
|
||||||
elif typ == 'bool':
|
|
||||||
assert json in (True, False)
|
|
||||||
return json
|
|
||||||
elif isinstance(typ, list):
|
|
||||||
return [dejsonize(element, typ[0]) for element in json]
|
|
||||||
elif isinstance(typ, str) and typ[:4] == 'byte':
|
|
||||||
return bytes.fromhex(json[2:])
|
|
||||||
elif hasattr(typ, 'fields'):
|
|
||||||
temp = {}
|
|
||||||
for field, subtype in typ.fields.items():
|
|
||||||
temp[field] = dejsonize(json[field], subtype)
|
|
||||||
if field + "_hash_tree_root" in json:
|
|
||||||
assert(json[field + "_hash_tree_root"][2:] ==
|
|
||||||
hash_tree_root(temp[field], subtype).hex())
|
|
||||||
ret = typ(**temp)
|
|
||||||
if "hash_tree_root" in json:
|
|
||||||
assert(json["hash_tree_root"][2:] ==
|
|
||||||
hash_tree_root(ret, typ).hex())
|
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
print(json, typ)
|
|
||||||
raise Exception("Type not recognized")
|
|
Loading…
Reference in New Issue