nimbus-eth2/beacon_chain/validator_bucket_sort.nim

91 lines
3.2 KiB
Nim
Raw Permalink Normal View History

# beacon_chain
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
import std/typetraits
import "."/spec/crypto
from "."/spec/datatypes/base import Validator, ValidatorIndex, pubkey, `==`
const
BUCKET_BITS = 9 # >= 13 gets slow to construct
NUM_BUCKETS = 1 shl BUCKET_BITS
type
# `newSeqUninitialized` requires its type to be SomeNumber
IntValidatorIndex = distinctBase ValidatorIndex
BucketSortedValidators* = object
bucketSorted*: seq[IntValidatorIndex]
bucketUpperBounds: array[NUM_BUCKETS, uint] # avoids over/underflow checks
extraItems*: seq[ValidatorIndex]
template getBucketNumber(h: ValidatorPubKey): uint =
# This assumes https://en.wikipedia.org/wiki/Avalanche_effect for uniform
# distribution across pubkeys. ValidatorPubKey specifically satisfies this
# criterion. If required, can look at more input bytes, but ultimately it
# doesn't affect correctness, only speed.
# Otherwise need more than 2 bytes of input
static: doAssert BUCKET_BITS <= 16
const BUCKET_MASK = (NUM_BUCKETS - 1)
((h.blob[0] * 256 + h.blob[1]) and BUCKET_MASK)
func sortValidatorBuckets*(validators: openArray[Validator]):
ref BucketSortedValidators {.noinline.} =
var bucketSizes: array[NUM_BUCKETS, uint]
for validator in validators:
inc bucketSizes[getBucketNumber(validator.pubkey)]
var
bucketInsertPositions: array[NUM_BUCKETS, uint]
accum: uint
for i, s in bucketSizes:
accum += s
bucketInsertPositions[i] = accum
doAssert accum == validators.len.uint
let res = (ref BucketSortedValidators)(
bucketSorted: newSeqUninitialized[IntValidatorIndex](validators.len),
bucketUpperBounds: bucketInsertPositions)
for i, validator in validators:
let insertPos =
addr bucketInsertPositions[getBucketNumber(validator.pubkey)]
dec insertPos[]
res.bucketSorted[insertPos[]] = i.IntValidatorIndex
doAssert bucketInsertPositions[0] == 0
for i in 1 ..< NUM_BUCKETS:
doAssert res.bucketUpperBounds[i - 1] == bucketInsertPositions[i]
res
func add*(
bucketSortedValidators: var BucketSortedValidators,
validatorIndex: ValidatorIndex) =
bucketSortedValidators.extraItems.add validatorIndex
func findValidatorIndex*(
validators: openArray[Validator], bsv: BucketSortedValidators,
pubkey: ValidatorPubKey): Opt[ValidatorIndex] =
for validatorIndex in bsv.extraItems:
if validators[validatorIndex.distinctBase].pubkey == pubkey:
return Opt.some validatorIndex
let
bucketNumber = getBucketNumber(pubkey)
lowerBounds =
if bucketNumber == 0:
0'u
else:
bsv.bucketUpperBounds[bucketNumber - 1]
for i in lowerBounds ..< bsv.bucketUpperBounds[bucketNumber]:
if validators[bsv.bucketSorted[i]].pubkey == pubkey:
return Opt.some bsv.bucketSorted[i].ValidatorIndex
Opt.none ValidatorIndex