Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkpoints in beacon spec #1210

Merged
merged 13 commits into from
Jun 28, 2019
107 changes: 51 additions & 56 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [Containers](#containers)
- [Misc dependencies](#misc-dependencies)
- [`Fork`](#fork)
- [`Checkpoint`](#checkpoint)
- [`Validator`](#validator)
- [`Crosslink`](#crosslink)
- [`AttestationData`](#attestationdata)
Expand Down Expand Up @@ -290,6 +291,14 @@ class Fork(Container):
epoch: Epoch # Epoch of latest fork
```

#### `Checkpoint`

```python
class Checkpoint(Container):
epoch: Epoch
root: Hash
```

#### `Validator`

```python
Expand Down Expand Up @@ -324,10 +333,8 @@ class AttestationData(Container):
# LMD GHOST vote
beacon_block_root: Hash
# FFG vote
source_epoch: Epoch
source_root: Hash
target_epoch: Epoch
target_root: Hash
source: Checkpoint
target: Checkpoint
# Crosslink vote
crosslink: Crosslink
```
Expand Down Expand Up @@ -520,15 +527,11 @@ class BeaconState(Container):
# Crosslinks
previous_crosslinks: Vector[Crosslink, SHARD_COUNT] # Previous epoch snapshot
current_crosslinks: Vector[Crosslink, SHARD_COUNT]
# Justification
previous_justified_epoch: Epoch # Previous epoch snapshot
previous_justified_root: Hash # Previous epoch snapshot
current_justified_epoch: Epoch
current_justified_root: Hash
justification_bitfield: uint64 # Bit set for every recent justified epoch
# Finality
finalized_epoch: Epoch
finalized_root: Hash
justification_bitfield: uint64 # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint # Previous epoch snapshot
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
```

## Helper functions
Expand Down Expand Up @@ -702,9 +705,9 @@ def get_epoch_start_shard(state: BeaconState, epoch: Epoch) -> Shard:

```python
def get_attestation_data_slot(state: BeaconState, data: AttestationData) -> Slot:
committee_count = get_epoch_committee_count(state, data.target_epoch)
offset = (data.crosslink.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target_epoch)) % SHARD_COUNT
return Slot(get_epoch_start_slot(data.target_epoch) + offset // (committee_count // SLOTS_PER_EPOCH))
committee_count = get_epoch_committee_count(state, data.target.epoch)
offset = (data.crosslink.shard + SHARD_COUNT - get_epoch_start_shard(state, data.target.epoch)) % SHARD_COUNT
return Slot(get_epoch_start_slot(data.target.epoch) + offset // (committee_count // SLOTS_PER_EPOCH))
```

### `get_block_root_at_slot`
Expand Down Expand Up @@ -863,13 +866,11 @@ def get_crosslink_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> S
### `get_attesting_indices`

```python
def get_attesting_indices(state: BeaconState,
attestation_data: AttestationData,
bitfield: bytes) -> Sequence[ValidatorIndex]:
def get_attesting_indices(state: BeaconState, data: AttestationData, bitfield: bytes) -> Sequence[ValidatorIndex]:
"""
Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``.
Return the sorted attesting indices corresponding to ``data`` and ``bitfield``.
"""
committee = get_crosslink_committee(state, attestation_data.target_epoch, attestation_data.crosslink.shard)
committee = get_crosslink_committee(state, data.target.epoch, data.crosslink.shard)
assert verify_bitfield(bitfield, len(committee))
return sorted([index for i, index in enumerate(committee) if get_bitfield_bit(bitfield, i) == 0b1])
```
Expand Down Expand Up @@ -989,7 +990,7 @@ def validate_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)),
],
signature=indexed_attestation.signature,
domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.target_epoch),
domain=get_domain(state, DOMAIN_ATTESTATION, indexed_attestation.data.target.epoch),
)
```

Expand All @@ -1002,9 +1003,9 @@ def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationDa
"""
return (
# Double vote
(data_1 != data_2 and data_1.target_epoch == data_2.target_epoch) or
(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)
(data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch)
)
```

Expand Down Expand Up @@ -1256,15 +1257,15 @@ def get_total_active_balance(state: BeaconState) -> Gwei:

```python
def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
assert epoch in (get_current_epoch(state), get_previous_epoch(state))
assert epoch in (get_previous_epoch(state), get_current_epoch(state))
return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations
```

```python
def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
return [
a for a in get_matching_source_attestations(state, epoch)
if a.data.target_root == get_block_root(state, epoch)
if a.data.target.root == get_block_root(state, epoch)
]
```

Expand Down Expand Up @@ -1316,46 +1317,40 @@ def process_justification_and_finalization(state: BeaconState) -> None:

previous_epoch = get_previous_epoch(state)
current_epoch = get_current_epoch(state)
old_previous_justified_epoch = state.previous_justified_epoch
old_current_justified_epoch = state.current_justified_epoch
old_previous_justified_checkpoint = state.previous_justified_checkpoint
old_current_justified_checkpoint = state.current_justified_checkpoint

# Process justifications
state.previous_justified_epoch = state.current_justified_epoch
state.previous_justified_root = state.current_justified_root
state.previous_justified_checkpoint = state.current_justified_checkpoint
state.justification_bitfield = (state.justification_bitfield << 1) % 2**64
previous_epoch_matching_target_balance = get_attesting_balance(
state, get_matching_target_attestations(state, previous_epoch)
)
if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
state.current_justified_epoch = previous_epoch
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch,
root=get_block_root(state, previous_epoch))
state.justification_bitfield |= (1 << 1)
current_epoch_matching_target_balance = get_attesting_balance(
state, get_matching_target_attestations(state, current_epoch)
)
if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2:
state.current_justified_epoch = current_epoch
state.current_justified_root = get_block_root(state, state.current_justified_epoch)
state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, root=get_block_root(state, current_epoch))
state.justification_bitfield |= (1 << 0)

# Process finalizations
bitfield = state.justification_bitfield
# The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source
if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch + 3 == current_epoch:
state.finalized_epoch = old_previous_justified_epoch
state.finalized_root = get_block_root(state, state.finalized_epoch)
if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_checkpoint.epoch + 3 == current_epoch:
state.finalized_checkpoint = old_previous_justified_checkpoint
# The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source
if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch + 2 == current_epoch:
state.finalized_epoch = old_previous_justified_epoch
state.finalized_root = get_block_root(state, state.finalized_epoch)
if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_checkpoint.epoch + 2 == current_epoch:
state.finalized_checkpoint = old_previous_justified_checkpoint
# The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source
if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch + 2 == current_epoch:
state.finalized_epoch = old_current_justified_epoch
state.finalized_root = get_block_root(state, state.finalized_epoch)
if (bitfield >> 0) % 8 == 0b111 and old_current_justified_checkpoint.epoch + 2 == current_epoch:
state.finalized_checkpoint = old_current_justified_checkpoint
# The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source
if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch + 1 == current_epoch:
state.finalized_epoch = old_current_justified_epoch
state.finalized_root = get_block_root(state, state.finalized_epoch)
if (bitfield >> 0) % 4 == 0b11 and old_current_justified_checkpoint.epoch + 1 == current_epoch:
state.finalized_checkpoint = old_current_justified_checkpoint
```

#### Crosslinks
Expand Down Expand Up @@ -1418,7 +1413,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
rewards[index] += Gwei(max_attester_reward * MIN_ATTESTATION_INCLUSION_DELAY // attestation.inclusion_delay)

# Inactivity penalty
finality_delay = previous_epoch - state.finalized_epoch
finality_delay = previous_epoch - state.finalized_checkpoint.epoch
if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY:
matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations)
for index in eligible_validator_indices:
Expand Down Expand Up @@ -1483,7 +1478,7 @@ def process_registry_updates(state: BeaconState) -> None:
activation_queue = sorted([
index for index, validator in enumerate(state.validators) if
validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH and
validator.activation_epoch >= get_delayed_activation_exit_epoch(state.finalized_epoch)
validator.activation_epoch >= get_delayed_activation_exit_epoch(state.finalized_checkpoint.epoch)
], key=lambda index: state.validators[index].activation_eligibility_epoch)
# Dequeued validators for activation up to churn limit (without resetting activation epoch)
for index in activation_queue[:get_churn_limit(state)]:
Expand Down Expand Up @@ -1684,9 +1679,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
Process ``Attestation`` operation.
"""
data = attestation.data

assert data.crosslink.shard < SHARD_COUNT
assert data.target_epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))

attestation_slot = get_attestation_data_slot(state, data)
assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH
Expand All @@ -1698,21 +1692,22 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
proposer_index=get_beacon_proposer_index(state),
)

if data.target_epoch == get_current_epoch(state):
ffg_data = (state.current_justified_epoch, state.current_justified_root, get_current_epoch(state))
if data.target.epoch == get_current_epoch(state):
assert data.source == state.current_justified_checkpoint
parent_crosslink = state.current_crosslinks[data.crosslink.shard]
state.current_epoch_attestations.append(pending_attestation)
else:
ffg_data = (state.previous_justified_epoch, state.previous_justified_root, get_previous_epoch(state))
assert data.source == state.previous_justified_checkpoint
parent_crosslink = state.previous_crosslinks[data.crosslink.shard]
state.previous_epoch_attestations.append(pending_attestation)

# Check FFG data, crosslink data, and signature
assert ffg_data == (data.source_epoch, data.source_root, data.target_epoch)
assert data.crosslink.start_epoch == parent_crosslink.end_epoch
assert data.crosslink.end_epoch == min(data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)
# Check crosslink against expected parent crosslink
assert data.crosslink.parent_root == hash_tree_root(parent_crosslink)
assert data.crosslink.start_epoch == parent_crosslink.end_epoch
assert data.crosslink.end_epoch == min(data.target.epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)
assert data.crosslink.data_root == ZERO_HASH # [to be removed in phase 1]

# Check signature
validate_indexed_attestation(state, convert_to_indexed(state, attestation))
```

Expand Down
28 changes: 9 additions & 19 deletions specs/core/0_fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,6 @@ The head block root associated with a `store` is defined as `get_head(store)`. A

### Helpers

#### `Checkpoint`

```python
@dataclass(eq=True, frozen=True)
class Checkpoint(object):
epoch: Epoch
root: Hash
```

#### `LatestMessage`

```python
Expand All @@ -73,7 +64,6 @@ class LatestMessage(object):
root: Hash
```


#### `Store`

```python
Expand All @@ -94,8 +84,8 @@ class Store(object):
def get_genesis_store(genesis_state: BeaconState) -> Store:
genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))
root = signing_root(genesis_block)
justified_checkpoint = Checkpoint(GENESIS_EPOCH, root)
finalized_checkpoint = Checkpoint(GENESIS_EPOCH, root)
justified_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
return Store(
time=genesis_state.genesis_time,
justified_checkpoint=justified_checkpoint,
Expand Down Expand Up @@ -179,21 +169,21 @@ def on_block(store: Store, block: BeaconBlock) -> None:
store.block_states[signing_root(block)] = state

# Update justified checkpoint
if state.current_justified_epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = Checkpoint(state.current_justified_epoch, state.current_justified_root)
elif state.previous_justified_epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = Checkpoint(state.previous_justified_epoch, state.previous_justified_root)
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.current_justified_checkpoint
elif state.previous_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.previous_justified_checkpoint

# Update finalized checkpoint
if state.finalized_epoch > state.finalized_epoch:
store.finalized_checkpoint = Checkpoint(state.finalized_epoch, state.finalized_root)
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
```

#### `on_attestation`

```python
def on_attestation(store: Store, attestation: Attestation) -> None:
target = Checkpoint(attestation.data.target_epoch, attestation.data.target_root)
target = attestation.data.target

# Cannot calculate the current shuffling if have not seen the target
assert target.root in store.blocks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
assert (
store.latest_messages[indexed_attestation.custody_bit_0_indices[0]] ==
spec.LatestMessage(
epoch=attestation.data.target_epoch,
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
)
)
Expand Down
22 changes: 10 additions & 12 deletions test_libs/pyspec/eth2spec/test/helpers/attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ def build_attestation_data(spec, state, slot, shard):
epoch_boundary_root = spec.get_block_root(state, spec.get_current_epoch(state))

if slot < current_epoch_start_slot:
justified_epoch = state.previous_justified_epoch
justified_block_root = state.previous_justified_root
source_epoch = state.previous_justified_checkpoint.epoch
source_root = state.previous_justified_checkpoint.root
else:
justified_epoch = state.current_justified_epoch
justified_block_root = state.current_justified_root
source_epoch = state.current_justified_checkpoint.epoch
source_root = state.current_justified_checkpoint.root

if spec.slot_to_epoch(slot) == spec.get_current_epoch(state):
parent_crosslink = state.current_crosslinks[shard]
Expand All @@ -37,10 +37,8 @@ def build_attestation_data(spec, state, slot, shard):

return spec.AttestationData(
beacon_block_root=block_root,
source_epoch=justified_epoch,
source_root=justified_block_root,
target_epoch=spec.slot_to_epoch(slot),
target_root=epoch_boundary_root,
source=spec.Checkpoint(epoch=source_epoch, root=source_root),
target=spec.Checkpoint(epoch=spec.slot_to_epoch(slot), root=epoch_boundary_root),
crosslink=spec.Crosslink(
shard=shard,
start_epoch=parent_crosslink.end_epoch,
Expand All @@ -64,8 +62,8 @@ def get_valid_attestation(spec, state, slot=None, signed=False):

crosslink_committee = spec.get_crosslink_committee(
state,
attestation_data.target_epoch,
attestation_data.crosslink.shard
attestation_data.target.epoch,
attestation_data.crosslink.shard,
)

committee_size = len(crosslink_committee)
Expand Down Expand Up @@ -126,15 +124,15 @@ def get_attestation_signature(spec, state, attestation_data, privkey, custody_bi
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_ATTESTATION,
message_epoch=attestation_data.target_epoch,
message_epoch=attestation_data.target.epoch,
)
)


def fill_aggregate_attestation(spec, state, attestation):
crosslink_committee = spec.get_crosslink_committee(
state,
attestation.data.target_epoch,
attestation.data.target.epoch,
attestation.data.crosslink.shard,
)
for i in range(len(crosslink_committee)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
attestation_1 = get_valid_attestation(spec, state, signed=signed_1)

attestation_2 = deepcopy(attestation_1)
attestation_2.data.target_root = b'\x01' * 32
attestation_2.data.target.root = b'\x01' * 32

if signed_2:
sign_attestation(spec, state, attestation_2)
Expand Down
Loading