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:
parent
9b89f58089
commit
ea9ceb693a
|
@ -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*(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 \{ \
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue