91 lines
3.2 KiB
Nim
91 lines
3.2 KiB
Nim
# 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
|