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

Shard fork choice rule #1773

Merged
merged 29 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
dab5a93
wip shard fork choice rule
hwwhww Apr 28, 2020
cddf9cf
Refactor
hwwhww Apr 30, 2020
8fafb6a
Make `ShardStore` an independent object
hwwhww May 1, 2020
fca1bbc
Remove `get_filtered_shard_block_tree`
hwwhww May 1, 2020
79b1b4b
Add `(shard, shard_root)` to `LatestMessage`
hwwhww May 1, 2020
870ad8b
Fix test
hwwhww May 29, 2020
63de59d
Merge branch 'dev' into hwwhww/shard_fork_choice_3
hwwhww May 29, 2020
142ba17
PR review from Danny
hwwhww Jun 2, 2020
5c5cedd
Apply PR feedback from Danny and Terence
hwwhww Jun 3, 2020
58e75c2
Merge branch 'dev' into hwwhww/shard_fork_choice
hwwhww Jun 3, 2020
e1981a7
`head_shard_root` -> `shard_head_root`
hwwhww Jun 3, 2020
d344521
Bugfix: should set `shard` for empty proposal
hwwhww Jun 3, 2020
26aae40
Use epoch of the shard_block.slot for generating seed
hwwhww Jun 3, 2020
c9a53b8
WIP test case
hwwhww Jun 3, 2020
727353c
Verify shard_block.slot fits the expected offset_slots
hwwhww Jun 4, 2020
f8597d2
Add `get_pendings_shard_blocks`
hwwhww Jun 4, 2020
ab42eee
Update shard fork choice rule to be able to handle mainnet config
hwwhww Jun 4, 2020
6f9c290
Add TODO flag of latest message
hwwhww Jun 4, 2020
a154d0c
Fix typo
hwwhww Jun 4, 2020
2d4788f
Fix `verify_shard_block_message`
hwwhww Jun 5, 2020
2afa315
clean leftover
hwwhww Jun 5, 2020
a71c0a5
Per #1704 discussion, remove `on_time_slot`: the given `beacon_state`
hwwhww Jun 5, 2020
a4cc189
Apply PR feedback from Danny
hwwhww Jun 5, 2020
4355057
PR feedback from Terence: fix `get_shard_latest_attesting_balance`
hwwhww Jun 8, 2020
7e67aae
Rename `build_shard_transitions_till_slot` to `get_shard_transitions`
hwwhww Jun 8, 2020
e03a970
PR feedback from danny: simplify `verify_shard_block_message` params
hwwhww Jun 8, 2020
9b3f45d
Merge pull request #1875 from ethereum/hwwhww/shard_fork_choice_part2
hwwhww Jun 8, 2020
3b749d7
Merge branch 'dev' into hwwhww/shard_fork_choice
hwwhww Jun 8, 2020
2d895e9
PR feedback from danny
hwwhww Jun 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def finalize_options(self):
specs/phase1/shard-transition.md
specs/phase1/fork-choice.md
specs/phase1/phase1-fork.md
specs/phase1/shard-fork-choice.md
"""
else:
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)
Expand Down
32 changes: 31 additions & 1 deletion specs/phase1/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [Extended `LatestMessage`](#extended-latestmessage)
- [Updated `update_latest_messages`](#updated-update_latest_messages)
- [Handlers](#handlers)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand All @@ -25,6 +28,33 @@ Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_atte

The rest of the fork choice remains stable.

### Helpers

#### Extended `LatestMessage`

```python
@dataclass(eq=True, frozen=True)
class LatestMessage(object):
epoch: Epoch
root: Root
shard: Shard
shard_root: Root
```

#### Updated `update_latest_messages`

```python
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
shard = get_shard(store.block_states[beacon_block_root], attestation)
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
for i in attesting_indices:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(
epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root
)
```

### Handlers

```python
Expand All @@ -49,4 +79,4 @@ def on_attestation(store: Store, attestation: Attestation) -> None:
if attestation.aggregation_bits[i]
]
update_latest_messages(store, attesting_indices, attestation)
```
```
182 changes: 182 additions & 0 deletions specs/phase1/shard-fork-choice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Ethereum 2.0 Phase 1 -- Beacon Chain + Shard Chain Fork Choice

**Notice**: This document is a work-in-progress for researchers and implementers.

## Table of contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [`ShardStore`](#shardstore)
- [`get_forkchoice_shard_store`](#get_forkchoice_shard_store)
- [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance)
- [`get_shard_head`](#get_shard_head)
- [`get_shard_ancestor`](#get_shard_ancestor)
- [`get_pending_shard_blocks`](#get_pending_shard_blocks)
- [Handlers](#handlers)
- [`on_shard_block`](#on_shard_block)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Introduction

This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. It assumes the [beacon chain fork choice spec](./fork-choice.md).

## Fork choice

### Helpers

#### `ShardStore`

```python
@dataclass
class ShardStore:
shard: Shard
blocks: Dict[Root, ShardBlock] = field(default_factory=dict)
block_states: Dict[Root, ShardState] = field(default_factory=dict)
```

#### `get_forkchoice_shard_store`

```python
def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore:
return ShardStore(
shard=shard,
blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot, shard=shard)},
block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]},
)
```

#### `get_shard_latest_attesting_balance`

```python
def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, root: Root) -> Gwei:
state = store.checkpoint_states[store.justified_checkpoint]
active_indices = get_active_validator_indices(state, get_current_epoch(state))
return Gwei(sum(
state.validators[i].effective_balance for i in active_indices
if (
i in store.latest_messages
# TODO: check the latest message logic: currently, validator's previous vote of another shard
# would be ignored once their newer vote is accepted. Check if it makes sense.
and store.latest_messages[i].shard == shard_store.shard
and get_shard_ancestor(
store, shard_store, store.latest_messages[i].shard_root, shard_store.blocks[root].slot
) == root
)
))
```

#### `get_shard_head`

```python
def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
# Execute the LMD-GHOST fork choice
beacon_head_root = get_head(store)
shard_head_state = store.block_states[beacon_head_root].shard_states[shard_store.shard]
shard_head_root = shard_head_state.latest_block_root
shard_blocks = {
root: shard_block for root, shard_block in shard_store.blocks.items()
if shard_block.slot > shard_head_state.slot
}
while True:
# Find the valid child block roots
children = [
root for root, shard_block in shard_blocks.items()
if shard_block.shard_parent_root == shard_head_root
]
if len(children) == 0:
return shard_head_root
# Sort by latest attesting balance with ties broken lexicographically
shard_head_root = max(
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard_store, root), root)
)
```

#### `get_shard_ancestor`

```python
def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root:
block = shard_store.blocks[root]
if block.slot > slot:
return get_shard_ancestor(store, shard_store, block.shard_parent_root, slot)
elif block.slot == slot:
return root
else:
# root is older than queried slot, thus a skip slot. Return most recent root prior to slot
return root
```

#### `get_pending_shard_blocks`

```python
def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]:
"""
Return the canonical shard block branch that has not yet been crosslinked.
"""
shard = shard_store.shard

beacon_head_root = get_head(store)
beacon_head_state = store.block_states[beacon_head_root]
latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root

shard_head_root = get_shard_head(store, shard_store)
root = shard_head_root
shard_blocks = []
while root != latest_shard_block_root:
shard_block = shard_store.blocks[root]
shard_blocks.append(shard_block)
root = shard_block.shard_parent_root

shard_blocks.reverse()
return shard_blocks
```

### Handlers

#### `on_shard_block`

```python
def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None:
shard_block = signed_shard_block.message
shard = shard_store.shard
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

# Check shard
# TODO: check it in networking spec
assert shard_block.shard == shard

# Check shard parent exists
assert shard_block.shard_parent_root in shard_store.block_states
shard_parent_state = shard_store.block_states[shard_block.shard_parent_root]

# Check beacon parent exists
assert shard_block.beacon_parent_root in store.block_states
beacon_parent_state = store.block_states[shard_block.beacon_parent_root]

# Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor)
finalized_beacon_state = store.block_states[store.finalized_checkpoint.root]
finalized_shard_state = finalized_beacon_state.shard_states[shard]
assert shard_block.slot > finalized_shard_state.slot

# Check block is a descendant of the finalized block at the checkpoint finalized slot
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert (
get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root
)

# Check the block is valid and compute the post-state
assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block)
assert verify_shard_block_signature(beacon_parent_state, signed_shard_block)

post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block)

# Add new block to the store
shard_store.blocks[hash_tree_root(shard_block)] = shard_block

# Add new state for this block to the store
shard_store.block_states[hash_tree_root(shard_block)] = post_state
```
89 changes: 33 additions & 56 deletions specs/phase1/shard-transition.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This document describes the shard transition function and fraud proofs as part o
### Misc

```python
def compute_shard_transition_digest(beacon_state: BeaconState,
def compute_shard_transition_digest(beacon_parent_state: BeaconState,
shard_state: ShardState,
beacon_parent_root: Root,
shard_body_root: Root) -> Bytes32:
Expand All @@ -43,15 +43,27 @@ def compute_shard_transition_digest(beacon_state: BeaconState,
### Shard block verification functions

```python
def verify_shard_block_message(beacon_state: BeaconState,
shard_state: ShardState,
block: ShardBlock,
slot: Slot,
shard: Shard) -> bool:
assert block.shard_parent_root == shard_state.latest_block_root
assert block.slot == slot
def verify_shard_block_message(beacon_parent_state: BeaconState,
shard_parent_state: ShardState,
block: ShardBlock) -> bool:
# Check `shard_parent_root` field
assert block.shard_parent_root == shard_parent_state.latest_block_root
# Check `beacon_parent_root` field
beacon_parent_block_header = beacon_parent_state.latest_block_header.copy()
if beacon_parent_block_header.state_root == Root():
beacon_parent_block_header.state_root = hash_tree_root(beacon_parent_state)
beacon_parent_root = hash_tree_root(beacon_parent_block_header)
assert block.beacon_parent_root == beacon_parent_root
# Check `slot` field
shard = block.shard
next_slot = Slot(block.slot + 1)
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot)
assert block.slot in offset_slots
# Check `shard` field
assert block.shard == shard
assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard)
# Check `proposer_index` field
assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard)
# Check `body` field
assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE
return True
```
Expand Down Expand Up @@ -172,38 +184,9 @@ def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[
return [hash_tree_root(proposal.message.body) for proposal in proposals]
```

```python
def get_proposal_choices_at_slot(beacon_state: BeaconState,
shard_state: ShardState,
slot: Slot,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock],
validate_signature: bool=True) -> Sequence[SignedShardBlock]:
"""
Return the valid shard blocks at the given ``slot``.
Note that this function doesn't change the state.
"""
choices = []
shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot]
for block in shard_blocks_at_slot:
try:
# Verify block message and signature
# TODO these validations should have been checked upon receiving shard blocks.
assert verify_shard_block_message(beacon_state, shard_state, block.message, slot, shard)
if validate_signature:
assert verify_shard_block_signature(beacon_state, block)

shard_state = get_post_shard_state(beacon_state, shard_state, block.message)
except Exception:
pass # TODO: throw error in the test helper
else:
choices.append(block)
return choices
```

```python
def get_proposal_at_slot(beacon_state: BeaconState,
shard_state: ShardState,
shard_parent_state: ShardState,
slot: Shard,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock],
Expand All @@ -212,24 +195,17 @@ def get_proposal_at_slot(beacon_state: BeaconState,
Return ``proposal``, ``shard_state`` of the given ``slot``.
Note that this function doesn't change the state.
"""
choices = get_proposal_choices_at_slot(
beacon_state=beacon_state,
shard_state=shard_state,
slot=slot,
shard=shard,
shard_blocks=shard_blocks,
validate_signature=validate_signature,
)
if len(choices) == 0:
block = ShardBlock(slot=slot)
shard_blocks = [block for block in shard_blocks if block.message.slot == slot]
if len(shard_blocks) == 0:
block = ShardBlock(slot=slot, shard=shard)
proposal = SignedShardBlock(message=block)
elif len(choices) == 1:
proposal = choices[0]
elif len(shard_blocks) == 1:
proposal = shard_blocks[0]
else:
proposal = get_winning_proposal(beacon_state, choices)
proposal = get_winning_proposal(beacon_state, shard_blocks)

# Apply state transition
shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message)
shard_state = get_post_shard_state(beacon_state, shard_parent_state, proposal.message)

return proposal, shard_state
```
Expand All @@ -244,10 +220,11 @@ def get_shard_state_transition_result(
proposals = []
shard_states = []
shard_state = beacon_state.shard_states[shard]
for slot in get_offset_slots(beacon_state, shard):
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
for slot in offset_slots:
proposal, shard_state = get_proposal_at_slot(
beacon_state=beacon_state,
shard_state=shard_state,
shard_parent_state=shard_state,
slot=slot,
shard=shard,
shard_blocks=shard_blocks,
Expand All @@ -269,7 +246,7 @@ Suppose you are a committee member on shard `shard` at slot `current_slot` and y
def get_shard_transition(beacon_state: BeaconState,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
offset_slots = get_offset_slots(beacon_state, shard)
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks)

shard_block_lengths = []
Expand Down
Loading