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

add previous and current crosslinks #874

Merged
merged 33 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
529cf42
add previous and current crosslinks
djrtwo Apr 2, 2019
d8df789
simplify get_winning_root logic
djrtwo Apr 2, 2019
39b4ef3
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 3, 2019
1fa88fb
remove previous crosslink check from process_crosslinks
djrtwo Apr 3, 2019
d1af914
Update 0_beacon-chain.md
JustinDrake Apr 3, 2019
a790afa
Update 0_beacon-chain.md
JustinDrake Apr 4, 2019
3e6dc59
Update helpers.py
JustinDrake Apr 4, 2019
dc325f7
clean up a few things from PR
djrtwo Apr 5, 2019
f677af2
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 5, 2019
26df4f4
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 7, 2019
42dc003
add previous_crosslink_root and enforce crosslinks form a chain
djrtwo Apr 7, 2019
e246c3f
source_crosslink_root to previous_crosslink_root
djrtwo Apr 8, 2019
71a28aa
fix tests
djrtwo Apr 8, 2019
0a5a5b7
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 13, 2019
a6b3b11
ensure no reward for crosslinks taht can't form a chain
djrtwo Apr 13, 2019
9489ae5
upate validator guide to new crosslink format
djrtwo Apr 13, 2019
eafcab7
check crosslinks validity root against previous
djrtwo Apr 13, 2019
3555ab8
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 14, 2019
cc68df8
Merge branch 'dev' into prev-cur-crosslinks
hwwhww Apr 17, 2019
ef14396
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 17, 2019
8c5f7a5
Merge branch 'dev' into prev-cur-crosslinks
djrtwo Apr 18, 2019
fbaf771
Update 0_beacon-chain.md
JustinDrake Apr 18, 2019
9ecafb2
Update 0_beacon-chain.md
JustinDrake Apr 18, 2019
40b55cf
More fixes
JustinDrake Apr 18, 2019
cae5c22
Simplify get_crosslink_committee_for_attestation and move to test hel…
JustinDrake Apr 18, 2019
964b4d3
Fix `pyspec/tests/helpers.py`
hwwhww Apr 18, 2019
743193a
nitpicks
hwwhww Apr 18, 2019
4244db9
More cleanups
JustinDrake Apr 18, 2019
2e09f1a
Merge
JustinDrake Apr 18, 2019
172e106
merge
JustinDrake Apr 18, 2019
857d9b2
Merge branch 'dev' into prev-cur-crosslinks
JustinDrake Apr 18, 2019
7a01648
Moar
JustinDrake Apr 18, 2019
741a74a
re-add crosslink tests and ensure pass
djrtwo Apr 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,8 @@ The types are defined topologically to aid in facilitating an executable version
'finalized_root': 'bytes32',

# Recent state
'latest_crosslinks': [Crosslink, SHARD_COUNT],
'current_crosslinks': [Crosslink, SHARD_COUNT],
'previous_crosslinks': [Crosslink, SHARD_COUNT],
'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH],
Expand Down Expand Up @@ -1040,7 +1041,7 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index:

```python
def get_crosslink_committee_for_attestation(state: BeaconState,
attestation_data: AttestationData) -> List[ValidatorIndex]:
attestation_data: AttestationData) -> List[ValidatorIndex]:
# Find the committee in the list with the desired shard
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)

Expand Down Expand Up @@ -1556,7 +1557,8 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
finalized_root=ZERO_HASH,

# Recent state
latest_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
current_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
previous_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
latest_block_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
latest_state_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
latest_active_index_roots=Vector([ZERO_HASH for _ in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH)]),
Expand Down Expand Up @@ -1760,11 +1762,11 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe
**Note**: Total balances computed for the previous epoch might be marginally different than the actual total balances during the previous epoch transition. Due to the tight bound on validator churn each epoch and small per-epoch rewards/penalties, the potential balance difference is very low and only marginally affects consensus safety.

```python
def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]:
all_attestations = state.current_epoch_attestations + state.previous_epoch_attestations
valid_attestations = [
a for a in all_attestations if a.data.previous_crosslink == state.latest_crosslinks[shard]
]
def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]:
attestations = state.current_epoch_attestations if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_epoch_attestations
crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

valid_attestations = [a for a in attestations if a.data.shard == shard]
all_roots = [a.data.crosslink_data_root for a in valid_attestations]

# handle when no attestations for shard available
Expand Down Expand Up @@ -1858,16 +1860,20 @@ def process_crosslinks(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
previous_epoch = max(current_epoch - 1, GENESIS_EPOCH)
next_epoch = current_epoch + 1
next_previous_crosslinks = [crosslink for crosslink in state.current_crosslinks]
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)):
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
winning_root, participants = get_winning_root_and_participants(state, shard)
winning_root, participants = get_winning_root_and_participants(state, slot, shard)
participating_balance = get_total_balance(state, participants)
total_balance = get_total_balance(state, crosslink_committee)
if 3 * participating_balance >= 2 * total_balance:
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
state.latest_crosslinks[shard] = Crosslink(
epoch=min(slot_to_epoch(slot), state.latest_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS),
crosslink_data_root=winning_root
state.current_crosslinks[shard] = Crosslink(
epoch=min(slot_to_epoch(slot), state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS),
crosslink_data_root=winning_root,
)

state.previous_crosslinks = next_previous_crosslinks
```

#### Eth1 data
Expand Down Expand Up @@ -1974,7 +1980,7 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
for slot in range(previous_epoch_start_slot, current_epoch_start_slot):
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
winning_root, participants = get_winning_root_and_participants(state, shard)
winning_root, participants = get_winning_root_and_participants(state, slot, shard)
participating_balance = get_total_balance(state, participants)
total_balance = get_total_balance(state, crosslink_committee)
for index in crosslink_committee:
Expand Down Expand Up @@ -2331,14 +2337,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:

# Check crosslink data
assert attestation.data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1]
assert state.latest_crosslinks[attestation.data.shard] in {
attestation.data.previous_crosslink, # Case 1: latest crosslink matches previous crosslink
Crosslink( # Case 2: latest crosslink matches current crosslink
crosslink_data_root=attestation.data.crosslink_data_root,
epoch=min(slot_to_epoch(attestation.data.slot),
attestation.data.previous_crosslink.epoch + MAX_CROSSLINK_EPOCHS)
),
}
crosslinks = state.current_crosslinks if slot_to_epoch(attestation.data.slot) == get_current_epoch(state) else state.previous_crosslinks
assert crosslinks[attestation.data.shard] == attestation.data.previous_crosslink

# Check signature and bitfields
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))
Expand Down
2 changes: 1 addition & 1 deletion tests/phase0/block_processing/test_process_attestation.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def test_bad_previous_crosslink(state):
attestation = get_valid_attestation(state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY

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 tests/phase0/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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
92 changes: 92 additions & 0 deletions tests/phase0/epoch_processing/test_process_crosslinks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from copy import deepcopy
import pytest

import build.phase0.spec as spec

from build.phase0.state_transition import (
state_transition,
)
from build.phase0.spec import (
ZERO_HASH,
cache_state,
get_crosslink_committee_for_attestation,
get_current_epoch,
process_crosslinks,
slot_to_epoch,
)
from tests.phase0.helpers import (
add_attestation_to_state,
build_empty_block_for_next_slot,
fill_aggregate_attestation,
get_valid_attestation,
next_epoch,
set_bitfield_bit,
)


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


def run_process_crosslinks(state, valid=True):
post_state = deepcopy(state)

# 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)

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)

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
46 changes: 39 additions & 7 deletions tests/phase0/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import build.phase0.spec as spec
from build.phase0.utils.minimal_ssz import signed_root
from build.phase0.state_transition import (
state_transition,
)
from build.phase0.spec import (
# constants
EMPTY_SIGNATURE,
Expand Down Expand Up @@ -47,6 +50,19 @@
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}


def set_bitfield_bit(bitfield, i):
"""
Set the bit in ``bitfield`` at position ``i`` to ``1``.
"""
byte_index = i // 8
bit_index = i % 8
return (
bitfield[:byte_index] +
bytes([bitfield[byte_index] | (1 << bit_index)]) +
bitfield[byte_index+1:]
)


def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None):
if not deposit_data_leaves:
deposit_data_leaves = []
Expand Down Expand Up @@ -97,10 +113,6 @@ def create_genesis_state(num_validators, deposit_data_leaves=None):


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


Expand Down Expand Up @@ -144,13 +156,14 @@ def build_attestation_data(state, slot, shard):
if epoch_start_slot == slot:
epoch_boundary_root = block_root
else:
get_block_root(state, epoch_start_slot)
epoch_boundary_root = get_block_root(state, epoch_start_slot)

if slot < epoch_start_slot:
justified_block_root = state.previous_justified_root
else:
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 @@ -159,7 +172,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=deepcopy(crosslinks[shard]),
)


Expand Down Expand Up @@ -260,7 +273,7 @@ def get_valid_attester_slashing(state):
def get_valid_attestation(state, slot=None):
if slot is None:
slot = state.slot
shard = state.latest_start_shard
shard = state.latest_start_shard + slot % spec.SLOTS_PER_EPOCH
attestation_data = build_attestation_data(state, slot, shard)

crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data)
Expand Down Expand Up @@ -312,3 +325,22 @@ def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0)
domain_type=spec.DOMAIN_ATTESTATION,
)
)


def fill_aggregate_attestation(state, attestation):
crosslink_committee = get_crosslink_committee_for_attestation(state, attestation.data)
for i in range(len(crosslink_committee)):
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_epoch(state):
block = build_empty_block_for_next_slot(state)
block.slot += spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH)
state_transition(state, block)
6 changes: 4 additions & 2 deletions tests/phase0/test_sanity.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
advance_slot,
cache_state,
set_balance,
slot_to_epoch,
verify_merkle_branch,
hash,
)
Expand Down Expand Up @@ -289,6 +290,7 @@ def test_voluntary_exit(state):

# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
pre_state.finalized_epoch = slot_to_epoch(pre_state.slot) - 3
# artificially trigger registry update at next epoch transition
force_registry_change_at_next_epoch(pre_state)

Expand Down Expand Up @@ -344,12 +346,12 @@ def test_no_exit_churn_too_long_since_change(state):
#
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# artificially trigger registry update at next epoch transition
force_registry_change_at_next_epoch(pre_state)
# make epochs since registry update greater than LATEST_SLASHED_EXIT_LENGTH
pre_state.validator_registry_update_epoch = (
get_current_epoch(pre_state) - spec.LATEST_SLASHED_EXIT_LENGTH
)
# artificially trigger registry update at next epoch transition
force_registry_change_at_next_epoch(pre_state)
# set validator to have previously initiated exit
pre_state.validator_registry[validator_index].initiated_exit = True

Expand Down