update ChainDAG.effective_balance() to use StateData; rm ChainDAG.getBlockByPreciseSlot() (#2622)

* update ChainDAG.effective_balance() to use StateData; rm unused ChainDAG.getBlockByPreciseSlot()

* update get_effective_balances to avoid god object; avoid most memory allocation in Altair epoch reward and penalty processing
This commit is contained in:
tersec 2021-06-01 12:40:13 +00:00 committed by GitHub
parent 9b89f58089
commit ea9ceb693a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 61 deletions

View File

@ -100,19 +100,18 @@ func parentOrSlot*(bs: BlockSlot): BlockSlot =
else:
BlockSlot(blck: bs.blck, slot: bs.slot - 1)
func get_effective_balances*(state: BeaconState): seq[Gwei] =
func get_effective_balances(validators: openArray[Validator], epoch: Epoch):
seq[Gwei] =
## Get the balances from a state as counted for fork choice
result.newSeq(state.validators.len) # zero-init
let epoch = state.get_current_epoch()
result.newSeq(validators.len) # zero-init
for i in 0 ..< result.len:
# All non-active validators have a 0 balance
let validator = unsafeAddr state.validators[i]
let validator = unsafeAddr validators[i]
if validator[].is_active_validator(epoch):
result[i] = validator[].effective_balance
proc init*(
func init(
T: type EpochRef, state: StateData, cache: var StateCache,
prevEpoch: EpochRef): T =
let
@ -181,8 +180,8 @@ proc init*(
epochRef.effective_balances_bytes =
snappyEncode(SSZ.encode(
List[Gwei, Limit VALIDATOR_REGISTRY_LIMIT](
get_effective_balances(state.data.data))))
List[Gwei, Limit VALIDATOR_REGISTRY_LIMIT](get_effective_balances(
getStateField(state, validators).asSeq, get_current_epoch(state)))))
epochRef
@ -256,7 +255,7 @@ func atEpochStart*(blck: BlockRef, epoch: Epoch): BlockSlot =
## Return the BlockSlot corresponding to the first slot in the given epoch
atSlot(blck, epoch.compute_start_slot_at_epoch)
func atEpochEnd*(blck: BlockRef, epoch: Epoch): BlockSlot =
func atEpochEnd(blck: BlockRef, epoch: Epoch): BlockSlot =
## Return the BlockSlot corresponding to the last slot in the given epoch
atSlot(blck, (epoch + 1).compute_start_slot_at_epoch - 1)
@ -285,7 +284,7 @@ func findEpochRef*(
return nil
proc loadStateCache*(
func loadStateCache(
dag: ChainDAGRef, cache: var StateCache, blck: BlockRef, epoch: Epoch) =
# When creating a state cache, we want the current and the previous epoch
# information to be preloaded as both of these are used in state transition
@ -467,7 +466,7 @@ proc init*(T: type ChainDAGRef,
res
proc getEpochRef*(
func getEpochRef*(
dag: ChainDAGRef, state: StateData, cache: var StateCache): EpochRef =
let
blck = state.blck
@ -575,7 +574,7 @@ proc getState(dag: ChainDAGRef, state: var StateData, bs: BlockSlot): bool =
false
proc putState*(dag: ChainDAGRef, state: var StateData) =
proc putState(dag: ChainDAGRef, state: var StateData) =
# Store a state and its root
logScope:
blck = shortLog(state.blck)
@ -671,12 +670,6 @@ func getBlockBySlot*(dag: ChainDAGRef, slot: Slot): BlockRef =
## with slot number less or equal to `slot`.
dag.head.atSlot(slot).blck
func getBlockByPreciseSlot*(dag: ChainDAGRef, slot: Slot): BlockRef =
## Retrieves a block from the canonical chain with a slot
## number equal to `slot`.
let found = dag.getBlockBySlot(slot)
if found.slot != slot: found else: nil
proc get*(dag: ChainDAGRef, blck: BlockRef): BlockData =
## Retrieve the associated block body of a block reference
doAssert (not blck.isNil), "Trying to get nil BlockRef"
@ -1160,7 +1153,7 @@ proc preInit*(
db.putStateRoot(genesisBlock.root, GENESIS_SLOT, genesisBlock.message.state_root)
db.putGenesisBlockRoot(genesisBlock.root)
proc setTailState*(dag: ChainDAGRef,
func setTailState*(dag: ChainDAGRef,
checkpointState: BeaconState,
checkpointBlock: TrustedSignedBeaconBlock) =
# TODO(zah)
@ -1171,7 +1164,7 @@ proc setTailState*(dag: ChainDAGRef,
proc getGenesisBlockData*(dag: ChainDAGRef): BlockData =
dag.get(dag.genesis)
proc getGenesisBlockSlot*(dag: ChainDAGRef): BlockSlot =
func getGenesisBlockSlot*(dag: ChainDAGRef): BlockSlot =
BlockSlot(blck: dag.genesis, slot: GENESIS_SLOT)
proc getProposer*(

View File

@ -46,6 +46,9 @@ const
PROPOSER_WEIGHT* = 8
WEIGHT_DENOMINATOR* = 64
PARTICIPATION_FLAG_WEIGHTS* =
[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT]
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/validator.md#misc
TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE* = 4
SYNC_COMMITTEE_SUBNET_COUNT* = 4
@ -73,8 +76,6 @@ let
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0])
PARTICIPATION_FLAG_WEIGHTS* =
[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT]
type
### New types

View File

@ -582,59 +582,68 @@ func get_base_reward(
increments * get_base_reward_per_increment(state, total_active_balance)
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#get_flag_index_deltas
proc get_flag_index_deltas(
iterator get_flag_index_deltas(
state: altair.BeaconState, flag_index: int, total_active_balance: Gwei):
(seq[Gwei], seq[Gwei]) =
(ValidatorIndex, Gwei, Gwei) =
## Return the deltas for a given ``flag_index`` by scanning through the
## participation flags.
var
rewards = repeat(Gwei(0), len(state.validators))
penalties = repeat(Gwei(0), len(state.validators))
let
previous_epoch = get_previous_epoch(state)
unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch)
unslashed_participating_indices =
get_unslashed_participating_indices(state, flag_index, previous_epoch)
weight = PARTICIPATION_FLAG_WEIGHTS[flag_index].uint64 # safe
unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices)
unslashed_participating_increments = unslashed_participating_balance div EFFECTIVE_BALANCE_INCREMENT
unslashed_participating_balance =
get_total_balance(state, unslashed_participating_indices)
unslashed_participating_increments =
unslashed_participating_balance div EFFECTIVE_BALANCE_INCREMENT
active_increments = total_active_balance div EFFECTIVE_BALANCE_INCREMENT
for index in 0 ..< state.validators.len:
# TODO Obviously not great
let v = state.validators[index]
if not (is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch)):
if not (is_active_validator(v, previous_epoch) or
(v.slashed and previous_epoch + 1 < v.withdrawable_epoch)):
continue
let base_reward = get_base_reward(state, index.ValidatorIndex, total_active_balance)
if index.ValidatorIndex in unslashed_participating_indices:
if not is_in_inactivity_leak(state):
let reward_numerator = base_reward * weight * unslashed_participating_increments
rewards[index] += Gwei(reward_numerator div (active_increments * WEIGHT_DENOMINATOR))
elif flag_index != TIMELY_HEAD_FLAG_INDEX:
penalties[index] += Gwei(base_reward * weight div WEIGHT_DENOMINATOR)
(rewards, penalties)
template vidx: ValidatorIndex = index.ValidatorIndex
let base_reward = get_base_reward(state, vidx, total_active_balance)
yield
if vidx in unslashed_participating_indices:
if not is_in_inactivity_leak(state):
let reward_numerator =
base_reward * weight * unslashed_participating_increments
(vidx, reward_numerator div (active_increments * WEIGHT_DENOMINATOR), 0.Gwei)
else:
(vidx, 0.Gwei, 0.Gwei)
elif flag_index != TIMELY_HEAD_FLAG_INDEX:
(vidx, 0.Gwei, base_reward * weight div WEIGHT_DENOMINATOR)
else:
(vidx, 0.Gwei, 0.Gwei)
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#modified-get_inactivity_penalty_deltas
func get_inactivity_penalty_deltas(state: altair.BeaconState): (seq[Gwei], seq[Gwei]) =
iterator get_inactivity_penalty_deltas(state: altair.BeaconState):
(ValidatorIndex, Gwei) =
## Return the inactivity penalty deltas by considering timely target
## participation flags and inactivity scores.
var
rewards = repeat(Gwei(0), len(state.validators))
penalties = repeat(Gwei(0), len(state.validators))
let
previous_epoch = get_previous_epoch(state)
matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
matching_target_indices =
get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch)
for index in 0 ..< state.validators.len:
# get_eligible_validator_indices()
let v = state.validators[index]
if not (is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch)):
if not (is_active_validator(v, previous_epoch) or
(v.slashed and previous_epoch + 1 < v.withdrawable_epoch)):
continue
if not (index.ValidatorIndex in matching_target_indices):
template vidx: untyped = index.ValidatorIndex
if not (vidx in matching_target_indices):
const penalty_denominator =
INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR
let
penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index]
penalty_denominator = uint64(INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR)
penalties[index] += Gwei(penalty_numerator div penalty_denominator)
(rewards, penalties)
penalty_numerator = state.validators[index].effective_balance *
state.inactivity_scores[index]
yield (vidx, Gwei(penalty_numerator div penalty_denominator))
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#process_rewards_and_penalties
func process_rewards_and_penalties(
@ -664,14 +673,27 @@ proc process_rewards_and_penalties(
return
# TODO assess relevance of missing phase0 optimizations
var deltas = mapIt(
0 ..< PARTICIPATION_FLAG_WEIGHTS.len,
get_flag_index_deltas(state, it, total_active_balance))
deltas.add get_inactivity_penalty_deltas(state)
for (rewards, penalties) in deltas:
for index in 0 ..< len(state.validators):
increase_balance(state, ValidatorIndex(index), rewards[index])
decrease_balance(state, ValidatorIndex(index), penalties[index])
# TODO probably both of these aren't necessary, but need to verify
# commutativity & associativity. Probably, since active validators
# get ejected at 16 Gwei, either it avoids over or underflow there
# or doesn't receive rewards or penalties so both are 0. But start
# with this.
var
rewards = newSeq[Gwei](state.validators.len)
penalties = newSeq[Gwei](state.validators.len)
for flag_index in 0 ..< PARTICIPATION_FLAG_WEIGHTS.len:
for validator_index, reward, penalty in get_flag_index_deltas(
state, flag_index, total_active_balance):
rewards[validator_index] += reward
penalties[validator_index] += penalty
for validator_index, penalty in get_inactivity_penalty_deltas(state):
penalties[validator_index] += penalty
for index in 0 ..< len(state.validators):
increase_balance(state, ValidatorIndex(index), rewards[index])
decrease_balance(state, ValidatorIndex(index), penalties[index])
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#slashings
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#slashings

View File

@ -1,6 +1,6 @@
# How to run Catalyst
- Clone Geth master into ~/client/catalyst: `git clone https://github.com/ethereum/go-ethereum ~/client/catalyst`
- Clone Geth master into ~/client/catalyst: `git clone https://github.com/ethereum/go-ethereum ~/clients/catalyst`
- Build Geth and Catalyst with `go build -o ./build/bin/catalyst ./cmd/geth`
- Run `scripts/run-catalyst.sh` to run Catalyst. It listens on port 8545.

View File

@ -3,7 +3,7 @@
# https://github.com/prysmaticlabs/bazel-go-ethereum/blob/catalyst/run-catalyst.sh
# To increase verbosity: debug.verbosity(4)
# MetaMask seed phrase for account with balance is:
# MetaMask seed phrase for address with balance is:
# lecture manual soon title cloth uncle gesture cereal common fruit tooth crater
echo \{ \

View File

@ -74,6 +74,5 @@ suite "Official - Phase 0 - Sanity - Blocks " & preset():
runTest("Official - Phase 0 - Sanity - Blocks", SanityBlocksDir, path)
suite "Official - Phase 0 - Finality " & preset():
# these seem to only exist in minimal presets
for kind, path in walkDir(FinalityDir, true):
runTest("Official - Phase 0 - Finality", FinalityDir, path)