Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up process_crosslinks(...) and get_crosslink_deltas(...) by 10x - 15x in state_sim #314

Merged
merged 5 commits into from Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 0 additions & 6 deletions beacon_chain/spec/datatypes.nim
Expand Up @@ -373,12 +373,6 @@ type
block_hash*: Eth2Digest ##\
## Block hash

## TODO remove or otherwise conditional-compile this, since it's for light
## client but not in spec
ValidatorSetDeltaFlags* {.pure.} = enum
Activation = 0
Exit = 1

# TODO to be replaced with some magic hash caching
HashedBeaconState* = object
data*: BeaconState
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/presets/mainnet.nim
Expand Up @@ -93,7 +93,7 @@ const
## TODO consistent time unit across projects, similar to C++ chrono?

# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#time-parameters
MIN_ATTESTATION_INCLUSION_DELAY* = 2'u64^2 ##\
MIN_ATTESTATION_INCLUSION_DELAY* = 1 ##\
## (24 seconds)
## Number of slots that attestations stay in the attestation
## pool before being added to a block.
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/presets/minimal.nim
Expand Up @@ -77,7 +77,7 @@ const

# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#time-parameters
# Unchanged
MIN_ATTESTATION_INCLUSION_DELAY* = 2'u64^0
MIN_ATTESTATION_INCLUSION_DELAY* = 1

# Changed
SLOTS_PER_EPOCH* {.intdefine.} = 64
Expand Down
112 changes: 83 additions & 29 deletions beacon_chain/spec/state_transition_epoch.nim
Expand Up @@ -67,7 +67,7 @@ func get_matching_head_attestations(state: BeaconState, epoch: Epoch):
)

func get_unslashed_attesting_indices(
state: BeaconState, attestations: seq[PendingAttestation],
state: BeaconState, attestations: openarray[PendingAttestation],
stateCache: var StateCache): HashSet[ValidatorIndex] =
result = initSet[ValidatorIndex]()
for a in attestations:
Expand Down Expand Up @@ -100,33 +100,72 @@ func get_winning_crosslink_and_attesting_indices(
filterIt(
get_matching_source_attestations(state, epoch),
it.data.crosslink.shard == shard)
# TODO don't keep h_t_r'ing state.current_crosslinks[shard]
root_current_shard_crosslink =
hash_tree_root(state.current_crosslinks[shard])
crosslinks =
filterIt(
mapIt(attestations, it.data.crosslink),
hash_tree_root(state.current_crosslinks[shard]) in
# TODO pointless memory allocation, etc.
@[it.parent_root, hash_tree_root(it)])
root_current_shard_crosslink == it.parent_root or
root_current_shard_crosslink == hash_tree_root(it))

# default=Crosslink()
if len(crosslinks) == 0:
return (Crosslink(), initSet[ValidatorIndex]())

## Not from spec. Don't repeatedly search/filter attestations in an O(n^2)
## way, but create lookup table in O(n) time with O(1) lookup by crosslink
## to cut out expensive inner loop.
##
## Could also sort attestations by .data.crosslink first, and rely on that
## ordering, among other approaches which don't change this function sig.
var attesting_indices = initTable[Eth2Digest, HashSet[ValidatorIndex]]()
for attestation in attestations:
let
crosslink = attestation.data.crosslink
crosslink_key = crosslink.data_root
var crosslink_attestation_indices =
if crosslink_key in attesting_indices:
attesting_indices[crosslink_key]
else:
initSet[ValidatorIndex]()

## See also how get_attesting_balance(...) works. This inverts the loop
## nesting order. Also, this ensures no duplicate indices, though it is
## not supposed to happen, regardless, if validators are only attesting
## on their assigned shards. Still, the right response there is slashed
## balances, not crashing clients.
crosslink_attestation_indices.incl(
get_unslashed_attesting_indices(state, [attestation], stateCache))
attesting_indices[crosslink_key] = crosslink_attestation_indices

## Winning crosslink has the crosslink data root with the most balance voting
## for it (ties broken lexicographically)
var
winning_crosslink: Crosslink
winning_crosslink_balance = 0.Gwei

for candidate_crosslink in crosslinks:
## TODO check if should cache this again
## let root_balance = get_attesting_balance_cached(
## state, attestations_for.getOrDefault(r), cache)
let crosslink_balance =
get_attesting_balance(
state,
filterIt(attestations, it.data.crosslink == candidate_crosslink),
stateCache)
when false:
let crosslink_balance_uncached =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a comment on the purpose of this when false, so we known that we can remove it if it's accomplished.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b8618f1

get_attesting_balance(
state,
filterIt(attestations, it.data.crosslink == candidate_crosslink),
stateCache)
# TODO verify if one can assume this cached balance always exists here, by
# doAsserting candidate_crosslink_key in attesting_indices
let
candidate_crosslink_key = candidate_crosslink.data_root
crosslink_balance =
if candidate_crosslink_key in attesting_indices:
get_total_balance(state, attesting_indices[candidate_crosslink_key])
else:
## See `get_total_balance(...)`
## But see above, this branch might never happen.
1.Gwei
## TODO factor out precalculation mechanism; consider adding compilation
## flag to enable long calculation & consistency/assumption checking.
when false:
doAssert crosslink_balance == crosslink_balance_uncached
if (crosslink_balance > winning_crosslink_balance or
(winning_crosslink_balance == crosslink_balance and
lowerThan(winning_crosslink.data_root,
Expand Down Expand Up @@ -241,7 +280,7 @@ func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
effective_balance * BASE_REWARD_FACTOR div
integer_squareroot(total_balance) div BASE_REWARDS_PER_EPOCH

# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#rewards-and-penalties-1
func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =
let
Expand All @@ -266,38 +305,54 @@ func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
matching_head_attestations =
get_matching_head_attestations(state, previous_epoch)
for attestations in
@[matching_source_attestations, matching_target_attestations,
matching_head_attestations]:
[matching_source_attestations, matching_target_attestations,
matching_head_attestations]:
let
unslashed_attesting_indices =
get_unslashed_attesting_indices(state, attestations, stateCache)
attesting_balance = get_attesting_balance(state, attestations, stateCache)
attesting_balance = get_total_balance(state, unslashed_attesting_indices)
for index in eligible_validator_indices:
if index in unslashed_attesting_indices:
rewards[index] +=
get_base_reward(state, index) * attesting_balance div total_balance
else:
penalties[index] += get_base_reward(state, index)

# Early-out not explicitly in spec
if matching_source_attestations.len == 0:
return (rewards, penalties)

# Proposer and inclusion delay micro-rewards
## This depends on matching_source_attestations being an indexable seq, not a
## set, hash table, etc.
let source_attestation_attesting_indices =
mapIt(
matching_source_attestations,
get_attesting_indices(state, it.data, it.aggregation_bits, stateCache))

## TODO if this is still a profiling issue, do higher-level semantic
## translation
for index in get_unslashed_attesting_indices(
state, matching_source_attestations, stateCache):
# Translation of attestation = min([...])
doAssert matching_source_attestations.len > 0
var attestation = matching_source_attestations[0]
for a in matching_source_attestations:
if index notin get_attesting_indices(
state, a.data, a.aggregation_bits, stateCache):
for source_attestation_index, a in matching_source_attestations:
if index notin
source_attestation_attesting_indices[source_attestation_index]:
continue

if a.inclusion_delay < attestation.inclusion_delay:
attestation = a
rewards[attestation.proposer_index] += get_base_reward(state, index) div
PROPOSER_REWARD_QUOTIENT

let proposer_reward =
(get_base_reward(state, index) div PROPOSER_REWARD_QUOTIENT).Gwei
rewards[attestation.proposer_index] += proposer_reward
let max_attester_reward = get_base_reward(state, index) - proposer_reward
rewards[index] +=
get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY div
attestation.inclusion_delay
(max_attester_reward *
((SLOTS_PER_EPOCH + MIN_ATTESTATION_INCLUSION_DELAY).uint64 -
attestation.inclusion_delay) div SLOTS_PER_EPOCH).Gwei

# Inactivity penalty
let finality_delay = previous_epoch - state.finalized_checkpoint.epoch
Expand All @@ -315,7 +370,7 @@ func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):

(rewards, penalties)

# TODO re-cache this one, as under 0.5 version, if profiling suggests it
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#rewards-and-penalties-1
func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
tuple[a: seq[Gwei], b: seq[Gwei]] =

Expand All @@ -327,7 +382,7 @@ func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
let
shard = (get_start_shard(state, epoch) + offset) mod SHARD_COUNT
crosslink_committee =
get_crosslink_committee(state, epoch, shard, cache)
toSet(get_crosslink_committee(state, epoch, shard, cache))
(winning_crosslink, attesting_indices) =
get_winning_crosslink_and_attesting_indices(
state, epoch, shard, cache)
Expand All @@ -337,10 +392,9 @@ func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
let base_reward = get_base_reward(state, index)
if index in attesting_indices:
rewards[index] +=
get_base_reward(state, index) * attesting_balance div
committee_balance
base_reward * attesting_balance div committee_balance
else:
penalties[index] += get_base_reward(state, index)
penalties[index] += base_reward

(rewards, penalties)

Expand Down
4 changes: 4 additions & 0 deletions beacon_chain/sync_protocol.nim
Expand Up @@ -5,6 +5,10 @@ import
beacon_node_types, eth2_network, beacon_chain_db, block_pool, time, ssz

type
ValidatorSetDeltaFlags {.pure.} = enum
Activation = 0
Exit = 1

ValidatorChangeLogEntry* = object
case kind*: ValidatorSetDeltaFlags
of Activation:
Expand Down