Merge pull request #2453 from ethereum/vbuterin-patch-11

Modify sync committee logic and parameters to reduce variance
This commit is contained in:
Danny Ryan 2021-06-04 12:23:16 -06:00 committed by GitHub
commit 1d5c4ecffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 122 additions and 79 deletions

View File

@ -14,8 +14,8 @@ PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2
# ---------------------------------------------------------------
# 2**9 (= 512)
SYNC_COMMITTEE_SIZE: 512
# 2**9 (= 512)
EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 512
# 2**8 (= 256)
EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256
# Sync protocol

View File

@ -86,10 +86,10 @@ Altair is the first beacon chain hard fork. Its main features are:
| Name | Value |
| - | - |
| `TIMELY_SOURCE_WEIGHT` | `uint64(12)` |
| `TIMELY_TARGET_WEIGHT` | `uint64(24)` |
| `TIMELY_HEAD_WEIGHT` | `uint64(12)` |
| `SYNC_REWARD_WEIGHT` | `uint64(8)` |
| `TIMELY_SOURCE_WEIGHT` | `uint64(14)` |
| `TIMELY_TARGET_WEIGHT` | `uint64(26)` |
| `TIMELY_HEAD_WEIGHT` | `uint64(14)` |
| `SYNC_REWARD_WEIGHT` | `uint64(2)` |
| `PROPOSER_WEIGHT` | `uint64(8)` |
| `WEIGHT_DENOMINATOR` | `uint64(64)` |
@ -129,7 +129,7 @@ This patch updates a few configuration values to move penalty parameters closer
| Name | Value | Unit | Duration |
| - | - | - | - |
| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | Validators | |
| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**9)` (= 512) | epochs | ~54 hours |
| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
## Configuration
@ -566,10 +566,12 @@ def process_sync_committee(state: BeaconState, aggregate: SyncAggregate) -> None
# Apply participant and proposer rewards
all_pubkeys = [v.pubkey for v in state.validators]
committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys]
participant_indices = [index for index, bit in zip(committee_indices, aggregate.sync_committee_bits) if bit]
for participant_index in participant_indices:
increase_balance(state, participant_index, participant_reward)
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
for participant_index, participation_bit in zip(committee_indices, aggregate.sync_committee_bits):
if participation_bit:
increase_balance(state, participant_index, participant_reward)
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
else:
decrease_balance(state, participant_index, participant_reward)
```
### Epoch processing

View File

@ -1,4 +1,3 @@
from collections import Counter
import random
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
@ -13,6 +12,9 @@ from eth2spec.test.helpers.constants import (
)
from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
compute_sync_committee_participant_reward_and_penalty,
compute_sync_committee_proposer_reward,
compute_committee_indices,
)
from eth2spec.test.context import (
expect_assertion_error,
@ -61,15 +63,6 @@ def get_committee_indices(spec, state, duplicates=False):
state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index])
def compute_committee_indices(spec, state, committee):
"""
Given a ``committee``, calculate and return the related indices
"""
all_pubkeys = [v.pubkey for v in state.validators]
committee_indices = [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
return committee_indices
@with_altair_and_later
@spec_state_test
@always_bls
@ -115,65 +108,20 @@ def test_invalid_signature_extra_participant(spec, state):
yield from run_sync_committee_processing(spec, state, block, expect_exception=True)
def compute_sync_committee_inclusion_reward(spec,
state,
participant_index,
committee_indices,
committee_bits):
total_active_increments = spec.get_total_active_balance(state) // spec.EFFECTIVE_BALANCE_INCREMENT
total_base_rewards = spec.Gwei(spec.get_base_reward_per_increment(state) * total_active_increments)
max_epoch_rewards = spec.Gwei(total_base_rewards * spec.SYNC_REWARD_WEIGHT // spec.WEIGHT_DENOMINATOR)
included_indices = [index for index, bit in zip(committee_indices, committee_bits) if bit]
max_slot_rewards = spec.Gwei(
max_epoch_rewards * len(included_indices)
// len(committee_indices) // spec.SLOTS_PER_EPOCH
)
# Compute the participant and proposer sync rewards
committee_effective_balance = sum([state.validators[index].effective_balance for index in included_indices])
committee_effective_balance = max(spec.EFFECTIVE_BALANCE_INCREMENT, committee_effective_balance)
effective_balance = state.validators[participant_index].effective_balance
return spec.Gwei(max_slot_rewards * effective_balance // committee_effective_balance)
def compute_sync_committee_participant_reward(spec, state, participant_index, committee_indices, committee_bits):
included_indices = [index for index, bit in zip(committee_indices, committee_bits) if bit]
multiplicities = Counter(included_indices)
inclusion_reward = compute_sync_committee_inclusion_reward(
spec, state, participant_index, committee_indices, committee_bits,
)
return spec.Gwei(inclusion_reward * multiplicities[participant_index])
def compute_sync_committee_proposer_reward(spec, state, committee_indices, committee_bits):
proposer_reward = 0
for index, bit in zip(committee_indices, committee_bits):
if not bit:
continue
inclusion_reward = compute_sync_committee_inclusion_reward(
spec, state, index, committee_indices, committee_bits,
)
proposer_reward_denominator = (
(spec.WEIGHT_DENOMINATOR - spec.PROPOSER_WEIGHT)
* spec.WEIGHT_DENOMINATOR
// spec.PROPOSER_WEIGHT
)
proposer_reward += spec.Gwei((inclusion_reward * spec.WEIGHT_DENOMINATOR) // proposer_reward_denominator)
return proposer_reward
def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indices, committee_bits, proposer_index):
for index in range(len(post_state.validators)):
reward = 0
penalty = 0
if index in committee_indices:
reward += compute_sync_committee_participant_reward(
_reward, _penalty = compute_sync_committee_participant_reward_and_penalty(
spec,
pre_state,
index,
committee_indices,
committee_bits,
)
reward += _reward
penalty += _penalty
if proposer_index == index:
reward += compute_sync_committee_proposer_reward(
@ -183,7 +131,7 @@ def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indic
committee_bits,
)
assert post_state.balances[index] == pre_state.balances[index] + reward
assert post_state.balances[index] == pre_state.balances[index] + reward - penalty
def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits):

View File

@ -2,6 +2,10 @@ from eth2spec.test.context import is_post_altair
from eth2spec.test.helpers.block_header import sign_block_header
from eth2spec.test.helpers.keys import pubkey_to_privkey
from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.sync_committee import (
compute_committee_indices,
compute_sync_committee_participant_reward_and_penalty,
)
def get_min_slashing_penalty_quotient(spec):
@ -11,7 +15,7 @@ def get_min_slashing_penalty_quotient(spec):
return spec.MIN_SLASHING_PENALTY_QUOTIENT
def check_proposer_slashing_effect(spec, pre_state, state, slashed_index):
def check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block=None):
slashed_validator = state.validators[slashed_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
@ -20,24 +24,51 @@ def check_proposer_slashing_effect(spec, pre_state, state, slashed_index):
proposer_index = spec.get_beacon_proposer_index(state)
slash_penalty = state.validators[slashed_index].effective_balance // get_min_slashing_penalty_quotient(spec)
whistleblower_reward = state.validators[slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT
# Altair introduces sync committee (SC) reward and penalty
sc_reward_for_slashed = sc_penalty_for_slashed = sc_reward_for_proposer = sc_penalty_for_proposer = 0
if is_post_altair(spec) and block is not None:
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_bits = block.body.sync_aggregate.sync_committee_bits
sc_reward_for_slashed, sc_penalty_for_slashed = compute_sync_committee_participant_reward_and_penalty(
spec,
pre_state,
slashed_index,
committee_indices,
committee_bits,
)
sc_reward_for_proposer, sc_penalty_for_proposer = compute_sync_committee_participant_reward_and_penalty(
spec,
pre_state,
proposer_index,
committee_indices,
committee_bits,
)
if proposer_index != slashed_index:
# slashed validator lost initial slash penalty
assert (
get_balance(state, slashed_index)
== get_balance(pre_state, slashed_index) - slash_penalty
== get_balance(pre_state, slashed_index) - slash_penalty + sc_reward_for_slashed - sc_penalty_for_slashed
)
# block proposer gained whistleblower reward
# >= because proposer could have reported multiple
assert (
get_balance(state, proposer_index)
>= get_balance(pre_state, proposer_index) + whistleblower_reward
>= (
get_balance(pre_state, proposer_index) + whistleblower_reward
+ sc_reward_for_proposer - sc_penalty_for_proposer
)
)
else:
# proposer reported themself so get penalty and reward
# >= because proposer could have reported multiple
assert (
get_balance(state, slashed_index)
>= get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward
>= (
get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward
+ sc_reward_for_slashed - sc_penalty_for_slashed
)
)

View File

@ -1,3 +1,5 @@
from collections import Counter
from eth2spec.test.helpers.keys import privkeys
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
@ -33,3 +35,42 @@ def compute_aggregate_sync_committee_signature(spec, state, slot, participants,
)
)
return bls.Aggregate(signatures)
def compute_sync_committee_inclusion_reward(spec, state):
total_active_increments = spec.get_total_active_balance(state) // spec.EFFECTIVE_BALANCE_INCREMENT
total_base_rewards = spec.get_base_reward_per_increment(state) * total_active_increments
max_participant_rewards = (total_base_rewards * spec.SYNC_REWARD_WEIGHT
// spec.WEIGHT_DENOMINATOR // spec.SLOTS_PER_EPOCH)
return max_participant_rewards // spec.SYNC_COMMITTEE_SIZE
def compute_sync_committee_participant_reward_and_penalty(
spec, state, participant_index, committee_indices, committee_bits):
inclusion_reward = compute_sync_committee_inclusion_reward(spec, state)
included_indices = [index for index, bit in zip(committee_indices, committee_bits) if bit]
not_included_indices = [index for index, bit in zip(committee_indices, committee_bits) if not bit]
included_multiplicities = Counter(included_indices)
not_included_multiplicities = Counter(not_included_indices)
return (
spec.Gwei(inclusion_reward * included_multiplicities[participant_index]),
spec.Gwei(inclusion_reward * not_included_multiplicities[participant_index])
)
def compute_sync_committee_proposer_reward(spec, state, committee_indices, committee_bits):
proposer_reward_denominator = spec.WEIGHT_DENOMINATOR - spec.PROPOSER_WEIGHT
inclusion_reward = compute_sync_committee_inclusion_reward(spec, state)
participant_number = committee_bits.count(True)
participant_reward = inclusion_reward * spec.PROPOSER_WEIGHT // proposer_reward_denominator
return spec.Gwei(participant_reward * participant_number)
def compute_committee_indices(spec, state, committee):
"""
Given a ``committee``, calculate and return the related indices
"""
all_pubkeys = [v.pubkey for v in state.validators]
committee_indices = [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
return committee_indices

View File

@ -24,6 +24,10 @@ from eth2spec.test.helpers.multi_operations import (
run_slash_and_exit,
run_test_full_random_operations,
)
from eth2spec.test.helpers.sync_committee import (
compute_committee_indices,
compute_sync_committee_participant_reward_and_penalty,
)
from eth2spec.test.helpers.constants import PHASE0, MINIMAL
from eth2spec.test.context import (
spec_test, spec_state_test, dump_skipping_message,
@ -416,7 +420,7 @@ def test_proposer_slashing(spec, state):
yield 'blocks', [signed_block]
yield 'post', state
check_proposer_slashing_effect(spec, pre_state, state, slashed_index)
check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block)
@with_all_phases
@ -491,7 +495,7 @@ def test_multiple_different_proposer_slashings_same_block(spec, state):
for proposer_slashing in proposer_slashings:
slashed_index = proposer_slashing.signed_header_1.message.proposer_index
check_proposer_slashing_effect(spec, pre_state, state, slashed_index)
check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block)
def check_attester_slashing_effect(spec, pre_state, state, slashed_indices):
@ -743,7 +747,8 @@ def test_deposit_top_up(spec, state):
initial_balances_len = len(state.balances)
validator_pre_balance = get_balance(state, validator_index)
yield 'pre', state
pre_state = state.copy()
yield 'pre', pre_state
block = build_empty_block_for_next_slot(spec, state)
block.body.deposits.append(deposit)
@ -755,7 +760,23 @@ def test_deposit_top_up(spec, state):
assert len(state.validators) == initial_registry_len
assert len(state.balances) == initial_balances_len
assert get_balance(state, validator_index) == validator_pre_balance + amount
# Altair introduces sync committee (sm) reward and penalty
sync_committee_reward = sync_committee_penalty = 0
if is_post_altair(spec):
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
committee_bits = block.body.sync_aggregate.sync_committee_bits
sync_committee_reward, sync_committee_penalty = compute_sync_committee_participant_reward_and_penalty(
spec,
pre_state,
validator_index,
committee_indices,
committee_bits,
)
assert get_balance(state, validator_index) == (
validator_pre_balance + amount + sync_committee_reward - sync_committee_penalty
)
@with_all_phases