Update 0_beacon-chain.md

This commit is contained in:
Justin 2018-12-05 11:22:15 +00:00 committed by GitHub
parent 4c0841ec6a
commit 2fc3f88795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 107 additions and 97 deletions

View File

@ -70,25 +70,26 @@
- [Routine for activating a validator](#routine-for-activating-a-validator)
- [Routine for exiting a validator](#routine-for-exiting-a-validator)
- [Per-slot processing](#per-slot-processing)
- [Verify attestations](#verify-attestations)
- [Verify proposer signature](#verify-proposer-signature)
- [Process RANDAO](#process-randao)
- [Process PoW receipt root](#process-pow-receipt-root)
- [Process special objects](#process-special-objects)
- [Proposer signature](#proposer-signature)
- [Attestations](#attestations)
- [RANDAO](#randao)
- [PoW receipt root](#pow-receipt-root)
- [Special objects](#special-objects)
- [`VOLUNTARY_EXIT`](#voluntary_exit)
- [`CASPER_SLASHING`](#casper_slashing)
- [`PROPOSER_SLASHING`](#proposer_slashing)
- [`DEPOSIT_PROOF`](#deposit_proof)
- [Per-epoch processing](#per-epoch-processing)
- [Precomputation](#precomputation)
- [Adjust justified slots and crosslink status](#adjust-justified-slots-and-crosslink-status)
- [Balance recalculations related to FFG rewards](#balance-recalculations-related-to-ffg-rewards)
- [Balance recalculations related to crosslink rewards](#balance-recalculations-related-to-crosslink-rewards)
- [Receipt root voting](#receipt-root-voting)
- [Validator registry change](#validator-registry-change)
- [If a validator registry change does NOT happen](#if-a-validator-registry-change-does-not-happen)
- [Helpers](#helpers)
- [Receipt roots](#receipt-roots)
- [Justification](#justification)
- [Finalization](#finalization)
- [Crosslinks](#crosslinks)
- [Justification and finalization rewards and penalties](#justification-and-finalization-rewards-and-penalties)
- [Crosslink rewards and penalties](#crosslink-rewards-and-penalties)
- [Validator registry](#validator-registry)
- [Proposer reshuffling](#proposer-reshuffling)
- [Finally...](#finally)
- [Final updates](#final-updates)
- [Appendix](#appendix)
- [Appendix A - Hash function](#appendix-a---hash-function)
- [References](#references)
@ -142,7 +143,6 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
| `BLS_WITHDRAWAL_CREDENTIALS` | `0x00` | - |
* For the safety of crosslinks a minimum committee size of 111 is [recommended](https://vitalik.ca/files/Ithaca201807_Sharding.pdf). (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) The shuffling algorithm generally ensures (assuming sufficient validators) committee sizes at least `TARGET_COMMITTEE_SIZE // 2`.
* At most `1/MAX_BALANCE_CHURN_QUOTIENT` of the [validators](#dfn-validator) can change during each [validator](#dfn-validator) registry change.
### Deposit contract
@ -381,7 +381,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
'withdrawal_credentials': 'hash32',
# RANDAO commitment
'randao_commitment': 'hash32',
# Slots the proposer has skipped (ie. layers of RANDAO expected)
# Slots the proposer has skipped (i.e. layers of RANDAO expected)
'randao_skips': 'uint64',
# Balance in Gwei
'balance': 'uint64',
@ -642,7 +642,7 @@ For a beacon chain block, `block`, to be processed by a node, the following cond
If these conditions are not met, the client should delay processing the beacon block until the conditions are all satisfied.
Beacon block production is significantly different because of the proof of stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block, and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (ie. within `SLOT_DURATION` seconds) synchronized with the other nodes.
Beacon block production is significantly different because of the proof of stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block, and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (i.e. within `SLOT_DURATION` seconds) synchronized with the other nodes.
### Beacon chain fork choice rule
@ -940,7 +940,7 @@ def integer_squareroot(n: int) -> int:
### On startup
A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the following values. Other validity rules (eg. requiring a signature) do not apply.
A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the following values. Other validity rules (e.g. requiring a signature) do not apply.
```python
{
@ -1185,9 +1185,9 @@ def exit_validator(index: int,
## Per-slot processing
Below are the steps that happen at every slot. Denote by `block` the associated block, possibly a "skip block".
Below are the processing steps that happen at every slot. Denote by `block` the associated block, possibly a skip block.
* Let `parent_hash` be the hash of the immediate previous beacon slot (ie. equal to `block.ancestor_hashes[0]`).
* Let `parent_hash` be the hash of the immediate previous beacon slot (i.e. equal to `block.ancestor_hashes[0]`).
* Let `parent` be the beacon slot with the hash `parent_hash`.
First, set `state.latest_block_hashes` to the output of the following:
@ -1214,7 +1214,13 @@ def update_ancestor_hashes(parent_ancestor_hashes: List[Hash32],
return new_ancestor_hashes
```
### Verify attestations
### Proposer signature
* Let `block_hash_without_sig` be the hash of `block` where `proposer_signature` is set to `[0, 0]`.
* Let `proposal_hash = hash(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`.
* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`.
### Attestations
* Verify that `len(block.attestations) <= MAX_ATTESTATIONS_PER_BLOCK`.
@ -1232,13 +1238,7 @@ For each `attestation` in `block.attestations`:
* [TO BE REMOVED IN PHASE 1] Verify that `shard_block_hash == ZERO_HASH`.
* Append `PendingAttestationRecord(data=attestation.data, participation_bitfield=attestation.participation_bitfield, custody_bitfield=attestation.custody_bitfield, slot_included=state.slot)` to `state.latest_attestations`.
### Verify proposer signature
* Let `block_hash_without_sig` be the hash of `block` where `proposer_signature` is set to `[0, 0]`.
* Let `proposal_hash = hash(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`.
* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`.
### Process RANDAO
### RANDAO
If `block` is a skip block:
@ -1253,11 +1253,11 @@ If `block` is not a skip block:
* Set `proposer.randao_commitment = block.randao_reveal`.
* Set `proposer.randao_skips = 0`.
### Process PoW receipt root
### PoW receipt root
If `block.candidate_pow_receipt_root` is `x.candidate_pow_receipt_root` for some `x` in `state.candidate_pow_receipt_roots`, set `x.votes += 1`. Otherwise, append to `state.candidate_pow_receipt_roots` a new `CandidatePoWReceiptRootRecord(candidate_pow_receipt_root=block.candidate_pow_receipt_root, votes=1)`.
### Process special objects
### Special objects
* Verify that the quantity of each type of object in `block.specials` is less than or equal to its maximum (see table at the top).
* Verify that objects are sorted in order of `kind`. That is, `block.specials[i+1].kind >= block.specials[i].kind` for `0 <= i < len(block.specials-1)`.
@ -1344,13 +1344,15 @@ process_deposit(
The steps below happen when `state.slot % EPOCH_LENGTH == 0`. For simplicity we denote `state.slot - EPOCH_LENGTH` by `s`.
### Precomputation
### Helpers
All [validators](#dfn-validator):
* Let `active_validators = [state.validator_registry[i] for i in get_active_validator_indices(state.validator_registry)]`.
* Let `total_balance = sum([get_effective_balance(v) for v in active_validators])`. Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`.
* Let `total_balance = sum([get_effective_balance(v) for v in active_validators])`.
* Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`.
* Let `reward_quotient = BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth)`. (The per-slot maximum interest rate is `2/reward_quotient`.)
* Let `base_reward(v) = get_effective_balance(v) // reward_quotient` for any validator `v`.
[Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch:
@ -1374,30 +1376,46 @@ For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots`:
* Let `winning_hash(obj)` be the winning `shard_block_hash` value.
* Let `total_balance(obj) = sum([get_effective_balance(v) for v in obj.committee])`.
Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation. We define a function `adjust_for_inclusion_distance(magnitude, distance)` which adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward). Returns a value between 0 and `magnitude`.
* Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation.
* Let `adjust_for_inclusion_distance(magnitude, distance)` be the function below.
```python
def adjust_for_inclusion_distance(magnitude: int, distance: int) -> int:
"""
Adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward).
Returns a value between ``0`` and ``magnitude``.
""
return magnitude // 2 + (magnitude // 2) * MIN_ATTESTATION_INCLUSION_DELAY // distance
```
For any [validator](#dfn-validator) `v`, let `base_reward(v) = get_effective_balance(v) // reward_quotient`.
### Receipt roots
### Adjust justified slots and crosslink status
If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then:
* Set `state.processed_pow_receipt_root = x.receipt_root` if `x.votes * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for any `x` in `state.candidate_pow_receipt_root`.
* Set `state.candidate_pow_receipt_roots = []`.
### Justification
* Set `state.justification_bitfield = (state.justification_bitfield * 2) % 2**64`.
* If `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 2` (ie. flip the second lowest bit to 1) and `new_justified_slot = s - EPOCH_LENGTH`.
* If `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 1` (ie. flip the lowest bit to 1) and `new_justified_slot = s`.
* If `state.justified_slot == s - EPOCH_LENGTH and state.justification_bitfield % 4 == 3`, set `state.finalized_slot = state.justified_slot`.
* If `state.justified_slot == s - EPOCH_LENGTH - EPOCH_LENGTH and state.justification_bitfield % 8 == 7`, set `state.finalized_slot = state.justified_slot`.
* If `state.justified_slot == s - EPOCH_LENGTH - 2 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`, set `state.finalized_slot = state.justified_slot`.
* Set `state.previous_justified_slot = state.justified_slot` and if `new_justified_slot` has been set, set `state.justified_slot = new_justified_slot`.
* If `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 2` (i.e. set the second lowest bit to 1) and `new_justified_slot = s - EPOCH_LENGTH`.
* If `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 1` (i.e. set the lowest bit to 1) and `new_justified_slot = s`.
* Set `state.previous_justified_slot = state.justified_slot`.
* Set `state.justified_slot = new_justified_slot` if `new_justified_slot` has been set.
For every `ShardCommittee` object `obj`:
### Finalization
* If `3 * total_attesting_balance(obj) >= 2 * total_balance(obj)`, set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, hash=winning_hash(obj))`.
* Set `state.finalized_slot = state.justified_slot` if `state.justified_slot == s - 1 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`.
* Set `state.finalized_slot = state.justified_slot` if `state.justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`.
* Set `state.finalized_slot = state.justified_slot` If `state.justified_slot == s - 3 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`.
### Balance recalculations related to FFG rewards
### Crosslinks
For every `shard_committee` in `state.shard_committees_at_slots`:
* Set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, hash=winning_hash(shard_committee))` if `3 * total_attesting_balance(shard_committee) >= 2 * total_balance(shard_committee)`.
### Justification and finalization rewards and penalties
Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow.
@ -1415,41 +1433,45 @@ Case 2: `time_since_finality > 4 * EPOCH_LENGTH`:
For each `v` in `previous_epoch_boundary_attesters`, we determine the proposer `proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))` and set `state.validator_registry[proposer_index].balance += base_reward(v) // INCLUDER_REWARD_QUOTIENT`.
### Balance recalculations related to crosslink rewards
### Crosslink rewards and penalties
For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots[:EPOCH_LENGTH]` (ie. the objects corresponding to the epoch before the current one), for each `v` in `[state.validator_registry[index] for index in obj.committee]`, adjust balances as follows:
For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots[:EPOCH_LENGTH]` (i.e. the objects corresponding to the epoch before the current one), for each `v` in `[state.validator_registry[index] for index in obj.committee]`, adjust balances as follows:
* If `v in attesting_validators(obj)`, `v.balance += adjust_for_inclusion_distance(base_reward(v) * total_attesting_balance(obj) // total_balance(obj)), inclusion_distance(v))`.
* If `v not in attesting_validators(obj)`, `v.balance -= base_reward(v)`.
### Receipt root voting
### Validator registry
If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then:
* Set `state.processed_pow_receipt_root = x.receipt_root` if `x.votes * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for any `x` in `state.candidate_pow_receipt_root`.
* Set `state.candidate_pow_receipt_roots = []`.
### Validator registry change
A [validator](#dfn-validator) registry change occurs if the following criteria are satisfied:
If the following are satisfied:
* `state.finalized_slot > state.validator_registry_latest_change_slot`
* `crosslinks[shard].slot > state.validator_registry_latest_change_slot` for every shard number `shard` in `state.shard_committees_at_slots`
A helper function is defined as:
update the validator registry by running
```python
def get_changed_validators(validators: List[ValidatorRecord],
latest_penalized_exit_balances: List[int],
validator_registry_delta_chain_tip: int,
current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]:
update_validator_registry(
copy.deepcopy(state.validator_registry),
copy.deepcopy(state.latest_penalized_exit_balances),
state.validator_registry_delta_chain_tip,
state.slot
)
```
where
```python
def update_validator_registry(validator_registry: List[ValidatorRecord],
latest_penalized_exit_balances: List[int],
validator_registry_delta_chain_tip: int,
current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]:
"""
Return changed validator registry and ``latest_penalized_exit_balances``, ``validator_registry_delta_chain_tip``.
Update the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``.
"""
# The active validators
active_validator_indices = get_active_validator_indices(validators)
active_validator_indices = get_active_validator_indices(validator_registry)
# The total effective balance of active validators
total_balance = sum([get_effective_balance(v) for i, v in enumerate(validators) if i in active_validator_indices])
total_balance = sum([get_effective_balance(v) for i, v in enumerate(validator_registry) if i in active_validator_indices])
# The maximum balance churn in Gwei (for deposits and exits separately)
max_balance_churn = max(
@ -1459,39 +1481,41 @@ def get_changed_validators(validators: List[ValidatorRecord],
# Activate validators within the allowable balance churn
balance_churn = 0
for i in range(len(validators)):
if validators[i].status == PENDING_ACTIVATION and validators[i].balance >= MAX_DEPOSIT:
for i in range(len(validator_registry)):
validator = validator_registry[i]
if validator.status == PENDING_ACTIVATION and validator.balance >= MAX_DEPOSIT:
# Check the balance churn would be within the allowance
balance_churn += get_effective_balance(validators[i])
balance_churn += get_effective_balance(validator)
if balance_churn > max_balance_churn:
break
# Activate validator
validators[i].status = ACTIVE
validators[i].latest_status_change_slot = current_slot
validator.status = ACTIVE
validator.latest_status_change_slot = current_slot
validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip(
validator_registry_delta_chain_tip=validator_registry_delta_chain_tip,
index=i,
pubkey=validators[i].pubkey,
pubkey=validator.pubkey,
flag=ACTIVATION,
)
# Exit validators within the allowable balance churn
balance_churn = 0
for i in range(len(validators)):
if validators[i].status == ACTIVE_PENDING_EXIT:
validator = validator_registry[i]
if validator.status == ACTIVE_PENDING_EXIT:
# Check the balance churn would be within the allowance
balance_churn += get_effective_balance(validators[i])
balance_churn += get_effective_balance(validator)
if balance_churn > max_balance_churn:
break
# Exit validator
validators[i].status = EXITED_WITHOUT_PENALTY
validators[i].latest_status_change_slot = current_slot
validator.status = EXITED_WITHOUT_PENALTY
validator.latest_status_change_slot = current_slot
validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip(
validator_registry_delta_chain_tip=validator_registry_delta_chain_tip,
index=i,
pubkey=validators[i].pubkey,
pubkey=validator.pubkey,
flag=EXIT,
)
@ -1506,38 +1530,24 @@ def get_changed_validators(validators: List[ValidatorRecord],
# Calculate penalties for slashed validators
def to_penalize(v):
return v.status == EXITED_WITH_PENALTY
validators_to_penalize = filter(to_penalize, validators)
validators_to_penalize = filter(to_penalize, validator_registry)
for v in validators_to_penalize:
v.balance -= get_effective_balance(v) * min(total_penalties * 3, total_balance) // total_balance
return validators, latest_penalized_exit_balances, validator_registry_delta_chain_tip
# Update the state
state.validator_registry = validator_registry
state.validator_registry_delta_chain_tip = validator_registry_delta_chain_tip
state.latest_penalized_exit_balances = latest_penalized_exit_balances
```
Then, run the following algorithm to update the [validator](#dfn-validator) registry:
```python
def change_validators(state: BeaconState,
current_slot: int) -> None:
"""
Change validator registry.
Note that this function mutates ``state``.
"""
state.validator_registry, state.latest_penalized_exit_balances = get_changed_validators(
copy.deepcopy(state.validator_registry),
copy.deepcopy(state.latest_penalized_exit_balances),
state.validator_registry_delta_chain_tip,
current_slot
)
```
And perform the following updates:
Also perform the following updates:
* Set `state.validator_registry_latest_change_slot = state.slot`.
* Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`.
* Set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, next_start_shard)` where next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`.
* Set `state.next_seed = state.randao_mix`.
### If a validator registry change does NOT happen
If a validator registry update does _not_ happen do the following:
* Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`.
* Let `time_since_finality = state.slot - state.validator_registry_latest_change_slot`.
@ -1571,10 +1581,10 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com
state.persistent_committees[reassignment.shard].append(reassignment.validator_index)
```
### Finally...
### Final updates
* Remove all attestation records older than slot `s`.
* For any [validator](#dfn-validator) with index `i` with balance less than `MIN_BALANCE` and status `ACTIVE`, run `exit_validator(i, state, penalize=False, current_slot=state.slot)`.
* Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < s`.
* Run `exit_validator(i, state, penalize=False, current_slot=state.slot)` for indices `i` such that `state.validator_registry[i].status = ACTIVE and state.validator_registry[i].balance < MIN_BALANCE`.
* Set `state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH:]`.
# Appendix