Precise per-component ETH-denominated rewards tracking
This is an alternative take on https://github.com/status-im/nimbus-eth2/pull/3107 that aims for more minimal interventions in the spec modules at the expense of duplicating more of the spec logic in ncli_db.
This commit is contained in:
parent
4e2d2ff7f4
commit
29aad0241b
|
@ -76,7 +76,7 @@ type
|
|||
## database - this may have a number of "natural" causes such as switching
|
||||
## between different versions of the client and accidentally using an old
|
||||
## database.
|
||||
db: SqStoreRef
|
||||
db*: SqStoreRef
|
||||
|
||||
v0: BeaconChainDBV0
|
||||
genesisDeposits*: DepositsSeq
|
||||
|
@ -269,25 +269,27 @@ proc loadImmutableValidators(vals: DbSeq[ImmutableValidatorDataDb2]): seq[Immuta
|
|||
proc new*(T: type BeaconChainDB,
|
||||
dir: string,
|
||||
inMemory = false,
|
||||
readOnly = false
|
||||
): BeaconChainDB =
|
||||
var db = if inMemory:
|
||||
SqStoreRef.init("", "test", inMemory = true).expect(
|
||||
SqStoreRef.init("", "test", readOnly = readOnly, inMemory = true).expect(
|
||||
"working database (out of memory?)")
|
||||
else:
|
||||
let s = secureCreatePath(dir)
|
||||
doAssert s.isOk # TODO(zah) Handle this in a better way
|
||||
|
||||
SqStoreRef.init(
|
||||
dir, "nbc", manualCheckpoint = true).expectDb()
|
||||
dir, "nbc", readOnly = readOnly, manualCheckpoint = true).expectDb()
|
||||
|
||||
# Remove the deposits table we used before we switched
|
||||
# to storing only deposit contract checkpoints
|
||||
if db.exec("DROP TABLE IF EXISTS deposits;").isErr:
|
||||
debug "Failed to drop the deposits table"
|
||||
if not readOnly:
|
||||
# Remove the deposits table we used before we switched
|
||||
# to storing only deposit contract checkpoints
|
||||
if db.exec("DROP TABLE IF EXISTS deposits;").isErr:
|
||||
debug "Failed to drop the deposits table"
|
||||
|
||||
# An old pubkey->index mapping that hasn't been used on any mainnet release
|
||||
if db.exec("DROP TABLE IF EXISTS validatorIndexFromPubKey;").isErr:
|
||||
debug "Failed to drop the validatorIndexFromPubKey table"
|
||||
# An old pubkey->index mapping that hasn't been used on any mainnet release
|
||||
if db.exec("DROP TABLE IF EXISTS validatorIndexFromPubKey;").isErr:
|
||||
debug "Failed to drop the validatorIndexFromPubKey table"
|
||||
|
||||
var
|
||||
# V0 compatibility tables - these were created WITHOUT ROWID which is slow
|
||||
|
|
|
@ -104,10 +104,8 @@ type
|
|||
## Per-epoch sums of slashed effective balances
|
||||
|
||||
# Participation
|
||||
previous_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
current_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
previous_epoch_participation*: EpochParticipationFlags
|
||||
current_epoch_participation*: EpochParticipationFlags
|
||||
|
||||
# Finality
|
||||
justification_bits*: JustificationBits
|
||||
|
@ -163,10 +161,8 @@ type
|
|||
## Per-epoch sums of slashed effective balances
|
||||
|
||||
# Participation
|
||||
previous_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
current_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
previous_epoch_participation*: EpochParticipationFlags
|
||||
current_epoch_participation*: EpochParticipationFlags
|
||||
|
||||
# Finality
|
||||
justification_bits*: JustificationBits
|
||||
|
|
|
@ -561,9 +561,10 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
|
|||
# Pruning metadata
|
||||
dag.lastPrunePoint = dag.finalizedHead
|
||||
|
||||
# Fill validator key cache in case we're loading an old database that doesn't
|
||||
# have a cache
|
||||
dag.updateValidatorKeys(getStateField(dag.headState.data, validators).asSeq())
|
||||
if not dag.db.db.readOnly:
|
||||
# Fill validator key cache in case we're loading an old database that doesn't
|
||||
# have a cache
|
||||
dag.updateValidatorKeys(getStateField(dag.headState.data, validators).asSeq())
|
||||
|
||||
withState(dag.headState.data):
|
||||
when stateFork >= BeaconStateFork.Altair:
|
||||
|
|
|
@ -121,7 +121,40 @@ func initiate_validator_exit*(cfg: RuntimeConfig, state: var ForkyBeaconState,
|
|||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#modified-slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.7/specs/merge/beacon-chain.md#modified-slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/merge/beacon-chain.md#modified-slash_validator
|
||||
proc get_slashing_penalty*(state: ForkyBeaconState,
|
||||
validator_effective_balance: Gwei): Gwei =
|
||||
# TODO Consider whether this is better than splitting the functions apart; in
|
||||
# each case, tradeoffs. Here, it's just changing a couple of constants.
|
||||
when state is phase0.BeaconState:
|
||||
validator_effective_balance div MIN_SLASHING_PENALTY_QUOTIENT
|
||||
elif state is altair.BeaconState:
|
||||
validator_effective_balance div MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR
|
||||
elif state is merge.BeaconState:
|
||||
validator_effective_balance div MIN_SLASHING_PENALTY_QUOTIENT_MERGE
|
||||
else:
|
||||
raiseAssert "invalid BeaconState type"
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/phase0/beacon-chain.md#slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/altair/beacon-chain.md#modified-slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/merge/beacon-chain.md#modified-slash_validator
|
||||
proc get_whistleblower_reward*(validator_effective_balance: Gwei): Gwei =
|
||||
validator_effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/phase0/beacon-chain.md#slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/altair/beacon-chain.md#modified-slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/merge/beacon-chain.md#modified-slash_validator
|
||||
proc get_proposer_reward(state: ForkyBeaconState, whistleblower_reward: Gwei): Gwei =
|
||||
when state is phase0.BeaconState:
|
||||
whistleblower_reward div PROPOSER_REWARD_QUOTIENT
|
||||
elif state is altair.BeaconState or state is merge.BeaconState:
|
||||
whistleblower_reward * PROPOSER_WEIGHT div WEIGHT_DENOMINATOR
|
||||
else:
|
||||
raiseAssert "invalid BeaconState type"
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/phase0/beacon-chain.md#slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/altair/beacon-chain.md#modified-slash_validator
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.6/specs/merge/beacon-chain.md#modified-slash_validator
|
||||
proc slash_validator*(
|
||||
cfg: RuntimeConfig, state: var ForkyBeaconState,
|
||||
slashed_index: ValidatorIndex, cache: var StateCache) =
|
||||
|
@ -145,19 +178,8 @@ proc slash_validator*(
|
|||
state.slashings[int(epoch mod EPOCHS_PER_SLASHINGS_VECTOR)] +=
|
||||
validator.effective_balance
|
||||
|
||||
# TODO Consider whether this is better than splitting the functions apart; in
|
||||
# each case, tradeoffs. Here, it's just changing a couple of constants.
|
||||
when state is phase0.BeaconState:
|
||||
decrease_balance(state, slashed_index,
|
||||
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
|
||||
elif state is altair.BeaconState:
|
||||
decrease_balance(state, slashed_index,
|
||||
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR)
|
||||
elif state is bellatrix.BeaconState:
|
||||
decrease_balance(state, slashed_index,
|
||||
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT_MERGE)
|
||||
else:
|
||||
raiseAssert "invalid BeaconState type"
|
||||
decrease_balance(state, slashed_index,
|
||||
get_slashing_penalty(state, validator.effective_balance))
|
||||
|
||||
# The rest doesn't make sense without there being any proposer index, so skip
|
||||
let proposer_index = get_beacon_proposer_index(state, cache)
|
||||
|
@ -169,15 +191,8 @@ proc slash_validator*(
|
|||
let
|
||||
# Spec has whistleblower_index as optional param, but it's never used.
|
||||
whistleblower_index = proposer_index.get
|
||||
whistleblower_reward =
|
||||
(validator.effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT).Gwei
|
||||
proposer_reward =
|
||||
when state is phase0.BeaconState:
|
||||
whistleblower_reward div PROPOSER_REWARD_QUOTIENT
|
||||
elif state is altair.BeaconState or state is bellatrix.BeaconState:
|
||||
whistleblower_reward * PROPOSER_WEIGHT div WEIGHT_DENOMINATOR
|
||||
else:
|
||||
raiseAssert "invalid BeaconState type"
|
||||
whistleblower_reward = get_whistleblower_reward(validator.effective_balance)
|
||||
proposer_reward = get_proposer_reward(state, whistleblower_reward)
|
||||
|
||||
increase_balance(state, proposer_index.get, proposer_reward)
|
||||
# TODO: evaluate if spec bug / underflow can be triggered
|
||||
|
@ -626,6 +641,31 @@ proc check_attestation*(
|
|||
|
||||
ok()
|
||||
|
||||
proc get_proposer_reward*(state: ForkyBeaconState,
|
||||
attestation: SomeAttestation,
|
||||
base_reward_per_increment: Gwei,
|
||||
cache: var StateCache,
|
||||
epoch_participation: var EpochParticipationFlags): uint64 =
|
||||
let participation_flag_indices = get_attestation_participation_flag_indices(
|
||||
state, attestation.data, state.slot - attestation.data.slot)
|
||||
for index in get_attesting_indices(
|
||||
state, attestation.data, attestation.aggregation_bits, cache):
|
||||
for flag_index, weight in PARTICIPATION_FLAG_WEIGHTS:
|
||||
if flag_index in participation_flag_indices and
|
||||
not has_flag(epoch_participation.asSeq[index], flag_index):
|
||||
epoch_participation.asSeq[index] =
|
||||
add_flag(epoch_participation.asSeq[index], flag_index)
|
||||
# these are all valid; TODO statically verify or do it type-safely
|
||||
result += get_base_reward(
|
||||
state, index, base_reward_per_increment) * weight.uint64
|
||||
epoch_participation.clearCache()
|
||||
|
||||
let proposer_reward_denominator =
|
||||
(WEIGHT_DENOMINATOR.uint64 - PROPOSER_WEIGHT.uint64) *
|
||||
WEIGHT_DENOMINATOR.uint64 div PROPOSER_WEIGHT.uint64
|
||||
|
||||
return result div proposer_reward_denominator
|
||||
|
||||
proc process_attestation*(
|
||||
state: var ForkyBeaconState, attestation: SomeAttestation, flags: UpdateFlags,
|
||||
base_reward_per_increment: Gwei, cache: var StateCache):
|
||||
|
@ -659,31 +699,8 @@ proc process_attestation*(
|
|||
|
||||
# Altair and Merge
|
||||
template updateParticipationFlags(epoch_participation: untyped) =
|
||||
var proposer_reward_numerator = 0'u64
|
||||
|
||||
# Participation flag indices
|
||||
let participation_flag_indices =
|
||||
get_attestation_participation_flag_indices(
|
||||
state, attestation.data, state.slot - attestation.data.slot)
|
||||
|
||||
for index in get_attesting_indices(
|
||||
state, attestation.data, attestation.aggregation_bits, cache):
|
||||
for flag_index, weight in PARTICIPATION_FLAG_WEIGHTS:
|
||||
if flag_index in participation_flag_indices and
|
||||
not has_flag(epoch_participation.asSeq[index], flag_index):
|
||||
epoch_participation.asSeq[index] =
|
||||
add_flag(epoch_participation.asSeq[index], flag_index)
|
||||
|
||||
# these are all valid; TODO statically verify or do it type-safely
|
||||
proposer_reward_numerator += get_base_reward(
|
||||
state, index, base_reward_per_increment) * weight.uint64
|
||||
epoch_participation.clearCache()
|
||||
|
||||
# Reward proposer
|
||||
let
|
||||
# TODO use correct type at source
|
||||
proposer_reward_denominator = (WEIGHT_DENOMINATOR.uint64 - PROPOSER_WEIGHT.uint64) * WEIGHT_DENOMINATOR.uint64 div PROPOSER_WEIGHT.uint64
|
||||
proposer_reward = Gwei(proposer_reward_numerator div proposer_reward_denominator)
|
||||
let proposer_reward = get_proposer_reward(
|
||||
state, attestation, base_reward_per_increment, cache, epoch_participation)
|
||||
increase_balance(state, proposer_index.get, proposer_reward)
|
||||
|
||||
when state is phase0.BeaconState:
|
||||
|
@ -780,8 +797,7 @@ func translate_participation(
|
|||
|
||||
proc upgrade_to_altair*(cfg: RuntimeConfig, pre: phase0.BeaconState): ref altair.BeaconState =
|
||||
var
|
||||
empty_participation =
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]()
|
||||
empty_participation = EpochParticipationFlags()
|
||||
inactivity_scores = HashList[uint64, Limit VALIDATOR_REGISTRY_LIMIT]()
|
||||
|
||||
doAssert empty_participation.data.setLen(pre.validators.len)
|
||||
|
|
|
@ -81,6 +81,9 @@ type
|
|||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#custom-types
|
||||
ParticipationFlags* = uint8
|
||||
|
||||
EpochParticipationFlags* =
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#syncaggregate
|
||||
SyncAggregate* = object
|
||||
sync_committee_bits*: BitArray[SYNC_COMMITTEE_SIZE]
|
||||
|
@ -225,10 +228,8 @@ type
|
|||
## Per-epoch sums of slashed effective balances
|
||||
|
||||
# Participation
|
||||
previous_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
current_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
previous_epoch_participation*: EpochParticipationFlags
|
||||
current_epoch_participation*: EpochParticipationFlags
|
||||
|
||||
# Finality
|
||||
justification_bits*: JustificationBits
|
||||
|
|
|
@ -116,10 +116,8 @@ type
|
|||
## Per-epoch sums of slashed effective balances
|
||||
|
||||
# Participation
|
||||
previous_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
current_epoch_participation*:
|
||||
HashList[ParticipationFlags, Limit VALIDATOR_REGISTRY_LIMIT]
|
||||
previous_epoch_participation*: EpochParticipationFlags
|
||||
current_epoch_participation*: EpochParticipationFlags
|
||||
|
||||
# Finality
|
||||
justification_bits*: JustificationBits
|
||||
|
|
|
@ -234,9 +234,11 @@ template withState*(x: ForkedHashedBeaconState, body: untyped): untyped =
|
|||
template withEpochInfo*(x: ForkedEpochInfo, body: untyped): untyped =
|
||||
case x.kind
|
||||
of EpochInfoFork.Phase0:
|
||||
const infoFork {.inject.} = EpochInfoFork.Phase0
|
||||
template info: untyped {.inject.} = x.phase0Data
|
||||
body
|
||||
of EpochInfoFork.Altair:
|
||||
const infoFork {.inject.} = EpochInfoFork.Altair
|
||||
template info: untyped {.inject.} = x.altairData
|
||||
body
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ func is_slashable_validator(validator: Validator, epoch: Epoch): bool =
|
|||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#proposer-slashings
|
||||
proc check_proposer_slashing*(
|
||||
state: var ForkyBeaconState, proposer_slashing: SomeProposerSlashing,
|
||||
state: ForkyBeaconState, proposer_slashing: SomeProposerSlashing,
|
||||
flags: UpdateFlags):
|
||||
Result[void, cstring] =
|
||||
|
||||
|
@ -198,7 +198,7 @@ func is_slashable_attestation_data(
|
|||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#attester-slashings
|
||||
proc check_attester_slashing*(
|
||||
state: var ForkyBeaconState,
|
||||
state: ForkyBeaconState,
|
||||
attester_slashing: SomeAttesterSlashing,
|
||||
flags: UpdateFlags
|
||||
): Result[seq[ValidatorIndex], cstring] =
|
||||
|
@ -255,6 +255,17 @@ proc process_attester_slashing*(
|
|||
|
||||
ok()
|
||||
|
||||
proc findValidatorIndex*(state: ForkyBeaconState, pubkey: ValidatorPubKey): int =
|
||||
# This linear scan is unfortunate, but should be fairly fast as we do a simple
|
||||
# byte comparison of the key. The alternative would be to build a Table, but
|
||||
# given that each block can hold no more than 16 deposits, it's slower to
|
||||
# build the table and use it for lookups than to scan it like this.
|
||||
# Once we have a reusable, long-lived cache, this should be revisited
|
||||
for i in 0 ..< state.validators.len:
|
||||
if state.validators.asSeq[i].pubkey == pubkey:
|
||||
return i
|
||||
return -1
|
||||
|
||||
proc process_deposit*(cfg: RuntimeConfig,
|
||||
state: var ForkyBeaconState,
|
||||
deposit: Deposit,
|
||||
|
@ -277,18 +288,7 @@ proc process_deposit*(cfg: RuntimeConfig,
|
|||
let
|
||||
pubkey = deposit.data.pubkey
|
||||
amount = deposit.data.amount
|
||||
|
||||
var index = -1
|
||||
|
||||
# This linear scan is unfortunate, but should be fairly fast as we do a simple
|
||||
# byte comparison of the key. The alternative would be to build a Table, but
|
||||
# given that each block can hold no more than 16 deposits, it's slower to
|
||||
# build the table and use it for lookups than to scan it like this.
|
||||
# Once we have a reusable, long-lived cache, this should be revisited
|
||||
for i in 0..<state.validators.len():
|
||||
if state.validators.asSeq()[i].pubkey == pubkey:
|
||||
index = i
|
||||
break
|
||||
index = findValidatorIndex(state, pubkey)
|
||||
|
||||
if index != -1:
|
||||
# Increase balance by deposit amount
|
||||
|
@ -425,7 +425,22 @@ proc process_operations(cfg: RuntimeConfig,
|
|||
|
||||
ok()
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#sync-aggregate-processing
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#sync-committee-processing
|
||||
func get_participant_reward*(total_active_balance: Gwei): Gwei =
|
||||
let
|
||||
total_active_increments =
|
||||
total_active_balance div EFFECTIVE_BALANCE_INCREMENT
|
||||
total_base_rewards =
|
||||
get_base_reward_per_increment(total_active_balance) * total_active_increments
|
||||
max_participant_rewards =
|
||||
total_base_rewards * SYNC_REWARD_WEIGHT div WEIGHT_DENOMINATOR div SLOTS_PER_EPOCH
|
||||
return max_participant_rewards div SYNC_COMMITTEE_SIZE
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#sync-committee-processing
|
||||
func get_proposer_reward*(participant_reward: Gwei): Gwei =
|
||||
participant_reward * PROPOSER_WEIGHT div (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#sync-committee-processing
|
||||
proc process_sync_aggregate*(
|
||||
state: var (altair.BeaconState | bellatrix.BeaconState),
|
||||
sync_aggregate: SomeSyncAggregate, total_active_balance: Gwei,
|
||||
|
@ -457,15 +472,8 @@ proc process_sync_aggregate*(
|
|||
|
||||
# Compute participant and proposer rewards
|
||||
let
|
||||
total_active_increments =
|
||||
total_active_balance div EFFECTIVE_BALANCE_INCREMENT
|
||||
total_base_rewards =
|
||||
get_base_reward_per_increment(total_active_balance) * total_active_increments
|
||||
max_participant_rewards =
|
||||
total_base_rewards * SYNC_REWARD_WEIGHT div WEIGHT_DENOMINATOR div SLOTS_PER_EPOCH
|
||||
participant_reward = max_participant_rewards div SYNC_COMMITTEE_SIZE
|
||||
proposer_reward =
|
||||
participant_reward * PROPOSER_WEIGHT div (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)
|
||||
participant_reward = get_participant_reward(total_active_balance)
|
||||
proposer_reward = state_transition_block.get_proposer_reward(participant_reward)
|
||||
proposer_index = get_beacon_proposer_index(state, cache)
|
||||
|
||||
if proposer_index.isNone:
|
||||
|
|
|
@ -456,14 +456,14 @@ func get_base_reward_sqrt*(state: phase0.BeaconState, index: ValidatorIndex,
|
|||
effective_balance * BASE_REWARD_FACTOR div
|
||||
total_balance_sqrt div BASE_REWARDS_PER_EPOCH
|
||||
|
||||
func get_proposer_reward(base_reward: Gwei): Gwei =
|
||||
func get_proposer_reward*(base_reward: Gwei): Gwei =
|
||||
# Spec version recalculates get_total_active_balance(state) quadratically
|
||||
base_reward div PROPOSER_REWARD_QUOTIENT
|
||||
|
||||
func is_in_inactivity_leak(finality_delay: uint64): bool =
|
||||
finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||
|
||||
func get_finality_delay(state: ForkyBeaconState): uint64 =
|
||||
func get_finality_delay*(state: ForkyBeaconState): uint64 =
|
||||
get_previous_epoch(state) - state.finalized_checkpoint.epoch
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#rewards-and-penalties-1
|
||||
|
@ -471,6 +471,19 @@ func is_in_inactivity_leak(state: altair.BeaconState | bellatrix.BeaconState): b
|
|||
# TODO remove this, see above
|
||||
get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||
|
||||
func get_attestation_component_reward*(attesting_balance: Gwei,
|
||||
total_balance: Gwei,
|
||||
base_reward: uint64,
|
||||
finality_delay: uint64): Gwei =
|
||||
if is_in_inactivity_leak(finality_delay):
|
||||
# Since full base reward will be canceled out by inactivity penalty deltas,
|
||||
# optimal participation receives full base reward compensation here.
|
||||
base_reward
|
||||
else:
|
||||
let reward_numerator =
|
||||
base_reward * (attesting_balance div EFFECTIVE_BALANCE_INCREMENT)
|
||||
reward_numerator div (total_balance div EFFECTIVE_BALANCE_INCREMENT)
|
||||
|
||||
func get_attestation_component_delta(is_unslashed_attester: bool,
|
||||
attesting_balance: Gwei,
|
||||
total_balance: Gwei,
|
||||
|
@ -479,15 +492,11 @@ func get_attestation_component_delta(is_unslashed_attester: bool,
|
|||
# Helper with shared logic for use by get source, target, and head deltas
|
||||
# functions
|
||||
if is_unslashed_attester:
|
||||
if is_in_inactivity_leak(finality_delay):
|
||||
# Since full base reward will be canceled out by inactivity penalty deltas,
|
||||
# optimal participation receives full base reward compensation here.
|
||||
RewardDelta(rewards: base_reward)
|
||||
else:
|
||||
let reward_numerator =
|
||||
base_reward * (attesting_balance div EFFECTIVE_BALANCE_INCREMENT)
|
||||
RewardDelta(rewards:
|
||||
reward_numerator div (total_balance div EFFECTIVE_BALANCE_INCREMENT))
|
||||
RewardDelta(rewards: get_attestation_component_reward(
|
||||
attesting_balance,
|
||||
total_balance,
|
||||
base_reward,
|
||||
finality_delay))
|
||||
else:
|
||||
RewardDelta(penalties: base_reward)
|
||||
|
||||
|
@ -614,7 +623,7 @@ func get_attestation_deltas(state: phase0.BeaconState, info: var phase0.EpochInf
|
|||
proposer_delta.get()[1])
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#get_base_reward
|
||||
func get_base_reward_increment(
|
||||
func get_base_reward_increment*(
|
||||
state: altair.BeaconState | bellatrix.BeaconState, index: ValidatorIndex,
|
||||
base_reward_per_increment: Gwei): Gwei =
|
||||
## Return the base reward for the validator defined by ``index`` with respect
|
||||
|
@ -623,6 +632,27 @@ func get_base_reward_increment(
|
|||
state.validators[index].effective_balance div EFFECTIVE_BALANCE_INCREMENT
|
||||
increments * base_reward_per_increment
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#get_flag_index_deltas
|
||||
func get_flag_index_reward*(state: altair.BeaconState | bellatrix.BeaconState,
|
||||
base_reward: Gwei, active_increments: Gwei,
|
||||
unslashed_participating_increments: Gwei,
|
||||
weight: uint64): Gwei =
|
||||
if not is_in_inactivity_leak(state):
|
||||
let reward_numerator =
|
||||
base_reward * weight * unslashed_participating_increments
|
||||
reward_numerator div (active_increments * WEIGHT_DENOMINATOR)
|
||||
else:
|
||||
0.Gwei
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#get_flag_index_deltas
|
||||
func get_unslashed_participating_increment*(
|
||||
info: altair.EpochInfo, flag_index: int): Gwei =
|
||||
info.balances.previous_epoch[flag_index] div EFFECTIVE_BALANCE_INCREMENT
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#get_flag_index_deltas
|
||||
func get_active_increments*(info: altair.EpochInfo): Gwei =
|
||||
info.balances.current_epoch div EFFECTIVE_BALANCE_INCREMENT
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#get_flag_index_deltas
|
||||
iterator get_flag_index_deltas*(
|
||||
state: altair.BeaconState | bellatrix.BeaconState, flag_index: int,
|
||||
|
@ -634,12 +664,9 @@ iterator get_flag_index_deltas*(
|
|||
let
|
||||
previous_epoch = get_previous_epoch(state)
|
||||
weight = PARTICIPATION_FLAG_WEIGHTS[flag_index].uint64 # safe
|
||||
unslashed_participating_balance =
|
||||
info.balances.previous_epoch[flag_index]
|
||||
unslashed_participating_increments =
|
||||
unslashed_participating_balance div EFFECTIVE_BALANCE_INCREMENT
|
||||
active_increments =
|
||||
info.balances.current_epoch div EFFECTIVE_BALANCE_INCREMENT
|
||||
unslashed_participating_increments = get_unslashed_participating_increment(
|
||||
info, flag_index)
|
||||
active_increments = get_active_increments(info)
|
||||
|
||||
for index in 0 ..< state.validators.len:
|
||||
if not is_eligible_validator(info.validators[index]):
|
||||
|
@ -659,14 +686,11 @@ iterator get_flag_index_deltas*(
|
|||
|
||||
info.validators[vidx].flags.incl pflag
|
||||
|
||||
if not is_in_inactivity_leak(state):
|
||||
let reward_numerator =
|
||||
base_reward * weight * unslashed_participating_increments
|
||||
(vidx, RewardDelta(
|
||||
rewards: reward_numerator div (active_increments * WEIGHT_DENOMINATOR),
|
||||
penalties: 0.Gwei))
|
||||
else:
|
||||
(vidx, RewardDelta(rewards: 0.Gwei, penalties: 0.Gwei))
|
||||
(vidx, RewardDelta(
|
||||
rewards: get_flag_index_reward(
|
||||
state, base_reward, active_increments,
|
||||
unslashed_participating_increments, weight),
|
||||
penalties: 0.Gwei))
|
||||
elif flag_index != TIMELY_HEAD_FLAG_INDEX:
|
||||
(vidx, RewardDelta(
|
||||
rewards: 0.Gwei,
|
||||
|
@ -829,34 +853,55 @@ func process_registry_updates*(
|
|||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.7/specs/merge/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/merge/beacon-chain.md#slashings
|
||||
func get_adjusted_total_slashing_balance*(
|
||||
state: ForkyBeaconState, total_balance: Gwei): Gwei =
|
||||
let multiplier =
|
||||
# tradeoff here about interleaving phase0/altair, but for these
|
||||
# single-constant changes...
|
||||
uint64(when state is phase0.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER
|
||||
elif state is altair.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR
|
||||
elif state is bellatrix.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER_MERGE
|
||||
else:
|
||||
raiseAssert "process_slashings: incorrect BeaconState type")
|
||||
return min(sum(state.slashings.data) * multiplier, total_balance)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/merge/beacon-chain.md#slashings
|
||||
func slashing_penalty_applies*(validator: Validator, epoch: Epoch): bool =
|
||||
return validator.slashed and
|
||||
epoch + EPOCHS_PER_SLASHINGS_VECTOR div 2 == validator.withdrawable_epoch
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/merge/beacon-chain.md#slashings
|
||||
func get_slashing_penalty*(validator: Validator,
|
||||
adjusted_total_slashing_balance,
|
||||
total_balance: Gwei): Gwei =
|
||||
const increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty
|
||||
# numerator to avoid uint64 overflow
|
||||
let penalty_numerator = validator.effective_balance div increment *
|
||||
adjusted_total_slashing_balance
|
||||
return penalty_numerator div total_balance * increment
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/beacon-chain.md#slashings
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/merge/beacon-chain.md#slashings
|
||||
func process_slashings*(state: var ForkyBeaconState, total_balance: Gwei) =
|
||||
let
|
||||
epoch = get_current_epoch(state)
|
||||
multiplier =
|
||||
# tradeoff here about interleaving phase0/altair, but for these
|
||||
# single-constant changes...
|
||||
uint64(when state is phase0.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER
|
||||
elif state is altair.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR
|
||||
elif state is bellatrix.BeaconState:
|
||||
PROPORTIONAL_SLASHING_MULTIPLIER_MERGE
|
||||
else:
|
||||
raiseAssert "process_slashings: incorrect BeaconState type")
|
||||
adjusted_total_slashing_balance =
|
||||
min(sum(state.slashings.data) * multiplier, total_balance)
|
||||
adjusted_total_slashing_balance = get_adjusted_total_slashing_balance(
|
||||
state, total_balance)
|
||||
|
||||
for index in 0..<state.validators.len:
|
||||
let validator = unsafeAddr state.validators.asSeq()[index]
|
||||
if validator[].slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR div 2 ==
|
||||
validator[].withdrawable_epoch:
|
||||
const increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty
|
||||
# numerator to avoid uint64 overflow
|
||||
let penalty_numerator =
|
||||
validator[].effective_balance div increment *
|
||||
adjusted_total_slashing_balance
|
||||
let penalty = penalty_numerator div total_balance * increment
|
||||
if slashing_penalty_applies(validator[], epoch):
|
||||
let penalty = validator[].get_slashing_penalty(
|
||||
adjusted_total_slashing_balance, total_balance)
|
||||
decrease_balance(state, index.ValidatorIndex, penalty)
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/beacon-chain.md#eth1-data-votes-updates
|
||||
|
|
796
ncli/ncli_db.nim
796
ncli/ncli_db.nim
|
@ -6,7 +6,8 @@ import
|
|||
../beacon_chain/consensus_object_pools/[blockchain_dag],
|
||||
../beacon_chain/spec/datatypes/[phase0, altair, merge],
|
||||
../beacon_chain/spec/[
|
||||
beaconstate, helpers, state_transition, state_transition_epoch, validator],
|
||||
beaconstate, helpers, state_transition, state_transition_epoch, validator,
|
||||
state_transition_block, signatures],
|
||||
../beacon_chain/sszdump,
|
||||
../research/simutils, ./e2store
|
||||
|
||||
|
@ -152,6 +153,11 @@ type
|
|||
defaultValue: false
|
||||
name: "perfect"
|
||||
desc: "Include perfect records (full rewards)".}: bool
|
||||
startEpoch* {.
|
||||
defaultValue: 0
|
||||
name: "start-epoch"
|
||||
desc: "Epoch from which to start recording statistics. " &
|
||||
"By default one more than the last epoch in the database.".}: uint
|
||||
|
||||
proc putState(db: BeaconChainDB, state: ForkedHashedBeaconState) =
|
||||
withState(state):
|
||||
|
@ -216,7 +222,7 @@ proc cmdBench(conf: DbConf, cfg: RuntimeConfig) =
|
|||
echo &"Loaded {dag.blocks.len} blocks, head slot {dag.head.slot}, selected {blockRefs.len} blocks"
|
||||
doAssert blockRefs.len() > 0, "Must select at least one block"
|
||||
|
||||
for b in 0..<blockRefs.len:
|
||||
for b in 0 ..< blockRefs.len:
|
||||
let blck = blockRefs[blockRefs.len - b - 1]
|
||||
withTimer(timers[tLoadBlock]):
|
||||
case cfg.blockForkAtEpoch(blck.slot.epoch)
|
||||
|
@ -520,7 +526,7 @@ proc cmdExportEra(conf: DbConf, cfg: RuntimeConfig) =
|
|||
tmp: seq[byte]
|
||||
timers: array[Timers, RunningStat]
|
||||
|
||||
for era in conf.era..<conf.era + conf.eraCount:
|
||||
for era in conf.era ..< conf.era + conf.eraCount:
|
||||
let
|
||||
firstSlot =
|
||||
if era == 0: none(Slot)
|
||||
|
@ -657,7 +663,7 @@ proc cmdValidatorPerf(conf: DbConf, cfg: RuntimeConfig) =
|
|||
of EpochInfoFork.Altair:
|
||||
echo "TODO altair"
|
||||
|
||||
for bi in 0..<blockRefs.len:
|
||||
for bi in 0 ..< blockRefs.len:
|
||||
blck = db.getPhase0Block(blockRefs[blockRefs.len - bi - 1].root).get()
|
||||
while getStateField(state[].data, slot) < blck.message.slot:
|
||||
let
|
||||
|
@ -708,36 +714,17 @@ proc cmdValidatorPerf(conf: DbConf, cfg: RuntimeConfig) =
|
|||
perf.first_slot_head_attester_when_first_slot_empty,",",
|
||||
perf.first_slot_head_attester_when_first_slot_not_empty
|
||||
|
||||
proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
||||
# Create a database with performance information for every epoch
|
||||
echo "Opening database..."
|
||||
let
|
||||
db = BeaconChainDB.new(conf.databaseDir.string,)
|
||||
defer:
|
||||
db.close()
|
||||
|
||||
if (let v = ChainDAGRef.isInitialized(db); v.isErr()):
|
||||
echo "Database not initialized: ", v.error()
|
||||
quit 1
|
||||
|
||||
echo "Initializing block pool..."
|
||||
let
|
||||
validatorMonitor = newClone(ValidatorMonitor.init())
|
||||
dag = ChainDAGRef.init(cfg, db, validatorMonitor, {})
|
||||
|
||||
let outDb = SqStoreRef.init(conf.outDir, "validatorDb").expect("DB")
|
||||
defer: outDb.close()
|
||||
|
||||
outDb.exec("""
|
||||
proc createValidatorsRawTable(db: SqStoreRef) =
|
||||
db.exec("""
|
||||
CREATE TABLE IF NOT EXISTS validators_raw(
|
||||
validator_index INTEGER PRIMARY KEY,
|
||||
pubkey BLOB NOT NULL,
|
||||
pubkey BLOB NOT NULL UNIQUE,
|
||||
withdrawal_credentials BLOB NOT NULL
|
||||
);
|
||||
""").expect("DB")
|
||||
|
||||
# For convenient viewing
|
||||
outDb.exec("""
|
||||
proc createValidatorsView(db: SqStoreRef) =
|
||||
db.exec("""
|
||||
CREATE VIEW IF NOT EXISTS validators AS
|
||||
SELECT
|
||||
validator_index,
|
||||
|
@ -746,8 +733,9 @@ proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
|||
FROM validators_raw;
|
||||
""").expect("DB")
|
||||
|
||||
outDb.exec("""
|
||||
CREATE TABLE IF NOT EXISTS epoch_info(
|
||||
proc createPhase0EpochInfoTable(db: SqStoreRef) =
|
||||
db.exec("""
|
||||
CREATE TABLE IF NOT EXISTS phase0_epoch_info(
|
||||
epoch INTEGER PRIMARY KEY,
|
||||
current_epoch_raw INTEGER NOT NULL,
|
||||
previous_epoch_raw INTEGER NOT NULL,
|
||||
|
@ -758,77 +746,515 @@ proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
|||
previous_epoch_head_attesters_raw INTEGER NOT NULL
|
||||
);
|
||||
""").expect("DB")
|
||||
outDb.exec("""
|
||||
|
||||
proc createAltairEpochInfoTable(db: SqStoreRef) =
|
||||
db.exec("""
|
||||
CREATE TABLE IF NOT EXISTS altair_epoch_info(
|
||||
epoch INTEGER PRIMARY KEY,
|
||||
previous_epoch_timely_source_balance INTEGER NOT NULL,
|
||||
previous_epoch_timely_target_balance INTEGER NOT NULL,
|
||||
previous_epoch_timely_head_balance INTEGER NOT NULL,
|
||||
current_epoch_timely_target_balance INTEGER NOT NULL,
|
||||
current_epoch_total_active_balance INTEGER NOT NULL
|
||||
);
|
||||
""").expect("DB")
|
||||
|
||||
proc createValidatorEpochInfoTable(db: SqStoreRef) =
|
||||
db.exec("""
|
||||
CREATE TABLE IF NOT EXISTS validator_epoch_info(
|
||||
validator_index INTEGER,
|
||||
epoch INTEGER,
|
||||
rewards INTEGER NOT NULL,
|
||||
penalties INTEGER NOT NULL,
|
||||
source_attester INTEGER NOT NULL,
|
||||
target_attester INTEGER NOT NULL,
|
||||
head_attester INTEGER NOT NULL,
|
||||
source_outcome INTEGER NOT NULL,
|
||||
max_source_reward INTEGER NOT NULL,
|
||||
target_outcome INTEGER NOT NULL,
|
||||
max_target_reward INTEGER NOT NULL,
|
||||
head_outcome INTEGER NOT NULL,
|
||||
max_head_reward INTEGER NOT NULL,
|
||||
inclusion_delay_outcome INTEGER NOT NULL,
|
||||
max_inclusion_delay_reward INTEGER NOT NULL,
|
||||
sync_committee_outcome INTEGER NOT NULL,
|
||||
max_sync_committee_reward INTEGER NOT NULL,
|
||||
proposer_outcome INTEGER NOT NULL,
|
||||
inactivity_penalty INTEGER NOT NULL,
|
||||
slashing_outcome INTEGER NOT NULL,
|
||||
inclusion_delay INTEGER NULL,
|
||||
PRIMARY KEY(validator_index, epoch)
|
||||
);
|
||||
""").expect("DB")
|
||||
|
||||
proc createInsertValidatorProc(db: SqStoreRef): auto =
|
||||
db.prepareStmt("""
|
||||
INSERT OR IGNORE INTO validators_raw(
|
||||
validator_index,
|
||||
pubkey,
|
||||
withdrawal_credentials)
|
||||
VALUES(?, ?, ?);""",
|
||||
(int64, array[48, byte], array[32, byte]), void).expect("DB")
|
||||
|
||||
proc createInsertPhase0EpochInfoProc(db: SqStoreRef): auto =
|
||||
db.prepareStmt("""
|
||||
INSERT OR IGNORE INTO phase0_epoch_info(
|
||||
epoch,
|
||||
current_epoch_raw,
|
||||
previous_epoch_raw,
|
||||
current_epoch_attesters_raw,
|
||||
current_epoch_target_attesters_raw,
|
||||
previous_epoch_attesters_raw,
|
||||
previous_epoch_target_attesters_raw,
|
||||
previous_epoch_head_attesters_raw)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?);""",
|
||||
(int64, int64, int64, int64, int64, int64, int64, int64), void).expect("DB")
|
||||
|
||||
proc createInsertAltairEpochInfoProc(db: SqStoreRef): auto =
|
||||
db.prepareStmt("""
|
||||
INSERT OR IGNORE INTO altair_epoch_info(
|
||||
epoch,
|
||||
previous_epoch_timely_source_balance,
|
||||
previous_epoch_timely_target_balance,
|
||||
previous_epoch_timely_head_balance,
|
||||
current_epoch_timely_target_balance,
|
||||
current_epoch_total_active_balance)
|
||||
VALUES(?, ?, ?, ?, ?, ?);""",
|
||||
(int64, int64, int64, int64, int64, int64), void).expect("DB")
|
||||
|
||||
proc createInsertValidatorEpochInfoProc(db: SqStoreRef): auto =
|
||||
db.prepareStmt("""
|
||||
INSERT OR IGNORE INTO validator_epoch_info(
|
||||
validator_index,
|
||||
epoch,
|
||||
source_outcome,
|
||||
max_source_reward,
|
||||
target_outcome,
|
||||
max_target_reward,
|
||||
head_outcome,
|
||||
max_head_reward,
|
||||
inclusion_delay_outcome,
|
||||
max_inclusion_delay_reward,
|
||||
sync_committee_outcome,
|
||||
max_sync_committee_reward,
|
||||
proposer_outcome,
|
||||
inactivity_penalty,
|
||||
slashing_outcome,
|
||||
inclusion_delay)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);""",
|
||||
(int64, int64, int64, int64, int64, int64, int64, int64, int64, int64,
|
||||
int64, int64, int64, int64, int64, Option[int64]), void).expect("DB")
|
||||
|
||||
type
|
||||
RewardsAndPenalties = object
|
||||
source_outcome: int64
|
||||
max_source_reward: Gwei
|
||||
target_outcome: int64
|
||||
max_target_reward: Gwei
|
||||
head_outcome: int64
|
||||
max_head_reward: Gwei
|
||||
inclusion_delay_outcome: int64
|
||||
max_inclusion_delay_reward: Gwei
|
||||
sync_committee_outcome: int64
|
||||
max_sync_committee_reward: Gwei
|
||||
proposer_outcome: int64
|
||||
inactivity_penalty: Gwei
|
||||
slashing_outcome: int64
|
||||
deposits: Gwei
|
||||
inclusion_delay: Option[int64]
|
||||
|
||||
ParticipationFlags = object
|
||||
currentEpochParticipation: EpochParticipationFlags
|
||||
previousEpochParticipation: EpochParticipationFlags
|
||||
|
||||
PubkeyToIndexTable = Table[ValidatorPubKey, int]
|
||||
|
||||
AuxiliaryState = object
|
||||
epochParticipationFlags: ParticipationFlags
|
||||
pubkeyToIndex: PubkeyToIndexTable
|
||||
|
||||
proc copyParticipationFlags(auxiliaryState: var AuxiliaryState,
|
||||
forkedState: ForkedHashedBeaconState) =
|
||||
withState(forkedState):
|
||||
when stateFork > BeaconStateFork.Phase0:
|
||||
template flags: untyped = auxiliaryState.epochParticipationFlags
|
||||
flags.currentEpochParticipation = state.data.current_epoch_participation
|
||||
flags.previousEpochParticipation = state.data.previous_epoch_participation
|
||||
|
||||
proc isPerfect(info: RewardsAndPenalties): bool =
|
||||
info.slashing_outcome >= 0 and
|
||||
info.source_outcome == info.max_source_reward.int64 and
|
||||
info.target_outcome == info.max_target_reward.int64 and
|
||||
info.head_outcome == info.max_head_reward.int64 and
|
||||
info.inclusion_delay_outcome == info.max_inclusion_delay_reward.int64 and
|
||||
info.sync_committee_outcome == info.max_sync_committee_reward.int64
|
||||
|
||||
proc getMaxEpochFromDbTable(db: SqStoreRef, tableName: string): int64 =
|
||||
var queryResult: int64
|
||||
discard db.exec(&"SELECT MAX(epoch) FROM {tableName}", ()) do (res: int64):
|
||||
queryResult = res
|
||||
return queryResult
|
||||
|
||||
proc collectBalances(balances: var seq[uint64], forkedState: ForkedHashedBeaconState) =
|
||||
withState(forkedState):
|
||||
balances = seq[uint64](state.data.balances.data)
|
||||
|
||||
proc calculateDelta(info: RewardsAndPenalties): int64 =
|
||||
info.source_outcome +
|
||||
info.target_outcome +
|
||||
info.head_outcome +
|
||||
info.inclusion_delay_outcome +
|
||||
info.sync_committee_outcome +
|
||||
info.proposer_outcome +
|
||||
info.slashing_outcome -
|
||||
info.inactivity_penalty.int64 +
|
||||
info.deposits.int64
|
||||
|
||||
proc printComponents(info: RewardsAndPenalties) =
|
||||
echo "Components:"
|
||||
echo "Source outcome: ", info.source_outcome
|
||||
echo "Target outcome: ", info.target_outcome
|
||||
echo "Head outcome: ", info.head_outcome
|
||||
echo "Inclusion delay outcome: ", info.inclusion_delay_outcome
|
||||
echo "Sync committee outcome: ", info.sync_committee_outcome
|
||||
echo "Proposer outcome: ", info.proposer_outcome
|
||||
echo "Slashing outcome: ", info.slashing_outcome
|
||||
echo "Inactivity penalty: ", info.inactivity_penalty
|
||||
echo "Deposits: ", info.deposits
|
||||
|
||||
proc checkBalance(validatorIndex: int64,
|
||||
validator: RewardStatus | ParticipationInfo,
|
||||
currentEpochBalance, previousEpochBalance: Gwei,
|
||||
validatorInfo: RewardsAndPenalties) =
|
||||
let delta = validatorInfo.calculateDelta
|
||||
if currentEpochBalance.int64 == previousEpochBalance.int64 + delta:
|
||||
return
|
||||
echo "Validator: ", validatorIndex
|
||||
echo "Is eligible: ", is_eligible_validator(validator)
|
||||
echo "Current epoch balance: ", currentEpochBalance
|
||||
echo "Previous epoch balance: ", previousEpochBalance
|
||||
echo "State delta: ", currentEpochBalance - previousEpochBalance
|
||||
echo "Computed delta: ", delta
|
||||
printComponents(validatorInfo)
|
||||
raiseAssert("Validator's previous epoch balance plus computed validator's " &
|
||||
"delta is not equal to the validator's current epoch balance.")
|
||||
|
||||
proc getDbValidatorsCount(db: SqStoreRef): int64 =
|
||||
var res: int64
|
||||
discard db.exec("SELECT count(*) FROM validators", ()) do (r: int64):
|
||||
res = r
|
||||
return res
|
||||
|
||||
template inTransaction(db: SqStoreRef, dbName: string, body: untyped) =
|
||||
try:
|
||||
db.exec("BEGIN TRANSACTION;").expect(dbName)
|
||||
body
|
||||
finally:
|
||||
db.exec("END TRANSACTION;").expect(dbName)
|
||||
|
||||
proc insertValidators(db: SqStoreRef, state: ForkedHashedBeaconState,
|
||||
startIndex, endIndex: int64) =
|
||||
var insertValidator {.global.}: SqliteStmt[
|
||||
(int64, array[48, byte], array[32, byte]), void]
|
||||
once: insertValidator = db.createInsertValidatorProc
|
||||
withState(state):
|
||||
db.inTransaction("DB"):
|
||||
for i in startIndex ..< endIndex:
|
||||
insertValidator.exec((i, state.data.validators[i].pubkey.toRaw,
|
||||
state.data.validators[i].withdrawal_credentials.data)).expect("DB")
|
||||
|
||||
proc getOutcome(delta: RewardDelta): int64 =
|
||||
delta.rewards.int64 - delta.penalties.int64
|
||||
|
||||
proc collectSlashings(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
state: ForkyBeaconState, total_balance: Gwei) =
|
||||
let
|
||||
insertValidator = outDb.prepareStmt("""
|
||||
INSERT INTO validators_raw(
|
||||
validator_index,
|
||||
pubkey,
|
||||
withdrawal_credentials)
|
||||
VALUES(?, ?, ?);""",
|
||||
(int64, array[48, byte], array[32, byte]), void).expect("DB")
|
||||
insertEpochInfo = outDb.prepareStmt("""
|
||||
INSERT INTO epoch_info(
|
||||
epoch,
|
||||
current_epoch_raw,
|
||||
previous_epoch_raw,
|
||||
current_epoch_attesters_raw,
|
||||
current_epoch_target_attesters_raw,
|
||||
previous_epoch_attesters_raw,
|
||||
previous_epoch_target_attesters_raw,
|
||||
previous_epoch_head_attesters_raw)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?);""",
|
||||
(int64, int64, int64, int64, int64, int64, int64, int64), void).expect("DB")
|
||||
insertValidatorInfo = outDb.prepareStmt("""
|
||||
INSERT INTO validator_epoch_info(
|
||||
validator_index,
|
||||
epoch,
|
||||
rewards,
|
||||
penalties,
|
||||
source_attester,
|
||||
target_attester,
|
||||
head_attester,
|
||||
inclusion_delay)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?);""",
|
||||
(int64, int64, int64, int64, int64, int64, int64, Option[int64]), void).expect("DB")
|
||||
epoch = get_current_epoch(state)
|
||||
adjusted_total_slashing_balance = get_adjusted_total_slashing_balance(
|
||||
state, total_balance)
|
||||
|
||||
var vals: int64
|
||||
discard outDb.exec("SELECT count(*) FROM validators", ()) do (res: int64):
|
||||
vals = res
|
||||
for index in 0 ..< state.validators.len:
|
||||
let validator = unsafeAddr state.validators.asSeq()[index]
|
||||
if slashing_penalty_applies(validator[], epoch):
|
||||
rewardsAndPenalties[index].slashing_outcome +=
|
||||
validator[].get_slashing_penalty(
|
||||
adjusted_total_slashing_balance, total_balance).int64
|
||||
|
||||
outDb.exec("BEGIN TRANSACTION;").expect("DB")
|
||||
proc collectEpochRewardsAndPenalties(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
state: phase0.BeaconState, cache: var StateCache, cfg: RuntimeConfig) =
|
||||
if get_current_epoch(state) == GENESIS_EPOCH:
|
||||
return
|
||||
|
||||
for i in vals..<getStateField(dag.headState.data, validators).len():
|
||||
insertValidator.exec((
|
||||
i,
|
||||
getStateField(dag.headState.data, validators).data[i].pubkey.toRaw(),
|
||||
getStateField(dag.headState.data, validators).data[i].withdrawal_credentials.data)).expect("DB")
|
||||
var info: phase0.EpochInfo
|
||||
|
||||
outDb.exec("COMMIT;").expect("DB")
|
||||
|
||||
var minEpoch: Epoch
|
||||
discard outDb.exec("SELECT MAX(epoch) FROM epoch_info", ()) do (res: int64):
|
||||
minEpoch = (res + 1).Epoch
|
||||
|
||||
var
|
||||
cache = StateCache()
|
||||
info = ForkedEpochInfo()
|
||||
blck: phase0.TrustedSignedBeaconBlock
|
||||
info.init(state)
|
||||
info.processAttestations(state, cache)
|
||||
doAssert info.validators.len == state.validators.len
|
||||
rewardsAndPenalties.setLen(state.validators.len)
|
||||
|
||||
let
|
||||
finality_delay = get_finality_delay(state)
|
||||
total_balance = info.balances.current_epoch
|
||||
total_balance_sqrt = integer_squareroot(total_balance)
|
||||
|
||||
for index, validator in info.validators.pairs:
|
||||
if not is_eligible_validator(validator):
|
||||
continue
|
||||
|
||||
let base_reward = get_base_reward_sqrt(
|
||||
state, index.ValidatorIndex, total_balance_sqrt)
|
||||
|
||||
template get_attestation_component_reward_helper(attesting_balance: Gwei): Gwei =
|
||||
get_attestation_component_reward(attesting_balance,
|
||||
info.balances.current_epoch, base_reward.uint64, finality_delay)
|
||||
|
||||
template rp: untyped = rewardsAndPenalties[index]
|
||||
|
||||
rp.source_outcome = get_source_delta(
|
||||
validator, base_reward, info.balances, finality_delay).getOutcome
|
||||
rp.max_source_reward = get_attestation_component_reward_helper(
|
||||
info.balances.previous_epoch_attesters)
|
||||
|
||||
rp.target_outcome = get_target_delta(
|
||||
validator, base_reward, info.balances, finality_delay).getOutcome
|
||||
rp.max_target_reward = get_attestation_component_reward_helper(
|
||||
info.balances.previous_epoch_target_attesters)
|
||||
|
||||
rp.head_outcome = get_head_delta(
|
||||
validator, base_reward, info.balances, finality_delay).getOutcome
|
||||
rp.max_head_reward = get_attestation_component_reward_helper(
|
||||
info.balances.previous_epoch_head_attesters)
|
||||
|
||||
let (inclusion_delay_delta, proposer_delta) = get_inclusion_delay_delta(
|
||||
validator, base_reward)
|
||||
rp.inclusion_delay_outcome = inclusion_delay_delta.getOutcome
|
||||
rp.max_inclusion_delay_reward =
|
||||
base_reward - state_transition_epoch.get_proposer_reward(base_reward)
|
||||
|
||||
rp.inactivity_penalty = get_inactivity_penalty_delta(
|
||||
validator, base_reward, finality_delay).penalties
|
||||
|
||||
if proposer_delta.isSome:
|
||||
let proposer_index = proposer_delta.get[0]
|
||||
if proposer_index < info.validators.lenu64:
|
||||
rewardsAndPenalties[proposer_index].proposer_outcome +=
|
||||
proposer_delta.get[1].getOutcome
|
||||
|
||||
rewardsAndPenalties.collectSlashings(state, info.balances.current_epoch)
|
||||
|
||||
proc collectEpochRewardsAndPenalties(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
state: altair.BeaconState | merge.BeaconState,
|
||||
cache: var StateCache, cfg: RuntimeConfig) =
|
||||
if get_current_epoch(state) == GENESIS_EPOCH:
|
||||
return
|
||||
|
||||
var info: altair.EpochInfo
|
||||
info.init(state)
|
||||
doAssert info.validators.len == state.validators.len
|
||||
rewardsAndPenalties.setLen(state.validators.len)
|
||||
|
||||
let
|
||||
total_active_balance = info.balances.current_epoch
|
||||
base_reward_per_increment = get_base_reward_per_increment(
|
||||
total_active_balance)
|
||||
|
||||
for flag_index in 0 ..< PARTICIPATION_FLAG_WEIGHTS.len:
|
||||
for validator_index, delta in get_flag_index_deltas(
|
||||
state, flag_index, base_reward_per_increment, info):
|
||||
template rp: untyped = rewardsAndPenalties[validator_index]
|
||||
|
||||
let
|
||||
base_reward = get_base_reward_increment(
|
||||
state, validator_index, base_reward_per_increment)
|
||||
active_increments = get_active_increments(info)
|
||||
unslashed_participating_increment =
|
||||
get_unslashed_participating_increment(info, flag_index)
|
||||
max_flag_index_reward = get_flag_index_reward(
|
||||
state, base_reward, active_increments,
|
||||
unslashed_participating_increment,
|
||||
PARTICIPATION_FLAG_WEIGHTS[flag_index].uint64)
|
||||
|
||||
case flag_index
|
||||
of TIMELY_SOURCE_FLAG_INDEX:
|
||||
rp.source_outcome = delta.getOutcome
|
||||
rp.max_source_reward = max_flag_index_reward
|
||||
of TIMELY_TARGET_FLAG_INDEX:
|
||||
rp.target_outcome = delta.getOutcome
|
||||
rp.max_target_reward = max_flag_index_reward
|
||||
of TIMELY_HEAD_FLAG_INDEX:
|
||||
rp.head_outcome = delta.getOutcome
|
||||
rp.max_head_reward = max_flag_index_reward
|
||||
else:
|
||||
raiseAssert(&"Unknown flag index {flag_index}.")
|
||||
|
||||
for validator_index, penalty in get_inactivity_penalty_deltas(
|
||||
cfg, state, info):
|
||||
rewardsAndPenalties[validator_index].inactivity_penalty += penalty
|
||||
|
||||
rewardsAndPenalties.collectSlashings(state, info.balances.current_epoch)
|
||||
|
||||
proc collectFromSlashedValidator(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
state: ForkyBeaconState, slashedIndex, proposerIndex: ValidatorIndex) =
|
||||
template slashed_validator: untyped = state.validators[slashedIndex]
|
||||
let slashingPenalty = get_slashing_penalty(state, slashed_validator.effective_balance)
|
||||
let whistleblowerReward = get_whistleblower_reward(slashed_validator.effective_balance)
|
||||
rewardsAndPenalties[slashedIndex].slashing_outcome -= slashingPenalty.int64
|
||||
rewardsAndPenalties[proposerIndex].slashing_outcome += whistleblowerReward.int64
|
||||
|
||||
proc collectFromProposerSlashings(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock) =
|
||||
withStateAndBlck(forkedState, forkedBlock):
|
||||
for proposer_slashing in blck.message.body.proposer_slashings:
|
||||
doAssert check_proposer_slashing(state.data, proposer_slashing, {}).isOk
|
||||
let slashedIndex = proposer_slashing.signed_header_1.message.proposer_index
|
||||
rewardsAndPenalties.collectFromSlashedValidator(state.data,
|
||||
slashedIndex.ValidatorIndex, blck.message.proposer_index.ValidatorIndex)
|
||||
|
||||
proc collectFromAttesterSlashings(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock) =
|
||||
withStateAndBlck(forkedState, forkedBlock):
|
||||
for attester_slashing in blck.message.body.attester_slashings:
|
||||
let attester_slashing_validity = check_attester_slashing(
|
||||
state.data, attester_slashing, {})
|
||||
doAssert attester_slashing_validity.isOk
|
||||
for slashedIndex in attester_slashing_validity.value:
|
||||
rewardsAndPenalties.collectFromSlashedValidator(
|
||||
state.data, slashedIndex, blck.message.proposer_index.ValidatorIndex)
|
||||
|
||||
proc collectFromAttestations(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock,
|
||||
epochParticipationFlags: var ParticipationFlags,
|
||||
cache: var StateCache) =
|
||||
withStateAndBlck(forkedState, forkedBlock):
|
||||
when stateFork > BeaconStateFork.Phase0:
|
||||
let base_reward_per_increment = get_base_reward_per_increment(
|
||||
get_total_active_balance(state.data, cache))
|
||||
doAssert base_reward_per_increment > 0
|
||||
for attestation in blck.message.body.attestations:
|
||||
doAssert check_attestation(state.data, attestation, {}, cache).isOk
|
||||
let proposerReward =
|
||||
if attestation.data.target.epoch == get_current_epoch(state.data):
|
||||
get_proposer_reward(
|
||||
state.data, attestation, base_reward_per_increment, cache,
|
||||
epochParticipationFlags.currentEpochParticipation)
|
||||
else:
|
||||
get_proposer_reward(
|
||||
state.data, attestation, base_reward_per_increment, cache,
|
||||
epochParticipationFlags.previousEpochParticipation)
|
||||
rewardsAndPenalties[blck.message.proposer_index].proposer_outcome +=
|
||||
proposerReward.int64
|
||||
let inclusionDelay = state.data.slot - attestation.data.slot
|
||||
for index in get_attesting_indices(
|
||||
state.data, attestation.data, attestation.aggregation_bits, cache):
|
||||
rewardsAndPenalties[index].inclusion_delay = some(inclusionDelay.int64)
|
||||
|
||||
proc collectFromDeposits(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock,
|
||||
pubkeyToIndex: var PubkeyToIndexTable,
|
||||
cfg: RuntimeConfig) =
|
||||
withStateAndBlck(forkedState, forkedBlock):
|
||||
for deposit in blck.message.body.deposits:
|
||||
let pubkey = deposit.data.pubkey
|
||||
let amount = deposit.data.amount
|
||||
var index = findValidatorIndex(state.data, pubkey)
|
||||
if index == -1:
|
||||
index = pubkeyToIndex.getOrDefault(pubkey, -1)
|
||||
if index != -1:
|
||||
rewardsAndPenalties[index].deposits += amount
|
||||
elif verify_deposit_signature(cfg, deposit.data):
|
||||
pubkeyToIndex[pubkey] = rewardsAndPenalties.len
|
||||
rewardsAndPenalties.add(
|
||||
RewardsAndPenalties(deposits: amount))
|
||||
|
||||
proc collectFromSyncAggregate(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock,
|
||||
cache: var StateCache) =
|
||||
withStateAndBlck(forkedState, forkedBlock):
|
||||
when stateFork > BeaconStateFork.Phase0:
|
||||
let total_active_balance = get_total_active_balance(state.data, cache)
|
||||
let participant_reward = get_participant_reward(total_active_balance)
|
||||
let proposer_reward =
|
||||
state_transition_block.get_proposer_reward(participant_reward)
|
||||
let indices = get_sync_committee_cache(state.data, cache).current_sync_committee
|
||||
|
||||
template aggregate: untyped = blck.message.body.sync_aggregate
|
||||
|
||||
doAssert indices.len == SYNC_COMMITTEE_SIZE
|
||||
doAssert aggregate.sync_committee_bits.len == SYNC_COMMITTEE_SIZE
|
||||
doAssert state.data.current_sync_committee.pubkeys.len == SYNC_COMMITTEE_SIZE
|
||||
|
||||
for i in 0 ..< SYNC_COMMITTEE_SIZE:
|
||||
rewardsAndPenalties[indices[i]].max_sync_committee_reward +=
|
||||
participant_reward
|
||||
if aggregate.sync_committee_bits[i]:
|
||||
rewardsAndPenalties[indices[i]].sync_committee_outcome +=
|
||||
participant_reward.int64
|
||||
rewardsAndPenalties[blck.message.proposer_index].proposer_outcome +=
|
||||
proposer_reward.int64
|
||||
else:
|
||||
rewardsAndPenalties[indices[i]].sync_committee_outcome -=
|
||||
participant_reward.int64
|
||||
|
||||
proc collectBlockRewardsAndPenalties(
|
||||
rewardsAndPenalties: var seq[RewardsAndPenalties],
|
||||
forkedState: ForkedHashedBeaconState,
|
||||
forkedBlock: ForkedTrustedSignedBeaconBlock,
|
||||
auxiliaryState: var AuxiliaryState,
|
||||
cache: var StateCache, cfg: RuntimeConfig) =
|
||||
rewardsAndPenalties.collectFromProposerSlashings(forkedState, forkedBlock)
|
||||
rewardsAndPenalties.collectFromAttesterSlashings(forkedState, forkedBlock)
|
||||
rewardsAndPenalties.collectFromAttestations(
|
||||
forkedState, forkedBlock, auxiliaryState.epochParticipationFlags, cache)
|
||||
rewardsAndPenalties.collectFromDeposits(
|
||||
forkedState, forkedBlock, auxiliaryState.pubkeyToIndex, cfg)
|
||||
# This table is needed only to resolve double deposits in the same block, so
|
||||
# it can be cleared after processing all deposits for the current block.
|
||||
auxiliaryState.pubkeyToIndex.clear
|
||||
rewardsAndPenalties.collectFromSyncAggregate(forkedState, forkedBlock, cache)
|
||||
|
||||
proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
||||
# Create a database with performance information for every epoch
|
||||
echo "Opening database..."
|
||||
let db = BeaconChainDB.new(conf.databaseDir.string, false, true)
|
||||
defer: db.close()
|
||||
|
||||
if (let v = ChainDAGRef.isInitialized(db); v.isErr()):
|
||||
echo "Database not initialized"
|
||||
quit 1
|
||||
|
||||
echo "Initializing block pool..."
|
||||
let
|
||||
validatorMonitor = newClone(ValidatorMonitor.init())
|
||||
dag = ChainDAGRef.init(cfg, db, validatorMonitor, {})
|
||||
|
||||
let outDb = SqStoreRef.init(conf.outDir, "validatorDb").expect("DB")
|
||||
defer: outDb.close()
|
||||
|
||||
outDb.createValidatorsRawTable
|
||||
outDb.createValidatorsView
|
||||
outDb.createPhase0EpochInfoTable
|
||||
outDb.createAltairEpochInfoTable
|
||||
outDb.createValidatorEpochInfoTable
|
||||
|
||||
let
|
||||
insertPhase0EpochInfo = outDb.createInsertPhase0EpochInfoProc
|
||||
insertAltairEpochInfo = outDb.createInsertAltairEpochInfoProc
|
||||
insertValidatorInfo = outDb.createInsertValidatorEpochInfoProc
|
||||
minEpoch =
|
||||
if conf.startEpoch == 0:
|
||||
Epoch(max(outDb.getMaxEpochFromDbTable("phase0_epoch_info"),
|
||||
outDb.getMaxEpochFromDbTable("altair_epoch_info")) + 1)
|
||||
else:
|
||||
Epoch(conf.startEpoch)
|
||||
start = minEpoch.start_slot()
|
||||
ends = dag.finalizedHead.slot # Avoid dealing with changes
|
||||
|
||||
|
@ -841,22 +1267,80 @@ proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
|||
echo "Analyzing performance for epochs ",
|
||||
start.epoch, " - ", ends.epoch
|
||||
|
||||
let state = newClone(dag.headState)
|
||||
doAssert dag.updateStateData(
|
||||
state[], blockRefs[^1].atSlot(if start > 0: start - 1 else: 0.Slot),
|
||||
false, cache)
|
||||
let tmpState = newClone(dag.headState)
|
||||
var cache = StateCache()
|
||||
let slot = if start > 0: start - 1 else: 0.Slot
|
||||
if blockRefs.len > 0:
|
||||
dag.updateStateData(tmpState[], blockRefs[^1].atSlot(slot), false, cache)
|
||||
else:
|
||||
dag.updateStateData(tmpState[], dag.head.atSlot(slot), false, cache)
|
||||
|
||||
let dbValidatorsCount = outDb.getDbValidatorsCount()
|
||||
var validatorsCount = getStateField(tmpState[].data, validators).len
|
||||
outDb.insertValidators(tmpState[].data, dbValidatorsCount, validatorsCount)
|
||||
|
||||
var previousEpochBalances: seq[uint64]
|
||||
collectBalances(previousEpochBalances, tmpState[].data)
|
||||
|
||||
var forkedInfo = ForkedEpochInfo()
|
||||
var rewardsAndPenalties: seq[RewardsAndPenalties]
|
||||
rewardsAndPenalties.setLen(validatorsCount)
|
||||
|
||||
var auxiliaryState: AuxiliaryState
|
||||
auxiliaryState.copyParticipationFlags(tmpState[].data)
|
||||
|
||||
var inTxn = false
|
||||
proc processEpoch() =
|
||||
echo getStateField(state[].data, slot).epoch
|
||||
if not inTxn:
|
||||
outDb.exec("BEGIN TRANSACTION;").expect("DB")
|
||||
inTxn = true
|
||||
case info.kind
|
||||
let epoch = getStateField(tmpState[].data, slot).epoch.int64
|
||||
echo epoch
|
||||
|
||||
withState(tmpState[].data):
|
||||
withEpochInfo(forkedInfo):
|
||||
doAssert state.data.balances.len == info.validators.len
|
||||
doAssert state.data.balances.len == previousEpochBalances.len
|
||||
doAssert state.data.balances.len == rewardsAndPenalties.len
|
||||
|
||||
for index, validator in info.validators.pairs:
|
||||
template outputInfo: untyped = rewardsAndPenalties[index]
|
||||
|
||||
checkBalance(index, validator, state.data.balances[index],
|
||||
previousEpochBalances[index], outputInfo)
|
||||
|
||||
let delay =
|
||||
when infoFork == EpochInfoFork.Phase0:
|
||||
let notSlashed = (RewardFlags.isSlashed notin validator.flags)
|
||||
if notSlashed and validator.is_previous_epoch_attester.isSome():
|
||||
some(int64(validator.is_previous_epoch_attester.get().delay))
|
||||
else:
|
||||
none(int64)
|
||||
else:
|
||||
rewardsAndPenalties[index].inclusion_delay
|
||||
|
||||
if conf.perfect or not outputInfo.isPerfect:
|
||||
insertValidatorInfo.exec((
|
||||
index.int64,
|
||||
epoch,
|
||||
outputInfo.source_outcome,
|
||||
outputInfo.max_source_reward.int64,
|
||||
outputInfo.target_outcome,
|
||||
outputInfo.max_target_reward.int64,
|
||||
outputInfo.head_outcome,
|
||||
outputInfo.max_head_reward.int64,
|
||||
outputInfo.inclusion_delay_outcome,
|
||||
outputInfo.max_inclusion_delay_reward.int64,
|
||||
outputInfo.sync_committee_outcome,
|
||||
outputInfo.max_sync_committee_reward.int64,
|
||||
outputInfo.proposer_outcome,
|
||||
outputInfo.inactivity_penalty.int64,
|
||||
outputInfo.slashing_outcome,
|
||||
delay)).expect("DB")
|
||||
|
||||
collectBalances(previousEpochBalances, tmpState[].data)
|
||||
|
||||
case forkedInfo.kind
|
||||
of EpochInfoFork.Phase0:
|
||||
template info: untyped = info.phase0Data
|
||||
insertEpochInfo.exec(
|
||||
(getStateField(state[].data, slot).epoch.int64,
|
||||
template info: untyped = forkedInfo.phase0Data
|
||||
insertPhase0EpochInfo.exec((
|
||||
epoch,
|
||||
info.balances.current_epoch_raw.int64,
|
||||
info.balances.previous_epoch_raw.int64,
|
||||
info.balances.current_epoch_attesters_raw.int64,
|
||||
|
@ -865,77 +1349,67 @@ proc cmdValidatorDb(conf: DbConf, cfg: RuntimeConfig) =
|
|||
info.balances.previous_epoch_target_attesters_raw.int64,
|
||||
info.balances.previous_epoch_head_attesters_raw.int64)
|
||||
).expect("DB")
|
||||
|
||||
for index, status in info.validators.pairs():
|
||||
if not is_eligible_validator(status):
|
||||
continue
|
||||
let
|
||||
notSlashed = (RewardFlags.isSlashed notin status.flags)
|
||||
source_attester =
|
||||
notSlashed and status.is_previous_epoch_attester.isSome()
|
||||
target_attester =
|
||||
notSlashed and RewardFlags.isPreviousEpochTargetAttester in status.flags
|
||||
head_attester =
|
||||
notSlashed and RewardFlags.isPreviousEpochHeadAttester in status.flags
|
||||
delay =
|
||||
if notSlashed and status.is_previous_epoch_attester.isSome():
|
||||
some(int64(status.is_previous_epoch_attester.get().delay))
|
||||
else:
|
||||
none(int64)
|
||||
|
||||
if conf.perfect or not
|
||||
(source_attester and target_attester and head_attester and
|
||||
delay.isSome() and delay.get() == 1):
|
||||
insertValidatorInfo.exec(
|
||||
(index.int64,
|
||||
getStateField(state[].data, slot).epoch.int64,
|
||||
status.delta.rewards.int64,
|
||||
status.delta.penalties.int64,
|
||||
int64(source_attester), # Source delta
|
||||
int64(target_attester), # Target delta
|
||||
int64(head_attester), # Head delta
|
||||
delay)).expect("DB")
|
||||
of EpochInfoFork.Altair:
|
||||
echo "TODO altair support"
|
||||
template info: untyped = forkedInfo.altairData
|
||||
insertAltairEpochInfo.exec((
|
||||
epoch,
|
||||
info.balances.previous_epoch[0].int64,
|
||||
info.balances.previous_epoch[1].int64,
|
||||
info.balances.previous_epoch[2].int64,
|
||||
info.balances.current_epoch_TIMELY_TARGET.int64,
|
||||
info.balances.current_epoch.int64)).expect("DB")
|
||||
|
||||
if getStateField(state[].data, slot).epoch.int64 mod 16 == 0:
|
||||
inTxn = false
|
||||
outDb.exec("COMMIT;").expect("DB")
|
||||
proc processSlots(ends: Slot, endsFlags: UpdateFlags) =
|
||||
var currentSlot = getStateField(tmpState[].data, slot)
|
||||
while currentSlot < ends:
|
||||
let nextSlot = currentSlot + 1
|
||||
let flags = if nextSlot == ends: endsFlags else: {}
|
||||
|
||||
for bi in 0..<blockRefs.len:
|
||||
blck = db.getPhase0Block(blockRefs[blockRefs.len - bi - 1].root).get()
|
||||
while getStateField(state[].data, slot) < blck.message.slot:
|
||||
let
|
||||
nextSlot = getStateField(state[].data, slot) + 1
|
||||
flags =
|
||||
if nextSlot == blck.message.slot: {skipLastStateRootCalculation}
|
||||
else: {}
|
||||
if nextSlot.isEpoch:
|
||||
withState(tmpState[].data):
|
||||
rewardsAndPenalties.collectEpochRewardsAndPenalties(
|
||||
state.data, cache, cfg)
|
||||
|
||||
process_slots(cfg, state[].data, nextSlot, cache, info, flags).expect(
|
||||
"Slot processing can't fail with correct inputs")
|
||||
let ok = process_slots(cfg, tmpState[].data, nextSlot, cache, forkedInfo, flags)
|
||||
doAssert ok, "Slot processing can't fail with correct inputs"
|
||||
|
||||
if getStateField(state[].data, slot).is_epoch():
|
||||
processEpoch()
|
||||
currentSlot = nextSlot
|
||||
|
||||
let res = state_transition_block(
|
||||
cfg, state[].data, blck, cache, {}, noRollback)
|
||||
if res.isErr():
|
||||
echo "State transition failed (!) ", res.error()
|
||||
quit 1
|
||||
if currentSlot.isEpoch:
|
||||
outDb.inTransaction("DB"):
|
||||
processEpoch()
|
||||
rewardsAndPenalties.setLen(0)
|
||||
rewardsAndPenalties.setLen(validatorsCount)
|
||||
auxiliaryState.copyParticipationFlags(tmpState[].data)
|
||||
|
||||
for bi in 0 ..< blockRefs.len:
|
||||
let forkedBlock = dag.getForkedBlock(blockRefs[blockRefs.len - bi - 1])
|
||||
withBlck(forkedBlock):
|
||||
processSlots(blck.message.slot, {skipLastStateRootCalculation})
|
||||
|
||||
rewardsAndPenalties.collectBlockRewardsAndPenalties(
|
||||
tmpState[].data, forkedBlock, auxiliaryState, cache, cfg)
|
||||
|
||||
let res = state_transition_block(
|
||||
cfg, tmpState[].data, blck, cache, {}, noRollback)
|
||||
if res.isErr:
|
||||
echo "State transition failed (!)"
|
||||
quit 1
|
||||
|
||||
let newValidatorsCount = getStateField(tmpState[].data, validators).len
|
||||
if newValidatorsCount > validatorsCount:
|
||||
# Resize the structures in case a new validator has appeared after
|
||||
# the state_transition_block procedure call ...
|
||||
rewardsAndPenalties.setLen(newValidatorsCount)
|
||||
previousEpochBalances.setLen(newValidatorsCount)
|
||||
# ... and add the new validators to the database.
|
||||
outDb.insertValidators(tmpState[].data, validatorsCount, newValidatorsCount)
|
||||
validatorsCount = newValidatorsCount
|
||||
|
||||
# Capture rewards of empty slots as well, including the epoch that got
|
||||
# finalized
|
||||
while getStateField(state[].data, slot) <= ends:
|
||||
process_slots(
|
||||
cfg, state[].data, getStateField(state[].data, slot) + 1, cache,
|
||||
info, {}).expect("Slot processing can't fail with correct inputs")
|
||||
|
||||
if getStateField(state[].data, slot).is_epoch():
|
||||
processEpoch()
|
||||
|
||||
if inTxn:
|
||||
inTxn = false
|
||||
outDb.exec("COMMIT;").expect("DB")
|
||||
let ok = processSlots(ends, {})
|
||||
doAssert ok, "Slot processing can't fail with correct inputs"
|
||||
|
||||
when isMainModule:
|
||||
var
|
||||
|
|
Loading…
Reference in New Issue