From 77d7aa76309a621e88b39ffa6922dbceff4e5952 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Apr 2019 11:02:39 -0500 Subject: [PATCH 01/20] Attestation committee refactor * Remove `get_crosslink_committees_at_slot` (that function's ugly man...) * Make the "base" that everything works off instead be `get_crosslink_committee` * Attestations store epoch, start shard and shard, no longer slot (slot can be calculated from the other three) * Retaining start shard in attestations allows `get_attesting_indices` to peek much further back into the past, making it useful for slashings (Phase 1) * Some two-layer-deep nested loops become one-layer-deep loops --- specs/core/0_beacon-chain.md | 135 +++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 55791e25f..2f9b3189f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -65,7 +65,8 @@ - [`get_epoch_committee_count`](#get_epoch_committee_count) - [`get_shard_delta`](#get_shard_delta) - [`compute_committee`](#compute_committee) - - [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot) + - [`get_epoch_start_shard`](#get_epoch_start_shard) + - [`committee_shard_to_slot`](#committee_shard_to_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) - [`get_state_root`](#get_state_root) @@ -74,6 +75,7 @@ - [`generate_seed`](#generate_seed) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) + - [`get_crosslink_committee`](#get_crosslink_committee) - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) @@ -307,7 +309,7 @@ The types are defined topologically to aid in facilitating an executable version ```python { # LMD GHOST vote - 'slot': 'uint64', + 'epoch': 'uint64', 'beacon_block_root': 'bytes32', # FFG vote @@ -316,6 +318,7 @@ The types are defined topologically to aid in facilitating an executable version 'target_root': 'bytes32', # Crosslink vote + 'epoch_start_shard': 'uint64', 'shard': 'uint64', 'previous_crosslink_root': 'bytes32', 'crosslink_data_root': 'bytes32', @@ -805,44 +808,30 @@ def compute_committee(validator_indices: List[ValidatorIndex], Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. -### `get_crosslink_committees_at_slot` +### `get_epoch_start_shard` ```python -def get_crosslink_committees_at_slot(state: BeaconState, - slot: Slot) -> List[Tuple[List[ValidatorIndex], Shard]]: - """ - Return the list of ``(committee, shard)`` tuples for the ``slot``. - """ - epoch = slot_to_epoch(slot) - current_epoch = get_current_epoch(state) - previous_epoch = get_previous_epoch(state) - next_epoch = current_epoch + 1 +def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard: + if epoch == get_current_epoch(state): + return state.latest_start_shard + elif epoch == get_previous_epoch(state): + previous_shard_delta = get_shard_delta(state, epoch) + return (state.latest_start_shard - previous_shard_delta) % SHARD_COUNT + elif epoch == get_current_epoch(state) + 1: + current_shard_delta = get_shard_delta(state, get_current_epoch(state)) + return (state.latest_start_shard + current_shard_delta) % SHARD_COUNT + else: + raise Exception("Not supported") +``` - assert previous_epoch <= epoch <= next_epoch - indices = get_active_validator_indices(state, epoch) +### `committee_shard_to_slot` - if epoch == current_epoch: - start_shard = state.latest_start_shard - elif epoch == previous_epoch: - previous_shard_delta = get_shard_delta(state, previous_epoch) - start_shard = (state.latest_start_shard - previous_shard_delta) % SHARD_COUNT - elif epoch == next_epoch: - current_shard_delta = get_shard_delta(state, current_epoch) - start_shard = (state.latest_start_shard + current_shard_delta) % SHARD_COUNT - - committees_per_epoch = get_epoch_committee_count(state, epoch) - committees_per_slot = committees_per_epoch // SLOTS_PER_EPOCH - offset = slot % SLOTS_PER_EPOCH - slot_start_shard = (start_shard + committees_per_slot * offset) % SHARD_COUNT - seed = generate_seed(state, epoch) - - return [ - ( - compute_committee(indices, seed, committees_per_slot * offset + i, committees_per_epoch), - (slot_start_shard + i) % SHARD_COUNT, - ) - for i in range(committees_per_slot) - ] +```python +def committee_shard_to_slot(state: BeaconState, epoch: Epoch, shard: Shard) -> Slot: + start_shard = get_epoch_start_shard(state, epoch) + committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH + offset = (shard - get_epoch_start_slot(epoch)) % SHARD_COUNT + return get_epoch_start_slot(epoch) + offset // committees_per_slot ``` ### `get_block_root_at_slot` @@ -927,7 +916,9 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: Return the beacon proposer index at ``state.slot``. """ current_epoch = get_current_epoch(state) - first_committee, _ = get_crosslink_committees_at_slot(state, state.slot)[0] + committees_per_slot = get_epoch_committee_count(state, current_epoch) // SLOTS_PER_EPOCH + offset = committees_per_slot * (state.slot % EPOCH_LENGTH) + first_committee = get_crosslink_committee(state, epoch, offset) MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: @@ -956,6 +947,18 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index: return value == root ``` +### `get_crosslink_committee` + +```python +def get_crosslink_committee(state: BeaconState, epoch: Epoch, offset: int): + return compute_committee( + validator_indices=get_active_validator_indices(state, epoch), + seed=generate_seed(state, epoch), + index=offset, + total_committees=get_epoch_committee_count(state, epoch) + ) +``` + ### `get_attesting_indices` ```python @@ -965,10 +968,10 @@ def get_attesting_indices(state: BeaconState, """ Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ - 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]) + offset = (attestation_data.shard - attestation_data.epoch_start_shard) % SHARD_COUNT + committee = get_crosslink_committee(state, attestation_data.epoch, offset) + assert verify_bitfield(bitfield, len(committee)) + return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1]) ``` ### `int_to_bytes1`, `int_to_bytes2`, ... @@ -1088,7 +1091,7 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)), ], signature=indexed_attestation.signature, - domain=get_domain(state, DOMAIN_ATTESTATION, slot_to_epoch(indexed_attestation.data.slot)), + domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.epoch), ) ``` @@ -1331,7 +1334,7 @@ def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[P def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ a for a in get_matching_source_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + if a.data.beacon_block_root == get_block_root_at_slot(state, committee_shard_to_slot(state, epoch, a.data.shard)) ] ``` @@ -1351,7 +1354,7 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat ```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), + epoch=min(data.epoch, state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS), previous_crosslink_root=data.previous_crosslink_root, crosslink_data_root=data.crosslink_data_root, ) @@ -1444,8 +1447,10 @@ def process_crosslinks(state: BeaconState) -> None: 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)): - epoch = slot_to_epoch(slot) - for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): + for epoch in (get_previous_epoch(state), get_current_epoch(state)): + for offset in range(get_epoch_committee_count(state, epoch)): + shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT + crosslink_committee = get_crosslink_committee(state, epoch, offset) winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink @@ -1492,7 +1497,8 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in get_unslashed_attesting_indices(state, matching_source_attestations): earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot + attestation_slot = committee_shard_to_slot(state, earliest_attestation.data.epoch, earliest_attestation.data.shard) + inclusion_delay = earliest_attestation.inclusion_slot - attestation_slot rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty @@ -1511,18 +1517,19 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: 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))] - for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): - epoch = slot_to_epoch(slot) - for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) - attesting_balance = get_total_balance(state, attesting_indices) - committee_balance = get_total_balance(state, crosslink_committee) - for index in crosslink_committee: - base_reward = get_base_reward(state, index) - if index in attesting_indices: - rewards[index] += base_reward * attesting_balance // committee_balance - else: - penalties[index] += base_reward + epoch = get_previous_epoch(state) + for offset in range(get_epoch_committee_count(state, epoch)): + shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT + crosslink_committee = get_crosslink_committee(state, epoch, offset) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) + attesting_balance = get_total_balance(state, attesting_indices) + committee_balance = get_total_balance(state, crosslink_committee) + for index in crosslink_committee: + base_reward = get_base_reward(state, index) + if index in attesting_indices: + rewards[index] += base_reward * attesting_balance // committee_balance + else: + penalties[index] += base_reward return [rewards, penalties] ``` @@ -1770,15 +1777,19 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: Note that this function mutates ``state``. """ data = attestation.data + attestation_slot = committee_shard_to_slot(state, data.epoch, attestation.shard) min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT - assert min_slot <= data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY + assert min_slot <= attestation_slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY # Check target epoch, source epoch, source root, and source crosslink - target_epoch = slot_to_epoch(data.slot) - assert (target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in { + assert (data.epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in { (get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, hash_tree_root(state.current_crosslinks[data.shard])), (get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, hash_tree_root(state.previous_crosslinks[data.shard])), } + + # Check shard and epoch start shard + assert data.epoch_start_shard == get_epoch_start_shard(state, data.epoch) + assert (data.shard - data.epoch_start_shard) % SHARD_COUNT < get_epoch_committee_count(state, data.epoch) # Check crosslink data root assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1] From c13c4c5c7b970259acbe64dd9d8d85f335af86f9 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 30 Apr 2019 02:09:52 -0500 Subject: [PATCH 02/20] Calculate historical start shards from state --- specs/core/0_beacon-chain.md | 54 +++++++++++++++--------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2f9b3189f..c7eabe80d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -63,8 +63,8 @@ - [`get_permuted_index`](#get_permuted_index) - [`get_split_offset`](#get_split_offset) - [`get_epoch_committee_count`](#get_epoch_committee_count) - - [`get_shard_delta`](#get_shard_delta) - [`compute_committee`](#compute_committee) + - [`get_shard_delta`](#get_shard_delta) - [`get_epoch_start_shard`](#get_epoch_start_shard) - [`committee_shard_to_slot`](#committee_shard_to_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) @@ -318,7 +318,6 @@ The types are defined topologically to aid in facilitating an executable version 'target_root': 'bytes32', # Crosslink vote - 'epoch_start_shard': 'uint64', 'shard': 'uint64', 'previous_crosslink_root': 'bytes32', 'crosslink_data_root': 'bytes32', @@ -777,16 +776,6 @@ def get_epoch_committee_count(state: BeaconState, epoch: Epoch) -> int: ) * SLOTS_PER_EPOCH ``` -### `get_shard_delta` - -```python -def get_shard_delta(state: BeaconState, epoch: Epoch) -> int: - """ - Return the number of shards to increment ``state.latest_start_shard`` during ``epoch``. - """ - return min(get_epoch_committee_count(state, epoch), SHARD_COUNT - SHARD_COUNT // SLOTS_PER_EPOCH) -``` - ### `compute_committee` ```python @@ -798,6 +787,7 @@ def compute_committee(validator_indices: List[ValidatorIndex], Return the ``index``'th shuffled committee out of a total ``total_committees`` using ``validator_indices`` and ``seed``. """ + assert index < total_committees start_offset = get_split_offset(len(validator_indices), total_committees, index) end_offset = get_split_offset(len(validator_indices), total_committees, index + 1) return [ @@ -808,20 +798,27 @@ def compute_committee(validator_indices: List[ValidatorIndex], Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. +### `get_shard_delta` + +```python +def get_shard_delta(state: BeaconState, epoch: Epoch) -> int: + """ + Return the number of shards to increment ``state.latest_start_shard`` during ``epoch``. + """ + return min(get_epoch_committee_count(state, epoch), SHARD_COUNT - SHARD_COUNT // SLOTS_PER_EPOCH) +``` + ### `get_epoch_start_shard` ```python def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard: - if epoch == get_current_epoch(state): - return state.latest_start_shard - elif epoch == get_previous_epoch(state): - previous_shard_delta = get_shard_delta(state, epoch) - return (state.latest_start_shard - previous_shard_delta) % SHARD_COUNT - elif epoch == get_current_epoch(state) + 1: - current_shard_delta = get_shard_delta(state, get_current_epoch(state)) - return (state.latest_start_shard + current_shard_delta) % SHARD_COUNT - else: - raise Exception("Not supported") + assert epoch <= get_current_epoch(state) + 1 + check_epoch = get_current_epoch(state) + 1 + shard = (state.latest_start_shard + get_shard_delta(state, get_current_epoch(state))) % SHARD_COUNT + while check_epoch > epoch: + check_epoch -= 1 + shard = (shard + SHARD_COUNT - get_shard_delta(state, check_epoch)) % SHARD_COUNT + return shard ``` ### `committee_shard_to_slot` @@ -950,11 +947,11 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index: ### `get_crosslink_committee` ```python -def get_crosslink_committee(state: BeaconState, epoch: Epoch, offset: int): +def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard): return compute_committee( validator_indices=get_active_validator_indices(state, epoch), seed=generate_seed(state, epoch), - index=offset, + index=(shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT, total_committees=get_epoch_committee_count(state, epoch) ) ``` @@ -968,8 +965,7 @@ def get_attesting_indices(state: BeaconState, """ Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ - offset = (attestation_data.shard - attestation_data.epoch_start_shard) % SHARD_COUNT - committee = get_crosslink_committee(state, attestation_data.epoch, offset) + committee = get_crosslink_committee(state, attestation_data.epoch, attestation_data.shard) assert verify_bitfield(bitfield, len(committee)) return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1]) ``` @@ -1450,7 +1446,7 @@ def process_crosslinks(state: BeaconState) -> None: for epoch in (get_previous_epoch(state), get_current_epoch(state)): for offset in range(get_epoch_committee_count(state, epoch)): shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT - crosslink_committee = get_crosslink_committee(state, epoch, offset) + crosslink_committee = get_crosslink_committee(state, epoch, shard) winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink @@ -1787,10 +1783,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: (get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, hash_tree_root(state.previous_crosslinks[data.shard])), } - # Check shard and epoch start shard - assert data.epoch_start_shard == get_epoch_start_shard(state, data.epoch) - assert (data.shard - data.epoch_start_shard) % SHARD_COUNT < get_epoch_committee_count(state, data.epoch) - # Check crosslink data root assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1] From 59d7be60db1c16ba81ef40eb3ce4cde80beb03b3 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Apr 2019 15:19:11 +0800 Subject: [PATCH 03/20] Fix `is_double_vote` and `is_surround_vote` --- specs/core/0_beacon-chain.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c7eabe80d..dac67c17d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1099,9 +1099,7 @@ def is_double_vote(attestation_data_1: AttestationData, """ Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. """ - target_epoch_1 = slot_to_epoch(attestation_data_1.slot) - target_epoch_2 = slot_to_epoch(attestation_data_2.slot) - return target_epoch_1 == target_epoch_2 + return attestation_data_1.epoch == attestation_data_2.epoch ``` ### `is_surround_vote` @@ -1114,10 +1112,7 @@ def is_surround_vote(attestation_data_1: AttestationData, """ source_epoch_1 = attestation_data_1.source_epoch source_epoch_2 = attestation_data_2.source_epoch - target_epoch_1 = slot_to_epoch(attestation_data_1.slot) - target_epoch_2 = slot_to_epoch(attestation_data_2.slot) - - return source_epoch_1 < source_epoch_2 and target_epoch_2 < target_epoch_1 + return source_epoch_1 < source_epoch_2 and attestation_data_2.epoch < attestation_data_1.epoch ``` ### `integer_squareroot` From bcd7a83af403864c9f4c69c47c63427b3b3c67af Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 10:39:18 +0100 Subject: [PATCH 04/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 65 +++++++++++++----------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index dac67c17d..66386fd4e 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -85,8 +85,6 @@ - [`verify_bitfield`](#verify_bitfield) - [`convert_to_indexed`](#convert_to_indexed) - [`verify_indexed_attestation`](#verify_indexed_attestation) - - [`is_double_vote`](#is_double_vote) - - [`is_surround_vote`](#is_surround_vote) - [`integer_squareroot`](#integer_squareroot) - [`get_delayed_activation_exit_epoch`](#get_delayed_activation_exit_epoch) - [`get_churn_limit`](#get_churn_limit) @@ -309,12 +307,12 @@ The types are defined topologically to aid in facilitating an executable version ```python { # LMD GHOST vote - 'epoch': 'uint64', 'beacon_block_root': 'bytes32', # FFG vote 'source_epoch': 'uint64', 'source_root': 'bytes32', + 'target_epoch': 'uint64', 'target_root': 'bytes32', # Crosslink vote @@ -965,7 +963,7 @@ def get_attesting_indices(state: BeaconState, """ Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ - committee = get_crosslink_committee(state, attestation_data.epoch, attestation_data.shard) + committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.shard) assert verify_bitfield(bitfield, len(committee)) return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1]) ``` @@ -1087,34 +1085,10 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)), ], signature=indexed_attestation.signature, - domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.epoch), + domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.target_epoch), ) ``` -### `is_double_vote` - -```python -def is_double_vote(attestation_data_1: AttestationData, - attestation_data_2: AttestationData) -> bool: - """ - Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. - """ - return attestation_data_1.epoch == attestation_data_2.epoch -``` - -### `is_surround_vote` - -```python -def is_surround_vote(attestation_data_1: AttestationData, - attestation_data_2: AttestationData) -> bool: - """ - Check if ``attestation_data_1`` surrounds ``attestation_data_2``. - """ - source_epoch_1 = attestation_data_1.source_epoch - source_epoch_2 = attestation_data_2.source_epoch - return source_epoch_1 < source_epoch_2 and attestation_data_2.epoch < attestation_data_1.epoch -``` - ### `integer_squareroot` ```python @@ -1345,7 +1319,7 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat ```python def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink: return Crosslink( - epoch=min(data.epoch, state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS), + epoch=min(data.target_epoch, state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS), previous_crosslink_root=data.previous_crosslink_root, crosslink_data_root=data.crosslink_data_root, ) @@ -1488,7 +1462,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in get_unslashed_attesting_indices(state, matching_source_attestations): earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - attestation_slot = committee_shard_to_slot(state, earliest_attestation.data.epoch, earliest_attestation.data.shard) + attestation_slot = committee_shard_to_slot(state, earliest_attestation.data.target_epoch, earliest_attestation.data.shard) inclusion_delay = earliest_attestation.inclusion_slot - attestation_slot rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay @@ -1730,19 +1704,26 @@ def process_attester_slashing(state: BeaconState, Process ``AttesterSlashing`` operation. Note that this function mutates ``state``. """ - attestation1 = attester_slashing.attestation_1 - attestation2 = attester_slashing.attestation_2 + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 # Check that the attestations are conflicting - assert attestation1.data != attestation2.data + assert attestation_1.data != attestation_2.data + + source_1 = attestation_1.data.source_epoch + target_1 = attestation_1.data.target_epoch + source_2 = attestation_2.data.source_epoch + target_2 = attestation_2.data.target_epoch assert ( - is_double_vote(attestation1.data, attestation2.data) or - is_surround_vote(attestation1.data, attestation2.data) + # Double vote + (target_1 == target_2) or + # Surround vote (attestation 1 surrounds attestation 2) + (source_1 < source_2 and target_2 < target_1) ) - assert verify_indexed_attestation(state, attestation1) - assert verify_indexed_attestation(state, attestation2) - attesting_indices_1 = attestation1.custody_bit_0_indices + attestation1.custody_bit_1_indices - attesting_indices_2 = attestation2.custody_bit_0_indices + attestation2.custody_bit_1_indices + assert verify_indexed_attestation(state, attestation_1) + assert verify_indexed_attestation(state, attestation_2) + attesting_indices_1 = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices + attesting_indices_2 = attestation_2.custody_bit_0_indices + attestation_2.custody_bit_1_indices slashable_indices = [ index for index in attesting_indices_1 if ( @@ -1768,12 +1749,12 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: Note that this function mutates ``state``. """ data = attestation.data - attestation_slot = committee_shard_to_slot(state, data.epoch, attestation.shard) + attestation_slot = committee_shard_to_slot(state, data.target_epoch, attestation.shard) min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT assert min_slot <= attestation_slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY # Check target epoch, source epoch, source root, and source crosslink - assert (data.epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in { + assert (data.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 92140d199e0699922d64efc9d784014b1f95746b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 10:41:09 +0100 Subject: [PATCH 05/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 66386fd4e..87906ce67 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1758,7 +1758,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: (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 assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1] From b19e7dbf0d208bcb5d679d4f1f8f2c5a242afee1 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 10:44:29 +0100 Subject: [PATCH 06/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 87906ce67..e4720f78d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1409,13 +1409,10 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: 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)): for epoch in (get_previous_epoch(state), get_current_epoch(state)): for offset in range(get_epoch_committee_count(state, epoch)): shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT - crosslink_committee = get_crosslink_committee(state, epoch, shard) + crosslink_committee = get_crosslink_committee(state, epoch, shard) winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink From 09ed9aea9802d9445a355243c6a6b15064be16d6 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 10:55:09 +0100 Subject: [PATCH 07/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e4720f78d..6a4edb8a3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -66,7 +66,7 @@ - [`compute_committee`](#compute_committee) - [`get_shard_delta`](#get_shard_delta) - [`get_epoch_start_shard`](#get_epoch_start_shard) - - [`committee_shard_to_slot`](#committee_shard_to_slot) + - [`get_attestation_slot`](#get_attestation_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) - [`get_state_root`](#get_state_root) @@ -819,13 +819,13 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard: return shard ``` -### `committee_shard_to_slot` +### `get_attestation_slot` ```python -def committee_shard_to_slot(state: BeaconState, epoch: Epoch, shard: Shard) -> Slot: - start_shard = get_epoch_start_shard(state, epoch) +def get_attestation_slot(state: BeaconState, attestation: Attestation) -> Slot: + epoch = attestation.data.target_epoch committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH - offset = (shard - get_epoch_start_slot(epoch)) % SHARD_COUNT + offset = (attestation.data.shard + SHARD_COUNT - get_epoch_start_slot(epoch)) % SHARD_COUNT return get_epoch_start_slot(epoch) + offset // committees_per_slot ``` @@ -1299,7 +1299,7 @@ def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[P def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ a for a in get_matching_source_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root_at_slot(state, committee_shard_to_slot(state, epoch, a.data.shard)) + if a.data.beacon_block_root == get_block_root_at_slot(state, get_attestation_slot(state, a)) ] ``` @@ -1459,7 +1459,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in get_unslashed_attesting_indices(state, matching_source_attestations): earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - attestation_slot = committee_shard_to_slot(state, earliest_attestation.data.target_epoch, earliest_attestation.data.shard) + attestation_slot = get_attestation_slot(state, earliest_attestation) inclusion_delay = earliest_attestation.inclusion_slot - attestation_slot rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay @@ -1745,12 +1745,12 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: Process ``Attestation`` operation. Note that this function mutates ``state``. """ - data = attestation.data - attestation_slot = committee_shard_to_slot(state, data.target_epoch, attestation.shard) + attestation_slot = get_attestation_slot(state, attestation) min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT assert min_slot <= attestation_slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY # Check target epoch, source epoch, source root, and source crosslink + data = attestation.data assert (data.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 66403ad8532bae73bce9ce17d1eaeffb77cddf6c Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 11:00:23 +0100 Subject: [PATCH 08/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6a4edb8a3..e85eafc23 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1459,8 +1459,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for index in get_unslashed_attesting_indices(state, matching_source_attestations): earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - attestation_slot = get_attestation_slot(state, earliest_attestation) - inclusion_delay = earliest_attestation.inclusion_slot - attestation_slot + inclusion_delay = earliest_attestation.inclusion_slot - get_attestation_slot(state, earliest_attestation) rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty @@ -1745,9 +1744,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: Process ``Attestation`` operation. Note that this function mutates ``state``. """ - attestation_slot = get_attestation_slot(state, attestation) min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT - assert min_slot <= attestation_slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY + assert min_slot <= get_attestation_slot(state, attestation) <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY # Check target epoch, source epoch, source root, and source crosslink data = attestation.data From 73603f4ed64d65a2ae69d7fb65a4b80e41e6db74 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 11:34:57 +0100 Subject: [PATCH 09/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 108 +++++++++++++---------------------- 1 file changed, 39 insertions(+), 69 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e85eafc23..7f1cd9a37 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -60,10 +60,7 @@ - [`get_active_validator_indices`](#get_active_validator_indices) - [`increase_balance`](#increase_balance) - [`decrease_balance`](#decrease_balance) - - [`get_permuted_index`](#get_permuted_index) - - [`get_split_offset`](#get_split_offset) - [`get_epoch_committee_count`](#get_epoch_committee_count) - - [`compute_committee`](#compute_committee) - [`get_shard_delta`](#get_shard_delta) - [`get_epoch_start_shard`](#get_epoch_start_shard) - [`get_attestation_slot`](#get_attestation_slot) @@ -75,6 +72,7 @@ - [`generate_seed`](#generate_seed) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) + - [`get_shuffled_index`](#get_shuffled_index) - [`get_crosslink_committee`](#get_crosslink_committee) - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) @@ -720,43 +718,6 @@ def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta ``` -### `get_permuted_index` - -```python -def get_permuted_index(index: int, list_size: int, seed: Bytes32) -> int: - """ - Return `p(index)` in a pseudorandom permutation `p` of `0...list_size - 1` with ``seed`` as entropy. - - Utilizes 'swap or not' shuffling found in - https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf - See the 'generalized domain' algorithm on page 3. - """ - 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 - position = max(index, flip) - source = hash(seed + int_to_bytes1(round) + int_to_bytes4(position // 256)) - byte = source[(position % 256) // 8] - bit = (byte >> (position % 8)) % 2 - index = flip if bit else index - - return index -``` - -### `get_split_offset` - -```python -def get_split_offset(list_size: int, chunks: int, index: int) -> int: - """ - Returns a value such that for a list L, chunk count k and index i, - split(L, k)[i] == L[get_split_offset(len(L), k, i): get_split_offset(len(L), k, i+1)] - """ - return (list_size * index) // chunks -``` - ### `get_epoch_committee_count` ```python @@ -774,28 +735,6 @@ def get_epoch_committee_count(state: BeaconState, epoch: Epoch) -> int: ) * SLOTS_PER_EPOCH ``` -### `compute_committee` - -```python -def compute_committee(validator_indices: List[ValidatorIndex], - seed: Bytes32, - index: int, - total_committees: int) -> List[ValidatorIndex]: - """ - Return the ``index``'th shuffled committee out of a total ``total_committees`` - using ``validator_indices`` and ``seed``. - """ - assert index < total_committees - start_offset = get_split_offset(len(validator_indices), total_committees, index) - end_offset = get_split_offset(len(validator_indices), total_committees, index + 1) - return [ - validator_indices[get_permuted_index(i, len(validator_indices), seed)] - for i in range(start_offset, end_offset) - ] -``` - -Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. - ### `get_shard_delta` ```python @@ -942,16 +881,47 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index: return value == root ``` +### `get_shuffled_index` + +```python +def get_shuffled_index(index: ValidatorIndex, index_count: int, seed: Bytes32) -> ValidatorIndex: + """ + Return the shuffled validator index corresponding to ``seed`` (and ``index_count``). + """ + assert index < index_count + assert index_count <= 2**40 + + # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) + # See the 'generalized domain' algorithm on page 3 + for round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_int(hash(seed + int_to_bytes1(round))[0:8]) % index_count + flip = (pivot - index) % index_count + position = max(index, flip) + source = hash(seed + int_to_bytes1(round) + int_to_bytes4(position // 256)) + byte = source[(position % 256) // 8] + bit = (byte >> (position % 8)) % 2 + index = flip if bit else index + + return index +``` + ### `get_crosslink_committee` ```python -def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard): - return compute_committee( - validator_indices=get_active_validator_indices(state, epoch), - seed=generate_seed(state, epoch), - index=(shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT, - total_committees=get_epoch_committee_count(state, epoch) - ) +def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> List[ValidatorIndex]: + """ + Return the crosslink committee at ``epoch`` for ``shard``. + """ + active_validator_indices = get_active_validator_indices(state, epoch) + committee_count = get_epoch_committee_count(state, epoch) + committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT + + start_validator_index = (len(active_indices) * committee_index) // committee_count + end_validator_index = (len(active_indices) * (committee_index + 1)) // committee_count + return [ + active_indices[get_shuffled_index(i, len(active_indices), generate_seed(state, epoch))] + for i in range(start_index, end_index) + ] ``` ### `get_attesting_indices` From adfa014a3002a2480f2588f74998b5c194b7580f Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 11:38:11 +0100 Subject: [PATCH 10/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 7f1cd9a37..dcd834281 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -912,7 +912,7 @@ def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> L """ Return the crosslink committee at ``epoch`` for ``shard``. """ - active_validator_indices = get_active_validator_indices(state, epoch) + active_indices = get_active_validator_indices(state, epoch) committee_count = get_epoch_committee_count(state, epoch) committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT From a23c9f712da890f885ac1eb719f44c80e771993e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 11:44:21 +0100 Subject: [PATCH 11/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index dcd834281..d762ddc37 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -920,7 +920,7 @@ def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> L end_validator_index = (len(active_indices) * (committee_index + 1)) // committee_count return [ active_indices[get_shuffled_index(i, len(active_indices), generate_seed(state, epoch))] - for i in range(start_index, end_index) + for i in range(start_validator_index, end_validator_index) ] ``` From 60888c0c48bffec7dfa5f065d8d2e838d74605ad Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 12:27:45 +0100 Subject: [PATCH 12/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 65 +++++++++++------------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d762ddc37..cfbed0771 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -66,7 +66,6 @@ - [`get_attestation_slot`](#get_attestation_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) - - [`get_state_root`](#get_state_root) - [`get_randao_mix`](#get_randao_mix) - [`get_active_index_root`](#get_active_index_root) - [`generate_seed`](#generate_seed) @@ -402,8 +401,8 @@ The types are defined topologically to aid in facilitating an executable version 'aggregation_bitfield': 'bytes', # Attestation data 'data': AttestationData, - # Inclusion slot - 'inclusion_slot': 'uint64', + # Inclusion delay + 'inclusion_delay': 'uint64', # Proposer index 'proposer_index': 'uint64', } @@ -763,9 +762,9 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard: ```python def get_attestation_slot(state: BeaconState, attestation: Attestation) -> Slot: epoch = attestation.data.target_epoch - committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH - offset = (attestation.data.shard + SHARD_COUNT - get_epoch_start_slot(epoch)) % SHARD_COUNT - return get_epoch_start_slot(epoch) + offset // committees_per_slot + committee_count = get_epoch_committee_count(state, epoch) + offset = (attestation.data.shard - get_epoch_start_slot(epoch)) % SHARD_COUNT + return get_epoch_start_slot(epoch) + offset // (committee_count // SLOTS_PER_EPOCH) ``` ### `get_block_root_at_slot` @@ -791,18 +790,6 @@ def get_block_root(state: BeaconState, return get_block_root_at_slot(state, get_epoch_start_slot(epoch)) ``` -### `get_state_root` - -```python -def get_state_root(state: BeaconState, - slot: Slot) -> Bytes32: - """ - Return the state root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] -``` - ### `get_randao_mix` ```python @@ -847,17 +834,17 @@ def generate_seed(state: BeaconState, ```python def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: """ - Return the beacon proposer index at ``state.slot``. + Return the current beacon proposer index. """ - current_epoch = get_current_epoch(state) - committees_per_slot = get_epoch_committee_count(state, current_epoch) // SLOTS_PER_EPOCH + epoch = get_current_epoch(state) + committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH offset = committees_per_slot * (state.slot % EPOCH_LENGTH) first_committee = get_crosslink_committee(state, epoch, offset) MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: - candidate_index = first_committee[(current_epoch + i) % len(first_committee)] - random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32] + candidate_index = first_committee[(epoch + i) % len(first_committee)] + random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] effective_balance = state.validator_registry[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -1117,7 +1104,6 @@ Note: All functions in this section mutate `state`. def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: """ Initiate the validator of the given ``index``. - Note that this function mutates ``state``. """ # Return if validator already initiated exit validator = state.validator_registry[index] @@ -1142,7 +1128,6 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex=None) -> None: """ Slash the validator with index ``slashed_index``. - Note that this function mutates ``state``. """ current_epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) @@ -1316,13 +1301,6 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink)) ``` -```python -def get_earliest_attestation(state: BeaconState, attestations: List[PendingAttestation], index: ValidatorIndex) -> PendingAttestation: - return min([ - a for a in attestations if index in get_attesting_indices(state, a.data, a.aggregation_bitfield) - ], key=lambda a: a.inclusion_slot) -``` - #### Justification and finalization Run the following function: @@ -1427,10 +1405,11 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: # Proposer and inclusion delay micro-rewards for index in get_unslashed_attesting_indices(state, matching_source_attestations): - earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) - rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - inclusion_delay = earliest_attestation.inclusion_slot - get_attestation_slot(state, earliest_attestation) - rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay + attestation = min([ + a for a in attestations if index in get_attesting_indices(state, a.data, a.aggregation_bitfield) + ], key=lambda a: a.inclusion_delay) + rewards[attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT + rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // attestation.inclusion_delay # Inactivity penalty finality_delay = previous_epoch - state.finalized_epoch @@ -1629,6 +1608,8 @@ def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None: #### Operations +Note: All functions in this section mutate `state`. + ##### Proposer slashings Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`. @@ -1640,7 +1621,6 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: """ Process ``ProposerSlashing`` operation. - Note that this function mutates ``state``. """ proposer = state.validator_registry[proposer_slashing.proposer_index] # Verify that the epoch is the same @@ -1668,7 +1648,6 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: """ Process ``AttesterSlashing`` operation. - Note that this function mutates ``state``. """ attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 @@ -1712,10 +1691,9 @@ For each `attestation` in `block.body.attestations`, run the following function: def process_attestation(state: BeaconState, attestation: Attestation) -> None: """ Process ``Attestation`` operation. - Note that this function mutates ``state``. """ - min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT - assert min_slot <= get_attestation_slot(state, attestation) <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY + attestation_slot = get_attestation_slot(state, attestation) + assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH # Check target epoch, source epoch, source root, and source crosslink data = attestation.data @@ -1734,7 +1712,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: pending_attestation = PendingAttestation( data=data, aggregation_bitfield=attestation.aggregation_bitfield, - inclusion_slot=state.slot, + inclusion_delay=state.slot - attestation_slot, proposer_index=get_beacon_proposer_index(state), ) if target_epoch == get_current_epoch(state): @@ -1753,7 +1731,6 @@ For each `deposit` in `block.body.deposits`, run the following function: def process_deposit(state: BeaconState, deposit: Deposit) -> None: """ Process an Eth1 deposit, registering a validator or increasing its balance. - Note that this function mutates ``state``. """ # Verify the Merkle branch assert verify_merkle_branch( @@ -1803,7 +1780,6 @@ For each `exit` in `block.body.voluntary_exits`, run the following function: def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: """ Process ``VoluntaryExit`` operation. - Note that this function mutates ``state``. """ validator = state.validator_registry[exit.validator_index] # Verify the validator is active @@ -1831,7 +1807,6 @@ For each `transfer` in `block.body.transfers`, run the following function: def process_transfer(state: BeaconState, transfer: Transfer) -> None: """ Process ``Transfer`` operation. - Note that this function mutates ``state``. """ # Verify the amount and fee are not individually too big (for anti-overflow purposes) assert state.balances[transfer.sender] >= max(transfer.amount, transfer.fee) From a40f37b9a27430917673006d9250405ec7819d97 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 30 Apr 2019 12:31:11 +0100 Subject: [PATCH 13/20] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cfbed0771..7c2c2ddcb 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1289,7 +1289,7 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard 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), [] + return Crosslink(), [] 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] From b3373a2d7156bb3ff822fd29c0c4bd3c9f130967 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 30 Apr 2019 12:55:14 -0600 Subject: [PATCH 14/20] fix up some PR feedback and testing for #1009 --- scripts/phase0/build_spec.py | 16 ++--- specs/core/0_beacon-chain.md | 67 ++++++++++++++----- .../test_process_attestation.py | 3 +- .../test_process_attester_slashing.py | 4 +- .../test_process_crosslinks.py | 6 +- test_libs/pyspec/tests/helpers.py | 18 ++--- 6 files changed, 69 insertions(+), 45 deletions(-) diff --git a/scripts/phase0/build_spec.py b/scripts/phase0/build_spec.py index 54adfdde7..1c1563a2c 100644 --- a/scripts/phase0/build_spec.py +++ b/scripts/phase0/build_spec.py @@ -46,23 +46,23 @@ Store = None code_lines.append(""" # Monkey patch validator get committee code -_compute_committee = compute_committee +_get_crosslink_committee = get_crosslink_committee committee_cache = {} -def compute_committee(validator_indices: List[ValidatorIndex], - seed: Bytes32, - index: int, - total_committees: int) -> List[ValidatorIndex]: - - param_hash = (hash_tree_root(validator_indices), seed, index, total_committees) +def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> List[ValidatorIndex]: + active_indices = get_active_validator_indices(state, epoch) + seed = generate_seed(state, epoch) + committee_count = get_epoch_committee_count(state, epoch) + committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT + param_hash = (hash_tree_root(active_indices), seed, committee_count, committee_index) if param_hash in committee_cache: # print("Cache hit, epoch={0}".format(epoch)) return committee_cache[param_hash] else: # print("Cache miss, epoch={0}".format(epoch)) - ret = _compute_committee(validator_indices, seed, index, total_committees) + ret = _get_crosslink_committee(state, epoch, shard) committee_cache[param_hash] = ret return ret diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 7c2c2ddcb..80ffddfc6 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -66,6 +66,7 @@ - [`get_attestation_slot`](#get_attestation_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) + - [`get_state_root`](#get_state_root) - [`get_randao_mix`](#get_randao_mix) - [`get_active_index_root`](#get_active_index_root) - [`generate_seed`](#generate_seed) @@ -82,6 +83,8 @@ - [`verify_bitfield`](#verify_bitfield) - [`convert_to_indexed`](#convert_to_indexed) - [`verify_indexed_attestation`](#verify_indexed_attestation) + - [`is_double_vote`](#is_double_vote) + - [`is_surround_vote`](#is_surround_vote) - [`integer_squareroot`](#integer_squareroot) - [`get_delayed_activation_exit_epoch`](#get_delayed_activation_exit_epoch) - [`get_churn_limit`](#get_churn_limit) @@ -763,7 +766,7 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard: def get_attestation_slot(state: BeaconState, attestation: Attestation) -> Slot: epoch = attestation.data.target_epoch committee_count = get_epoch_committee_count(state, epoch) - offset = (attestation.data.shard - get_epoch_start_slot(epoch)) % SHARD_COUNT + offset = (attestation.data.shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT return get_epoch_start_slot(epoch) + offset // (committee_count // SLOTS_PER_EPOCH) ``` @@ -790,6 +793,18 @@ def get_block_root(state: BeaconState, return get_block_root_at_slot(state, get_epoch_start_slot(epoch)) ``` +### `get_state_root` + +```python +def get_state_root(state: BeaconState, + slot: Slot) -> Bytes32: + """ + Return the state root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] +``` + ### `get_randao_mix` ```python @@ -838,8 +853,9 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: """ epoch = get_current_epoch(state) committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH - offset = committees_per_slot * (state.slot % EPOCH_LENGTH) - first_committee = get_crosslink_committee(state, epoch, offset) + offset = committees_per_slot * (state.slot % SLOTS_PER_EPOCH) + shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT + first_committee = get_crosslink_committee(state, epoch, shard) MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: @@ -905,8 +921,9 @@ def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> L start_validator_index = (len(active_indices) * committee_index) // committee_count end_validator_index = (len(active_indices) * (committee_index + 1)) // committee_count + seed = generate_seed(state, epoch) return [ - active_indices[get_shuffled_index(i, len(active_indices), generate_seed(state, epoch))] + active_indices[get_shuffled_index(i, len(active_indices), seed)] for i in range(start_validator_index, end_validator_index) ] ``` @@ -1046,6 +1063,29 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA ) ``` +### `is_double_vote` + +```python +def is_double_vote(attestation_data_1: AttestationData, attestation_data_2: AttestationData) -> bool: + """ + Check if ``attestation_data_1`` and ``attestation_data_2`` violate Casper "double" slashing rule. + """ + return attestation_data_1.target_epoch == attestation_data_2.target_epoch +``` + +### `is_surround_vote` + +```python +def is_surround_vote(attestation_data_1: AttestationData, attestation_data_2: AttestationData) -> bool: + """ + Check if ``attestation_data_1`` and ``attestation_data_2`` violate Casper "surround" slashing rule. + """ + return ( + attestation_data_1.source_epoch < attestation_data_2.source_epoch and + attestation_data_2.target_epoch < attestation_data_1.target_epoch + ) +``` + ### `integer_squareroot` ```python @@ -1359,7 +1399,7 @@ def process_crosslinks(state: BeaconState) -> None: state.previous_crosslinks = [c for c in state.current_crosslinks] for epoch in (get_previous_epoch(state), get_current_epoch(state)): for offset in range(get_epoch_committee_count(state, epoch)): - shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT + shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT crosslink_committee = get_crosslink_committee(state, epoch, shard) winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): @@ -1429,8 +1469,8 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: penalties = [0 for index in range(len(state.validator_registry))] epoch = get_previous_epoch(state) for offset in range(get_epoch_committee_count(state, epoch)): - shard = (get_epoch_start_shard(epoch) + offset) % SHARD_COUNT - crosslink_committee = get_crosslink_committee(state, epoch, offset) + shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT + crosslink_committee = get_crosslink_committee(state, epoch, shard) winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) @@ -1653,16 +1693,9 @@ def process_attester_slashing(state: BeaconState, attestation_2 = attester_slashing.attestation_2 # Check that the attestations are conflicting assert attestation_1.data != attestation_2.data - - source_1 = attestation_1.data.source_epoch - target_1 = attestation_1.data.target_epoch - source_2 = attestation_2.data.source_epoch - target_2 = attestation_2.data.target_epoch assert ( - # Double vote - (target_1 == target_2) or - # Surround vote (attestation 1 surrounds attestation 2) - (source_1 < source_2 and target_2 < target_1) + is_double_vote(attestation_1.data, attestation_2.data) or + is_surround_vote(attestation_1.data, attestation_2.data) ) assert verify_indexed_attestation(state, attestation_1) @@ -1715,7 +1748,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: inclusion_delay=state.slot - attestation_slot, proposer_index=get_beacon_proposer_index(state), ) - if target_epoch == get_current_epoch(state): + if data.target_epoch == get_current_epoch(state): state.current_epoch_attestations.append(pending_attestation) else: state.previous_epoch_attestations.append(pending_attestation) diff --git a/test_libs/pyspec/tests/block_processing/test_process_attestation.py b/test_libs/pyspec/tests/block_processing/test_process_attestation.py index 1be60c860..bcf71376c 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attestation.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attestation.py @@ -38,8 +38,7 @@ def run_attestation_processing(state, attestation, valid=True): process_attestation(post_state, attestation) current_epoch = get_current_epoch(state) - target_epoch = slot_to_epoch(attestation.data.slot) - if target_epoch == current_epoch: + if attestation.data.target_epoch == current_epoch: assert len(post_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 else: assert len(post_state.previous_epoch_attestations) == len(state.previous_epoch_attestations) + 1 diff --git a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py index bcaf6fb7a..2ea16f13d 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py @@ -65,7 +65,7 @@ def test_success_surround(state): # set attestion1 to surround attestation 2 attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1 - attester_slashing.attestation_1.data.slot = attester_slashing.attestation_2.data.slot + spec.SLOTS_PER_EPOCH + attester_slashing.attestation_1.data.target_epoch = attester_slashing.attestation_2.data.target_epoch + 1 pre_state, post_state = run_attester_slashing_processing(state, attester_slashing) @@ -85,7 +85,7 @@ def test_same_data(state): def test_no_double_or_surround(state): attester_slashing = get_valid_attester_slashing(state) - attester_slashing.attestation_1.data.slot += spec.SLOTS_PER_EPOCH + attester_slashing.attestation_1.data.target_epoch += 1 pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False) diff --git a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py index fe694724a..d6765e3a7 100644 --- a/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py +++ b/test_libs/pyspec/tests/epoch_processing/test_process_crosslinks.py @@ -15,7 +15,7 @@ from tests.helpers import ( add_attestation_to_state, build_empty_block_for_next_slot, fill_aggregate_attestation, - get_crosslink_committee_for_attestation, + get_crosslink_committee, get_valid_attestation, next_epoch, next_slot, @@ -88,7 +88,7 @@ def test_single_crosslink_update_from_previous_epoch(state): 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): + for index in get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.shard): assert crosslink_deltas[0][index] > 0 assert crosslink_deltas[1][index] == 0 @@ -129,7 +129,7 @@ def test_double_late_crosslink(state): # 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): + for index in get_crosslink_committee(state, attestation_2.data.target_epoch, attestation_2.data.shard): assert crosslink_deltas[0][index] == 0 assert crosslink_deltas[1][index] > 0 diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 63e4cd710..5f694cc84 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -29,7 +29,7 @@ from eth2spec.phase0.spec import ( get_attesting_indices, get_block_root, get_block_root_at_slot, - get_crosslink_committees_at_slot, + get_crosslink_committee, get_current_epoch, get_domain, get_epoch_start_slot, @@ -174,11 +174,11 @@ def build_attestation_data(state, slot, shard): crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks return AttestationData( - slot=slot, shard=shard, beacon_block_root=block_root, source_epoch=justified_epoch, source_root=justified_block_root, + target_epoch=slot_to_epoch(slot), target_root=epoch_boundary_root, crosslink_data_root=spec.ZERO_HASH, previous_crosslink_root=hash_tree_root(crosslinks[shard]), @@ -276,14 +276,6 @@ def get_valid_attester_slashing(state): ) -def get_crosslink_committee_for_attestation(state, attestation_data): - """ - Return the crosslink committee corresponding to ``attestation_data``. - """ - crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot) - return [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0] - - def get_valid_attestation(state, slot=None): if slot is None: slot = state.slot @@ -296,7 +288,7 @@ def get_valid_attestation(state, slot=None): attestation_data = build_attestation_data(state, slot, shard) - crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data) + crosslink_committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.shard) committee_size = len(crosslink_committee) bitfield_length = (committee_size + 7) // 8 @@ -383,13 +375,13 @@ def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0) domain=get_domain( state=state, domain_type=spec.DOMAIN_ATTESTATION, - message_epoch=slot_to_epoch(attestation_data.slot), + message_epoch=attestation_data.target_epoch, ) ) def fill_aggregate_attestation(state, attestation): - crosslink_committee = get_crosslink_committee_for_attestation(state, attestation.data) + crosslink_committee = get_crosslink_committee(state, attestation.data.target_epoch, attestation.data.shard) for i in range(len(crosslink_committee)): attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i) From 2e5ab130c1dbafc17474c6211f184a57a7c30846 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 1 May 2019 07:42:49 +0100 Subject: [PATCH 15/20] Simplify presentation --- specs/core/0_beacon-chain.md | 48 ++++++--------------- specs/validator/0_beacon-chain-validator.md | 2 +- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 80ffddfc6..a4b057964 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -83,8 +83,7 @@ - [`verify_bitfield`](#verify_bitfield) - [`convert_to_indexed`](#convert_to_indexed) - [`verify_indexed_attestation`](#verify_indexed_attestation) - - [`is_double_vote`](#is_double_vote) - - [`is_surround_vote`](#is_surround_vote) + - [`is_slashable_attestation_data`](#is_slashable_attestation_data) - [`integer_squareroot`](#integer_squareroot) - [`get_delayed_activation_exit_epoch`](#get_delayed_activation_exit_epoch) - [`get_churn_limit`](#get_churn_limit) @@ -1063,26 +1062,16 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA ) ``` -### `is_double_vote` +### `is_slashable_attestation_data` ```python -def is_double_vote(attestation_data_1: AttestationData, attestation_data_2: AttestationData) -> bool: +def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: """ - Check if ``attestation_data_1`` and ``attestation_data_2`` violate Casper "double" slashing rule. - """ - return attestation_data_1.target_epoch == attestation_data_2.target_epoch -``` - -### `is_surround_vote` - -```python -def is_surround_vote(attestation_data_1: AttestationData, attestation_data_2: AttestationData) -> bool: - """ - Check if ``attestation_data_1`` and ``attestation_data_2`` violate Casper "surround" slashing rule. + Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. """ return ( - attestation_data_1.source_epoch < attestation_data_2.source_epoch and - attestation_data_2.target_epoch < attestation_data_1.target_epoch + (data_1 != data_2 and data_1.target == data_2.target) or # Double vote + (data_1.source < data_2.source and data_2.target < data_1.target) # Surround vote ) ``` @@ -1691,27 +1680,18 @@ def process_attester_slashing(state: BeaconState, """ attestation_1 = attester_slashing.attestation_1 attestation_2 = attester_slashing.attestation_2 - # Check that the attestations are conflicting - assert attestation_1.data != attestation_2.data - assert ( - is_double_vote(attestation_1.data, attestation_2.data) or - is_surround_vote(attestation_1.data, attestation_2.data) - ) - + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) assert verify_indexed_attestation(state, attestation_1) assert verify_indexed_attestation(state, attestation_2) + + slashed_any = False attesting_indices_1 = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices attesting_indices_2 = attestation_2.custody_bit_0_indices + attestation_2.custody_bit_1_indices - slashable_indices = [ - index for index in attesting_indices_1 - if ( - index in attesting_indices_2 and - is_slashable_validator(state.validator_registry[index], get_current_epoch(state)) - ) - ] - assert len(slashable_indices) >= 1 - for index in slashable_indices: - slash_validator(state, index) + for index in set(attesting_indices_1).intersection(attesting_indices_2): + if is_slashable_validator(state.validator_registry[index], get_current_epoch(state)): + slash_validator(state, index) + slashed_any = True + assert slashed_any ``` ##### Attestations diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index dab02b773..3da1615a4 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -410,7 +410,7 @@ If the software crashes at some point within this routine, then when the validat ### Attester slashing -To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](../core/0_beacon-chain.md#attestationdata) objects where conflicting is defined as a set of two attestations that satisfy either [`is_double_vote`](../core/0_beacon-chain.md#is_double_vote) or [`is_surround_vote`](../core/0_beacon-chain.md#is_surround_vote). +To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](../core/0_beacon-chain.md#attestationdata) objects, i.e. two attestations that satisfy [`is_slashable_attestation_data`](../core/0_beacon-chain.md#is_slashable_attestation_data). Specifically, when signing an `Attestation`, a validator should perform the following steps in the following order: 1. Save a record to hard disk that an attestation has been signed for source -- `attestation_data.source_epoch` -- and target -- `slot_to_epoch(attestation_data.slot)`. From a6e76ef9c6331130a9af04a1d37e90b1cd6629a0 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 1 May 2019 08:45:29 +0100 Subject: [PATCH 16/20] Fix --- specs/core/0_beacon-chain.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a4b057964..2ffaf053f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1070,8 +1070,10 @@ def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationDa Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. """ return ( - (data_1 != data_2 and data_1.target == data_2.target) or # Double vote - (data_1.source < data_2.source and data_2.target < data_1.target) # Surround vote + # Double vote + (data_1 != data_2 and data_1.target_epoch == data_2.target_epoch) or + # Surround vote + (data_1.source_epoch < data_2.source_epoch and data_2.target_epoch < data_1.target_epoch) ) ``` From a0158c606e38e2daee8a3418b9cb76adbe1a93b9 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 1 May 2019 09:09:24 +0100 Subject: [PATCH 17/20] Expose get_commitee --- specs/core/0_beacon-chain.md | 26 +++++++++++++------------- specs/core/1_shard-data-chains.md | 17 +++-------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2ffaf053f..083fe786b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -73,6 +73,7 @@ - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) - [`get_shuffled_index`](#get_shuffled_index) + - [`get_committee`](#get_committee) - [`get_crosslink_committee`](#get_crosslink_committee) - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) @@ -907,24 +908,23 @@ def get_shuffled_index(index: ValidatorIndex, index_count: int, seed: Bytes32) - return index ``` +### `get_committee` + +```python +def get_committee(state: BeaconState, epoch: Epoch, index: int, count: int) -> List[ValidatorIndex]: + active_indices = get_active_validator_indices(state, epoch) + return [ + active_indices[get_shuffled_index(i, len(active_indices), generate_seed(state, epoch))] + for i in range((len(active_indices) * index) // count, (len(active_indices) * (index + 1)) // count) + ] +``` + ### `get_crosslink_committee` ```python def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> List[ValidatorIndex]: - """ - Return the crosslink committee at ``epoch`` for ``shard``. - """ - active_indices = get_active_validator_indices(state, epoch) - committee_count = get_epoch_committee_count(state, epoch) committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT - - start_validator_index = (len(active_indices) * committee_index) // committee_count - end_validator_index = (len(active_indices) * (committee_index + 1)) // committee_count - seed = generate_seed(state, epoch) - return [ - active_indices[get_shuffled_index(i, len(active_indices), seed)] - for i in range(start_validator_index, end_validator_index) - ] + return get_committee(state, epoch, committee_index, get_epoch_committee_count(state, epoch)) ``` ### `get_attesting_indices` diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 1e1a232fe..45fe91337 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -120,22 +120,11 @@ This document describes the shard data layer and the shard fork choice rule in P ### `get_period_committee` ```python -def get_period_committee(state: BeaconState, - shard: Shard, - committee_start_epoch: Epoch, - index: int, - committee_count: int) -> List[ValidatorIndex]: +def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard, index: int, count: int) -> List[ValidatorIndex]: """ Return committee for a period. Used to construct persistent committees. """ - active_validator_indices = get_active_validator_indices(state.validator_registry, committee_start_epoch) - seed = generate_seed(state, committee_start_epoch) - return compute_committee( - validator_indices=active_validator_indices, - seed=seed, - index=shard * committee_count + index, - total_committees=SHARD_COUNT * committee_count, - ) + return get_committee(state, epoch, shard * count + index, SHARD_COUNT * count) ``` ### `get_switchover_epoch` @@ -165,7 +154,7 @@ def get_persistent_committee(state: BeaconState, len(get_active_validator_indices(state.validator_registry, later_start_epoch)) // (SHARD_COUNT * TARGET_COMMITTEE_SIZE), ) + 1 - + index = slot % committee_count earlier_committee = get_period_committee(state, shard, earlier_start_epoch, index, committee_count) later_committee = get_period_committee(state, shard, later_start_epoch, index, committee_count) From e85678ac155baaf133fb4be6558c9d95e37a845d Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 1 May 2019 15:21:38 +0100 Subject: [PATCH 18/20] restore compute_committee --- specs/core/0_beacon-chain.md | 22 ++++++++++++---------- specs/core/1_shard-data-chains.md | 7 ++++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 083fe786b..f92294455 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -73,7 +73,7 @@ - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`verify_merkle_branch`](#verify_merkle_branch) - [`get_shuffled_index`](#get_shuffled_index) - - [`get_committee`](#get_committee) + - [`compute_committee`](#compute_committee) - [`get_crosslink_committee`](#get_crosslink_committee) - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) @@ -908,23 +908,25 @@ def get_shuffled_index(index: ValidatorIndex, index_count: int, seed: Bytes32) - return index ``` -### `get_committee` +### `compute_committee` ```python -def get_committee(state: BeaconState, epoch: Epoch, index: int, count: int) -> List[ValidatorIndex]: - active_indices = get_active_validator_indices(state, epoch) - return [ - active_indices[get_shuffled_index(i, len(active_indices), generate_seed(state, epoch))] - for i in range((len(active_indices) * index) // count, (len(active_indices) * (index + 1)) // count) - ] +def compute_committee(indices: List[ValidatorIndex], seed: Bytes32, index: int, count: int) -> List[ValidatorIndex]: + start = (len(indices) * index) // count + end = (len(indices) * (index + 1)) // count + return [indices[get_shuffled_index(i, len(indices), seed)] for i in range(start, end)] ``` ### `get_crosslink_committee` ```python def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> List[ValidatorIndex]: - committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT - return get_committee(state, epoch, committee_index, get_epoch_committee_count(state, epoch)) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=generate_seed(state, epoch), + index=(shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT, + count=get_epoch_committee_count(state, epoch), + ) ``` ### `get_attesting_indices` diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 45fe91337..33ef8632b 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -124,7 +124,12 @@ def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard, index: """ Return committee for a period. Used to construct persistent committees. """ - return get_committee(state, epoch, shard * count + index, SHARD_COUNT * count) + return compute_committee( + indices=get_active_validator_indices(state, epoch), + seed=generate_seed(state, epoch), + index=shard * count + index, + count=SHARD_COUNT * count, + ) ``` ### `get_switchover_epoch` From 8f2c7a36635d450d397cdb43408db37b4ec01988 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 May 2019 12:56:48 -0600 Subject: [PATCH 19/20] revert cache to compute_committee --- scripts/phase0/build_spec.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/scripts/phase0/build_spec.py b/scripts/phase0/build_spec.py index 1c1563a2c..c8e115576 100644 --- a/scripts/phase0/build_spec.py +++ b/scripts/phase0/build_spec.py @@ -45,24 +45,20 @@ Store = None code_lines += function_puller.get_spec(sourcefile) code_lines.append(""" -# Monkey patch validator get committee code -_get_crosslink_committee = get_crosslink_committee +# Monkey patch validator compute committee code +_compute_committee = compute_committee committee_cache = {} -def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> List[ValidatorIndex]: - active_indices = get_active_validator_indices(state, epoch) - seed = generate_seed(state, epoch) - committee_count = get_epoch_committee_count(state, epoch) - committee_index = (shard + SHARD_COUNT - get_epoch_start_shard(state, epoch)) % SHARD_COUNT - param_hash = (hash_tree_root(active_indices), seed, committee_count, committee_index) +def compute_committee(indices: List[ValidatorIndex], seed: Bytes32, index: int, count: int) -> List[ValidatorIndex]: + param_hash = (hash_tree_root(indices), seed, index, count) if param_hash in committee_cache: - # print("Cache hit, epoch={0}".format(epoch)) + print("Cache hit, param_hash: ", param_hash) return committee_cache[param_hash] else: - # print("Cache miss, epoch={0}".format(epoch)) - ret = _get_crosslink_committee(state, epoch, shard) + print("Cache miss, param_hash: ", param_hash) + ret = _compute_committee(indices, seed, index, count) committee_cache[param_hash] = ret return ret From 002e27973c7c31170b756493c0b9d4c6d316788f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 May 2019 15:51:43 -0600 Subject: [PATCH 20/20] alter get_winning_crosslink.. to have same function signature as get_crosslink_committee --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f92294455..f496952fc 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1314,7 +1314,7 @@ def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationDat ``` ```python -def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard, epoch: Epoch) -> Tuple[Crosslink, List[ValidatorIndex]]: +def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: shard_attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] candidate_crosslinks = [ @@ -1394,7 +1394,7 @@ def process_crosslinks(state: BeaconState) -> None: for offset in range(get_epoch_committee_count(state, epoch)): shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT crosslink_committee = get_crosslink_committee(state, epoch, shard) - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1464,7 +1464,7 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for offset in range(get_epoch_committee_count(state, epoch)): shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT crosslink_committee = get_crosslink_committee(state, epoch, shard) - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: