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

Compact committee roots #1219

Merged
merged 11 commits into from
Jun 29, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion configs/constant_presets/mainnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SHARD_COUNT: 1024
# 2**7 (= 128)
TARGET_COMMITTEE_SIZE: 128
# 2**12 (= 4,096)
MAX_INDICES_PER_ATTESTATION: 4096
MAX_VALIDATORS_PER_COMMITTEE: 4096
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
Expand Down
2 changes: 1 addition & 1 deletion configs/constant_presets/minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ SHARD_COUNT: 8
# [customized] unsecure, but fast
TARGET_COMMITTEE_SIZE: 4
# 2**12 (= 4,096)
MAX_INDICES_PER_ATTESTATION: 4096
MAX_VALIDATORS_PER_COMMITTEE: 4096
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
Expand Down
42 changes: 42 additions & 0 deletions motes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
* `BLS_WITHDRAWAL_PREFIX`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Loving your motes :) Will comment on the various points later today 😂

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh no! I accidentally pushed. Haaa

djrtwo marked this conversation as resolved.
Show resolved Hide resolved
* Why int rather than bytes?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should probably be bytes :)

* `MIN_SEED_LOOKAHEAD`
* Is this actually tunable?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't any reason why it's not tunable

* If so, what are the reprecussions?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can think of two repercussions:

  1. It changes the lookahead (duh)
  2. For security we need to correspondingly adjust ACTIVATION_EXIT_DELAY to be at least MIN_SEED_LOOKAHEAD + 3.

* `ACTIVATION_EXIT_DELAY`
* Reaquaint with purpose
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that attackers can know the randomness beyond MIN_SEED_LOOKAHEAD, and hence bias the shuffling by strategically registering/deregistering validators in the window between MIN_SEED_LOOKAHEAD and MIN_SEED_LOOKAHEAD + 3 to "grind" the shuffling. (The number 3 is large enough to guarantee that, with high probability, an attacker's lookahead cannot go beyond MIN_SEED_LOOKAHEAD + 3.)

* AttesterSlashings
* `MAX_ATTESTER_SLASHINGS` is 1.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, the reasons being:

  1. 1 is sufficiently large
  2. Attester slashings are huge objects, and could be used as a DoS vector

* Are there scenarios in which validators can create more effective slashable
messages than can be included on chain? For example, Validators split up to
create double attestations for checkpoints but different (junk) crosslink
data to prevent them from being aggregatable to the fullest
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I remember thinking about this. One way to create more effective slashable messages is as follows. Let's say you control n validators, and you want to make bad attestations a_1, ..., a_n for each of these validators. The attacker can broadcast aggregated attestations (a_1, a_i) for i > 1 and never reveal the individual a_i. This is an argument for generalising the bitfield to a multibitfield.

* Max is for block size, no?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

MAX_ATTESTER_SLASHINGS is to cap the maximum block size, yes.

* Signature domains
* Max 4byte ints
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a lot of signature domains :)

* `Version` not defined in one of the lists of custom types (2!!). ensure in spec
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be cleaned up

* `PendingAttestation`
* Don't think `proposer_index` is actually necessary here because effective
balance is stable until end of epoch so can do dynamic lookups
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, nice find! We only use proposer_index once so this is a nice cleanup I guess.

* is_genesis_trigger
* only run at ends of blocks to preserve invariant that eth1data.deposit_root
is the deposit root at the _end_ of an eth1 block
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

* `Attestation`
* why bitfields not together?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by "together"? Neither concatenation nor interleaving is nice. A mathematically clean solution would be (ternary) "trits".

Copy link
Contributor

Choose a reason for hiding this comment

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

I just meant in the field ordering

* `Transfer`
* replay mechanism... say the slot gets missed and you sign another transfer
* in a fork you could include both transfers
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I discussed this with Vitalik at Devcon. The current replay mechanism is quite bad and user unfriendly. The latest suggestion is to have a buffer of the latest n (where n is sufficiently large) transfer roots, and use that as replay protection.

Since we know we want to change the current transfer logic, can we please remove transfers from phase 0? Including the current logic is now actively misleading.

* `get_previous_epoch`
* do a once over on the genesis stuff
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I understand

Copy link
Contributor

Choose a reason for hiding this comment

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

Was personal note to dig into how previous_epoch performs near genesis epoch. Generally, we are just skipping things in 0th or 1st epoch

* `get_epoch_start_shard`
* checking next hinges upon the fact that the validator set for the next
epoch is 100% known at the current epoch. Ensure this is the case
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't understand

Copy link
Contributor

Choose a reason for hiding this comment

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

Ensure that there are no ways that during epoch N, we don't 100% know the active v-set during epoch N+1. In general is our assumption but want to go through and check.

* `get_block_root_at_slot` .. `generate_seed` can be bade into one line
function signatures
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yay for one-line function signatures. This is part of the cleanup planned in #918.

* `get_shuffled_index`
* I think it should be maybe `assert index_count <= VALIDATOR_REGISTRY_LIMIT`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right

* is the `2**40` special for security of alg? probably.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hum, not sure.



pubkey/privkey g1 vs g2
Copy link
Contributor Author

Choose a reason for hiding this comment

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

pubkey should be in g1 since we're adding a bunch of them when aggregating.


1 change: 1 addition & 0 deletions notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* `BLS_WITHDRAWAL_PREFIX` --
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
64 changes: 37 additions & 27 deletions specs/core/0_beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- [`Eth1Data`](#eth1data)
- [`HistoricalBatch`](#historicalbatch)
- [`DepositData`](#depositdata)
- [`CompactCommittee`](#compactcommittee)
- [`BeaconBlockHeader`](#beaconblockheader)
- [Beacon operations](#beacon-operations)
- [`ProposerSlashing`](#proposerslashing)
Expand Down Expand Up @@ -68,7 +69,7 @@
- [`get_block_root_at_slot`](#get_block_root_at_slot)
- [`get_block_root`](#get_block_root)
- [`get_randao_mix`](#get_randao_mix)
- [`get_active_index_root`](#get_active_index_root)
- [`get_compact_committees_root`](#get_compact_committees_root)
- [`generate_seed`](#generate_seed)
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
- [`verify_merkle_branch`](#verify_merkle_branch)
Expand Down Expand Up @@ -187,7 +188,7 @@ The following values are (non-configurable) constants used throughout the specif
| - | - |
| `SHARD_COUNT` | `2**10` (= 1,024) |
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
| `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) |
| `MAX_VALIDATORS_PER_COMMITTEE` | `2**12` (= 4,096) |
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `90` |
Expand Down Expand Up @@ -344,8 +345,8 @@ class AttestationDataAndCustodyBit(Container):

```python
class IndexedAttestation(Container):
custody_bit_0_indices: List[ValidatorIndex, MAX_INDICES_PER_ATTESTATION] # Indices with custody bit equal to 0
custody_bit_1_indices: List[ValidatorIndex, MAX_INDICES_PER_ATTESTATION] # Indices with custody bit equal to 1
custody_bit_0_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 0
custody_bit_1_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 1
data: AttestationData
signature: BLSSignature
```
Expand All @@ -354,7 +355,7 @@ class IndexedAttestation(Container):

```python
class PendingAttestation(Container):
aggregation_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
aggregation_bitfield: Bytes[MAX_VALIDATORS_PER_COMMITTEE // 8]
data: AttestationData
inclusion_delay: Slot
proposer_index: ValidatorIndex
Expand Down Expand Up @@ -387,6 +388,14 @@ class DepositData(Container):
signature: BLSSignature
```

#### `CompactCommittee`

```python
class CompactCommittee(Container):
pubkeys: List[Bytes48, MAX_VALIDATORS_PER_COMMITTEE]
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
```

#### `BeaconBlockHeader`

```python
Expand Down Expand Up @@ -421,9 +430,9 @@ class AttesterSlashing(Container):

```python
class Attestation(Container):
aggregation_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
aggregation_bitfield: Bytes[MAX_VALIDATORS_PER_COMMITTEE // 8]
data: AttestationData
custody_bitfield: Bytes[MAX_INDICES_PER_ATTESTATION // 8]
custody_bitfield: Bytes[MAX_VALIDATORS_PER_COMMITTEE // 8]
signature: BLSSignature
```

Expand Down Expand Up @@ -511,7 +520,7 @@ class BeaconState(Container):
# Shuffling
start_shard: Shard
randao_mixes: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR]
active_index_roots: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR] # Active registry digests for light clients
compact_committees_roots: Vector[Hash, EPOCHS_PER_HISTORICAL_VECTOR] # Committee digests for light clients
# Slashings
slashed_balances: Vector[Gwei, EPOCHS_PER_SLASHED_BALANCES_VECTOR] # Sums of slashed effective balances
# Attestations
Expand Down Expand Up @@ -742,17 +751,25 @@ def get_randao_mix(state: BeaconState,
return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
```

### `get_active_index_root`
### `get_compact_committees_root`

```python
def get_active_index_root(state: BeaconState,
epoch: Epoch) -> Hash:
def get_compact_committees_root(state: BeaconState, epoch: Epoch) -> Hash:
"""
Return the index root at a recent ``epoch``.
``epoch`` expected to be between
(current_epoch - EPOCHS_PER_HISTORICAL_VECTOR + ACTIVATION_EXIT_DELAY, current_epoch + ACTIVATION_EXIT_DELAY].
Return the compact committee root for the current epoch.
"""
return state.active_index_roots[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
committees = [CompactCommittee() for _ in range(SHARD_COUNT)]
start_shard = get_epoch_start_shard(state, epoch)
for committee_number in range(get_epoch_committee_count(state, epoch)):
shard = (start_shard + committee_number) % SHARD_COUNT
for index in get_crosslink_committee(state, epoch, shard):
validator = state.validators[index]
committees[shard].pubkeys.append(validator.pubkey)
compact_balance = validator.effective_balance // EFFECTIVE_BALANCE_INCREMENT
# `index` (top 6 bytes) + `slashed` (16th bit) + `compact_balance` (bottom 15 bits)
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
compact_validator = uint64(index << 16 + validator.slashed << 15 + compact_balance)
committees[shard].compact_validators.append(compact_validator)
return hash_tree_root(Vector[CompactCommittee, SHARD_COUNT](committees))
```

### `generate_seed`
Expand All @@ -765,7 +782,7 @@ def generate_seed(state: BeaconState,
"""
return hash(
get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD)) +
get_active_index_root(state, epoch) +
state.compact_committees_roots[epoch] +
int_to_bytes(epoch, length=32)
)
```
Expand Down Expand Up @@ -973,7 +990,7 @@ def validate_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
assert len(bit_1_indices) == 0
# Verify max number of indices
assert len(bit_0_indices) + len(bit_1_indices) <= MAX_INDICES_PER_ATTESTATION
assert len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE
# Verify index sets are disjoint
assert len(set(bit_0_indices).intersection(bit_1_indices)) == 0
# Verify indices are sorted
Expand Down Expand Up @@ -1173,12 +1190,9 @@ def get_genesis_beacon_state(deposits: Sequence[Deposit], genesis_time: int, eth
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH

# Populate active_index_roots
genesis_active_index_root = hash_tree_root(
List[ValidatorIndex, VALIDATOR_REGISTRY_LIMIT](get_active_validator_indices(state, GENESIS_EPOCH))
)
# Populate compact_committees_roots
for index in range(EPOCHS_PER_HISTORICAL_VECTOR):
state.active_index_roots[index] = genesis_active_index_root
state.compact_committees_roots[index] = get_compact_committees_root(state, GENESIS_EPOCH)

return state
```
Expand Down Expand Up @@ -1532,11 +1546,7 @@ def process_final_updates(state: BeaconState) -> None:
state.start_shard = Shard((state.start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT)
# Set active index root
index_root_position = (next_epoch + ACTIVATION_EXIT_DELAY) % EPOCHS_PER_HISTORICAL_VECTOR
state.active_index_roots[index_root_position] = hash_tree_root(
List[ValidatorIndex, VALIDATOR_REGISTRY_LIMIT](
get_active_validator_indices(state, Epoch(next_epoch + ACTIVATION_EXIT_DELAY))
)
)
state.compact_committees_roots[index_root_position] = get_compact_committees_root(state, next_epoch + ACTIVATION_EXIT_DELAY)
# Set total slashed balances
state.slashed_balances[next_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR] = (
state.slashed_balances[current_epoch % EPOCHS_PER_SLASHED_BALANCES_VECTOR]
Expand Down
4 changes: 2 additions & 2 deletions specs/light_client/sync_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ We define an "expansion" of an object as an object where a field in an object th

We define two expansions:

* `ExtendedBeaconState`, which is identical to a `BeaconState` except `active_index_roots: List[Bytes32]` is replaced by `active_indices: List[List[ValidatorIndex]]`, where `BeaconState.active_index_roots[i] = hash_tree_root(ExtendedBeaconState.active_indices[i])`.
* `ExtendedBeaconState`, which is identical to a `BeaconState` except `compact_committees_roots: List[Bytes32]` is replaced by `active_indices: List[List[ValidatorIndex]]`, where `BeaconState.compact_committees_roots[i] = hash_tree_root(ExtendedBeaconState.active_indices[i])`.
* `ExtendedBeaconBlock`, which is identical to a `BeaconBlock` except `state_root` is replaced with the corresponding `state: ExtendedBeaconState`.

### `get_active_validator_indices`
Expand All @@ -40,7 +40,7 @@ Note that there is now a new way to compute `get_active_validator_indices`:

```python
def get_active_validator_indices(state: ExtendedBeaconState, epoch: Epoch) -> List[ValidatorIndex]:
return state.active_indices[epoch % ACTIVE_INDEX_ROOTS_LENGTH]
return state.active_indices[epoch % compact_committees_rootS_LENGTH]
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
```

Note that it takes `state` instead of `state.validators` as an argument. This does not affect its use in `get_shuffled_committee`, because `get_shuffled_committee` has access to the full `state` as one of its arguments.
Expand Down
4 changes: 2 additions & 2 deletions test_libs/pyspec/eth2spec/test/helpers/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def create_genesis_state(spec, num_validators):
validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
validator.activation_epoch = spec.GENESIS_EPOCH

genesis_active_index_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
genesis_compact_committees_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
spec.get_active_validator_indices(state, spec.GENESIS_EPOCH)))
for index in range(spec.EPOCHS_PER_HISTORICAL_VECTOR):
state.active_index_roots[index] = genesis_active_index_root
state.compact_committees_roots[index] = genesis_compact_committees_root

return state