bitvector[8] -> uint8, for efficient packing in flags merkle tree

This commit is contained in:
protolambda 2021-02-01 21:46:27 +01:00
parent 1ba4917119
commit 3677073812
No known key found for this signature in database
GPG Key ID: EC89FDBB2B4C7623
5 changed files with 63 additions and 31 deletions

View File

@ -55,15 +55,26 @@ This is a patch implementing the first hard fork to the beacon chain, tentativel
and [TODO] reducing the cost of processing chains that have very little or zero participation for a long span of epochs and [TODO] reducing the cost of processing chains that have very little or zero participation for a long span of epochs
* Fork choice rule changes to address weaknesses recently discovered in the existing fork choice * Fork choice rule changes to address weaknesses recently discovered in the existing fork choice
## Custom types
| Name | SSZ equivalent | Description |
| - | - | - |
| `ValidatorFlags` | `uint8` | Bitflags to track validator actions with |
## Constants ## Constants
### Participation flags ### Validator action flags
This is formatted as an enum, with values `2**i` that can be combined as bit-flags.
The `0` value is reserved as default. Remaining bits in `ValidatorFlags` may be used in future hardforks.
**Note**: unlike Phase0, a `TIMELY_TARGET_FLAG` does not imply a `TIMELY_SOURCE_FLAG`.
| Name | Value | | Name | Value |
| - | - | | - | - |
| `TIMELY_HEAD_FLAG` | `0` | | `TIMELY_HEAD_FLAG` | `ValidatorFlags(2**0)` (= 1) |
| `TIMELY_SOURCE_FLAG` | `1` | | `TIMELY_SOURCE_FLAG` | `ValidatorFlags(2**1)` (= 2) |
| `TIMELY_TARGET_FLAG` | `2` | | `TIMELY_TARGET_FLAG` | `ValidatorFlags(2**2)` (= 4) |
### Participation rewards ### Participation rewards
@ -80,7 +91,6 @@ The reward fractions add up to 7/8, leaving the remaining 1/8 for proposer rewar
| Name | Value | | Name | Value |
| - | - | | - | - |
| `PARTICIPATION_FLAGS_LENGTH` | `8` |
| `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | | `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` |
## Configuration ## Configuration
@ -146,8 +156,8 @@ class BeaconState(Container):
# Slashings # Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation # Participation
previous_epoch_participation: List[Bitvector[PARTICIPATION_FLAGS_LENGTH], VALIDATOR_REGISTRY_LIMIT] previous_epoch_participation: List[ValidatorFlags, VALIDATOR_REGISTRY_LIMIT]
current_epoch_participation: List[Bitvector[PARTICIPATION_FLAGS_LENGTH], VALIDATOR_REGISTRY_LIMIT] current_epoch_participation: List[ValidatorFlags, VALIDATOR_REGISTRY_LIMIT]
# Finality # Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint previous_justified_checkpoint: Checkpoint
@ -197,7 +207,15 @@ def get_flags_and_numerators() -> Sequence[Tuple[int, int]]:
) )
``` ```
```python
def add_flags(flags: ValidatorFlags, add: ValidatorFlags) -> ValidatorFlags:
return flags | add
```
```python
def has_flags(flags: ValidatorFlags, has: ValidatorFlags) -> bool:
return flags & has == has
```
### Beacon state accessors ### Beacon state accessors
@ -257,7 +275,10 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
#### `get_unslashed_participating_indices` #### `get_unslashed_participating_indices`
```python ```python
def get_unslashed_participating_indices(state: BeaconState, flag: uint8, epoch: Epoch) -> Set[ValidatorIndex]: def get_unslashed_participating_indices(state: BeaconState, flags: ValidatorFlags, epoch: Epoch) -> Set[ValidatorIndex]:
"""
Retrieves the active validator indices of the given epoch, who are not slashed, and have all of the given flags.
"""
assert epoch in (get_previous_epoch(state), get_current_epoch(state)) assert epoch in (get_previous_epoch(state), get_current_epoch(state))
if epoch == get_current_epoch(state): if epoch == get_current_epoch(state):
epoch_participation = state.current_epoch_participation epoch_participation = state.current_epoch_participation
@ -265,7 +286,7 @@ def get_unslashed_participating_indices(state: BeaconState, flag: uint8, epoch:
epoch_participation = state.previous_epoch_participation epoch_participation = state.previous_epoch_participation
participating_indices = [ participating_indices = [
index for index in get_active_validator_indices(state, epoch) index for index in get_active_validator_indices(state, epoch)
if epoch_participation[index][flag] if has_flags(epoch_participation[index], flags)
] ]
return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) return set(filter(lambda index: not state.validators[index].slashed, participating_indices))
``` ```
@ -273,7 +294,9 @@ def get_unslashed_participating_indices(state: BeaconState, flag: uint8, epoch:
#### `get_flag_deltas` #### `get_flag_deltas`
```python ```python
def get_flag_deltas(state: BeaconState, flag: uint8, numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: def get_flag_deltas(state: BeaconState,
flag: ValidatorFlags,
numerator: uint64) -> Tuple[Sequence[Gwei], Sequence[Gwei]]:
""" """
Computes the rewards and penalties associated with a particular duty, by scanning through the participation Computes the rewards and penalties associated with a particular duty, by scanning through the participation
flags to determine who participated and who did not and assigning them the appropriate rewards and penalties. flags to determine who participated and who did not and assigning them the appropriate rewards and penalties.
@ -385,8 +408,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
proposer_reward_numerator = 0 proposer_reward_numerator = 0
for index in get_attesting_indices(state, data, attestation.aggregation_bits): for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for flag, numerator in get_flags_and_numerators(): for flag, numerator in get_flags_and_numerators():
if flag in participation_flags and not epoch_participation[index][flag]: if flag in participation_flags and not has_flags(epoch_participation[index], flag):
epoch_participation[index][flag] = True epoch_participation[index] = add_flags(epoch_participation[index], flag)
proposer_reward_numerator += get_base_reward(state, index) * numerator proposer_reward_numerator += get_base_reward(state, index) * numerator
# Reward proposer # Reward proposer
@ -432,8 +455,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
state.validators.append(get_validator_from_deposit(state, deposit)) state.validators.append(get_validator_from_deposit(state, deposit))
state.balances.append(amount) state.balances.append(amount)
# [Added in hf-1] Initialize empty participation flags for new validator # [Added in hf-1] Initialize empty participation flags for new validator
state.previous_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]()) state.previous_epoch_participation.append(ValidatorFlags(0))
state.current_epoch_participation.append(Bitvector[PARTICIPATION_FLAGS_LENGTH]()) state.current_epoch_participation.append(ValidatorFlags(0))
else: else:
# Increase balance by deposit amount # Increase balance by deposit amount
index = ValidatorIndex(validator_pubkeys.index(pubkey)) index = ValidatorIndex(validator_pubkeys.index(pubkey))
@ -572,5 +595,5 @@ def process_participation_flag_updates(state: BeaconState) -> None:
Call to ``process_participation_flag_updates`` added to ``process_epoch`` in HF1 Call to ``process_participation_flag_updates`` added to ``process_epoch`` in HF1
""" """
state.previous_epoch_participation = state.current_epoch_participation state.previous_epoch_participation = state.current_epoch_participation
state.current_epoch_participation = [Bitvector[PARTICIPATION_FLAGS_LENGTH]() for _ in range(len(state.validators))] state.current_epoch_participation = [ValidatorFlags(0) for _ in range(len(state.validators))]
``` ```

View File

@ -67,8 +67,8 @@ def upgrade_to_lightclient_patch(pre: phase0.BeaconState) -> BeaconState:
# Slashings # Slashings
slashings=pre.slashings, slashings=pre.slashings,
# Attestations # Attestations
previous_epoch_participation=[Bitvector[PARTICIPATION_FLAGS_LENGTH]() for _ in range(len(pre.validators))], previous_epoch_participation=[ValidatorFlags(0) for _ in range(len(pre.validators))],
current_epoch_participation=[Bitvector[PARTICIPATION_FLAGS_LENGTH]() for _ in range(len(pre.validators))], current_epoch_participation=[ValidatorFlags(0) for _ in range(len(pre.validators))],
# Finality # Finality
justification_bits=pre.justification_bits, justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint, previous_justified_checkpoint=pre.previous_justified_checkpoint,

View File

@ -6,7 +6,7 @@ from eth2spec.test.context import is_post_lightclient_patch
from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations
from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.deposits import mock_deposit
from eth2spec.test.helpers.state import next_epoch from eth2spec.test.helpers.state import next_epoch
from eth2spec.utils.ssz.ssz_typing import Container, uint64, List, Bitvector from eth2spec.utils.ssz.ssz_typing import Container, uint64, List
class Deltas(Container): class Deltas(Container):
@ -314,7 +314,7 @@ def run_test_full_but_partial_participation(spec, state, rng=Random(5522)):
else: else:
for index in range(len(state.validators)): for index in range(len(state.validators)):
if rng.choice([True, False]): if rng.choice([True, False]):
state.previous_epoch_participation[index] = Bitvector[spec.PARTICIPATION_FLAGS_LENGTH]() state.previous_epoch_participation[index] = spec.ValidatorFlags(0)
yield from run_deltas(spec, state) yield from run_deltas(spec, state)
@ -328,7 +328,7 @@ def run_test_partial(spec, state, fraction_filled):
state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations] state.previous_epoch_attestations = state.previous_epoch_attestations[:num_attestations]
else: else:
for index in range(int(len(state.validators) * fraction_filled)): for index in range(int(len(state.validators) * fraction_filled)):
state.previous_epoch_participation[index] = Bitvector[spec.PARTICIPATION_FLAGS_LENGTH]() state.previous_epoch_participation[index] = spec.ValidatorFlags(0)
yield from run_deltas(spec, state) yield from run_deltas(spec, state)
@ -394,7 +394,7 @@ def run_test_some_very_low_effective_balances_that_did_not_attest(spec, state):
else: else:
index = 0 index = 0
state.validators[index].effective_balance = 1 state.validators[index].effective_balance = 1
state.previous_epoch_participation[index] = Bitvector[spec.PARTICIPATION_FLAGS_LENGTH]() state.previous_epoch_participation[index] = spec.ValidatorFlags(0)
yield from run_deltas(spec, state) yield from run_deltas(spec, state)
@ -519,16 +519,25 @@ def run_test_full_random(spec, state, rng=Random(8020)):
for index in range(len(state.validators)): for index in range(len(state.validators)):
# ~1/3 have bad head or bad target or not timely enough # ~1/3 have bad head or bad target or not timely enough
is_timely_correct_head = rng.randint(0, 2) != 0 is_timely_correct_head = rng.randint(0, 2) != 0
state.previous_epoch_participation[index][spec.TIMELY_HEAD_FLAG] = is_timely_correct_head flags = state.previous_epoch_participation[index]
def set_flag(f, v):
nonlocal flags
if v:
flags |= f
else:
flags &= 0xff ^ f
set_flag(spec.TIMELY_HEAD_FLAG, is_timely_correct_head)
if is_timely_correct_head: if is_timely_correct_head:
# If timely head, then must be timely target # If timely head, then must be timely target
state.previous_epoch_participation[index][spec.TIMELY_TARGET_FLAG] = True set_flag(spec.TIMELY_TARGET_FLAG, True)
# If timely head, then must be timely source # If timely head, then must be timely source
state.previous_epoch_participation[index][spec.TIMELY_SOURCE_FLAG] = True set_flag(spec.TIMELY_SOURCE_FLAG, True)
else: else:
# ~50% of remaining have bad target or not timely enough # ~50% of remaining have bad target or not timely enough
state.previous_epoch_participation[index][spec.TIMELY_TARGET_FLAG] = rng.choice([True, False]) set_flag(spec.TIMELY_TARGET_FLAG, rng.choice([True, False]))
# ~50% of remaining have bad source or not timely enough # ~50% of remaining have bad source or not timely enough
state.previous_epoch_participation[index][spec.TIMELY_SOURCE_FLAG] = rng.choice([True, False]) set_flag(spec.TIMELY_SOURCE_FLAG, rng.choice([True, False]))
state.previous_epoch_participation[index] = flags
yield from run_deltas(spec, state) yield from run_deltas(spec, state)

View File

@ -78,10 +78,10 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support
else: else:
for i, index in enumerate(committee): for i, index in enumerate(committee):
if aggregation_bits[i]: if aggregation_bits[i]:
epoch_participation[index][spec.TIMELY_HEAD_FLAG] = True epoch_participation[index] |= spec.TIMELY_HEAD_FLAG
epoch_participation[index][spec.TIMELY_SOURCE_FLAG] = True epoch_participation[index] |= spec.TIMELY_SOURCE_FLAG
if not messed_up_target: if not messed_up_target:
epoch_participation[index][spec.TIMELY_TARGET_FLAG] = True epoch_participation[index] |= spec.TIMELY_TARGET_FLAG
def get_checkpoints(spec, epoch): def get_checkpoints(spec, epoch):

View File

@ -806,7 +806,7 @@ def test_attestation(spec, state):
assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
else: else:
for index in range(len(state.validators)): for index in range(len(state.validators)):
assert state.current_epoch_participation[index] == spec.Bitvector[spec.PARTICIPATION_FLAGS_LENGTH]() assert state.current_epoch_participation[index] == 0
assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root