Skip to content

Commit b5d592d

Browse files
committed
Fix test_num_validators
Update random.py Update `get_active_validator_indices` Fix get_new_shuffling Rebased Add `isqrt` Add edge cases for `int_sqrt`
1 parent b272c8f commit b5d592d

File tree

8 files changed

+208
-108
lines changed

8 files changed

+208
-108
lines changed

eth/beacon/constants.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@
55
# The size of 3 bytes in integer
66
# sample_range = 2 ** (3 * 8) = 2 ** 24 = 16777216
77
# sample_range = 16777216
8-
SAMPLE_RANGE = 16777216
8+
9+
# Entropy is consumed from the seed in 3-byte (24 bit) chunks.
10+
RAND_BYTES = 3
11+
# The highest possible result of the RNG.
12+
RAND_MAX = 2 ** (RAND_BYTES * 8) - 1

eth/beacon/helpers.py

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
Any,
77
Iterable,
88
Sequence,
9-
Tuple,
109
TYPE_CHECKING,
1110
)
1211

@@ -23,6 +22,9 @@
2322
)
2423

2524
from eth.beacon.block_committees_info import BlockCommitteesInfo
25+
from eth.beacon.enums.validator_status_codes import (
26+
ValidatorStatusCode,
27+
)
2628
from eth.beacon.types.shard_and_committees import (
2729
ShardAndCommittee,
2830
)
@@ -37,6 +39,7 @@
3739
from eth.beacon.types.attestation_records import AttestationRecord # noqa: F401
3840
from eth.beacon.types.blocks import BaseBeaconBlock # noqa: F401
3941
from eth.beacon.types.crystallized_states import CrystallizedState # noqa: F401
42+
from eth.beacon.types.states import BeaconState # noqa: F401
4043
from eth.beacon.types.validator_records import ValidatorRecord # noqa: F401
4144

4245

@@ -170,6 +173,9 @@ def get_shards_and_committees_for_slot(
170173
crystallized_state: 'CrystallizedState',
171174
slot: int,
172175
cycle_length: int) -> Iterable[ShardAndCommittee]:
176+
"""
177+
FIXME
178+
"""
173179
if len(crystallized_state.shard_and_committee_for_slots) != cycle_length * 2:
174180
raise ValueError(
175181
"Length of shard_and_committee_for_slots != cycle_length * 2"
@@ -192,6 +198,7 @@ def get_attestation_indices(crystallized_state: 'CrystallizedState',
192198
attestation: 'AttestationRecord',
193199
cycle_length: int) -> Iterable[int]:
194200
"""
201+
FIXME
195202
Return committee of the given attestation.
196203
"""
197204
shard_id = attestation.shard_id
@@ -208,63 +215,32 @@ def get_attestation_indices(crystallized_state: 'CrystallizedState',
208215

209216

210217
@to_tuple
211-
def get_active_validator_indices(dynasty: int,
212-
validators: Iterable['ValidatorRecord']) -> Iterable[int]:
218+
def get_active_validator_indices(validators: Sequence['ValidatorRecord']) -> Iterable[int]:
213219
"""
214-
TODO: Logic changed in the latest spec, will have to update when we add validator
215-
rotation logic.
216-
https://github.com/ethereum/eth2.0-specs/commit/52cf7f943dc99cfd27db9fb2c03c692858e2a789#diff-a08ecec277db4a6ed0b3635cfadc9af1 # noqa: E501
220+
Return the active validators.
217221
"""
218222
o = []
219223
for index, validator in enumerate(validators):
220-
if (validator.start_dynasty <= dynasty and dynasty < validator.end_dynasty):
224+
if (validator.status == ValidatorStatusCode.ACTIVE):
221225
o.append(index)
222226
return o
223227

224228

225229
#
226230
# Shuffling
227231
#
228-
def _get_shuffling_committee_slot_portions(
229-
active_validators_size: int,
230-
cycle_length: int,
231-
min_committee_size: int,
232-
shard_count: int) -> Tuple[int, int]:
233-
"""
234-
Return committees number per slot and slots number per committee.
235-
"""
236-
# If there are enough active validators to form committees for every slot
237-
if active_validators_size >= cycle_length * min_committee_size:
238-
# One slot can be attested by many committees, but not more than shard_count // cycle_length
239-
committees_per_slot = min(
240-
active_validators_size // cycle_length // (min_committee_size * 2) + 1,
241-
shard_count // cycle_length
242-
)
243-
# One committee only has to attest one slot
244-
slots_per_committee = 1
245-
else:
246-
# One slot can only be asttested by one committee
247-
committees_per_slot = 1
248-
# One committee has to asttest more than one slot
249-
slots_per_committee = 1
250-
bound = cycle_length * min(min_committee_size, active_validators_size)
251-
while(active_validators_size * slots_per_committee < bound):
252-
slots_per_committee *= 2
253-
254-
return committees_per_slot, slots_per_committee
255-
256-
257232
@to_tuple
258233
def _get_shards_and_committees_for_shard_indices(
259234
shard_indices: Sequence[Sequence[int]],
260-
shard_start: int,
235+
start_shard: int,
261236
shard_count: int) -> Iterable[ShardAndCommittee]:
262237
"""
238+
FIXME
263239
Returns filled [ShardAndCommittee] tuple.
264240
"""
265241
for index, indices in enumerate(shard_indices):
266242
yield ShardAndCommittee(
267-
shard=(shard_start + index) % shard_count,
243+
shard=(start_shard + index) % shard_count,
268244
committee=indices
269245
)
270246

@@ -273,10 +249,9 @@ def _get_shards_and_committees_for_shard_indices(
273249
def get_new_shuffling(*,
274250
seed: Hash32,
275251
validators: Sequence['ValidatorRecord'],
276-
dynasty: int,
277252
crosslinking_start_shard: int,
278253
cycle_length: int,
279-
min_committee_size: int,
254+
target_committee_size: int,
280255
shard_count: int) -> Iterable[Iterable[ShardAndCommittee]]:
281256
"""
282257
Return shuffled ``shard_and_committee_for_slots`` (``[[ShardAndCommittee]]``) of
@@ -319,15 +294,13 @@ def get_new_shuffling(*,
319294
ShardAndCommittee(shard_id=5, committee=[7, 3, 11]),
320295
],
321296
]
322-
323-
NOTE: The spec might be updated to output an array rather than an array of arrays.
324297
"""
325-
active_validators = get_active_validator_indices(dynasty, validators)
298+
active_validators = get_active_validator_indices(validators)
326299
active_validators_size = len(active_validators)
327300
committees_per_slot = clamp(
328301
1,
329302
shard_count // cycle_length,
330-
active_validators_size // cycle_length // (min_committee_size * 2) + 1,
303+
active_validators_size // cycle_length // target_committee_size,
331304
)
332305
shuffled_active_validator_indices = shuffle(active_validators, seed)
333306

@@ -336,10 +309,10 @@ def get_new_shuffling(*,
336309
for index, slot_indices in enumerate(validators_per_slot):
337310
# Split the shuffled list into committees_per_slot pieces
338311
shard_indices = split(slot_indices, committees_per_slot)
339-
shard_id_start = crosslinking_start_shard + index * committees_per_slot
312+
start_shard = crosslinking_start_shard + index * committees_per_slot
340313
yield _get_shards_and_committees_for_shard_indices(
341314
shard_indices,
342-
shard_id_start,
315+
start_shard,
343316
shard_count,
344317
)
345318

@@ -356,6 +329,7 @@ def get_block_committees_info(parent_block: 'BaseBeaconBlock',
356329
cycle_length,
357330
)
358331
"""
332+
FIXME
359333
Return the block committees and proposer info with BlockCommitteesInfo pack.
360334
"""
361335
# `proposer_index_in_committee` th attester in `shard_and_committee`

eth/beacon/utils/random.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,70 +17,80 @@
1717
blake,
1818
)
1919
from eth.beacon.constants import (
20-
SAMPLE_RANGE,
20+
RAND_BYTES,
21+
RAND_MAX,
2122
)
2223

2324

2425
TItem = TypeVar('TItem')
2526

2627

28+
@to_tuple
2729
def shuffle(values: Sequence[Any],
2830
seed: Hash32) -> Iterable[Any]:
2931
"""
3032
Returns the shuffled ``values`` with seed as entropy.
3133
Mainly for shuffling active validators in-protocol.
3234
33-
Spec: https://github.com/ethereum/eth2.0-specs/blob/0941d592de7546a428066c0473fd1000a7e3e3af/specs/beacon-chain.md#helper-functions # noqa: E501
35+
Spec: https://github.com/ethereum/eth2.0-specs/blob/70cef14a08de70e7bd0455d75cf380eb69694bfb/specs/core/0_beacon-chain.md#helper-functions # noqa: E501
3436
"""
3537
values_count = len(values)
3638

37-
if values_count > SAMPLE_RANGE:
39+
if values_count > RAND_MAX:
3840
raise ValueError(
39-
"values_count (%s) should less than or equal to SAMPLE_RANGE (%s)." %
40-
(values_count, SAMPLE_RANGE)
41+
"values_count (%s) should less than or equal to RAND_MAX (%s)." %
42+
(values_count, RAND_MAX)
4143
)
4244

4345
output = [x for x in values]
4446
source = seed
4547
index = 0
46-
while index < values_count:
47-
# Re-hash the source
48+
while index < values_count - 1:
49+
# Re-hash the `source` to obtain a new pattern of bytes.
4850
source = blake(source)
49-
for position in range(0, 30, 3): # gets indices 3 bytes at a time
50-
# Select a 3-byte sampled int
51-
sample_from_source = int.from_bytes(source[position:position + 3], 'big')
52-
# `remaining` is the size of remaining indices of this round
51+
52+
# Iterate through the `source` bytes in 3-byte chunks.
53+
for position in range(0, 32 - (32 % RAND_BYTES), RAND_BYTES):
54+
# Determine the number of indices remaining in `values` and exit
55+
# once the last index is reached.
5356
remaining = values_count - index
54-
if remaining == 0:
57+
if remaining == 1:
5558
break
5659

57-
# Set a random maximum bound of sample_from_source
58-
rand_max = SAMPLE_RANGE - SAMPLE_RANGE % remaining
60+
# Read 3-bytes of `source` as a 24-bit big-endian integer.
61+
sample_from_source = int.from_bytes(
62+
source[position:position + RAND_BYTES], 'big'
63+
)
64+
65+
# Sample values greater than or equal to `sample_max` will cause
66+
# modulo bias when mapped into the `remaining` range.
67+
sample_max = RAND_MAX - RAND_MAX % remaining
5968

60-
# Select `replacement_position` with the given `sample_from_source` and `remaining`
61-
if sample_from_source < rand_max:
62-
# Use random number to get `replacement_position`, where it's not `index`
69+
# Perform a swap if the consumed entropy will not cause modulo bias.
70+
if sample_from_source < sample_max:
71+
# Select a replacement index for the current index.
6372
replacement_position = (sample_from_source % remaining) + index
64-
# Swap the index-th and replacement_position-th elements
73+
# Swap the current index with the replacement index.
6574
(output[index], output[replacement_position]) = (
6675
output[replacement_position],
6776
output[index]
6877
)
6978
index += 1
7079
else:
80+
# The sample causes modulo bias. A new sample should be read.
7181
pass
7282

7383
return output
7484

7585

7686
@to_tuple
77-
def split(seq: Sequence[TItem], pieces: int) -> Iterable[Any]:
87+
def split(seq: Sequence[TItem], split_count: int) -> Iterable[Any]:
7888
"""
79-
Returns the split ``seq`` in ``pieces`` pieces in protocol.
80-
Spec: https://github.com/ethereum/eth2.0-specs/blob/0941d592de7546a428066c0473fd1000a7e3e3af/specs/beacon-chain.md#helper-functions # noqa: E501
89+
Returns the split ``seq`` in ``split_count`` pieces in protocol.
90+
Spec: https://github.com/ethereum/eth2.0-specs/blob/70cef14a08de70e7bd0455d75cf380eb69694bfb/specs/core/0_beacon-chain.md#helper-functions # noqa: E501
8191
"""
8292
list_length = len(seq)
8393
return [
84-
seq[(list_length * i // pieces): (list_length * (i + 1) // pieces)]
85-
for i in range(pieces)
94+
seq[(list_length * i // split_count): (list_length * (i + 1) // split_count)]
95+
for i in range(split_count)
8696
]

eth/utils/numeric.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,29 @@ def clamp(inclusive_lower_bound: int,
9999
return inclusive_upper_bound
100100
else:
101101
return value
102+
103+
104+
def int_sqrt(value: int) -> int:
105+
"""
106+
Return the largest integer ``x`` such that ``x**2 <= value``.
107+
Ref: https://en.wikipedia.org/wiki/Integer_square_root
108+
"""
109+
if not isinstance(value, int) or isinstance(value, bool):
110+
raise ValueError(
111+
"Value must be an integer: Got: {0}".format(
112+
type(value),
113+
)
114+
)
115+
if value < 0:
116+
raise ValueError(
117+
"Value cannot be negative: Got: {0}".format(
118+
value,
119+
)
120+
)
121+
122+
x = value
123+
y = (x + 1) // 2
124+
while y < x:
125+
x = y
126+
y = (x + value // x) // 2
127+
return x

tests/beacon/conftest.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import pytest
22

3+
from eth_utils import denoms
4+
from eth_utils import (
5+
to_tuple,
6+
)
7+
8+
from eth.constants import (
9+
ZERO_HASH32,
10+
)
311
import eth.utils.bls as bls
412
from eth.utils.blake import blake
513

14+
from eth.beacon.enums.validator_status_codes import (
15+
ValidatorStatusCode,
16+
)
617
from eth.beacon.state_machines.forks.serenity.configs import SERENITY_CONFIG
18+
from eth.beacon.types.validator_records import (
19+
ValidatorRecord,
20+
)
721

822
from eth.beacon.types.attestation_signed_data import (
923
AttestationSignedData,
@@ -349,3 +363,25 @@ def max_attestation_count():
349363
@pytest.fixture
350364
def initial_fork_version():
351365
return SERENITY_CONFIG.INITIAL_FORK_VERSION
366+
367+
368+
#
369+
# genesis
370+
#
371+
@pytest.fixture
372+
@to_tuple
373+
def genesis_validators(init_validator_keys,
374+
init_randao,
375+
deposit_size):
376+
return [
377+
ValidatorRecord(
378+
pubkey=pub,
379+
withdrawal_credentials=ZERO_HASH32,
380+
randao_commitment=init_randao,
381+
randao_skips=0,
382+
balance=deposit_size * denoms.gwei,
383+
status=ValidatorStatusCode.ACTIVE,
384+
last_status_change_slot=0,
385+
exit_seq=0,
386+
) for pub in init_validator_keys
387+
]

0 commit comments

Comments
 (0)