From 529cf4223e221bb8e782f76eb7b0c22aa051e7bc Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Apr 2019 16:00:36 +1100 Subject: [PATCH 01/22] add previous and current crosslinks --- specs/core/0_beacon-chain.md | 38 ++++++++++--------- .../test_process_attestation.py | 2 +- .../test_process_proposer_slashing.py | 2 +- tests/phase0/conftest.py | 3 +- tests/phase0/helpers.py | 7 +--- tests/phase0/test_sanity.py | 6 ++- 6 files changed, 31 insertions(+), 27 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d08828692f..1032a905ae 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -610,7 +610,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], @@ -1554,7 +1555,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)]), @@ -1758,10 +1760,14 @@ 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]]: +def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]: all_attestations = state.current_epoch_attestations + state.previous_epoch_attestations + crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks + valid_attestations = [ - a for a in all_attestations if a.data.previous_crosslink == state.latest_crosslinks[shard] + a for a in all_attestations + if a.data.previous_crosslink == crosslinks[shard] and + a.data.shard == shard and a.data.slot == slot ] all_roots = [a.data.crosslink_data_root for a in valid_attestations] @@ -1856,16 +1862,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] + 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: - 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 @@ -1972,7 +1982,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: @@ -2327,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)) diff --git a/tests/phase0/block_processing/test_process_attestation.py b/tests/phase0/block_processing/test_process_attestation.py index ca6933ce7b..c946feb056 100644 --- a/tests/phase0/block_processing/test_process_attestation.py +++ b/tests/phase0/block_processing/test_process_attestation.py @@ -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) diff --git a/tests/phase0/block_processing/test_process_proposer_slashing.py b/tests/phase0/block_processing/test_process_proposer_slashing.py index 467d2164ba..b5aedc8bb6 100644 --- a/tests/phase0/block_processing/test_process_proposer_slashing.py +++ b/tests/phase0/block_processing/test_process_proposer_slashing.py @@ -11,7 +11,7 @@ get_valid_proposer_slashing, ) -# mark entire file as 'header' +# mark entire file as 'proposer_slashings' pytestmark = pytest.mark.proposer_slashings diff --git a/tests/phase0/conftest.py b/tests/phase0/conftest.py index 36a0879410..809d1239e2 100644 --- a/tests/phase0/conftest.py +++ b/tests/phase0/conftest.py @@ -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] diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index e5e335d80f..083d31b80d 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -95,10 +95,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 @@ -149,6 +145,7 @@ def build_attestation_data(state, slot, shard): 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, @@ -157,7 +154,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]), ) diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index 3b4497ca52..a7a6b99612 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -24,6 +24,7 @@ advance_slot, cache_state, set_balance, + slot_to_epoch, verify_merkle_branch, hash, ) @@ -254,6 +255,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) @@ -309,12 +311,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 From d8df789a7058383176ed1c4447f15cbaa7d6c042 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Apr 2019 10:13:05 +1100 Subject: [PATCH 02/22] simplify get_winning_root logic --- specs/core/0_beacon-chain.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1032a905ae..fa0c97acb6 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1761,13 +1761,12 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ```python def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]: - all_attestations = state.current_epoch_attestations + state.previous_epoch_attestations + 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 valid_attestations = [ - a for a in all_attestations - if a.data.previous_crosslink == crosslinks[shard] and - a.data.shard == shard and a.data.slot == slot + a for a in attestations + if a.data.previous_crosslink == crosslinks[shard] and a.data.shard == shard ] all_roots = [a.data.crosslink_data_root for a in valid_attestations] From 1fa88fb6d1363a10512e179cb460cd6026d0c3c7 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Apr 2019 16:56:13 +1100 Subject: [PATCH 03/22] remove previous crosslink check from process_crosslinks --- specs/core/0_beacon-chain.md | 7 +- .../test_process_crosslinks.py | 92 +++++++++++++++++++ tests/phase0/helpers.py | 39 +++++++- 3 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 tests/phase0/epoch_processing/test_process_crosslinks.py diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f95cb29e80..f3f66ce410 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1041,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) @@ -1766,10 +1766,7 @@ def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Sha 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 - valid_attestations = [ - a for a in attestations - if a.data.previous_crosslink == crosslinks[shard] and a.data.shard == shard - ] + 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 diff --git a/tests/phase0/epoch_processing/test_process_crosslinks.py b/tests/phase0/epoch_processing/test_process_crosslinks.py new file mode 100644 index 0000000000..a69950f217 --- /dev/null +++ b/tests/phase0/epoch_processing/test_process_crosslinks.py @@ -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 diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 0fd7bab732..ead0a0489a 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -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, @@ -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 = [] @@ -140,7 +156,7 @@ 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 @@ -257,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) @@ -309,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) From d1af9144bc0ae464e5ceca4b9dd19993a5ab7297 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 3 Apr 2019 23:40:54 +0400 Subject: [PATCH 04/22] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f3f66ce410..65e48c69cc 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -354,7 +354,7 @@ The types are defined topologically to aid in facilitating an executable version # Crosslink vote 'shard': 'uint64', - 'previous_crosslink': Crosslink, + 'source_crosslink': Crosslink, 'crosslink_data_root': 'bytes32', } ``` @@ -2325,27 +2325,26 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: Process ``Attestation`` operation. Note that this function mutates ``state``. """ - assert max(GENESIS_SLOT, state.slot - SLOTS_PER_EPOCH) <= attestation.data.slot - assert attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY + data = attestation.data + assert max(GENESIS_SLOT, state.slot - SLOTS_PER_EPOCH) <= data.slot + assert 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.source_crosslink) in { + (get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, state.current_crosslinks[data.shard]), + (get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, state.previous_crosslinks[data.shard]), } - # Check crosslink data - assert attestation.data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1] - 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 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, custody_bitfield=attestation.custody_bitfield, inclusion_slot=state.slot From a790afa8915b31a0d0ea798f1b87d6ccecd6cd5e Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 4 Apr 2019 08:23:48 +0400 Subject: [PATCH 05/22] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 65e48c69cc..1e9aa43081 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1764,7 +1764,6 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ```python 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 valid_attestations = [a for a in attestations if a.data.shard == shard] all_roots = [a.data.crosslink_data_root for a in valid_attestations] @@ -1860,7 +1859,6 @@ 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] 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): @@ -1873,7 +1871,7 @@ def process_crosslinks(state: BeaconState) -> None: crosslink_data_root=winning_root, ) - state.previous_crosslinks = next_previous_crosslinks + state.previous_crosslinks = state.current_crosslinks ``` #### Eth1 data From 3e6dc59ec056c88617cf6b40056da46667b06f13 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 4 Apr 2019 08:26:11 +0400 Subject: [PATCH 06/22] Update helpers.py --- tests/phase0/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index ead0a0489a..afb03157ad 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -172,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(crosslinks[shard]), + source_crosslink=deepcopy(crosslinks[shard]), ) From dc325f7682cd4dec3270b1c2b76d638c3219bf91 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Apr 2019 19:39:03 +1100 Subject: [PATCH 07/22] clean up a few things from PR --- Makefile | 2 +- specs/core/0_beacon-chain.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 88f17dcf99..5be5ce4fe5 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ clean: # runs a limited set of tests against a minimal config # run pytest with `-m` option to full suite -test: +test: clean $(BUILD_DIR)/phase0 pytest -m minimal_config tests/ diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1e9aa43081..4e1bcab16f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1860,6 +1860,8 @@ def process_crosslinks(state: BeaconState) -> None: previous_epoch = max(current_epoch - 1, GENESIS_EPOCH) next_epoch = current_epoch + 1 + state.previous_crosslinks = [crosslink for crosslink in state.current_crosslinks] + 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, slot, shard) @@ -1870,8 +1872,6 @@ def process_crosslinks(state: BeaconState) -> None: epoch=min(slot_to_epoch(slot), state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), crosslink_data_root=winning_root, ) - - state.previous_crosslinks = state.current_crosslinks ``` #### Eth1 data From 42dc003e911ff98d3eede0238e4c2f469426a4a3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 7 Apr 2019 17:55:38 +1000 Subject: [PATCH 08/22] add previous_crosslink_root and enforce crosslinks form a chain --- specs/core/0_beacon-chain.md | 36 ++++++++++------- .../test_process_crosslinks.py | 39 ++++++++++++++++++- tests/phase0/helpers.py | 3 +- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 8438966294..dd003f83ec 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -316,6 +316,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', } ``` @@ -358,7 +360,7 @@ The types are defined topologically to aid in facilitating an executable version # Crosslink vote 'shard': 'uint64', - 'source_crosslink': Crosslink, + 'source_crosslink_root': 'bytes32', 'crosslink_data_root': 'bytes32', } ``` @@ -1565,8 +1567,8 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], finalized_root=ZERO_HASH, # Recent state - 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)]), + 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)]), @@ -1771,24 +1773,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, slot: Slot, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]: +def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Shard) -> Tuple[Bytes32, Bytes32, List[ValidatorIndex]]: 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 for a in valid_attestations] + all_roots = [(a.data.crosslink_data_root, a.data.source_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_attesting_indices(state, get_attestations_for(winning_root)) + return ( + winning_root, + previous_crosslink_root, + get_attesting_indices(state, get_attestations_for(winning_root)), + ) ``` ```python @@ -1873,13 +1879,15 @@ def process_crosslinks(state: BeaconState) -> None: 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, slot, 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: + 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, ) ``` @@ -1987,7 +1995,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, slot, 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: @@ -2338,9 +2346,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: # 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.source_crosslink) in { - (get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, state.current_crosslinks[data.shard]), - (get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, state.previous_crosslinks[data.shard]), + assert (target_epoch, data.source_epoch, data.source_root, data.source_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 root diff --git a/tests/phase0/epoch_processing/test_process_crosslinks.py b/tests/phase0/epoch_processing/test_process_crosslinks.py index a69950f217..f2be142c63 100644 --- a/tests/phase0/epoch_processing/test_process_crosslinks.py +++ b/tests/phase0/epoch_processing/test_process_crosslinks.py @@ -29,8 +29,6 @@ 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) @@ -40,6 +38,7 @@ def run_process_crosslinks(state, valid=True): # cache state before epoch transition cache_state(state) + post_state = deepcopy(state) process_crosslinks(post_state) return state, post_state @@ -90,3 +89,39 @@ def test_single_crosslink_update_from_previous_epoch(state): assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard] 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) + + state.slot = attestation_1.data.slot + spec.SLOTS_PER_EPOCH + attestation_2 = get_valid_attestation(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) + + shard_1 = attestation_1.data.shard + shard_2 = attestation_2.data.shard + assert shard_1 == shard_2 + shard = shard_1 + + # ensure that the current crosslinks were not updated by the second attestation + assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard] + + return pre_state, post_state diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index afb03157ad..1641a5290a 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -34,6 +34,7 @@ get_empty_block, get_epoch_start_slot, get_genesis_beacon_state, + hash_tree_root, slot_to_epoch, verify_merkle_branch, hash, @@ -172,7 +173,7 @@ def build_attestation_data(state, slot, shard): source_root=justified_block_root, target_root=epoch_boundary_root, crosslink_data_root=spec.ZERO_HASH, - source_crosslink=deepcopy(crosslinks[shard]), + source_crosslink_root=hash_tree_root(crosslinks[shard]), ) From e246c3fb049816f90b81dcedf88c928194350fad Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 8 Apr 2019 12:59:42 +1000 Subject: [PATCH 09/22] source_crosslink_root to previous_crosslink_root --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index dd003f83ec..c03ce93adc 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -360,7 +360,7 @@ The types are defined topologically to aid in facilitating an executable version # Crosslink vote 'shard': 'uint64', - 'source_crosslink_root': 'bytes32', + 'previous_crosslink_root': 'bytes32', 'crosslink_data_root': 'bytes32', } ``` @@ -1777,7 +1777,7 @@ def get_winning_root_and_participants(state: BeaconState, slot: Slot, shard: Sha 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.source_crosslink_root) for a in valid_attestations] + all_roots = [(a.data.crosslink_data_root, a.data.previous_crosslink_root) for a in valid_attestations] # handle when no attestations for shard available if len(all_roots) == 0: @@ -1995,7 +1995,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, slot, shard) + winning_root, previous_crosslink_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: @@ -2346,7 +2346,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: # 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.source_crosslink_root) in { + 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])), } From 71a28aa1c9c6e1b997b9cebe3b20101f0ddb233b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 8 Apr 2019 13:03:29 +1000 Subject: [PATCH 10/22] fix tests --- tests/phase0/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 1641a5290a..48cbdbe199 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -173,7 +173,7 @@ def build_attestation_data(state, slot, shard): source_root=justified_block_root, target_root=epoch_boundary_root, crosslink_data_root=spec.ZERO_HASH, - source_crosslink_root=hash_tree_root(crosslinks[shard]), + previous_crosslink_root=hash_tree_root(crosslinks[shard]), ) From a6b3b11356e13d22ad49e84d9985f971810a447a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sat, 13 Apr 2019 17:49:25 +1000 Subject: [PATCH 11/22] ensure no reward for crosslinks taht can't form a chain --- specs/core/0_beacon-chain.md | 7 ++++++ .../test_process_crosslinks.py | 23 +++++++++++-------- tests/phase0/helpers.py | 7 +++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6a81dadcf9..70b5bac9b9 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1917,6 +1917,13 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: 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, 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) + current_crosslink_root = hash_tree_root(state.current_crosslinks[shard]) + if not current_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: diff --git a/tests/phase0/epoch_processing/test_process_crosslinks.py b/tests/phase0/epoch_processing/test_process_crosslinks.py index f2be142c63..06dc07d853 100644 --- a/tests/phase0/epoch_processing/test_process_crosslinks.py +++ b/tests/phase0/epoch_processing/test_process_crosslinks.py @@ -7,12 +7,9 @@ state_transition, ) from build.phase0.spec import ( - ZERO_HASH, cache_state, - get_crosslink_committee_for_attestation, - get_current_epoch, + get_crosslink_deltas, process_crosslinks, - slot_to_epoch, ) from tests.phase0.helpers import ( add_attestation_to_state, @@ -20,6 +17,7 @@ fill_aggregate_attestation, get_valid_attestation, next_epoch, + next_slot, set_bitfield_bit, ) @@ -102,8 +100,11 @@ def test_double_late_crosslink(state): next_epoch(state) add_attestation_to_state(state, attestation_1, state.slot + 1) - state.slot = attestation_1.data.slot + spec.SLOTS_PER_EPOCH - attestation_2 = get_valid_attestation(state) + 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 @@ -115,13 +116,15 @@ def test_double_late_crosslink(state): assert len(state.current_epoch_attestations) == 0 pre_state, post_state = run_process_crosslinks(state) + crosslink_deltas = get_crosslink_deltas(state) - shard_1 = attestation_1.data.shard - shard_2 = attestation_2.data.shard - assert shard_1 == shard_2 - shard = shard_1 + shard = attestation_2.data.shard + slot = attestation_2.data.slot # 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 + assert crosslink_deltas[0][slot % spec.SLOTS_PER_EPOCH] == 0 + assert crosslink_deltas[1][slot % spec.SLOTS_PER_EPOCH] > 0 return pre_state, post_state diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index cd731e49c0..219e77c474 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -274,7 +274,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 + slot % spec.SLOTS_PER_EPOCH + 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) @@ -341,6 +341,11 @@ def add_attestation_to_state(state, attestation, slot): state_transition(state, block) +def next_slot(state): + block = build_empty_block_for_next_slot(state) + 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) From 9489ae5dcdaf68fba9214205f89fcee1d16daea9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sat, 13 Apr 2019 18:02:12 +1000 Subject: [PATCH 12/22] upate validator guide to new crosslink format --- specs/validator/0_beacon-chain-validator.md | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index 60d2836646..77d9f0ce65 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -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) @@ -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. @@ -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 From eafcab7e58bdf5045eb001fcc80f9b7fa8f69d45 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 14 Apr 2019 08:24:54 +1000 Subject: [PATCH 13/22] check crosslinks validity root against previous --- specs/core/0_beacon-chain.md | 4 ++-- tests/phase0/epoch_processing/test_process_crosslinks.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 70b5bac9b9..c1dc9de48b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1920,8 +1920,8 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: # 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) - current_crosslink_root = hash_tree_root(state.current_crosslinks[shard]) - if not current_crosslink_root in {previous_crosslink_root, hash_tree_root(attempted_crosslink) }: + 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) diff --git a/tests/phase0/epoch_processing/test_process_crosslinks.py b/tests/phase0/epoch_processing/test_process_crosslinks.py index 06dc07d853..5f080f6f40 100644 --- a/tests/phase0/epoch_processing/test_process_crosslinks.py +++ b/tests/phase0/epoch_processing/test_process_crosslinks.py @@ -81,10 +81,15 @@ def test_single_crosslink_update_from_previous_epoch(state): 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 + slot = attestation.data.slot + assert crosslink_deltas[0][slot % spec.SLOTS_PER_EPOCH] > 0 + assert crosslink_deltas[1][slot % spec.SLOTS_PER_EPOCH] == 0 return pre_state, post_state From fbaf771b1a184f550967254b6c5594cdff9e186a Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 18 Apr 2019 14:20:34 +1000 Subject: [PATCH 14/22] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 109 ++++++++++++++++------------------- 1 file changed, 50 insertions(+), 59 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cdf34ae277..492caf5112 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -77,7 +77,7 @@ - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) - [`get_crosslink_committee_for_attestation`](#get_crosslink_committee_for_attestation) - - [`get_attestation_participants`](#get_attestation_participants) + - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) - [`get_effective_balance`](#get_effective_balance) @@ -180,7 +180,7 @@ These configurations are updated for releases, but may be out of sync during `de | - | - | | `SHARD_COUNT` | `2**10` (= 1,024) | | `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) | -| `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) | +| `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | | `SHUFFLE_ROUND_COUNT` | 90 | @@ -303,10 +303,10 @@ The types are defined topologically to aid in facilitating an executable version { # Epoch number 'epoch': 'uint64', - # Shard data since the previous crosslink - 'crosslink_data_root': 'bytes32', # Root of the previous crosslink 'previous_crosslink_root': 'bytes32', + # Shard data since the previous crosslink + 'crosslink_data_root': 'bytes32', } ``` @@ -1023,14 +1023,14 @@ def get_crosslink_committee_for_attestation(state: BeaconState, return crosslink_committee ``` -### `get_attestation_participants` +### `get_attesting_indices` ```python -def get_attestation_participants(state: BeaconState, +def get_attesting_indices(state: BeaconState, attestation_data: AttestationData, bitfield: bytes) -> List[ValidatorIndex]: """ - Return the sorted participant indices corresponding to ``attestation_data`` and ``bitfield``. + Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data) assert verify_bitfield(bitfield, len(crosslink_committee)) @@ -1129,8 +1129,8 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA """ Convert ``attestation`` to (almost) indexed-verifiable form. """ - attesting_indices = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield) - custody_bit_1_indices = get_attestation_participants(state, attestation.data, attestation.custody_bitfield) + attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield) + custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bitfield) custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices] return IndexedAttestation( @@ -1151,14 +1151,13 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA custody_bit_0_indices = indexed_attestation.custody_bit_0_indices custody_bit_1_indices = indexed_attestation.custody_bit_1_indices - # ensure no duplicate indices across custody bits + # Ensure no duplicate indices across custody bits assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0 if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1] return False - total_attesting_indices = len(custody_bit_0_indices) + len(custody_bit_1_indices) - if not (1 <= total_attesting_indices <= MAX_ATTESTATION_PARTICIPANTS): + if not (1 <= len(custody_bit_0_indices) + len(custody_bit_1_indices) <= MAX_INDICES_PER_ATTESTATION): return False if custody_bit_0_indices != sorted(custody_bit_0_indices): @@ -1615,7 +1614,7 @@ def get_previous_total_balance(state: BeaconState) -> Gwei: def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]: output = set() for a in attestations: - output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield)) + output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield)) return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output))) ``` @@ -1651,34 +1650,31 @@ 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, slot: Slot, shard: Shard) -> Tuple[Bytes32, Bytes32, List[ValidatorIndex]]: - 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] - - # handle when no attestations for shard available - if len(all_roots) == 0: - return ZERO_HASH, ZERO_HASH, [] +def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: + pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + crosslink_data_roots = [(a.data.crosslink_data_root, a.data.previous_crosslink_root) for a in pending_attestations if a.data.shard == shard] def get_attestations_for(root) -> List[PendingAttestation]: - return [a for a in valid_attestations if a.data.crosslink_data_root == root] + return [a for a in valid_attestations if a.data.shard == shard and 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, previous_crosslink_root = max(all_roots, key=lambda r: (get_attesting_balance(state, get_attestations_for(r[0])), r[0])) - - return ( - winning_root, - previous_crosslink_root, - get_unslashed_attesting_indices(state, get_attestations_for(winning_root)), + # Winning crosslink data root is the root with the most votes for it (ties broken lexicographically) + crosslink_data_root, previous_crosslink_root = max(crosslink_data_roots, + key=lambda r: (get_attesting_balance(state, get_attestations_for(r[0])), r[0]), + default=ZERO_HASH, ZERO_HASH + ) + winning_crosslink = Crosslink( + epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), + crosslink_data_root=crosslink_data_root, + previous_crosslink_root=previous_crosslink_root, ) + + return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(crosslink_data_root)) ``` ```python def get_earliest_attestation(state: BeaconState, attestations: List[PendingAttestation], index: ValidatorIndex) -> PendingAttestation: return min([ - a for a in attestations if index in get_attestation_participants(state, a.data, a.aggregation_bitfield) + a for a in attestations if index in get_attesting_indices(state, a.data, a.aggregation_bitfield) ], key=lambda a: a.inclusion_slot) ``` @@ -1735,22 +1731,18 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: - state.previous_crosslinks = [crosslink for crosslink in state.current_crosslinks] + state.previous_crosslinks = 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 slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state) + 1)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - 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 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, - ) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot, shard) + attesting_balance = get_total_balance(state, attesting_indices) + committee_balance = get_total_balance(state, crosslink_committee) + if ( + winning_crosslink.previous_crosslink_root == hash_tree_root(state.current_crosslinks[shard]) and + 3 * attesting_balance >= 2 * committee_balance + ): + state.current_crosslinks[shard] = winning_crosslink ``` #### Rewards and penalties @@ -1838,23 +1830,22 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] - previous_epoch_start_slot = get_epoch_start_slot(get_previous_epoch(state)) - 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 slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_root, previous_crosslink_root, participants = get_winning_root_and_participants(state, slot, shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(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 = [] + # Do not count as success if winning_crosslink did not or cannot form a chain + if not hash_tree_root(state.previous_crosslinks[shard]) in ( + winning_crosslink.previous_crosslink_root, + hash_tree_root(winning_crosslink) + ): + attesting_indices = [] - participating_balance = get_total_balance(state, participants) - total_balance = get_total_balance(state, crosslink_committee) + attesting_balance = get_total_balance(state, attesting_indices) + committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: - if index in participants: - rewards[index] += get_base_reward(state, index) * participating_balance // total_balance + if index in attesting_indices: + rewards[index] += get_base_reward(state, index) * attesting_balance // committee_balance else: penalties[index] += get_base_reward(state, index) return [rewards, penalties] From 9ecafb2a1cc010047440f9e6aa56cdf36bc59dac Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 18 Apr 2019 14:33:45 +1000 Subject: [PATCH 15/22] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 492caf5112..139b8024b9 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1652,23 +1652,23 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - crosslink_data_roots = [(a.data.crosslink_data_root, a.data.previous_crosslink_root) for a in pending_attestations if a.data.shard == shard] + candidate_crosslinks = [Crosslink( + epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), + crosslink_data_root=a.data.crosslink_data_root, + previous_crosslink_root=a.data.previous_crosslink_root, + ) for a in pending_attestations if a.data.shard == shard] - def get_attestations_for(root) -> List[PendingAttestation]: - return [a for a in valid_attestations if a.data.shard == shard and a.data.crosslink_data_root == root] + if len(candidate_crosslinks) == 0: + return Crosslink(GENESIS_EPOCH, ZERO_HASH, ZERO_HASH), [] - # Winning crosslink data root is the root with the most votes for it (ties broken lexicographically) - crosslink_data_root, previous_crosslink_root = max(crosslink_data_roots, - key=lambda r: (get_attesting_balance(state, get_attestations_for(r[0])), r[0]), - default=ZERO_HASH, ZERO_HASH - ) - winning_crosslink = Crosslink( - epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), - crosslink_data_root=crosslink_data_root, - previous_crosslink_root=previous_crosslink_root, - ) + def get_attestations_for(crosslink_data_root) -> List[PendingAttestation]: + return [a for a in pending_attestations if a.data.shard == shard and a.data.crosslink_data_root == crosslink_data_root] - return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(crosslink_data_root)) + # Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically) + winning_crosslink = max(candidate_crosslinks, key=lambda c: ( + get_attesting_balance(state, get_attestations_for(c.crosslink_data_root)), c.crosslink_data_root + )) + return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink.crosslink_data_root)) ``` ```python @@ -1735,7 +1735,7 @@ def process_crosslinks(state: BeaconState) -> None: for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state) + 1)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot, shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) if ( @@ -1832,7 +1832,7 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: penalties = [0 for index in range(len(state.validator_registry))] for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot, shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) # Do not count as success if winning_crosslink did not or cannot form a chain if not hash_tree_root(state.previous_crosslinks[shard]) in ( From 40b55cf433998f5af3f7a3c43edd1f682fa63876 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Thu, 18 Apr 2019 14:43:24 +1000 Subject: [PATCH 16/22] More fixes --- specs/core/0_beacon-chain.md | 10 +++++----- specs/core/1_custody-game.md | 6 +++--- specs/validator/0_beacon-chain-validator.md | 2 +- test_libs/pyspec/tests/helpers.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 139b8024b9..ac73c08aae 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -577,7 +577,7 @@ The types are defined topologically to aid in facilitating an executable version # Randomness and committees 'latest_randao_mixes': ['bytes32', LATEST_RANDAO_MIXES_LENGTH], 'latest_start_shard': 'uint64', - + # Finality 'previous_epoch_attestations': [PendingAttestation], 'current_epoch_attestations': [PendingAttestation], @@ -651,7 +651,7 @@ Note: We aim to migrate to a S[T/N]ARK-friendly hash function in a future Ethere ```python def get_temporary_block_header(block: BeaconBlock) -> BeaconBlockHeader: """ - Return the block header corresponding to a block with ``state_root`` set to ``ZERO_HASH``. + Return the block header corresponding to a block with ``state_root`` set to ``ZERO_HASH``. """ return BeaconBlockHeader( slot=block.slot, @@ -796,7 +796,7 @@ def get_permuted_index(index: int, list_size: int, seed: Bytes32) -> int: """ assert index < list_size assert list_size <= 2**40 - + for round in range(SHUFFLE_ROUND_COUNT): pivot = bytes_to_int(hash(seed + int_to_bytes1(round))[0:8]) % list_size flip = (pivot - index) % list_size @@ -1659,7 +1659,7 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch ) for a in pending_attestations if a.data.shard == shard] if len(candidate_crosslinks) == 0: - return Crosslink(GENESIS_EPOCH, ZERO_HASH, ZERO_HASH), [] + return Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_root=ZERO_HASH), [] def get_attestations_for(crosslink_data_root) -> List[PendingAttestation]: return [a for a in pending_attestations if a.data.shard == shard and a.data.crosslink_data_root == crosslink_data_root] @@ -1899,7 +1899,7 @@ def update_registry(state: BeaconState) -> None: ], key=lambda index: state.validator_registry[index].activation_eligibility_epoch) for index in activation_queue[:get_churn_limit(state)]: - activate_validator(state, index, is_genesis=False) + activate_validator(state, index, is_genesis=False) state.latest_start_shard = ( state.latest_start_shard + diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 138e69feec..74b086219d 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -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: @@ -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: diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index 53712880c6..632bf2b620 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -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 diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index fca9d3bd02..384490d839 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -25,7 +25,7 @@ # functions convert_to_indexed, get_active_validator_indices, - get_attestation_participants, + get_attesting_indices, get_block_root, get_crosslink_committee_for_attestation, get_current_epoch, @@ -300,7 +300,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, From cae5c227188e4340550de625fd448688b3ce9b68 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Thu, 18 Apr 2019 18:11:18 +1000 Subject: [PATCH 17/22] Simplify get_crosslink_committee_for_attestation and move to test helpers --- specs/core/0_beacon-chain.md | 26 ++++---------------------- test_libs/pyspec/tests/helpers.py | 9 +++++++++ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ac73c08aae..b0cfb900c3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -76,7 +76,6 @@ - [`generate_seed`](#generate_seed) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) - - [`get_crosslink_committee_for_attestation`](#get_crosslink_committee_for_attestation) - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) @@ -1005,34 +1004,17 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index: return value == root ``` -### `get_crosslink_committee_for_attestation` - -```python -def get_crosslink_committee_for_attestation(state: BeaconState, - attestation_data: AttestationData) -> List[ValidatorIndex]: - """ - 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 - assert attestation_data.shard in [shard for _, shard in crosslink_committees] - crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0] - - return crosslink_committee -``` - ### `get_attesting_indices` ```python def get_attesting_indices(state: BeaconState, - attestation_data: AttestationData, - bitfield: bytes) -> List[ValidatorIndex]: + attestation_data: AttestationData, + bitfield: bytes) -> List[ValidatorIndex]: """ Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ - crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data) + crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot) + crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0] assert verify_bitfield(bitfield, len(crosslink_committee)) return sorted([index for i, index in enumerate(crosslink_committee) if get_bitfield_bit(bitfield, i) == 0b1]) ``` diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 384490d839..214e93f75e 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -276,6 +276,15 @@ def get_valid_attester_slashing(state): ) +def get_crosslink_committee_for_attestation(state: BeaconState, + attestation_data: AttestationData) -> List[ValidatorIndex]: + """ + 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 From 964b4d380dd29f17797886465309cd4a7873ae27 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Apr 2019 16:21:53 +0800 Subject: [PATCH 18/22] Fix `pyspec/tests/helpers.py` --- test_libs/pyspec/tests/helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 214e93f75e..44d2dcb4d6 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -27,7 +27,7 @@ get_active_validator_indices, get_attesting_indices, get_block_root, - get_crosslink_committee_for_attestation, + get_crosslink_committees_at_slot, get_current_epoch, get_domain, get_empty_block, @@ -276,8 +276,7 @@ def get_valid_attester_slashing(state): ) -def get_crosslink_committee_for_attestation(state: BeaconState, - attestation_data: AttestationData) -> List[ValidatorIndex]: +def get_crosslink_committee_for_attestation(state, attestation_data): """ Return the crosslink committee corresponding to ``attestation_data``. """ From 743193aa7d8f8aad717d85b6c420f8bd98566c4f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Apr 2019 16:53:02 +0800 Subject: [PATCH 19/22] nitpicks --- specs/core/0_beacon-chain.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index b0cfb900c3..ece6c19276 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -126,7 +126,7 @@ - [Per-block processing](#per-block-processing) - [Block header](#block-header) - [RANDAO](#randao) - - [Eth1 data](#eth1-data-1) + - [Eth1 data](#eth1-data) - [Operations](#operations) - [Proposer slashings](#proposer-slashings) - [Attester slashings](#attester-slashings) @@ -1429,8 +1429,8 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], finalized_root=ZERO_HASH, # Recent state - 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)]), + current_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]), + previous_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, 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)]), @@ -1634,14 +1634,17 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - candidate_crosslinks = [Crosslink( - epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), - crosslink_data_root=a.data.crosslink_data_root, - previous_crosslink_root=a.data.previous_crosslink_root, - ) for a in pending_attestations if a.data.shard == shard] + candidate_crosslinks = [ + Crosslink( + epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), + previous_crosslink_root=a.data.previous_crosslink_root, + crosslink_data_root=a.data.crosslink_data_root, + ) + for a in pending_attestations if a.data.shard == shard + ] if len(candidate_crosslinks) == 0: - return Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_root=ZERO_HASH), [] + return Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH), [] def get_attestations_for(crosslink_data_root) -> List[PendingAttestation]: return [a for a in pending_attestations if a.data.shard == shard and a.data.crosslink_data_root == crosslink_data_root] @@ -1714,8 +1717,9 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: state.previous_crosslinks = state.current_crosslinks - - for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state) + 1)): + 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_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) attesting_balance = get_total_balance(state, attesting_indices) @@ -1817,7 +1821,7 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) # Do not count as success if winning_crosslink did not or cannot form a chain - if not hash_tree_root(state.previous_crosslinks[shard]) in ( + if hash_tree_root(state.previous_crosslinks[shard]) not in ( winning_crosslink.previous_crosslink_root, hash_tree_root(winning_crosslink) ): @@ -1881,7 +1885,7 @@ def update_registry(state: BeaconState) -> None: ], key=lambda index: state.validator_registry[index].activation_eligibility_epoch) for index in activation_queue[:get_churn_limit(state)]: - activate_validator(state, index, is_genesis=False) + activate_validator(state, index) state.latest_start_shard = ( state.latest_start_shard + From 4244db92ad0a8f338004d91803ea110dc4f67697 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Thu, 18 Apr 2019 18:53:22 +1000 Subject: [PATCH 20/22] More cleanups --- specs/core/0_beacon-chain.md | 34 ++++++++++++------------------- test_libs/pyspec/tests/helpers.py | 5 ++--- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index b0cfb900c3..b95d9acac1 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1634,22 +1634,28 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - candidate_crosslinks = [Crosslink( + shard_attestations = [a for a in pending_attestations if a.data.shard == shard] + shard_crosslinks = [Crosslink( epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), crosslink_data_root=a.data.crosslink_data_root, previous_crosslink_root=a.data.previous_crosslink_root, - ) for a in pending_attestations if a.data.shard == shard] + ) for a in shard_attestations] + candidate_crosslinks = [c for c in shard_crosslinks if + hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c)) + ] if len(candidate_crosslinks) == 0: return Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_root=ZERO_HASH), [] def get_attestations_for(crosslink_data_root) -> List[PendingAttestation]: - return [a for a in pending_attestations if a.data.shard == shard and a.data.crosslink_data_root == crosslink_data_root] + return [a for a in shard_attestations if a.data.crosslink_data_root == crosslink_data_root] # Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically) - winning_crosslink = max(candidate_crosslinks, key=lambda c: ( - get_attesting_balance(state, get_attestations_for(c.crosslink_data_root)), c.crosslink_data_root - )) + winning_crosslink = max(candidate_crosslinks, + key=lambda c: (get_attesting_balance(state, get_attestations_for(c.crosslink_data_root)), c.crosslink_data_root), + default=Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_root=ZERO_HASH), + ) + return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink.crosslink_data_root)) ``` @@ -1714,16 +1720,10 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: state.previous_crosslinks = state.current_crosslinks - for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state) + 1)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) - attesting_balance = get_total_balance(state, attesting_indices) - committee_balance = get_total_balance(state, crosslink_committee) - if ( - winning_crosslink.previous_crosslink_root == hash_tree_root(state.current_crosslinks[shard]) and - 3 * attesting_balance >= 2 * committee_balance - ): + if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1815,14 +1815,6 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) - - # Do not count as success if winning_crosslink did not or cannot form a chain - if not hash_tree_root(state.previous_crosslinks[shard]) in ( - winning_crosslink.previous_crosslink_root, - hash_tree_root(winning_crosslink) - ): - attesting_indices = [] - attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 214e93f75e..563786c7cc 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -27,7 +27,6 @@ get_active_validator_indices, get_attesting_indices, get_block_root, - get_crosslink_committee_for_attestation, get_current_epoch, get_domain, get_empty_block, @@ -35,6 +34,7 @@ get_genesis_beacon_state, get_previous_epoch, get_shard_delta, + get_crosslink_committees_at_slot, hash_tree_root, slot_to_epoch, verify_merkle_branch, @@ -276,8 +276,7 @@ def get_valid_attester_slashing(state): ) -def get_crosslink_committee_for_attestation(state: BeaconState, - attestation_data: AttestationData) -> List[ValidatorIndex]: +def get_crosslink_committee_for_attestation(state, attestation_data): """ Return the crosslink committee corresponding to ``attestation_data``. """ From 7a016489c15dffc6c7e7ff3dc36a442922ea6abd Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Thu, 18 Apr 2019 19:33:38 +1000 Subject: [PATCH 21/22] Moar --- specs/core/0_beacon-chain.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 14a3874671..1c00ac659b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1627,33 +1627,34 @@ 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_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink: + return Crosslink( + epoch=min(slot_to_epoch(data.slot), state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS), + crosslink_data_root=data.crosslink_data_root, + previous_crosslink_root=data.previous_crosslink_root, + ) +``` + ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations shard_attestations = [a for a in pending_attestations if a.data.shard == shard] - shard_crosslinks = [ - Crosslink( - epoch=min(epoch, state.current_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS), - crosslink_data_root=a.data.crosslink_data_root, - previous_crosslink_root=a.data.previous_crosslink_root, - ) for a in shard_attestations - ] + shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] candidate_crosslinks = [c for c in shard_crosslinks if hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c)) ] if len(candidate_crosslinks) == 0: return Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH), [] - def get_attestations_for(crosslink_data_root) -> List[PendingAttestation]: - return [a for a in shard_attestations if a.data.crosslink_data_root == crosslink_data_root] - + def get_attestations_for(crosslink: Crosslink) -> List[PendingAttestation]: + return [a for a in shard_attestations if get_crosslink_from_attestation_data(state, a.data) == crosslink] # Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically) - winning_crosslink = max(candidate_crosslinks, - key=lambda c: (get_attesting_balance(state, get_attestations_for(c.crosslink_data_root)), c.crosslink_data_root), - default=Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH, previous_crosslink_root=ZERO_HASH), - ) + winning_crosslink = max(candidate_crosslinks, key=lambda crosslink: ( + get_attesting_balance(state, get_attestations_for(crosslink)), crosslink.crosslink_data_root + )) - return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink.crosslink_data_root)) + return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink)) ``` ```python From 741a74a02cbb4d7efce905ed8cfc4732d2fd7c00 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 18 Apr 2019 11:16:50 -0600 Subject: [PATCH 22/22] re-add crosslink tests and ensure pass --- specs/core/0_beacon-chain.md | 9 +- .../test_process_crosslinks.py | 136 ++++++++++++++++++ 2 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1c00ac659b..eaceec6ac1 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1631,8 +1631,8 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink: return Crosslink( epoch=min(slot_to_epoch(data.slot), state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS), - crosslink_data_root=data.crosslink_data_root, previous_crosslink_root=data.previous_crosslink_root, + crosslink_data_root=data.crosslink_data_root, ) ``` @@ -1641,8 +1641,9 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations shard_attestations = [a for a in pending_attestations if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] - candidate_crosslinks = [c for c in shard_crosslinks if - hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c)) + candidate_crosslinks = [ + c for c in shard_crosslinks + if hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c)) ] if len(candidate_crosslinks) == 0: return Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH), [] @@ -1718,7 +1719,7 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: - state.previous_crosslinks = state.current_crosslinks + state.previous_crosslinks = [c for c 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)): diff --git a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py new file mode 100644 index 0000000000..fe694724a6 --- /dev/null +++ b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py @@ -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