Skip to content

Commit dfcbf3c

Browse files
authored
Merge pull request #1521 from hwwhww/rework_helper2
Rework helper functions - part 1
2 parents d0f9c96 + 3b21141 commit dfcbf3c

File tree

9 files changed

+241
-121
lines changed

9 files changed

+241
-121
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/enums/validator_status_codes.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ class ValidatorStatusCode(IntEnum):
55
PENDING_ACTIVATION = 0
66
ACTIVE = 1
77
PENDING_EXIT = 2
8-
PENDING_WITHDRAW = 3
9-
WITHDRAWN = 4
10-
PENALIZED = 127
8+
EXITED_WITHOUT_PENALTY = 3
9+
EXITED_WITH_PENALTY = 4

eth/beacon/helpers.py

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
)
2424

2525
from eth.beacon.block_committees_info import BlockCommitteesInfo
26+
from eth.beacon.enums.validator_status_codes import (
27+
ValidatorStatusCode,
28+
)
2629
from eth.beacon.types.shard_and_committees import (
2730
ShardAndCommittee,
2831
)
@@ -37,6 +40,7 @@
3740
from eth.beacon.types.attestation_records import AttestationRecord # noqa: F401
3841
from eth.beacon.types.blocks import BaseBeaconBlock # noqa: F401
3942
from eth.beacon.types.crystallized_states import CrystallizedState # noqa: F401
43+
from eth.beacon.types.states import BeaconState # noqa: F401
4044
from eth.beacon.types.validator_records import ValidatorRecord # noqa: F401
4145

4246

@@ -170,6 +174,9 @@ def get_shards_and_committees_for_slot(
170174
crystallized_state: 'CrystallizedState',
171175
slot: int,
172176
cycle_length: int) -> Iterable[ShardAndCommittee]:
177+
"""
178+
FIXME
179+
"""
173180
if len(crystallized_state.shard_and_committee_for_slots) != cycle_length * 2:
174181
raise ValueError(
175182
"Length of shard_and_committee_for_slots != cycle_length * 2"
@@ -192,6 +199,7 @@ def get_attestation_indices(crystallized_state: 'CrystallizedState',
192199
attestation: 'AttestationRecord',
193200
cycle_length: int) -> Iterable[int]:
194201
"""
202+
FIXME
195203
Return committee of the given attestation.
196204
"""
197205
shard_id = attestation.shard_id
@@ -207,64 +215,30 @@ def get_attestation_indices(crystallized_state: 'CrystallizedState',
207215
yield from shard_and_committee.committee
208216

209217

210-
@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']) -> Tuple[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+
Gets indices of active validators from ``validators``.
217221
"""
218-
o = []
219-
for index, validator in enumerate(validators):
220-
if (validator.start_dynasty <= dynasty and dynasty < validator.end_dynasty):
221-
o.append(index)
222-
return o
222+
return tuple(
223+
i for i, v in enumerate(validators)
224+
if v.status in [ValidatorStatusCode.ACTIVE, ValidatorStatusCode.PENDING_EXIT]
225+
)
223226

224227

225228
#
226229
# Shuffling
227230
#
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-
257231
@to_tuple
258232
def _get_shards_and_committees_for_shard_indices(
259233
shard_indices: Sequence[Sequence[int]],
260-
shard_start: int,
234+
start_shard: int,
261235
shard_count: int) -> Iterable[ShardAndCommittee]:
262236
"""
263237
Returns filled [ShardAndCommittee] tuple.
264238
"""
265239
for index, indices in enumerate(shard_indices):
266240
yield ShardAndCommittee(
267-
shard=(shard_start + index) % shard_count,
241+
shard=(start_shard + index) % shard_count,
268242
committee=indices
269243
)
270244

@@ -273,14 +247,13 @@ def _get_shards_and_committees_for_shard_indices(
273247
def get_new_shuffling(*,
274248
seed: Hash32,
275249
validators: Sequence['ValidatorRecord'],
276-
dynasty: int,
277250
crosslinking_start_shard: int,
278251
cycle_length: int,
279-
min_committee_size: int,
252+
target_committee_size: int,
280253
shard_count: int) -> Iterable[Iterable[ShardAndCommittee]]:
281254
"""
282255
Return shuffled ``shard_and_committee_for_slots`` (``[[ShardAndCommittee]]``) of
283-
the given active ``validators``.
256+
the given active ``validators`` using ``seed`` as entropy.
284257
285258
Two-dimensional:
286259
The first layer is ``slot`` number
@@ -319,27 +292,26 @@ def get_new_shuffling(*,
319292
ShardAndCommittee(shard_id=5, committee=[7, 3, 11]),
320293
],
321294
]
322-
323-
NOTE: The spec might be updated to output an array rather than an array of arrays.
324295
"""
325-
active_validators = get_active_validator_indices(dynasty, validators)
296+
active_validators = get_active_validator_indices(validators)
326297
active_validators_size = len(active_validators)
327298
committees_per_slot = clamp(
328299
1,
329300
shard_count // cycle_length,
330-
active_validators_size // cycle_length // (min_committee_size * 2) + 1,
301+
active_validators_size // cycle_length // target_committee_size,
331302
)
303+
# Shuffle with seed
332304
shuffled_active_validator_indices = shuffle(active_validators, seed)
333305

334-
# Split the shuffled list into cycle_length pieces
306+
# Split the shuffled list into epoch_length pieces
335307
validators_per_slot = split(shuffled_active_validator_indices, cycle_length)
336308
for index, slot_indices in enumerate(validators_per_slot):
337309
# Split the shuffled list into committees_per_slot pieces
338310
shard_indices = split(slot_indices, committees_per_slot)
339-
shard_id_start = crosslinking_start_shard + index * committees_per_slot
311+
start_shard = crosslinking_start_shard + index * committees_per_slot
340312
yield _get_shards_and_committees_for_shard_indices(
341313
shard_indices,
342-
shard_id_start,
314+
start_shard,
343315
shard_count,
344316
)
345317

@@ -356,6 +328,7 @@ def get_block_committees_info(parent_block: 'BaseBeaconBlock',
356328
cycle_length,
357329
)
358330
"""
331+
FIXME
359332
Return the block committees and proposer info with BlockCommitteesInfo pack.
360333
"""
361334
# `proposer_index_in_committee` th attester in `shard_and_committee`

eth/beacon/utils/random.py

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Any,
44
Iterable,
55
Sequence,
6+
Tuple,
67
TypeVar,
78
)
89

@@ -17,70 +18,81 @@
1718
blake,
1819
)
1920
from eth.beacon.constants import (
20-
SAMPLE_RANGE,
21+
RAND_BYTES,
22+
RAND_MAX,
2123
)
2224

2325

2426
TItem = TypeVar('TItem')
2527

2628

29+
@to_tuple
2730
def shuffle(values: Sequence[Any],
2831
seed: Hash32) -> Iterable[Any]:
2932
"""
3033
Returns the shuffled ``values`` with seed as entropy.
3134
Mainly for shuffling active validators in-protocol.
3235
33-
Spec: https://github.com/ethereum/eth2.0-specs/blob/0941d592de7546a428066c0473fd1000a7e3e3af/specs/beacon-chain.md#helper-functions # noqa: E501
36+
Spec: https://github.com/ethereum/eth2.0-specs/blob/70cef14a08de70e7bd0455d75cf380eb69694bfb/specs/core/0_beacon-chain.md#helper-functions # noqa: E501
3437
"""
3538
values_count = len(values)
3639

37-
if values_count > SAMPLE_RANGE:
40+
# The range of the RNG places an upper-bound on the size of the list that
41+
# may be shuffled. It is a logic error to supply an oversized list.
42+
if values_count >= RAND_MAX:
3843
raise ValueError(
39-
"values_count (%s) should less than or equal to SAMPLE_RANGE (%s)." %
40-
(values_count, SAMPLE_RANGE)
44+
"values_count (%s) should less than RAND_MAX (%s)." %
45+
(values_count, RAND_MAX)
4146
)
4247

4348
output = [x for x in values]
4449
source = seed
4550
index = 0
46-
while index < values_count:
47-
# Re-hash the source
51+
while index < values_count - 1:
52+
# Re-hash the `source` to obtain a new pattern of bytes.
4853
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
54+
55+
# Iterate through the `source` bytes in 3-byte chunks.
56+
for position in range(0, 32 - (32 % RAND_BYTES), RAND_BYTES):
57+
# Determine the number of indices remaining in `values` and exit
58+
# once the last index is reached.
5359
remaining = values_count - index
54-
if remaining == 0:
60+
if remaining == 1:
5561
break
5662

57-
# Set a random maximum bound of sample_from_source
58-
rand_max = SAMPLE_RANGE - SAMPLE_RANGE % remaining
63+
# Read 3-bytes of `source` as a 24-bit big-endian integer.
64+
sample_from_source = int.from_bytes(
65+
source[position:position + RAND_BYTES], 'big'
66+
)
5967

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`
68+
# Sample values greater than or equal to `sample_max` will cause
69+
# modulo bias when mapped into the `remaining` range.
70+
sample_max = RAND_MAX - RAND_MAX % remaining
71+
72+
# Perform a swap if the consumed entropy will not cause modulo bias.
73+
if sample_from_source < sample_max:
74+
# Select a replacement index for the current index.
6375
replacement_position = (sample_from_source % remaining) + index
64-
# Swap the index-th and replacement_position-th elements
76+
# Swap the current index with the replacement index.
6577
(output[index], output[replacement_position]) = (
6678
output[replacement_position],
6779
output[index]
6880
)
6981
index += 1
7082
else:
83+
# The sample causes modulo bias. A new sample should be read.
7184
pass
7285

7386
return output
7487

7588

76-
@to_tuple
77-
def split(seq: Sequence[TItem], pieces: int) -> Iterable[Any]:
89+
def split(values: Sequence[TItem], split_count: int) -> Tuple[Any, ...]:
7890
"""
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
91+
Returns the split ``values`` in ``split_count`` pieces in protocol.
92+
Spec: https://github.com/ethereum/eth2.0-specs/blob/70cef14a08de70e7bd0455d75cf380eb69694bfb/specs/core/0_beacon-chain.md#helper-functions # noqa: E501
8193
"""
82-
list_length = len(seq)
83-
return [
84-
seq[(list_length * i // pieces): (list_length * (i + 1) // pieces)]
85-
for i in range(pieces)
86-
]
94+
list_length = len(values)
95+
return tuple(
96+
values[(list_length * i // split_count): (list_length * (i + 1) // split_count)]
97+
for i in range(split_count)
98+
)

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 integer_squareroot(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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
import pytest
22

3+
from eth_utils import denoms
4+
5+
from eth.constants import (
6+
ZERO_HASH32,
7+
)
38
import eth.utils.bls as bls
49
from eth.utils.blake import blake
510

11+
from eth.beacon.enums.validator_status_codes import (
12+
ValidatorStatusCode,
13+
)
614
from eth.beacon.state_machines.forks.serenity.configs import SERENITY_CONFIG
15+
from eth.beacon.types.validator_records import (
16+
ValidatorRecord,
17+
)
718

819
from eth.beacon.types.attestation_signed_data import (
920
AttestationSignedData,
@@ -349,3 +360,24 @@ def max_attestation_count():
349360
@pytest.fixture
350361
def initial_fork_version():
351362
return SERENITY_CONFIG.INITIAL_FORK_VERSION
363+
364+
365+
#
366+
# genesis
367+
#
368+
@pytest.fixture
369+
def genesis_validators(init_validator_keys,
370+
init_randao,
371+
deposit_size):
372+
return tuple(
373+
ValidatorRecord(
374+
pubkey=pub,
375+
withdrawal_credentials=ZERO_HASH32,
376+
randao_commitment=init_randao,
377+
randao_skips=0,
378+
balance=deposit_size * denoms.gwei,
379+
status=ValidatorStatusCode.ACTIVE,
380+
last_status_change_slot=0,
381+
exit_seq=0,
382+
) for pub in init_validator_keys
383+
)

0 commit comments

Comments
 (0)