mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-13 04:04:19 +00:00
Merge branch 'master' into fix-recent_block_hashes
This commit is contained in:
commit
c519608eec
@ -34,6 +34,7 @@ The primary source of load on the beacon chain are "attestations". Attestations
|
|||||||
| --- | --- | :---: | - |
|
| --- | --- | :---: | - |
|
||||||
| `SHARD_COUNT` | 2**10 (= 1,024)| shards |
|
| `SHARD_COUNT` | 2**10 (= 1,024)| shards |
|
||||||
| `DEPOSIT_SIZE` | 2**5 (= 32) | ETH |
|
| `DEPOSIT_SIZE` | 2**5 (= 32) | ETH |
|
||||||
|
| `GWEI_PER_ETH` | 10**9 | Gwei/ETH |
|
||||||
| `MIN_COMMITTEE_SIZE` | 2**7 (= 128) | validators |
|
| `MIN_COMMITTEE_SIZE` | 2**7 (= 128) | validators |
|
||||||
| `GENESIS_TIME` | **TBD** | seconds |
|
| `GENESIS_TIME` | **TBD** | seconds |
|
||||||
| `SLOT_DURATION` | 2**4 (= 16) | seconds |
|
| `SLOT_DURATION` | 2**4 (= 16) | seconds |
|
||||||
@ -229,7 +230,7 @@ A `ValidatorRecord` has the following fields:
|
|||||||
# RANDAO commitment
|
# RANDAO commitment
|
||||||
'randao_commitment': 'hash32',
|
'randao_commitment': 'hash32',
|
||||||
# Balance
|
# Balance
|
||||||
'balance': 'int128',
|
'balance': 'int64',
|
||||||
# Status code
|
# Status code
|
||||||
'status': 'int8',
|
'status': 'int8',
|
||||||
# Slot when validator exited (or 0)
|
# Slot when validator exited (or 0)
|
||||||
@ -316,61 +317,107 @@ def get_active_validator_indices(validators):
|
|||||||
Now, a function that shuffles this list:
|
Now, a function that shuffles this list:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def shuffle(lst, seed):
|
def shuffle(values: List[Any],
|
||||||
# entropy is consumed in 3 byte chunks
|
seed: Hash32) -> List[Any]:
|
||||||
# rand_max is defined to remove the modulo bias from this entropy source
|
"""
|
||||||
rand_max = 2**24
|
Returns the shuffled ``values`` with seed as entropy.
|
||||||
assert len(lst) <= rand_max
|
"""
|
||||||
|
values_count = len(values)
|
||||||
|
|
||||||
o = [x for x in lst]
|
# entropy is consumed in 3 byte chunks
|
||||||
|
# sample_max is defined to remove the modulo bias from this entropy source
|
||||||
|
sample_max = 2 ** 24
|
||||||
|
assert values_count <= sample_max
|
||||||
|
|
||||||
|
output = [x for x in values]
|
||||||
source = seed
|
source = seed
|
||||||
i = 0
|
index = 0
|
||||||
while i < len(lst):
|
while index < values_count:
|
||||||
|
# Re-hash the source
|
||||||
source = hash(source)
|
source = hash(source)
|
||||||
for pos in range(0, 30, 3):
|
for position in range(0, 30, 3): # gets indices 3 bytes at a time
|
||||||
m = int.from_bytes(source[pos:pos+3], 'big')
|
# Select a 3-byte sampled int
|
||||||
remaining = len(lst) - i
|
sample_from_source = int.from_bytes(source[position:position + 3], 'big')
|
||||||
if remaining == 0:
|
# `remaining` is the size of remaining indices of this round
|
||||||
|
remaining = values_count - index
|
||||||
|
if remaining == 1:
|
||||||
break
|
break
|
||||||
rand_max = rand_max - rand_max % remaining
|
|
||||||
if m < rand_max:
|
# Set a random maximum bound of sample_from_source
|
||||||
replacement_pos = (m % remaining) + i
|
sample_max = sample_max - sample_max % remaining
|
||||||
o[i], o[replacement_pos] = o[replacement_pos], o[i]
|
|
||||||
i += 1
|
# Select `replacement_position` with the given `sample_from_source` and `remaining`
|
||||||
return o
|
if sample_from_source < sample_max:
|
||||||
|
# Use random number to get `replacement_position`, where it's not `index`
|
||||||
|
replacement_position = (sample_from_source % remaining) + index
|
||||||
|
# Swap the index-th and replacement_position-th elements
|
||||||
|
output[index], output[replacement_position] = output[replacement_position], output[index]
|
||||||
|
index += 1
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return output
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's a function that splits a list into `N` pieces:
|
Here's a function that splits a list into `split_count` pieces:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def split(lst, N):
|
def split(seq: List[Any], split_count: int) -> List[Any]:
|
||||||
return [lst[len(lst)*i//N: len(lst)*(i+1)//N] for i in range(N)]
|
"""
|
||||||
|
Returns the split ``seq`` in ``split_count`` pieces in protocol.
|
||||||
|
"""
|
||||||
|
list_length = len(seq)
|
||||||
|
return [
|
||||||
|
seq[(list_length * i // split_count): (list_length * (i + 1) // split_count)]
|
||||||
|
for i in range(split_count)
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, our combined helper method:
|
Now, our combined helper method:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_new_shuffling(seed, validators, crosslinking_start_shard):
|
def get_new_shuffling(seed: Hash32,
|
||||||
|
validators: List[ValidatorRecord],
|
||||||
|
crosslinking_start_shard: int) -> List[List[ShardAndCommittee]]:
|
||||||
active_validators = get_active_validator_indices(validators)
|
active_validators = get_active_validator_indices(validators)
|
||||||
if len(active_validators) >= CYCLE_LENGTH * MIN_COMMITTEE_SIZE:
|
active_validators_size = len(active_validators)
|
||||||
committees_per_slot = min(len(active_validators) // CYCLE_LENGTH // (MIN_COMMITTEE_SIZE * 2) + 1, SHARD_COUNT // CYCLE_LENGTH)
|
|
||||||
|
if active_validators_size >= CYCLE_LENGTH * MIN_COMMITTEE_SIZE:
|
||||||
|
committees_per_slot = min(active_validators_size // CYCLE_LENGTH // (MIN_COMMITTEE_SIZE * 2) + 1, SHARD_COUNT // CYCLE_LENGTH)
|
||||||
slots_per_committee = 1
|
slots_per_committee = 1
|
||||||
else:
|
else:
|
||||||
committees_per_slot = 1
|
committees_per_slot = 1
|
||||||
slots_per_committee = 1
|
slots_per_committee = 1
|
||||||
while len(active_validators) * slots_per_committee < CYCLE_LENGTH * MIN_COMMITTEE_SIZE \
|
while active_validators_size * slots_per_committee < CYCLE_LENGTH * MIN_COMMITTEE_SIZE \
|
||||||
and slots_per_committee < CYCLE_LENGTH:
|
and slots_per_committee < CYCLE_LENGTH:
|
||||||
slots_per_committee *= 2
|
slots_per_committee *= 2
|
||||||
o = []
|
|
||||||
for i, slot_indices in enumerate(split(shuffle(active_validators, seed), CYCLE_LENGTH)):
|
output = []
|
||||||
|
|
||||||
|
# Shuffle with seed
|
||||||
|
shuffled_active_validator_indices = shuffle(active_validators, seed)
|
||||||
|
|
||||||
|
# Split the shuffled list into cycle_length pieces
|
||||||
|
validators_per_slot = split(shuffled_active_validator_indices, CYCLE_LENGTH)
|
||||||
|
|
||||||
|
for slot, slot_indices in enumerate(validators_per_slot):
|
||||||
|
# Split the shuffled list into committees_per_slot pieces
|
||||||
shard_indices = split(slot_indices, committees_per_slot)
|
shard_indices = split(slot_indices, committees_per_slot)
|
||||||
shard_start = crosslinking_start_shard + \
|
|
||||||
i * committees_per_slot // slots_per_committee
|
shard_id_start = (
|
||||||
o.append([ShardAndCommittee(
|
crosslinking_start_shard +
|
||||||
shard = (shard_start + j) % SHARD_COUNT,
|
(slot * committees_per_slot // slots_per_committee)
|
||||||
committee = indices
|
)
|
||||||
) for j, indices in enumerate(shard_indices)])
|
shards_and_committees_for_shard_indices = [
|
||||||
return o
|
ShardAndCommittee(
|
||||||
|
shard_id=(shard_id_start + j) % SHARD_COUNT,
|
||||||
|
committee=indices
|
||||||
|
)
|
||||||
|
for slot, indices in enumerate(shard_indices)
|
||||||
|
]
|
||||||
|
output.append(shards_and_committees_for_shard_indices)
|
||||||
|
|
||||||
|
return output
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's a diagram of what's going on:
|
Here's a diagram of what's going on:
|
||||||
@ -380,14 +427,17 @@ Here's a diagram of what's going on:
|
|||||||
We also define two functions for retrieving data from the state:
|
We also define two functions for retrieving data from the state:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_shards_and_committees_for_slot(crystallized_state, slot):
|
def get_shards_and_committees_for_slot(crystallized_state: CrystallizedState,
|
||||||
earliest_slot_in_array = crystallized_state.last_state_recalculation_slot - CYCLE_LENGTH
|
slot: int) -> List[ShardAndCommittee]:
|
||||||
|
earliest_slot_in_array = crystallized_state.last_state_recalculation - CYCLE_LENGTH
|
||||||
assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2
|
assert earliest_slot_in_array <= slot < earliest_slot_in_array + CYCLE_LENGTH * 2
|
||||||
return crystallized_state.shard_and_committee_for_slots[slot - earliest_slot_in_array]
|
return crystallized_state.shard_and_committee_for_slots[slot - earliest_slot_in_array]
|
||||||
|
|
||||||
def get_block_hash(active_state, curblock, slot):
|
def get_block_hash(active_state: ActiveState,
|
||||||
|
current_block: BeaconBlock,
|
||||||
|
slot: int) -> Hash32:
|
||||||
earliest_slot_in_array = curblock.slot - len(active_state.recent_block_hashes)
|
earliest_slot_in_array = curblock.slot - len(active_state.recent_block_hashes)
|
||||||
assert earliest_slot_in_array <= slot < curblock.slot
|
assert earliest_slot_in_array <= slot < current_block.slot
|
||||||
return active_state.recent_block_hashes[slot - earliest_slot_in_array]
|
return active_state.recent_block_hashes[slot - earliest_slot_in_array]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -396,7 +446,10 @@ def get_block_hash(active_state, curblock, slot):
|
|||||||
We define a function to "add a link" to the validator hash chain, used when a validator is added or removed:
|
We define a function to "add a link" to the validator hash chain, used when a validator is added or removed:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def add_validator_set_change_record(crystallized_state, index, pubkey, flag):
|
def add_validator_set_change_record(crystallized_state: CrystallizedState,
|
||||||
|
index: int,
|
||||||
|
pubkey: int,
|
||||||
|
flag: int) -> None:
|
||||||
crystallized_state.validator_set_delta_hash_chain = \
|
crystallized_state.validator_set_delta_hash_chain = \
|
||||||
hash(crystallized_state.validator_set_delta_hash_chain +
|
hash(crystallized_state.validator_set_delta_hash_chain +
|
||||||
bytes1(flag) + bytes3(index) + bytes32(pubkey))
|
bytes1(flag) + bytes3(index) + bytes32(pubkey))
|
||||||
@ -405,7 +458,7 @@ def add_validator_set_change_record(crystallized_state, index, pubkey, flag):
|
|||||||
Finally, we abstractly define `int_sqrt(n)` for use in reward/penalty calculations as the largest integer `k` such that `k**2 <= n`. Here is one possible implementation, though clients are free to use their own including standard libraries for [integer square root](https://en.wikipedia.org/wiki/Integer_square_root) if available and meet the specification.
|
Finally, we abstractly define `int_sqrt(n)` for use in reward/penalty calculations as the largest integer `k` such that `k**2 <= n`. Here is one possible implementation, though clients are free to use their own including standard libraries for [integer square root](https://en.wikipedia.org/wiki/Integer_square_root) if available and meet the specification.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def int_sqrt(n):
|
def int_sqrt(n: int) -> int:
|
||||||
x = n
|
x = n
|
||||||
y = (x + 1) // 2
|
y = (x + 1) // 2
|
||||||
while y < x:
|
while y < x:
|
||||||
@ -420,23 +473,60 @@ def int_sqrt(n):
|
|||||||
Run the following code:
|
Run the following code:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def on_startup(initial_validator_entries):
|
def on_startup(initial_validator_entries: List[Any]) -> Tuple[CrystallizedState, ActiveState]:
|
||||||
# Induct validators
|
# Induct validators
|
||||||
validators = []
|
validators = []
|
||||||
for pubkey, proof_of_possession, withdrawal_shard, withdrawal_address, \
|
for pubkey, proof_of_possession, withdrawal_shard, withdrawal_address, \
|
||||||
randao_commitment in initial_validator_entries:
|
randao_commitment in initial_validator_entries:
|
||||||
add_validator(validators, pubkey, proof_of_possession,
|
add_validator(
|
||||||
withdrawal_shard, withdrawal_address, randao_commitment)
|
validators=validators,
|
||||||
|
pubkey=pubkey,
|
||||||
|
proof_of_possession=proof_of_possession,
|
||||||
|
withdrawal_shard=withdrawal_shard,
|
||||||
|
withdrawal_address=withdrawal_address,
|
||||||
|
randao_commitment=randao_commitment
|
||||||
|
)
|
||||||
# Setup crystallized state
|
# Setup crystallized state
|
||||||
cs = CrystallizedState()
|
|
||||||
x = get_new_shuffling(bytes([0] * 32), validators, 0)
|
x = get_new_shuffling(bytes([0] * 32), validators, 0)
|
||||||
cs.shard_and_committee_for_slots = x + x
|
crosslinks = [
|
||||||
cs.dynasty = 1
|
CrosslinkRecord(
|
||||||
cs.crosslinks = [CrosslinkRecord(dynasty=0, slot=0, hash=bytes([0] * 32))
|
dynasty=0,
|
||||||
for i in range(SHARD_COUNT)]
|
slot=0,
|
||||||
|
hash=bytes([0] * 32)
|
||||||
|
)
|
||||||
|
for i in range(SHARD_COUNT)
|
||||||
|
]
|
||||||
|
crystallized_state = CrystallizedState(
|
||||||
|
dynasty=1,
|
||||||
|
dynasty_seed=bytes([0] * 32), # stub
|
||||||
|
dynasty_start_slot=0,
|
||||||
|
validators=validators,
|
||||||
|
crosslinks=crosslinks,
|
||||||
|
last_state_recalculation_slot=0,
|
||||||
|
last_finalized_slot=0,
|
||||||
|
last_justified_slot=0,
|
||||||
|
justified_streak=0,
|
||||||
|
shard_and_committee_for_slots=x + x,
|
||||||
|
deposits_penalized_in_period=[],
|
||||||
|
validator_set_delta_hash_chain=bytes([0] * 32), # stub
|
||||||
|
pre_fork_version=0,
|
||||||
|
post_fork_version=0,
|
||||||
|
fork_slot_number=0
|
||||||
|
)
|
||||||
|
|
||||||
# Setup active state
|
# Setup active state
|
||||||
as = ActiveState()
|
recent_block_hashes = [
|
||||||
as.recent_block_hashes = [bytes([0] * 32) for _ in range(CYCLE_LENGTH * 2)]
|
bytes([0] * 32)
|
||||||
|
for _ in range(CYCLE_LENGTH * 2)
|
||||||
|
]
|
||||||
|
active_state = ActiveState(
|
||||||
|
pending_attestations=[],
|
||||||
|
pending_specials=[],
|
||||||
|
recent_block_hashes=recent_block_hashes,
|
||||||
|
randao_mix=bytes([0] * 32) # stub
|
||||||
|
)
|
||||||
|
|
||||||
|
return crystallized_state, active_state
|
||||||
```
|
```
|
||||||
|
|
||||||
The `CrystallizedState()` and `ActiveState()` constructors should initialize all values to zero bytes, an empty value or an empty array depending on context. The `add_validator` routine is defined below.
|
The `CrystallizedState()` and `ActiveState()` constructors should initialize all values to zero bytes, an empty value or an empty array depending on context. The `add_validator` routine is defined below.
|
||||||
@ -446,8 +536,12 @@ The `CrystallizedState()` and `ActiveState()` constructors should initialize all
|
|||||||
This routine should be run for every validator that is inducted as part of a log created on the PoW chain [TODO: explain where to check for these logs]. These logs should be processed in the order in which they are emitted by the PoW chain. Define `min_empty_validator(validators)` as a function that returns the lowest validator index `i` such that `validators[i].status == WITHDRAWN`, otherwise `None`.
|
This routine should be run for every validator that is inducted as part of a log created on the PoW chain [TODO: explain where to check for these logs]. These logs should be processed in the order in which they are emitted by the PoW chain. Define `min_empty_validator(validators)` as a function that returns the lowest validator index `i` such that `validators[i].status == WITHDRAWN`, otherwise `None`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def add_validator(validators, pubkey, proof_of_possession, withdrawal_shard,
|
def add_validator(validators: List[ValidatorRecord],
|
||||||
withdrawal_address, randao_commitment):
|
pubkey: int,
|
||||||
|
proof_of_possession: bytes,
|
||||||
|
withdrawal_shard: int,
|
||||||
|
withdrawal_address: Address,
|
||||||
|
randao_commitment: Hash32) -> int:
|
||||||
# if following assert fails, validator induction failed
|
# if following assert fails, validator induction failed
|
||||||
# move on to next validator registration log
|
# move on to next validator registration log
|
||||||
assert BLSVerify(pub=pubkey,
|
assert BLSVerify(pub=pubkey,
|
||||||
@ -458,7 +552,7 @@ def add_validator(validators, pubkey, proof_of_possession, withdrawal_shard,
|
|||||||
withdrawal_shard=withdrawal_shard,
|
withdrawal_shard=withdrawal_shard,
|
||||||
withdrawal_address=withdrawal_address,
|
withdrawal_address=withdrawal_address,
|
||||||
randao_commitment=randao_commitment,
|
randao_commitment=randao_commitment,
|
||||||
balance=DEPOSIT_SIZE, # in WEI
|
balance=DEPOSIT_SIZE * GWEI_PER_ETH, # in Gwei
|
||||||
status=PENDING_ACTIVATION,
|
status=PENDING_ACTIVATION,
|
||||||
exit_slot=0
|
exit_slot=0
|
||||||
)
|
)
|
||||||
@ -478,8 +572,10 @@ This procedure should be carried out every block.
|
|||||||
First, set `recent_block_hashes` to the output of the following, where `parent_hash` is the hash of the immediate previous block (ie. must be equal to `ancestor_hashes[0]`):
|
First, set `recent_block_hashes` to the output of the following, where `parent_hash` is the hash of the immediate previous block (ie. must be equal to `ancestor_hashes[0]`):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def append_to_recent_block_hashes(old_block_hashes, parent_slot,
|
def append_to_recent_block_hashes(old_block_hashes: List[Hash32],
|
||||||
current_slot, parent_hash):
|
parent_slot: int,
|
||||||
|
current_slot: int,
|
||||||
|
parent_hash: Hash32) -> List[Hash32]:
|
||||||
d = current_slot - parent_slot
|
d = current_slot - parent_slot
|
||||||
return old_block_hashes + [parent_hash] * d
|
return old_block_hashes + [parent_hash] * d
|
||||||
```
|
```
|
||||||
@ -487,7 +583,9 @@ def append_to_recent_block_hashes(old_block_hashes, parent_slot,
|
|||||||
The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm:
|
The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def update_ancestor_hashes(parent_ancestor_hashes, parent_slot_number, parent_hash):
|
def update_ancestor_hashes(parent_ancestor_hashes: List[Hash32],
|
||||||
|
parent_slot_number: int,
|
||||||
|
parent_hash: Hash32) -> List[Hash32]:
|
||||||
new_ancestor_hashes = copy.copy(parent_ancestor_hashes)
|
new_ancestor_hashes = copy.copy(parent_ancestor_hashes)
|
||||||
for i in range(32):
|
for i in range(32):
|
||||||
if parent_slot_number % 2**i == 0:
|
if parent_slot_number % 2**i == 0:
|
||||||
@ -520,45 +618,52 @@ Repeat while `slot - last_state_recalculation_slot >= CYCLE_LENGTH`:
|
|||||||
|
|
||||||
#### Adjust justified slots and crosslink status
|
#### Adjust justified slots and crosslink status
|
||||||
|
|
||||||
For all slots `s` in `last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`:
|
For every slot `s` in the range `last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`:
|
||||||
|
|
||||||
* Determine the total set of validators that attested to that block at least once
|
* Let `total_balance` be the total balance of active validators.
|
||||||
* Determine the total balance of these validators. If this value times three equals or exceeds the total balance of all active validators times two, set `last_justified_slot = max(last_justified_slot, s)` and `justified_streak += 1`. Otherwise, set `justified_streak = 0`
|
* Let `total_balance_attesting_at_s` be the total balance of validators that attested to the beacon chain block at slot `s`.
|
||||||
* If `justified_streak >= CYCLE_LENGTH + 1`, set `last_finalized_slot = max(last_finalized_slot, s - CYCLE_LENGTH - 1)`
|
* If `3 * total_balance_attesting_at_s >= 2 * total_balance` set `last_justified_slot = max(last_justified_slot, s)` and `justified_streak += 1`. Otherwise set `justified_streak = 0`.
|
||||||
|
* If `justified_streak >= CYCLE_LENGTH + 1` set `last_finalized_slot = max(last_finalized_slot, s - CYCLE_LENGTH - 1)`.
|
||||||
|
|
||||||
For all (`shard`, `shard_block_hash`) tuples, compute the total deposit size of validators that attested to that block hash for that shard. If this value times three equals or exceeds the total balance of all validators in the committee times two, and the current dynasty exceeds `crosslinks[shard].dynasty`, set `crosslinks[shard] = CrosslinkRecord(dynasty=dynasty, slot=block.last_state_recalculation_slot + CYCLE_LENGTH, hash=shard_block_hash)`.
|
For every `(shard, shard_block_hash)` tuple:
|
||||||
|
|
||||||
|
* Let `total_balance_attesting_to_h` be the total balance of validators that attested to the shard block with hash `shard_block_hash`.
|
||||||
|
* Let `total_committee_balance` be the total balance in the committee of validators that could have attested to the shard block with hash `shard_block_hash`.
|
||||||
|
* If `3 * total_balance_attesting_to_h >= 2 * total_committee_balance` and `dynasty > crosslinks[shard].dynasty`, set `crosslinks[shard] = CrosslinkRecord(dynasty=dynasty, slot=block.last_state_recalculation_slot + CYCLE_LENGTH, hash=shard_block_hash)`.
|
||||||
|
|
||||||
#### Balance recalculations related to FFG rewards
|
#### Balance recalculations related to FFG rewards
|
||||||
|
|
||||||
Let `time_since_finality = block.slot - last_finalized_slot`, and let `B` be the balance of any given validator whose balance we are adjusting, not including any balance changes from this round of state recalculation. Let:
|
* Let `total_balance` be the total balance of active validators.
|
||||||
|
* Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`.
|
||||||
|
* Let `reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)`. (The per-slot maximum interest rate is `1/reward_quotient`.)
|
||||||
|
* Let `quadratic_penalty_quotient = SQRT_E_DROP_TIME**2`. (The portion lost by offline validators after `D` slots is about `D*D/2/quadratic_penalty_quotient`.)
|
||||||
|
* Let `time_since_finality = block.slot - last_finalized_slot`.
|
||||||
|
|
||||||
* `total_deposits = sum([v.balance for i, v in enumerate(validators) if i in get_active_validator_indices(validators, dynasty)])` and `total_deposits_in_ETH = total_deposits // 10**18`
|
For every slot `s` in the range `last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`:
|
||||||
* `reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_deposits_in_ETH)` (`1/reward_quotient` is the per-slot max interest rate)
|
|
||||||
* `quadratic_penalty_quotient = SQRT_E_DROP_TIME**2` (after `D` slots about `D*D/2/quadratic_penalty_quotient` is the portion lost by offline validators)
|
|
||||||
|
|
||||||
For each slot `S` in the range `last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`:
|
* Let `total_balance_participating` be the total balance of validators that voted for the canonical beacon chain block at slot `s`. In the normal case every validator will be in one of the `CYCLE_LENGTH` slots following slot `s` and so can vote for a block at slot `s`.
|
||||||
|
* Let `B` be the balance of any given validator whose balance we are adjusting, not including any balance changes from this round of state recalculation.
|
||||||
|
* If `time_since_finality <= 3 * CYCLE_LENGTH` adjust the balance of participating and non-participating validators as follows:
|
||||||
|
* Participating validators gain `B // reward_quotient * (2 * total_balance_participating - total_balance) // total_balance`. (Note that this value may be negative.)
|
||||||
|
* Non-participating validators lose `B // reward_quotient`.
|
||||||
|
* Otherwise:
|
||||||
|
* Participating validators gain nothing.
|
||||||
|
* Non-participating validators lose `B // reward_quotient + B * time_since_finality // quadratic_penalty_quotient`.
|
||||||
|
|
||||||
* Let `total_participated_deposits` be the total balance of validators that voted for the correct hash in slot `S` (ie. the hash that actually is the hash of the block at that slot in the current chain); note that in the normal case, every validator will be in one of the `CYCLE_LENGTH` slots following the slot and so can vote for a hash in slot `S`. If `time_since_finality <= 3 * CYCLE_LENGTH`, then adjust participating and non-participating validators' balances as follows:
|
In addition, validators with `status == PENALIZED` lose `B // reward_quotient + B * time_since_finality // quadratic_penalty_quotient`.
|
||||||
* Participating validators gain `B // reward_quotient * (2 * total_participated_deposits - total_deposits) // total_deposits` (note: this may be negative)
|
|
||||||
* Nonparticipating validators lose `B // reward_quotient`
|
|
||||||
* Otherwise, adjust as follows:
|
|
||||||
* Participating validators' balances are unchanged
|
|
||||||
* Nonparticipating validators lose `B // reward_quotient + B * time_since_finality // quadratic_penalty_quotient`
|
|
||||||
|
|
||||||
Validators with `status == PENALIZED` also lose `B // reward_quotient + B * time_since_finality // quadratic_penalty_quotient`.
|
|
||||||
|
|
||||||
#### Balance recalculations related to crosslink rewards
|
#### Balance recalculations related to crosslink rewards
|
||||||
|
|
||||||
For each shard `S` for which a crosslink committee exists in the cycle prior to the most recent cycle (`last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`), let `V` be the corresponding validator set. Let `B` be the balance of any given validator whose balance we are adjusting, not including any balance changes from this round of state recalculation. For each `S`, `V`:
|
For every shard number `shard` for which a crosslink committee exists in the cycle prior to the most recent cycle (`last_state_recalculation_slot - CYCLE_LENGTH ... last_state_recalculation_slot - 1`), let `V` be the corresponding validator set. Let `B` be the balance of any given validator whose balance we are adjusting, not including any balance changes from this round of state recalculation. For each `shard`, `V`:
|
||||||
|
|
||||||
* Let `total_v_deposits` be the total balance of `V`
|
* Let `total_balance_of_v` be the total balance of `V`.
|
||||||
* Let `total_participated_v_deposits` be the total balance of the subset of `V` that participated (note that `total_participated_v_deposits <= total_v_deposits`)
|
* Let `total_balance_of_v_participating` be the total balance of the subset of `V` that participated.
|
||||||
* Let `time_since_last_confirmation` be `block.slot - crosslinks[S].slot`
|
* Let `time_since_last_confirmation = block.slot - crosslinks[shard].slot`.
|
||||||
* Adjust balances as follows:
|
* If `dynasty > crosslinks[shard].dynasty` adjust balances as follows:
|
||||||
* If `crosslinks[S].dynasty == dynasty`, no reward adjustments
|
* Participating validators gain `B // reward_quotient * (2 * total_balance_of_v_participating - total_balance_of_v) // total_balance_of_v`.
|
||||||
* Otherwise, participating validators' balances are increased by `B // reward_quotient * (2 * total_participated_v_deposits - total_v_deposits) // total_v_deposits`, and the balances of non-participating validators are decreased by `B // reward_quotient + B * time_since_last_confirmation // quadratic_penalty_quotient`
|
* Non-participating validators lose `B // reward_quotient + B * time_since_last_confirmation // quadratic_penalty_quotient`.
|
||||||
|
|
||||||
Let `committees` be the set of committees processed and `time_since_last_confirmation(c)` be the value of `time_since_last_confirmation` in that committee. Validators with `status == PENALIZED` lose `B // reward_quotient + B * sum([time_since_last_confirmation(c) for c in committees]) // len(committees) // quadratic_penalty_quotient`.
|
In addition, validators with `status == PENALIZED` lose `B // reward_quotient + B * sum([time_since_last_confirmation(c) for c in committees]) // len(committees) // quadratic_penalty_quotient`, where `committees` is the set of committees processed and `time_since_last_confirmation(c)` is the value of `time_since_last_confirmation` in committee `c`.
|
||||||
|
|
||||||
#### Process penalties, logouts and other special objects
|
#### Process penalties, logouts and other special objects
|
||||||
|
|
||||||
@ -586,33 +691,43 @@ A dynasty transition can happen after a state recalculation if all of the follow
|
|||||||
|
|
||||||
* `block.slot - crystallized_state.dynasty_start_slot >= MIN_DYNASTY_LENGTH`
|
* `block.slot - crystallized_state.dynasty_start_slot >= MIN_DYNASTY_LENGTH`
|
||||||
* `last_finalized_slot > dynasty_start_slot`
|
* `last_finalized_slot > dynasty_start_slot`
|
||||||
* For every shard `S` in `shard_and_committee_for_slots`, `crosslinks[S].slot > dynasty_start_slot`
|
* For every shard number `shard` in `shard_and_committee_for_slots`, `crosslinks[shard].slot > dynasty_start_slot`
|
||||||
|
|
||||||
Then, run the following algorithm to update the validator set:
|
Then, run the following algorithm to update the validator set:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def change_validators(validators):
|
def change_validators(validators: List[ValidatorRecord]) -> None:
|
||||||
# The active validator set
|
# The active validator set
|
||||||
active_validators = get_active_validator_indices(validators, dynasty)
|
active_validators = get_active_validator_indices(validators)
|
||||||
# The total size of active deposits
|
# The total balance of active validators
|
||||||
total_deposits = sum([v.balance for i, v in enumerate(validators) if i in active_validators])
|
total_balance = sum([v.balance for i, v in enumerate(validators) if i in active_validators])
|
||||||
# The maximum total wei that can deposit+withdraw
|
# The maximum total wei that can deposit+withdraw
|
||||||
max_allowable_change = max(
|
max_allowable_change = max(
|
||||||
DEPOSIT_SIZE * 2,
|
2 * DEPOSIT_SIZE * GWEI_PER_ETH,
|
||||||
total_deposits // MAX_VALIDATOR_CHURN_QUOTIENT
|
total_balance // MAX_VALIDATOR_CHURN_QUOTIENT
|
||||||
)
|
)
|
||||||
# Go through the list start to end depositing+withdrawing as many as possible
|
# Go through the list start to end depositing+withdrawing as many as possible
|
||||||
total_changed = 0
|
total_changed = 0
|
||||||
for i in range(len(validators)):
|
for i in range(len(validators)):
|
||||||
if validators[i].status == PENDING_ACTIVATION:
|
if validators[i].status == PENDING_ACTIVATION:
|
||||||
validators[i].status = ACTIVE
|
validators[i].status = ACTIVE
|
||||||
total_changed += DEPOSIT_SIZE
|
total_changed += DEPOSIT_SIZE * GWEI_PER_ETH
|
||||||
add_validator_set_change_record(crystallized_state, i, validators[i].pubkey, ENTRY)
|
add_validator_set_change_record(
|
||||||
|
crystallized_state=crystallized_state,
|
||||||
|
index=i,
|
||||||
|
pubkey=validators[i].pubkey,
|
||||||
|
flag=ENTRY
|
||||||
|
)
|
||||||
if validators[i].status == PENDING_EXIT:
|
if validators[i].status == PENDING_EXIT:
|
||||||
validators[i].status = PENDING_WITHDRAW
|
validators[i].status = PENDING_WITHDRAW
|
||||||
validators[i].exit_slot = current_slot
|
validators[i].exit_slot = current_slot
|
||||||
total_changed += validators[i].balance
|
total_changed += validators[i].balance
|
||||||
add_validator_set_change_record(crystallized_state, i, validators[i].pubkey, EXIT)
|
add_validator_set_change_record(
|
||||||
|
crystallized_state=crystallized_state,
|
||||||
|
index=i,
|
||||||
|
pubkey=validators[i].pubkey,
|
||||||
|
flag=EXIT
|
||||||
|
)
|
||||||
if total_changed >= max_allowable_change:
|
if total_changed >= max_allowable_change:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -628,7 +743,7 @@ def change_validators(validators):
|
|||||||
for i in range(len(validators)):
|
for i in range(len(validators)):
|
||||||
if validators[i].status in (PENDING_WITHDRAW, PENALIZED) and current_slot >= validators[i].exit_slot + WITHDRAWAL_PERIOD:
|
if validators[i].status in (PENDING_WITHDRAW, PENALIZED) and current_slot >= validators[i].exit_slot + WITHDRAWAL_PERIOD:
|
||||||
if validators[i].status == PENALIZED:
|
if validators[i].status == PENALIZED:
|
||||||
validators[i].balance -= validators[i].balance * min(total_penalties * 3, total_deposits) // total_deposits
|
validators[i].balance -= validators[i].balance * min(total_penalties * 3, total_balance) // total_balance
|
||||||
validators[i].status = WITHDRAWN
|
validators[i].status = WITHDRAWN
|
||||||
|
|
||||||
withdraw_amount = validators[i].balance
|
withdraw_amount = validators[i].balance
|
||||||
|
@ -343,6 +343,6 @@ return deserialized_list, new_index
|
|||||||
| Language | Implementation | Description |
|
| Language | Implementation | Description |
|
||||||
|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||||
| Python | [ https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py ](https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py) | Beacon chain reference implementation written in Python. |
|
| Python | [ https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py ](https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py) | Beacon chain reference implementation written in Python. |
|
||||||
| Rust | [ https://github.com/sigp/lighthouse/tree/master/ssz ](https://github.com/sigp/lighthouse/tree/master/ssz) | Lighthouse (Rust Ethereum 2.0 Node) maintained SSZ. |
|
| Rust | [ https://github.com/sigp/lighthouse/tree/master/beacon_chain/utils/ssz ](https://github.com/sigp/lighthouse/tree/master/beacon_chain/utils/ssz) | Lighthouse (Rust Ethereum 2.0 Node) maintained SSZ. |
|
||||||
| Nim | [ https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim ](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | Nim Implementation maintained SSZ. |
|
| Nim | [ https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim ](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | Nim Implementation maintained SSZ. |
|
||||||
| Rust | [ https://github.com/paritytech/shasper/tree/master/util/ssz ](https://github.com/paritytech/shasper/tree/master/util/ssz) | Shasper implementation of SSZ maintained by ParityTech. |
|
| Rust | [ https://github.com/paritytech/shasper/tree/master/util/ssz ](https://github.com/paritytech/shasper/tree/master/util/ssz) | Shasper implementation of SSZ maintained by ParityTech. |
|
||||||
|
Loading…
x
Reference in New Issue
Block a user