speed up epoch processing by caching shuffling results (#153)

* speed up epoch processing by caching shuffling results
This commit is contained in:
Dustin Brody 2019-03-05 19:10:36 +00:00 committed by GitHub
parent 38f48753b2
commit b8402e9809
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 10 deletions

View File

@ -151,6 +151,26 @@ func slash_validator*(state: var BeaconState, index: ValidatorIndex) =
validator.withdrawable_epoch = get_current_epoch(state) + validator.withdrawable_epoch = get_current_epoch(state) +
LATEST_SLASHED_EXIT_LENGTH LATEST_SLASHED_EXIT_LENGTH
func update_shuffling_cache*(state: var BeaconState) =
let
list_size = state.validator_registry.len.uint64
shuffling_seq = mapIt(
get_shuffled_seq(state.current_shuffling_seed, list_size),
# No intrinsic reason for this conversion; SSZ requirement artifact.
it.int)
doAssert state.shuffling_cache.index in [0, 1]
# Do a dance to keep everything JSON-encodable.
state.shuffling_cache.seeds[state.shuffling_cache.index] =
state.current_shuffling_seed
state.shuffling_cache.list_sizes[state.shuffling_cache.index] = list_size
if state.shuffling_cache.index == 0:
state.shuffling_cache.shuffling_0 = shuffling_seq
else:
state.shuffling_cache.shuffling_1 = shuffling_seq
state.shuffling_cache.index = 1 - state.shuffling_cache.index
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#on-genesis # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#on-genesis
func get_genesis_beacon_state*( func get_genesis_beacon_state*(
initial_validator_deposits: openArray[Deposit], initial_validator_deposits: openArray[Deposit],
@ -238,6 +258,8 @@ func get_genesis_beacon_state*(
state.latest_active_index_roots[index] = genesis_active_index_root state.latest_active_index_roots[index] = genesis_active_index_root
state.current_shuffling_seed = generate_seed(state, GENESIS_EPOCH) state.current_shuffling_seed = generate_seed(state, GENESIS_EPOCH)
update_shuffling_cache(state)
state state
# TODO candidate for spec? # TODO candidate for spec?

View File

@ -417,6 +417,9 @@ type
eth1_data_votes*: seq[Eth1DataVote] eth1_data_votes*: seq[Eth1DataVote]
deposit_index*: uint64 deposit_index*: uint64
# Not in spec. TODO: don't serialize or deserialize this.
shuffling_cache*: ShufflingCache
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#validator
Validator* = object Validator* = object
pubkey*: ValidatorPubKey ##\ pubkey*: ValidatorPubKey ##\
@ -493,6 +496,21 @@ type
# TODO: not in spec # TODO: not in spec
CrosslinkCommittee* = tuple[committee: seq[ValidatorIndex], shard: uint64] CrosslinkCommittee* = tuple[committee: seq[ValidatorIndex], shard: uint64]
ShufflingCache* = object
## https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
## Note: this definition and the next few definitions make heavy use of
## repetitive computing. Production implementations are expected to
## appropriately use caching/memoization to avoid redoing work.
##
## TODO use native ValidatorIndex, once this doesn't need to be serialized.
## `seed` and `list_size` determine the shuffle. For now, only need two, at
## any given time. If the next_epoch variations of shuffling get called, it
## might increase to three at once.
seeds*: array[2, Eth2Digest]
list_sizes*: array[2, uint64]
index*: int
shuffling_0*: seq[int]
shuffling_1*: seq[int]
func shortValidatorKey*(state: BeaconState, validatorIdx: int): string = func shortValidatorKey*(state: BeaconState, validatorIdx: int): string =
($state.validator_registry[validatorIdx].pubkey)[0..7] ($state.validator_registry[validatorIdx].pubkey)[0..7]

View File

@ -15,7 +15,7 @@ import
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_permuted_index # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_permuted_index
func get_shuffled_seq*(seed: Eth2Digest, func get_shuffled_seq*(seed: Eth2Digest,
validators: openArray[Validator], list_size: uint64,
): seq[ValidatorIndex] = ): seq[ValidatorIndex] =
## Via https://github.com/protolambda/eth2-shuffle/blob/master/shuffle.go ## Via https://github.com/protolambda/eth2-shuffle/blob/master/shuffle.go
## Shuffles ``validators`` into crosslink committees seeded by ``seed`` and ## Shuffles ``validators`` into crosslink committees seeded by ``seed`` and
@ -25,7 +25,6 @@ func get_shuffled_seq*(seed: Eth2Digest,
## ##
## Invert the inner/outer loops from the spec, essentially. Most useful ## Invert the inner/outer loops from the spec, essentially. Most useful
## hash result re-use occurs within a round. ## hash result re-use occurs within a round.
let list_size = validators.len.uint64
var var
# Share these buffers. # Share these buffers.
pivot_buffer: array[(32+1), byte] pivot_buffer: array[(32+1), byte]
@ -75,21 +74,31 @@ func get_shuffled_seq*(seed: Eth2Digest,
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_shuffling
func get_shuffling*(seed: Eth2Digest, func get_shuffling*(seed: Eth2Digest,
validators: openArray[Validator], validators: openArray[Validator],
epoch: Epoch epoch: Epoch,
shuffling_cache: ShufflingCache
): seq[seq[ValidatorIndex]] = ): seq[seq[ValidatorIndex]] =
## This function is factored to facilitate testing with ## This function is factored to facilitate testing with
## https://github.com/ethereum/eth2.0-test-generators/tree/master/permutated_index ## https://github.com/ethereum/eth2.0-test-generators/tree/master/permutated_index
## test vectors, which the split of get_shuffling obfuscates. ## test vectors, which the split of get_shuffling obfuscates.
let list_size = validators.len.uint64
let let
active_validator_indices = get_active_validator_indices(validators, epoch) active_validator_indices = get_active_validator_indices(validators, epoch)
committees_per_epoch = get_epoch_committee_count( committees_per_epoch = get_epoch_committee_count(
len(active_validator_indices)).int len(active_validator_indices)).int
# Both mapIt-type-conversions are an SSZ artifact. TODO remove.
shuffled_seq =
if shuffling_cache.seeds[0] == seed and
shuffling_cache.list_sizes[0] == list_size:
mapIt(shuffling_cache.shuffling_0, it.ValidatorIndex)
elif shuffling_cache.seeds[1] == seed and
shuffling_cache.list_sizes[1] == list_size:
mapIt(shuffling_cache.shuffling_1, it.ValidatorIndex)
else:
get_shuffled_seq(seed, list_size)
# Split the shuffled list into committees_per_epoch pieces # Split the shuffled list into committees_per_epoch pieces
result = split( result = split(shuffled_seq, committees_per_epoch)
get_shuffled_seq(seed, validators),
committees_per_epoch)
assert result.len() == committees_per_epoch # what split should do.. assert result.len() == committees_per_epoch # what split should do..
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_previous_epoch_committee_count # https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#get_previous_epoch_committee_count
@ -196,6 +205,7 @@ func get_crosslink_committees_at_slot*(state: BeaconState, slot: Slot,
seed, seed,
state.validator_registry, state.validator_registry,
shuffling_epoch, shuffling_epoch,
state.shuffling_cache
) )
offset = slot mod SLOTS_PER_EPOCH offset = slot mod SLOTS_PER_EPOCH
committees_per_slot = committees_per_epoch div SLOTS_PER_EPOCH committees_per_slot = committees_per_epoch div SLOTS_PER_EPOCH

View File

@ -916,6 +916,8 @@ func processEpoch(state: var BeaconState) =
state.current_shuffling_seed = generate_seed(state, state.current_shuffling_epoch) state.current_shuffling_seed = generate_seed(state, state.current_shuffling_epoch)
# /Note/ that state.current_shuffling_start_shard is left unchanged # /Note/ that state.current_shuffling_start_shard is left unchanged
updateShufflingCache(state)
## Regardless of whether or not a validator set change happens run ## Regardless of whether or not a validator set change happens run
## process_slashings(state) and process_exit_queue(state) ## process_slashings(state) and process_exit_queue(state)
process_slashings(state) process_slashings(state)

View File

@ -19,9 +19,7 @@ func sumCommittees(v: openArray[seq[ValidatorIndex]], reqCommitteeLen: int): int
func checkPermutation(index: int, list_size: uint64, func checkPermutation(index: int, list_size: uint64,
permuted_index: int, seed: Eth2Digest): bool = permuted_index: int, seed: Eth2Digest): bool =
let let
validators = repeat( s = get_shuffled_seq(seed, list_size)
Validator(exit_epoch: FAR_FUTURE_EPOCH), list_size)
s = get_shuffled_seq(seed, validators)
s[index] == permuted_index.ValidatorIndex s[index] == permuted_index.ValidatorIndex
suite "Validators": suite "Validators":
@ -35,7 +33,7 @@ suite "Validators":
Validator( Validator(
exit_epoch: FAR_FUTURE_EPOCH exit_epoch: FAR_FUTURE_EPOCH
), num_validators) ), num_validators)
s = get_shuffling(Eth2Digest(), validators, 0) s = get_shuffling(Eth2Digest(), validators, 0, ShufflingCache())
committees = get_epoch_committee_count(len(validators)).int committees = get_epoch_committee_count(len(validators)).int
check: check:
# def b(s): return "Eth2Digest(data: [0x" + "'u8, 0x".join((s[i:i+2] for i in range(0, 64, 2))) + "'u8])" # def b(s): return "Eth2Digest(data: [0x" + "'u8, 0x".join((s[i:i+2] for i in range(0, 64, 2))) + "'u8])"