Merge pull request #1745 from ethereum/hwwhww/validator-tests

Add sanity, unit test for validator guide
This commit is contained in:
Danny Ryan
2020-04-27 09:56:02 -06:00
committed by GitHub
4 changed files with 410 additions and 9 deletions

View File

@@ -89,7 +89,7 @@ MIN_SEED_LOOKAHEAD: 1
# 2**2 (= 4) epochs
MAX_SEED_LOOKAHEAD: 4
# [customized] higher frequency new deposits from eth1 for testing
EPOCHS_PER_ETH1_VOTING_PERIOD: 2
EPOCHS_PER_ETH1_VOTING_PERIOD: 4
# [customized] smaller state
SLOTS_PER_HISTORICAL_ROOT: 64
# 2**8 (= 256) epochs

View File

@@ -281,8 +281,8 @@ def voting_period_start_time(state: BeaconState) -> uint64:
```python
def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool:
return (
block.timestamp <= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE
and block.timestamp >= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE * 2
block.timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= period_start
and block.timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE * 2 >= period_start
)
```
@@ -340,9 +340,10 @@ It is useful to be able to run a state transition function (working on a copy of
```python
def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root:
process_slots(state, block.slot)
process_block(state, block)
return hash_tree_root(state)
temp_state: BeaconState = state.copy()
signed_block = SignedBeaconBlock(message=block)
temp_state = state_transition(temp_state, signed_block, validate_result=False)
return hash_tree_root(temp_state)
```
##### Signature
@@ -350,9 +351,9 @@ def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root:
`signed_block = SignedBeaconBlock(message=block, signature=block_signature)`, where `block_signature` is obtained from:
```python
def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(header.slot))
signing_root = compute_signing_root(header, domain)
def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot))
signing_root = compute_signing_root(block, domain)
return bls.Sign(privkey, signing_root)
```

View File

@@ -0,0 +1,395 @@
from eth2spec.test.context import spec_state_test, never_bls, with_all_phases
from eth2spec.test.helpers.attestations import build_attestation_data
from eth2spec.test.helpers.block import build_empty_block
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.state import next_epoch
from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist
def run_get_signature_test(spec, state, obj, domain, get_signature_fn, privkey, pubkey):
signature = get_signature_fn(state, obj, privkey)
signing_root = spec.compute_signing_root(obj, domain)
assert bls.Verify(pubkey, signing_root, signature)
def run_get_committee_assignment(spec, state, epoch, validator_index, valid=True):
try:
assignment = spec.get_committee_assignment(state, epoch, validator_index)
committee, committee_index, slot = assignment
assert spec.compute_epoch_at_slot(slot) == epoch
assert committee == spec.get_beacon_committee(state, slot, committee_index)
assert committee_index < spec.get_committee_count_at_slot(state, slot)
assert validator_index in committee
assert valid
except AssertionError:
assert not valid
else:
assert valid
def run_is_candidate_block(spec, eth1_block, period_start, success=True):
assert success == spec.is_candidate_block(eth1_block, period_start)
def get_min_new_period_epochs(spec):
return int(
spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2 # to seconds
/ spec.SECONDS_PER_SLOT / spec.SLOTS_PER_EPOCH
)
def get_mock_aggregate(spec):
return spec.Attestation(
data=spec.AttestationData(
slot=10,
)
)
#
# Becoming a validator
#
@with_all_phases
@spec_state_test
@never_bls
def test_check_if_validator_active(spec, state):
active_validator_index = len(state.validators) - 1
assert spec.check_if_validator_active(state, active_validator_index)
new_validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, new_validator_index, amount, signed=True)
spec.process_deposit(state, deposit)
assert not spec.check_if_validator_active(state, new_validator_index)
#
# Validator assignments
#
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_current_epoch(spec, state):
epoch = spec.get_current_epoch(state)
validator_index = len(state.validators) - 1
run_get_committee_assignment(spec, state, epoch, validator_index, valid=True)
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_next_epoch(spec, state):
epoch = spec.get_current_epoch(state) + 1
validator_index = len(state.validators) - 1
run_get_committee_assignment(spec, state, epoch, validator_index, valid=True)
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_out_bound_epoch(spec, state):
epoch = spec.get_current_epoch(state) + 2
validator_index = len(state.validators) - 1
run_get_committee_assignment(spec, state, epoch, validator_index, valid=False)
@with_all_phases
@spec_state_test
@never_bls
def test_is_proposer(spec, state):
proposer_index = spec.get_beacon_proposer_index(state)
assert spec.is_proposer(state, proposer_index)
proposer_index = proposer_index + 1 % len(state.validators)
assert not spec.is_proposer(state, proposer_index)
#
# Beacon chain responsibilities
#
# Block proposal
@with_all_phases
@spec_state_test
def test_get_epoch_signature(spec, state):
block = spec.BeaconBlock()
privkey = privkeys[0]
pubkey = pubkeys[0]
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, spec.compute_epoch_at_slot(block.slot))
run_get_signature_test(
spec=spec,
state=state,
obj=block,
domain=domain,
get_signature_fn=spec.get_epoch_signature,
privkey=privkey,
pubkey=pubkey,
)
@with_all_phases
@spec_state_test
def test_is_candidate_block(spec, state):
period_start = spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2 + 1000
run_is_candidate_block(
spec,
spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE),
period_start,
success=True,
)
run_is_candidate_block(
spec,
spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE + 1),
period_start,
success=False,
)
run_is_candidate_block(
spec,
spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2),
period_start,
success=True,
)
run_is_candidate_block(
spec,
spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2 - 1),
period_start,
success=False,
)
@with_all_phases
@spec_state_test
def test_get_eth1_vote_default_vote(spec, state):
min_new_period_epochs = get_min_new_period_epochs(spec)
for _ in range(min_new_period_epochs):
next_epoch(spec, state)
state.eth1_data_votes = ()
eth1_chain = []
eth1_data = spec.get_eth1_vote(state, eth1_chain)
assert eth1_data == state.eth1_data
@with_all_phases
@spec_state_test
def test_get_eth1_vote_consensus_vote(spec, state):
min_new_period_epochs = get_min_new_period_epochs(spec)
for _ in range(min_new_period_epochs + 2):
next_epoch(spec, state)
period_start = spec.voting_period_start_time(state)
votes_length = spec.get_current_epoch(state) % spec.EPOCHS_PER_ETH1_VOTING_PERIOD
assert votes_length >= 3 # We need to have the majority vote
state.eth1_data_votes = ()
block_1 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1)
block_2 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE)
eth1_chain = [block_1, block_2]
eth1_data_votes = []
# Only the first vote is for block_1
eth1_data_votes.append(spec.get_eth1_data(block_1))
# Other votes are for block_2
for _ in range(votes_length - 1):
eth1_data_votes.append(spec.get_eth1_data(block_2))
state.eth1_data_votes = eth1_data_votes
eth1_data = spec.get_eth1_vote(state, eth1_chain)
assert eth1_data.block_hash == block_2.hash_tree_root()
@with_all_phases
@spec_state_test
def test_get_eth1_vote_tie(spec, state):
min_new_period_epochs = get_min_new_period_epochs(spec)
for _ in range(min_new_period_epochs + 1):
next_epoch(spec, state)
period_start = spec.voting_period_start_time(state)
votes_length = spec.get_current_epoch(state) % spec.EPOCHS_PER_ETH1_VOTING_PERIOD
assert votes_length > 0 and votes_length % 2 == 0
state.eth1_data_votes = ()
block_1 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE - 1)
block_2 = spec.Eth1Block(timestamp=period_start - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE)
eth1_chain = [block_1, block_2]
eth1_data_votes = []
# Half votes are for block_1, another half votes are for block_2
for i in range(votes_length):
if i % 2 == 0:
block = block_1
else:
block = block_2
eth1_data_votes.append(spec.get_eth1_data(block))
state.eth1_data_votes = eth1_data_votes
eth1_data = spec.get_eth1_vote(state, eth1_chain)
# Tiebreak by smallest distance -> eth1_chain[0]
assert eth1_data.block_hash == eth1_chain[0].hash_tree_root()
@with_all_phases
@spec_state_test
def test_compute_new_state_root(spec, state):
pre_state = state.copy()
post_state = state.copy()
block = build_empty_block(spec, state, state.slot + 1)
state_root = spec.compute_new_state_root(state, block)
assert state_root != pre_state.hash_tree_root()
assert state == pre_state
# dumb verification
spec.process_slots(post_state, block.slot)
spec.process_block(post_state, block)
assert state_root == post_state.hash_tree_root()
@with_all_phases
@spec_state_test
def test_get_block_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
block = build_empty_block(spec, state)
domain = spec.get_domain(state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot))
run_get_signature_test(
spec=spec,
state=state,
obj=block,
domain=domain,
get_signature_fn=spec.get_block_signature,
privkey=privkey,
pubkey=pubkey,
)
# Attesting
@with_all_phases
@spec_state_test
def test_get_attestation_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
attestation_data = spec.AttestationData(slot=10)
domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
run_get_signature_test(
spec=spec,
state=state,
obj=attestation_data,
domain=domain,
get_signature_fn=spec.get_attestation_signature,
privkey=privkey,
pubkey=pubkey,
)
# Attestation aggregation
@with_all_phases
@spec_state_test
def test_get_slot_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
slot = spec.Slot(10)
domain = spec.get_domain(state, spec.DOMAIN_SELECTION_PROOF, spec.compute_epoch_at_slot(slot))
run_get_signature_test(
spec=spec,
state=state,
obj=slot,
domain=domain,
get_signature_fn=spec.get_slot_signature,
privkey=privkey,
pubkey=pubkey,
)
@with_all_phases
@spec_state_test
def test_is_aggregator(spec, state):
# TODO: we can test the probabilistic result against `TARGET_AGGREGATORS_PER_COMMITTEE`
# if we have more validators and larger committeee size
slot = state.slot
committee_index = 0
has_aggregator = False
beacon_committee = spec.get_beacon_committee(state, slot, committee_index)
for validator_index in beacon_committee:
privkey = privkeys[validator_index]
slot_signature = spec.get_slot_signature(state, slot, privkey)
if spec.is_aggregator(state, slot, committee_index, slot_signature):
has_aggregator = True
break
assert has_aggregator
@with_all_phases
@spec_state_test
def test_get_aggregate_signature(spec, state):
attestations = []
pubkeys = []
slot = state.slot
committee_index = 0
attestation_data = build_attestation_data(spec, state, slot=slot, index=committee_index)
beacon_committee = spec.get_beacon_committee(
state,
attestation_data.slot,
attestation_data.index,
)
committee_size = len(beacon_committee)
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
for i, validator_index in enumerate(beacon_committee):
bits = aggregation_bits
bits[i] = True
attestations.append(
spec.Attestation(
data=attestation_data,
aggregation_bits=bits,
)
)
pubkeys.append(state.validators[validator_index].pubkey)
pubkey = bls.AggregatePKs(pubkeys)
signature = spec.get_aggregate_signature(attestations)
domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
signing_root = spec.compute_signing_root(attestation_data, domain)
assert bls.Verify(pubkey, signing_root, signature)
@with_all_phases
@spec_state_test
def test_get_aggregate_and_proof(spec, state):
privkey = privkeys[0]
aggregator_index = spec.ValidatorIndex(10)
aggregate = get_mock_aggregate(spec)
aggregate_and_proof = spec.get_aggregate_and_proof(state, aggregator_index, aggregate, privkey)
assert aggregate_and_proof.aggregator_index == aggregator_index
assert aggregate_and_proof.aggregate == aggregate
assert aggregate_and_proof.selection_proof == spec.get_slot_signature(state, aggregate.data.slot, privkey)
@with_all_phases
@spec_state_test
def test_get_aggregate_and_proof_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
aggregate = get_mock_aggregate(spec)
aggregate_and_proof = spec.get_aggregate_and_proof(state, spec.ValidatorIndex(1), aggregate, privkey)
domain = spec.get_domain(state, spec.DOMAIN_AGGREGATE_AND_PROOF, spec.compute_epoch_at_slot(aggregate.data.slot))
run_get_signature_test(
spec=spec,
state=state,
obj=aggregate_and_proof,
domain=domain,
get_signature_fn=spec.get_aggregate_and_proof_signature,
privkey=privkey,
pubkey=pubkey,
)

View File

@@ -51,3 +51,8 @@ def Sign(SK, message):
@only_with_bls(alt_return=STUB_COORDINATES)
def signature_to_G2(signature):
return _signature_to_G2(signature)
@only_with_bls(alt_return=STUB_PUBKEY)
def AggregatePKs(pubkeys):
return bls._AggregatePKs(pubkeys)