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 21 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
83 changes: 48 additions & 35 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ The types are defined topologically to aid in facilitating an executable version
'epoch': 'uint64',
# Shard data since the previous crosslink
'crosslink_data_root': 'bytes32',
# Root of the previous crosslink
'previous_crosslink_root': 'bytes32',
}
```

Expand Down Expand Up @@ -336,7 +338,7 @@ The types are defined topologically to aid in facilitating an executable version

# Crosslink vote
'shard': 'uint64',
'previous_crosslink': Crosslink,
'previous_crosslink_root': 'bytes32',
'crosslink_data_root': 'bytes32',
}
```
Expand Down Expand Up @@ -588,7 +590,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 @@ -1010,6 +1013,7 @@ def get_crosslink_committee_for_attestation(state: BeaconState,
"""
Return the crosslink committee corresponding to ``attestation_data``.
"""
# Find the committee in the list with the desired shard
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)

# Find the committee in the list with the desired shard
Expand Down Expand Up @@ -1444,7 +1448,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, previous_crosslink_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
previous_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_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 @@ -1646,25 +1651,28 @@ 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]
]
all_roots = [a.data.crosslink_data_root for a in valid_attestations]
def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Bytes32, Bytes32, List[ValidatorIndex]]:
Copy link
Contributor

@hwwhww hwwhww Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now returns winning_root, previous_crosslink_root, participants. Alternatively, we can reuse Crosslink by creating Crosslink(epoch=FAR_FUTURE_EPOCH, crosslink_data_root=winning_root, previous_crosslink_root=previous_crosslink_root). So we can change it function to:

def get_crosslink_candidate_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I did 👍

attestations = state.current_epoch_attestations if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_epoch_attestations

valid_attestations = [a for a in attestations if a.data.shard == shard]
all_roots = [(a.data.crosslink_data_root, a.data.previous_crosslink_root) for a in valid_attestations]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, alternatively:

    crosslink_candidates = [Crosslink(epoch=FAR_FUTURE_EPOCH, crosslink_data_root=a.data.crosslink_data_root, previous_crosslink_root=a.data.previous_crosslink_root) for a in valid_attestations]


# handle when no attestations for shard available
if len(all_roots) == 0:
return ZERO_HASH, []
return ZERO_HASH, ZERO_HASH, []

def get_attestations_for(root) -> List[PendingAttestation]:
return [a for a in valid_attestations if a.data.crosslink_data_root == root]

# Winning crosslink root is the root with the most votes for it, ties broken in favor of
# lexicographically higher hash
winning_root = max(all_roots, key=lambda r: (get_attesting_balance(state, get_attestations_for(r)), r))
winning_root, previous_crosslink_root = max(all_roots, key=lambda r: (get_attesting_balance(state, get_attestations_for(r[0])), r[0]))

return winning_root, get_unslashed_attesting_indices(state, get_attestations_for(winning_root))
return (
winning_root,
previous_crosslink_root,
get_unslashed_attesting_indices(state, get_attestations_for(winning_root)),
)
```

```python
Expand Down Expand Up @@ -1727,17 +1735,21 @@ Run the following function:

```python
def process_crosslinks(state: BeaconState) -> None:
state.previous_crosslinks = [crosslink for crosslink in state.current_crosslinks]

previous_epoch = get_previous_epoch(state)
next_epoch = get_current_epoch(state) + 1
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, previous_crosslink_root, participants = get_winning_root_and_participants(state, slot, shard)
expected_crosslink_root = hash_tree_root(state.current_crosslinks[shard])
participating_balance = get_total_balance(state, participants)
total_balance = get_total_balance(state, crosslink_committee)
if 3 * participating_balance >= 2 * total_balance:
state.latest_crosslinks[shard] = Crosslink(
epoch=min(slot_to_epoch(slot), state.latest_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS),
crosslink_data_root=winning_root
if previous_crosslink_root == expected_crosslink_root and 3 * participating_balance >= 2 * total_balance:
state.current_crosslinks[shard] = Crosslink(
epoch=min(slot_to_epoch(slot), state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS),
crosslink_data_root=winning_root,
previous_crosslink_root=previous_crosslink_root,
)
```

Expand Down Expand Up @@ -1830,7 +1842,14 @@ 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, previous_crosslink_root, participants = get_winning_root_and_participants(state, slot, shard)

# do not count as success if winning_root did not or cannot form a chain
attempted_crosslink = Crosslink(epoch=slot_to_epoch(slot), crosslink_data_root=winning_root, previous_crosslink_root=previous_crosslink_root)
actual_crosslink_root = hash_tree_root(state.previous_crosslinks[shard])
if not actual_crosslink_root in {previous_crosslink_root, hash_tree_root(attempted_crosslink)}:
participants = []

participating_balance = get_total_balance(state, participants)
total_balance = get_total_balance(state, crosslink_committee)
for index in crosslink_committee:
Expand Down Expand Up @@ -2105,32 +2124,26 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
Process ``Attestation`` operation.
Note that this function mutates ``state``.
"""
assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation.data.slot + SLOTS_PER_EPOCH
data = attestation.data
min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT
assert min_slot <= data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY

# Check target epoch, source epoch, and source root
target_epoch = slot_to_epoch(attestation.data.slot)
assert (target_epoch, attestation.data.source_epoch, attestation.data.source_root) in {
(get_current_epoch(state), state.current_justified_epoch, state.current_justified_root),
(get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root),
# Check target epoch, source epoch, source root, and source crosslink
target_epoch = slot_to_epoch(data.slot)
assert (target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in {
(get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, hash_tree_root(state.current_crosslinks[data.shard])),
(get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, hash_tree_root(state.previous_crosslinks[data.shard])),
}

# 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)
),
}
# Check crosslink data root
assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1]

# Check signature and bitfields
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))

# Cache pending attestation
pending_attestation = PendingAttestation(
data=attestation.data,
data=data,
aggregation_bitfield=attestation.aggregation_bitfield,
inclusion_slot=state.slot
)
Expand Down
38 changes: 19 additions & 19 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 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
12 changes: 11 additions & 1 deletion test_libs/pyspec/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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 @@ -342,6 +345,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
28 changes: 1 addition & 27 deletions test_libs/pyspec/tests/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 @@ -382,33 +383,6 @@ def test_voluntary_exit(state):
return pre_state, [initiate_exit_block, exit_block], post_state


def test_no_exit_churn_too_long_since_change(state):
pre_state = deepcopy(state)
validator_index = get_active_validator_indices(
pre_state,
get_current_epoch(pre_state)
)[-1]

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

post_state = deepcopy(pre_state)

#
# Process registry change but ensure no exit
#
block = build_empty_block_for_next_slot(post_state)
block.slot += spec.SLOTS_PER_EPOCH
state_transition(post_state, block)

assert post_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH

return pre_state, [block], post_state


def test_transfer(state):
pre_state = deepcopy(state)
current_epoch = get_current_epoch(pre_state)
Expand Down