Skip to content

Commit

Permalink
Merge pull request #874 from ethereum/prev-cur-crosslinks
Browse files Browse the repository at this point in the history
add previous and current crosslinks
  • Loading branch information
djrtwo authored Apr 18, 2019
2 parents d8a4a48 + 741a74a commit aaea74e
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 141 deletions.
149 changes: 66 additions & 83 deletions specs/core/0_beacon-chain.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions specs/core/1_custody-game.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def process_chunk_challenge(state: BeaconState,
responder = state.validator_registry[challenge.responder_index]
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
# Verify the responder participated in the attestation
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
assert challenge.responder_index in attesters
# Verify the challenge is not a duplicate
for record in state.custody_chunk_challenge_records:
Expand Down Expand Up @@ -359,9 +359,9 @@ def process_bit_challenge(state: BeaconState,
# Verify the attestation is eligible for challenging
responder = state.validator_registry[challenge.responder_index]
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
assert min_challengeable_epoch <= slot_to_epoch(challenge.attestation.data.slot)
assert min_challengeable_epoch <= slot_to_epoch(challenge.attestation.data.slot)
# Verify the responder participated in the attestation
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
assert challenge.responder_index in attesters
# A validator can be the challenger or responder for at most one challenge at a time
for record in state.custody_bit_challenge_records:
Expand Down
40 changes: 20 additions & 20 deletions specs/validator/0_beacon-chain-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
- [Attestations](#attestations-1)
- [Attestation data](#attestation-data)
- [Slot](#slot-1)
- [Shard](#shard)
- [Beacon block root](#beacon-block-root)
- [Target root](#target-root)
- [Crosslink data root](#crosslink-data-root)
- [Latest crosslink](#latest-crosslink)
- [Source epoch](#source-epoch)
- [Source root](#source-root)
- [Target root](#target-root)
- [Shard](#shard)
- [Previous crosslink root](#previous-crosslink-root)
- [Crosslink data root](#crosslink-data-root)
- [Construct attestation](#construct-attestation)
- [Data](#data)
- [Aggregation bitfield](#aggregation-bitfield)
Expand Down Expand Up @@ -250,14 +250,18 @@ First the validator should construct `attestation_data`, an [`AttestationData`](

Set `attestation_data.slot = head_state.slot`.

##### Shard

Set `attestation_data.shard = shard` where `shard` is the shard associated with the validator's committee defined by `get_crosslink_committees_at_slot`.

##### Beacon block root

Set `attestation_data.beacon_block_root = signing_root(head_block)`.

##### Source epoch

Set `attestation_data.source_epoch = head_state.justified_epoch`.

##### Source root

Set `attestation_data.source_root = head_state.current_justified_root`.

##### Target root

Set `attestation_data.target_root = signing_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
Expand All @@ -266,23 +270,19 @@ _Note:_ This can be looked up in the state using:
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
* Set `epoch_boundary = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.

##### Crosslink data root

Set `attestation_data.crosslink_data_root = ZERO_HASH`.

_Note:_ This is a stub for phase 0.
##### Shard

##### Latest crosslink
Set `attestation_data.shard = shard` where `shard` is the shard associated with the validator's committee defined by `get_crosslink_committees_at_slot`.

Set `attestation_data.previous_crosslink = head_state.latest_crosslinks[shard]`.
##### Previous crosslink root

##### Source epoch
Set `attestation_data.previous_crosslink_root = hash_tree_root(head_state.current_crosslinks[shard])`.

Set `attestation_data.source_epoch = head_state.justified_epoch`.
##### Crosslink data root

##### Source root
Set `attestation_data.crosslink_data_root = ZERO_HASH`.

Set `attestation_data.source_root = head_state.current_justified_root`.
_Note:_ This is a stub for phase 0.

#### Construct attestation

Expand All @@ -299,7 +299,7 @@ Set `attestation.data = attestation_data` where `attestation_data` is the `Attes
* Set `aggregation_bitfield[index_into_committee // 8] |= 2 ** (index_into_committee % 8)`.
* Set `attestation.aggregation_bitfield = aggregation_bitfield`.

_Note_: Calling `get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.
_Note_: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.

##### Custody bitfield

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from tests.helpers import (
build_empty_block_for_next_slot,
get_valid_attestation,
next_epoch,
next_slot,
)


Expand Down Expand Up @@ -120,10 +122,12 @@ def test_non_zero_crosslink_data_root(state):


def test_bad_previous_crosslink(state):
next_epoch(state)
attestation = get_valid_attestation(state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
next_slot(state)

state.latest_crosslinks[attestation.data.shard].epoch += 10
state.current_crosslinks[attestation.data.shard].epoch += 10

pre_state, post_state = run_attestation_processing(state, attestation, False)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
get_valid_proposer_slashing,
)

# mark entire file as 'header'
# mark entire file as 'proposer_slashings'
pytestmark = pytest.mark.proposer_slashings


Expand Down
3 changes: 2 additions & 1 deletion test_libs/pyspec/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def overwrite_spec_config(config):
if field == "LATEST_RANDAO_MIXES_LENGTH":
spec.BeaconState.fields['latest_randao_mixes'][1] = config[field]
elif field == "SHARD_COUNT":
spec.BeaconState.fields['latest_crosslinks'][1] = config[field]
spec.BeaconState.fields['current_crosslinks'][1] = config[field]
spec.BeaconState.fields['previous_crosslinks'][1] = config[field]
elif field == "SLOTS_PER_HISTORICAL_ROOT":
spec.BeaconState.fields['latest_block_roots'][1] = config[field]
spec.BeaconState.fields['latest_state_roots'][1] = config[field]
Expand Down
136 changes: 136 additions & 0 deletions test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from copy import deepcopy
import pytest

import eth2spec.phase0.spec as spec

from eth2spec.phase0.state_transition import (
state_transition,
)
from eth2spec.phase0.spec import (
cache_state,
get_crosslink_deltas,
process_crosslinks,
)
from tests.helpers import (
add_attestation_to_state,
build_empty_block_for_next_slot,
fill_aggregate_attestation,
get_crosslink_committee_for_attestation,
get_valid_attestation,
next_epoch,
next_slot,
set_bitfield_bit,
)


# mark entire file as 'crosslinks'
pytestmark = pytest.mark.crosslinks


def run_process_crosslinks(state, valid=True):
# transition state to slot before state transition
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
block = build_empty_block_for_next_slot(state)
block.slot = slot
state_transition(state, block)

# cache state before epoch transition
cache_state(state)

post_state = deepcopy(state)
process_crosslinks(post_state)

return state, post_state


def test_no_attestations(state):
pre_state, post_state = run_process_crosslinks(state)

for shard in range(spec.SHARD_COUNT):
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]

return pre_state, post_state


def test_single_crosslink_update_from_current_epoch(state):
next_epoch(state)

attestation = get_valid_attestation(state)

fill_aggregate_attestation(state, attestation)
add_attestation_to_state(state, attestation, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)

assert len(state.current_epoch_attestations) == 1

pre_state, post_state = run_process_crosslinks(state)

shard = attestation.data.shard
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]

return pre_state, post_state


def test_single_crosslink_update_from_previous_epoch(state):
next_epoch(state)

attestation = get_valid_attestation(state)

fill_aggregate_attestation(state, attestation)
add_attestation_to_state(state, attestation, state.slot + spec.SLOTS_PER_EPOCH)

assert len(state.previous_epoch_attestations) == 1

pre_state, post_state = run_process_crosslinks(state)
crosslink_deltas = get_crosslink_deltas(state)

shard = attestation.data.shard
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
# ensure rewarded
for index in get_crosslink_committee_for_attestation(state, attestation.data):
assert crosslink_deltas[0][index] > 0
assert crosslink_deltas[1][index] == 0

return pre_state, post_state


def test_double_late_crosslink(state):
next_epoch(state)
state.slot += 4

attestation_1 = get_valid_attestation(state)
fill_aggregate_attestation(state, attestation_1)

# add attestation_1 in the next epoch
next_epoch(state)
add_attestation_to_state(state, attestation_1, state.slot + 1)

for slot in range(spec.SLOTS_PER_EPOCH):
attestation_2 = get_valid_attestation(state)
if attestation_2.data.shard == attestation_1.data.shard:
break
next_slot(state)
fill_aggregate_attestation(state, attestation_2)

# add attestation_2 in the next epoch after attestation_1 has
# already updated the relevant crosslink
next_epoch(state)
add_attestation_to_state(state, attestation_2, state.slot + 1)

assert len(state.previous_epoch_attestations) == 1
assert len(state.current_epoch_attestations) == 0

pre_state, post_state = run_process_crosslinks(state)
crosslink_deltas = get_crosslink_deltas(state)

shard = attestation_2.data.shard

# ensure that the current crosslinks were not updated by the second attestation
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
# ensure no reward, only penalties for the failed crosslink
for index in get_crosslink_committee_for_attestation(state, attestation_2.data):
assert crosslink_deltas[0][index] == 0
assert crosslink_deltas[1][index] > 0

return pre_state, post_state
26 changes: 22 additions & 4 deletions test_libs/pyspec/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@
# functions
convert_to_indexed,
get_active_validator_indices,
get_attestation_participants,
get_attesting_indices,
get_block_root,
get_crosslink_committee_for_attestation,
get_crosslink_committees_at_slot,
get_current_epoch,
get_domain,
get_empty_block,
get_epoch_start_slot,
get_genesis_beacon_state,
get_previous_epoch,
get_shard_delta,
hash_tree_root,
slot_to_epoch,
verify_merkle_branch,
hash,
Expand Down Expand Up @@ -154,6 +155,7 @@ def build_attestation_data(state, slot, shard):

current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
if slot < current_epoch_start_slot:
print(slot)
epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
elif slot == current_epoch_start_slot:
epoch_boundary_root = block_root
Expand All @@ -167,6 +169,7 @@ def build_attestation_data(state, slot, shard):
justified_epoch = state.current_justified_epoch
justified_block_root = state.current_justified_root

crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks
return AttestationData(
slot=slot,
shard=shard,
Expand All @@ -175,7 +178,7 @@ def build_attestation_data(state, slot, shard):
source_root=justified_block_root,
target_root=epoch_boundary_root,
crosslink_data_root=spec.ZERO_HASH,
previous_crosslink=deepcopy(state.latest_crosslinks[shard]),
previous_crosslink_root=hash_tree_root(crosslinks[shard]),
)


Expand Down Expand Up @@ -273,6 +276,14 @@ def get_valid_attester_slashing(state):
)


def get_crosslink_committee_for_attestation(state, attestation_data):
"""
Return the crosslink committee corresponding to ``attestation_data``.
"""
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
return [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]


def get_valid_attestation(state, slot=None):
if slot is None:
slot = state.slot
Expand All @@ -297,7 +308,7 @@ def get_valid_attestation(state, slot=None):
custody_bitfield=custody_bitfield,
aggregate_signature=EMPTY_SIGNATURE,
)
participants = get_attestation_participants(
participants = get_attesting_indices(
state,
attestation.data,
attestation.aggregation_bitfield,
Expand Down Expand Up @@ -342,6 +353,13 @@ def fill_aggregate_attestation(state, attestation):
attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)


def add_attestation_to_state(state, attestation, slot):
block = build_empty_block_for_next_slot(state)
block.slot = slot
block.body.attestations.append(attestation)
state_transition(state, block)


def next_slot(state):
block = build_empty_block_for_next_slot(state)
state_transition(state, block)
Expand Down
Loading

0 comments on commit aaea74e

Please sign in to comment.