Merge pull request #732 from ethereum/epoch-start
epoch transition at start of epoch
This commit is contained in:
commit
0bcc350b7b
|
@ -110,18 +110,7 @@
|
|||
- [Beacon chain processing](#beacon-chain-processing)
|
||||
- [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule)
|
||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||
- [Per-slot processing](#per-slot-processing)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Block header](#block-header)
|
||||
- [RANDAO](#randao)
|
||||
- [Eth1 data](#eth1-data)
|
||||
- [Transactions](#transactions)
|
||||
- [Proposer slashings](#proposer-slashings-1)
|
||||
- [Attester slashings](#attester-slashings-1)
|
||||
- [Attestations](#attestations-1)
|
||||
- [Deposits](#deposits-1)
|
||||
- [Voluntary exits](#voluntary-exits-1)
|
||||
- [Transfers](#transfers-1)
|
||||
- [State-root caching](#state-root-caching)
|
||||
- [Per-epoch processing](#per-epoch-processing)
|
||||
- [Helper functions](#helper-functions-1)
|
||||
- [Justification](#justification)
|
||||
|
@ -135,7 +124,19 @@
|
|||
- [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data)
|
||||
- [Slashings and exit queue](#slashings-and-exit-queue)
|
||||
- [Final updates](#final-updates)
|
||||
- [State root verification](#state-root-verification)
|
||||
- [Per-slot processing](#per-slot-processing)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Block header](#block-header)
|
||||
- [RANDAO](#randao)
|
||||
- [Eth1 data](#eth1-data)
|
||||
- [Transactions](#transactions)
|
||||
- [Proposer slashings](#proposer-slashings-1)
|
||||
- [Attester slashings](#attester-slashings-1)
|
||||
- [Attestations](#attestations-1)
|
||||
- [Deposits](#deposits-1)
|
||||
- [Voluntary exits](#voluntary-exits-1)
|
||||
- [Transfers](#transfers-1)
|
||||
- [State root verification](#state-root-verification)
|
||||
- [References](#references)
|
||||
- [Normative](#normative)
|
||||
- [Informative](#informative)
|
||||
|
@ -1659,327 +1660,39 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock)
|
|||
|
||||
## Beacon chain state transition function
|
||||
|
||||
We now define the state transition function. At a high level the state transition is made up of three parts:
|
||||
We now define the state transition function. At a high level the state transition is made up of four parts:
|
||||
|
||||
1. The per-slot transitions, which happens at the start of every slot.
|
||||
2. The per-block transitions, which happens at every block.
|
||||
3. The per-epoch transitions, which happens at the end of the last slot of every epoch (i.e. `(state.slot + 1) % SLOTS_PER_EPOCH == 0`).
|
||||
1. State-root caching, which happens at the start of every slot.
|
||||
2. The per-epoch transitions, which happens at the start of the first slot of every epoch.
|
||||
3. The per-slot transitions, which happens at every slot.
|
||||
4. The per-block transitions, which happens at every block.
|
||||
|
||||
The per-slot transitions focus on the slot counter and block roots records updates; the per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`; the per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization.
|
||||
Transition section notes:
|
||||
* The state-root caching, caches the state root of the previous slot;
|
||||
* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization;
|
||||
* The per-slot transitions focus on the slot counter and block roots records updates;
|
||||
* The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`.
|
||||
|
||||
Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid.
|
||||
|
||||
_Note_: If there are skipped slots between a block and its parent block, run the steps in the [per-slot](#per-slot-processing) and [per-epoch](#per-epoch-processing) sections once for each skipped slot and then once for the slot containing the new block.
|
||||
_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-root-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block.
|
||||
|
||||
### Per-slot processing
|
||||
### State-root caching
|
||||
|
||||
At every `slot > GENESIS_SLOT` run the following function:
|
||||
|
||||
```python
|
||||
def advance_slot(state: BeaconState) -> None:
|
||||
def cache_state_root(state: BeaconState) -> None:
|
||||
state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state)
|
||||
state.slot += 1
|
||||
if state.latest_block_header.state_root == ZERO_HASH:
|
||||
state.latest_block_header.state_root = get_state_root(state, state.slot - 1)
|
||||
state.latest_block_roots[(state.slot - 1) % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header)
|
||||
```
|
||||
|
||||
### Per-block processing
|
||||
|
||||
For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`.
|
||||
|
||||
#### Block header
|
||||
|
||||
```python
|
||||
def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
||||
# Verify that the slots match
|
||||
assert block.slot == state.slot
|
||||
# Verify that the parent matches
|
||||
assert block.previous_block_root == hash_tree_root(state.latest_block_header)
|
||||
# Save current block as the new latest block
|
||||
state.latest_block_header = get_temporary_block_header(block)
|
||||
# Verify proposer signature
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signed_root(block),
|
||||
signature=block.signature,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
```
|
||||
|
||||
#### RANDAO
|
||||
|
||||
```python
|
||||
def process_randao(state: BeaconState, block: BeaconBlock) -> None:
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]
|
||||
# Verify that the provided randao value is valid
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=hash_tree_root(get_current_epoch(state)),
|
||||
signature=block.body.randao_reveal,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)
|
||||
)
|
||||
# Mix it in
|
||||
state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = (
|
||||
xor(get_randao_mix(state, get_current_epoch(state)),
|
||||
hash(block.body.randao_reveal))
|
||||
)
|
||||
```
|
||||
|
||||
#### Eth1 data
|
||||
|
||||
```python
|
||||
def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None:
|
||||
for eth1_data_vote in state.eth1_data_votes:
|
||||
# If someone else has already voted for the same hash, add to its counter
|
||||
if eth1_data_vote.eth1_data == block.body.eth1_data:
|
||||
eth1_data_vote.vote_count += 1
|
||||
return
|
||||
# If we're seeing this hash for the first time, make a new counter
|
||||
state.eth1_data_votes.append(Eth1DataVote(eth1_data=block.body.eth1_data, vote_count=1))
|
||||
```
|
||||
|
||||
#### Transactions
|
||||
|
||||
##### Proposer slashings
|
||||
|
||||
Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`.
|
||||
|
||||
For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_proposer_slashing(state: BeaconState,
|
||||
proposer_slashing: ProposerSlashing) -> None:
|
||||
"""
|
||||
Process ``ProposerSlashing`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
proposer = state.validator_registry[proposer_slashing.proposer_index]
|
||||
# Verify that the slot is the same
|
||||
assert proposer_slashing.header_1.slot == proposer_slashing.header_2.slot
|
||||
# But the roots are different!
|
||||
assert hash_tree_root(proposer_slashing.header_1) != hash_tree_root(proposer_slashing.header_2)
|
||||
# Proposer is not yet slashed
|
||||
assert proposer.slashed is False
|
||||
# Signatures are valid
|
||||
for header in (proposer_slashing.header_1, proposer_slashing.header_2):
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signed_root(header),
|
||||
signature=header.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
slash_validator(state, proposer_slashing.proposer_index)
|
||||
```
|
||||
|
||||
##### Attester slashings
|
||||
|
||||
Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`.
|
||||
|
||||
For each `attester_slashing` in `block.body.attester_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attester_slashing(state: BeaconState,
|
||||
attester_slashing: AttesterSlashing) -> None:
|
||||
"""
|
||||
Process ``AttesterSlashing`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
attestation1 = attester_slashing.slashable_attestation_1
|
||||
attestation2 = attester_slashing.slashable_attestation_2
|
||||
# Check that the attestations are conflicting
|
||||
assert attestation1.data != attestation2.data
|
||||
assert (
|
||||
is_double_vote(attestation1.data, attestation2.data) or
|
||||
is_surround_vote(attestation1.data, attestation2.data)
|
||||
)
|
||||
assert verify_slashable_attestation(state, attestation1)
|
||||
assert verify_slashable_attestation(state, attestation2)
|
||||
slashable_indices = [
|
||||
index for index in attestation1.validator_indices
|
||||
if (
|
||||
index in attestation2.validator_indices and
|
||||
state.validator_registry[index].slashed is False
|
||||
)
|
||||
]
|
||||
assert len(slashable_indices) >= 1
|
||||
for index in slashable_indices:
|
||||
slash_validator(state, index)
|
||||
```
|
||||
|
||||
##### Attestations
|
||||
|
||||
Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`.
|
||||
|
||||
For each `attestation` in `block.body.attestations`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
"""
|
||||
Process ``Attestation`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Can't submit attestations that are too far in history (or in prehistory)
|
||||
assert attestation.data.slot >= GENESIS_SLOT
|
||||
assert state.slot < attestation.data.slot + SLOTS_PER_EPOCH
|
||||
# Can't submit attestations too quickly
|
||||
assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot
|
||||
# Verify that the justified epoch is correct, case 1: current epoch attestations
|
||||
if slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state):
|
||||
assert attestation.data.justified_epoch == state.justified_epoch
|
||||
# Case 2: previous epoch attestations
|
||||
else:
|
||||
assert attestation.data.justified_epoch == state.previous_justified_epoch
|
||||
# Check that the justified block root is correct
|
||||
assert attestation.data.justified_block_root == get_block_root(
|
||||
state, get_epoch_start_slot(attestation.data.justified_epoch)
|
||||
)
|
||||
# Check that the crosslink data is valid
|
||||
acceptable_crosslink_data = {
|
||||
# Case 1: Latest crosslink matches the one in the state
|
||||
attestation.data.latest_crosslink,
|
||||
# Case 2: State has already been updated, state's latest crosslink matches the crosslink
|
||||
# the attestation is trying to create
|
||||
Crosslink(
|
||||
crosslink_data_root=attestation.data.crosslink_data_root,
|
||||
epoch=slot_to_epoch(attestation.data.slot)
|
||||
)
|
||||
}
|
||||
assert state.latest_crosslinks[attestation.data.shard] in acceptable_crosslink_data
|
||||
# Attestation must be nonempty!
|
||||
assert attestation.aggregation_bitfield != b'\x00' * len(attestation.aggregation_bitfield)
|
||||
# Custody must be empty (to be removed in phase 1)
|
||||
assert attestation.custody_bitfield == b'\x00' * len(attestation.custody_bitfield)
|
||||
# Get the committee for the specific shard that this attestation is for
|
||||
crosslink_committee = [
|
||||
committee for committee, shard in get_crosslink_committees_at_slot(state, attestation.data.slot)
|
||||
if shard == attestation.data.shard
|
||||
][0]
|
||||
# Custody bitfield must be a subset of the attestation bitfield
|
||||
for i in range(len(crosslink_committee)):
|
||||
if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b0:
|
||||
assert get_bitfield_bit(attestation.custody_bitfield, i) == 0b0
|
||||
# Verify aggregate signature
|
||||
participants = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
custody_bit_1_participants = get_attestation_participants(state, attestation.data, attestation.custody_bitfield)
|
||||
custody_bit_0_participants = [i for i in participants if i not in custody_bit_1_participants]
|
||||
|
||||
assert bls_verify_multiple(
|
||||
pubkeys=[
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_participants]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_participants]),
|
||||
],
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
signature=attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(attestation.data.slot), DOMAIN_ATTESTATION),
|
||||
)
|
||||
# Crosslink data root is zero (to be removed in phase 1)
|
||||
assert attestation.data.crosslink_data_root == ZERO_HASH
|
||||
# Apply the attestation
|
||||
pending_attestation = PendingAttestation(
|
||||
data=attestation.data,
|
||||
aggregation_bitfield=attestation.aggregation_bitfield,
|
||||
custody_bitfield=attestation.custody_bitfield,
|
||||
inclusion_slot=state.slot,
|
||||
)
|
||||
if slot_to_epoch(attestation.data.slot) == get_current_epoch(state):
|
||||
state.current_epoch_attestations.append(pending_attestation)
|
||||
elif slot_to_epoch(attestation.data.slot) == get_previous_epoch(state):
|
||||
state.previous_epoch_attestations.append(pending_attestation)
|
||||
```
|
||||
|
||||
##### Deposits
|
||||
|
||||
Verify that `len(block.body.deposits) <= MAX_DEPOSITS`.
|
||||
|
||||
For each `deposit` in `block.body.deposits`, run `process_deposit(state, deposit)`.
|
||||
|
||||
##### Voluntary exits
|
||||
|
||||
Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`.
|
||||
|
||||
For each `exit` in `block.body.voluntary_exits`, run the following function:
|
||||
|
||||
```python
|
||||
def process_exit(state: BeaconState, exit: VoluntaryExit) -> None:
|
||||
"""
|
||||
Process ``VoluntaryExit`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
validator = state.validator_registry[exit.validator_index]
|
||||
# Verify the validator has not yet exited
|
||||
assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state))
|
||||
# Exits must specify an epoch when they become valid; they are not valid before then
|
||||
assert get_current_epoch(state) >= exit.epoch
|
||||
# Verify signature
|
||||
assert bls_verify(
|
||||
pubkey=validator.pubkey,
|
||||
message_hash=signed_root(exit),
|
||||
signature=exit.signature,
|
||||
domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT)
|
||||
)
|
||||
# Run the exit
|
||||
initiate_validator_exit(state, exit.validator_index)
|
||||
```
|
||||
|
||||
##### Transfers
|
||||
|
||||
Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2.
|
||||
|
||||
Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct.
|
||||
|
||||
For each `transfer` in `block.body.transfers`, run the following function:
|
||||
|
||||
```python
|
||||
def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
"""
|
||||
Process ``Transfer`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Verify the amount and fee aren't individually too big (for anti-overflow purposes)
|
||||
assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee)
|
||||
# Verify that we have enough ETH to send, and that after the transfer the balance will be either
|
||||
# exactly zero or at least MIN_DEPOSIT_AMOUNT
|
||||
assert (
|
||||
state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or
|
||||
state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT
|
||||
)
|
||||
# A transfer is valid in only one slot
|
||||
assert state.slot == transfer.slot
|
||||
# Only withdrawn or not-yet-deposited accounts can transfer
|
||||
assert (
|
||||
get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or
|
||||
state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH
|
||||
)
|
||||
# Verify that the pubkey is valid
|
||||
assert (
|
||||
state.validator_registry[transfer.sender].withdrawal_credentials ==
|
||||
BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:]
|
||||
)
|
||||
# Verify that the signature is valid
|
||||
assert bls_verify(
|
||||
pubkey=transfer.pubkey,
|
||||
message_hash=signed_root(transfer),
|
||||
signature=transfer.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER)
|
||||
)
|
||||
# Process the transfer
|
||||
state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee
|
||||
state.validator_balances[transfer.recipient] += transfer.amount
|
||||
state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee
|
||||
```
|
||||
|
||||
### Per-epoch processing
|
||||
|
||||
The steps below happen when `(state.slot + 1) % SLOTS_PER_EPOCH == 0`.
|
||||
The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SLOTS_PER_EPOCH == 0`.
|
||||
|
||||
#### Helper functions
|
||||
|
||||
We define some helper functions:
|
||||
|
||||
We define some helper functions utilized when processing an epoch transition:
|
||||
|
||||
```python
|
||||
def get_current_total_balance(state: BeaconState) -> Gwei:
|
||||
|
@ -2007,24 +1720,24 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat
|
|||
```python
|
||||
def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||
return [
|
||||
a for a in state.current_epoch_attestations if
|
||||
a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
||||
a for a in state.current_epoch_attestations
|
||||
if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
||||
]
|
||||
```
|
||||
|
||||
```python
|
||||
def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||
return [
|
||||
a for a in state.previous_epoch_attestations if
|
||||
a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
||||
a for a in state.previous_epoch_attestations
|
||||
if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
||||
]
|
||||
```
|
||||
|
||||
```python
|
||||
def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||
return [
|
||||
a for a in state.previous_epoch_attestations if
|
||||
a.data.beacon_block_root == get_block_root(state, a.data.slot)
|
||||
a for a in state.previous_epoch_attestations
|
||||
if a.data.beacon_block_root == get_block_root(state, a.data.slot)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -2042,7 +1755,7 @@ def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple
|
|||
if len(all_roots) == 0:
|
||||
return ZERO_HASH, []
|
||||
|
||||
def get_attestations_for(root: Bytes32) -> List[PendingAttestation]:
|
||||
def get_attestations_for(root) -> List[PendingAttestation]:
|
||||
return [a for a in valid_attestations if a.data.crosslink_data_root == root]
|
||||
|
||||
# Winning crosslink root is the root with the most votes for it, ties broken in favor of
|
||||
|
@ -2090,7 +1803,7 @@ def update_justification_and_finalization(state: BeaconState) -> None:
|
|||
if current_boundary_attesting_balance * 3 >= get_current_total_balance(state) * 2:
|
||||
new_justified_epoch = get_current_epoch(state)
|
||||
state.justification_bitfield |= 1
|
||||
|
||||
|
||||
# Process finalizations
|
||||
bitfield = state.justification_bitfield
|
||||
current_epoch = get_current_epoch(state)
|
||||
|
@ -2103,10 +1816,10 @@ def update_justification_and_finalization(state: BeaconState) -> None:
|
|||
# The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3rd as source
|
||||
if (bitfield >> 0) % 8 == 0b111 and state.justified_epoch == current_epoch - 2:
|
||||
state.finalized_epoch = state.justified_epoch
|
||||
# The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source
|
||||
# The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source
|
||||
if (bitfield >> 0) % 4 == 0b11 and state.justified_epoch == current_epoch - 1:
|
||||
state.finalized_epoch = state.justified_epoch
|
||||
|
||||
|
||||
# Rotate justified epochs
|
||||
state.previous_justified_epoch = state.justified_epoch
|
||||
state.justified_epoch = new_justified_epoch
|
||||
|
@ -2119,7 +1832,7 @@ Run the following function:
|
|||
```python
|
||||
def process_crosslinks(state: BeaconState) -> None:
|
||||
current_epoch = get_current_epoch(state)
|
||||
previous_epoch = get_previous_epoch(state)
|
||||
previous_epoch = current_epoch - 1
|
||||
next_epoch = current_epoch + 1
|
||||
for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)):
|
||||
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
|
||||
|
@ -2129,7 +1842,7 @@ def process_crosslinks(state: BeaconState) -> None:
|
|||
if 3 * participating_balance >= 2 * total_balance:
|
||||
state.latest_crosslinks[shard] = Crosslink(
|
||||
epoch=slot_to_epoch(slot),
|
||||
crosslink_data_root=winning_root,
|
||||
crosslink_data_root=winning_root
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -2229,7 +1942,7 @@ def compute_normal_justification_and_finalization_deltas(state: BeaconState) ->
|
|||
```
|
||||
|
||||
When blocks are not finalizing normally...
|
||||
|
||||
|
||||
```python
|
||||
def compute_inactivity_leak_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||
# deltas[0] for rewards
|
||||
|
@ -2313,7 +2026,7 @@ def apply_rewards(state: BeaconState) -> None:
|
|||
|
||||
#### Ejections
|
||||
|
||||
* Run `process_ejections(state)`.
|
||||
Run `process_ejections(state)`.
|
||||
|
||||
```python
|
||||
def process_ejections(state: BeaconState) -> None:
|
||||
|
@ -2435,9 +2148,8 @@ def process_slashings(state: BeaconState) -> None:
|
|||
total_balance = get_total_balance(state, active_validator_indices)
|
||||
|
||||
# Compute `total_penalties`
|
||||
epoch_index = current_epoch % LATEST_SLASHED_EXIT_LENGTH
|
||||
total_at_start = state.latest_slashed_balances[(epoch_index + 1) % LATEST_SLASHED_EXIT_LENGTH]
|
||||
total_at_end = state.latest_slashed_balances[epoch_index]
|
||||
total_at_start = state.latest_slashed_balances[(current_epoch + 1) % LATEST_SLASHED_EXIT_LENGTH]
|
||||
total_at_end = state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH]
|
||||
total_penalties = total_at_end - total_at_start
|
||||
|
||||
for index, validator in enumerate(state.validator_registry):
|
||||
|
@ -2500,9 +2212,315 @@ def finish_epoch_update(state: BeaconState) -> None:
|
|||
state.current_epoch_attestations = []
|
||||
```
|
||||
|
||||
### State root verification
|
||||
### Per-slot processing
|
||||
|
||||
Verify `block.state_root == hash_tree_root(state)` if there exists a `block` for the slot being processed.
|
||||
At every `slot > GENESIS_SLOT` run the following function:
|
||||
|
||||
```python
|
||||
def advance_slot(state: BeaconState) -> None:
|
||||
if state.latest_block_header.state_root == ZERO_HASH:
|
||||
state.latest_block_header.state_root = state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT]
|
||||
state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header)
|
||||
state.slot += 1
|
||||
```
|
||||
|
||||
### Per-block processing
|
||||
|
||||
For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`.
|
||||
|
||||
#### Block header
|
||||
|
||||
```python
|
||||
def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
||||
# Verify that the slots match
|
||||
assert block.slot == state.slot
|
||||
# Verify that the parent matches
|
||||
assert block.previous_block_root == hash_tree_root(state.latest_block_header)
|
||||
# Save current block as the new latest block
|
||||
state.latest_block_header = get_temporary_block_header(block)
|
||||
# Verify proposer signature
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signed_root(block),
|
||||
signature=block.signature,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
```
|
||||
|
||||
#### RANDAO
|
||||
|
||||
```python
|
||||
def process_randao(state: BeaconState, block: BeaconBlock) -> None:
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]
|
||||
# Verify that the provided randao value is valid
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=hash_tree_root(get_current_epoch(state)),
|
||||
signature=block.body.randao_reveal,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)
|
||||
)
|
||||
# Mix it in
|
||||
state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = (
|
||||
xor(get_randao_mix(state, get_current_epoch(state)),
|
||||
hash(block.body.randao_reveal))
|
||||
)
|
||||
```
|
||||
|
||||
#### Eth1 data
|
||||
|
||||
```python
|
||||
def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None:
|
||||
for eth1_data_vote in state.eth1_data_votes:
|
||||
# If someone else has already voted for the same hash, add to its counter
|
||||
if eth1_data_vote.eth1_data == block.body.eth1_data:
|
||||
eth1_data_vote.vote_count += 1
|
||||
return
|
||||
# If we're seeing this hash for the first time, make a new counter
|
||||
state.eth1_data_votes.append(Eth1DataVote(eth1_data=block.body.eth1_data, vote_count=1))
|
||||
```
|
||||
|
||||
#### Transactions
|
||||
|
||||
##### Proposer slashings
|
||||
|
||||
Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`.
|
||||
|
||||
For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_proposer_slashing(state: BeaconState,
|
||||
proposer_slashing: ProposerSlashing) -> None:
|
||||
"""
|
||||
Process ``ProposerSlashing`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
proposer = state.validator_registry[proposer_slashing.proposer_index]
|
||||
# Verify that the slot is the same
|
||||
assert proposer_slashing.header_1.slot == proposer_slashing.header_2.slot
|
||||
# But the roots are different!
|
||||
assert hash_tree_root(proposer_slashing.header_1) != hash_tree_root(proposer_slashing.header_2)
|
||||
# Proposer is not yet slashed
|
||||
assert proposer.slashed is False
|
||||
# Signatures are valid
|
||||
for header in (proposer_slashing.header_1, proposer_slashing.header_2):
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signed_root(header),
|
||||
signature=header.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
slash_validator(state, proposer_slashing.proposer_index)
|
||||
```
|
||||
|
||||
##### Attester slashings
|
||||
|
||||
Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`.
|
||||
|
||||
For each `attester_slashing` in `block.body.attester_slashings`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attester_slashing(state: BeaconState,
|
||||
attester_slashing: AttesterSlashing) -> None:
|
||||
"""
|
||||
Process ``AttesterSlashing`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
attestation1 = attester_slashing.slashable_attestation_1
|
||||
attestation2 = attester_slashing.slashable_attestation_2
|
||||
# Check that the attestations are conflicting
|
||||
assert attestation1.data != attestation2.data
|
||||
assert (
|
||||
is_double_vote(attestation1.data, attestation2.data) or
|
||||
is_surround_vote(attestation1.data, attestation2.data)
|
||||
)
|
||||
assert verify_slashable_attestation(state, attestation1)
|
||||
assert verify_slashable_attestation(state, attestation2)
|
||||
slashable_indices = [
|
||||
index for index in attestation1.validator_indices
|
||||
if (
|
||||
index in attestation2.validator_indices and
|
||||
state.validator_registry[index].slashed is False
|
||||
)
|
||||
]
|
||||
assert len(slashable_indices) >= 1
|
||||
for index in slashable_indices:
|
||||
slash_validator(state, index)
|
||||
```
|
||||
|
||||
##### Attestations
|
||||
|
||||
Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`.
|
||||
|
||||
For each `attestation` in `block.body.attestations`, run the following function:
|
||||
|
||||
```python
|
||||
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
"""
|
||||
Process ``Attestation`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Can't submit attestations that are too far in history (or in prehistory)
|
||||
assert attestation.data.slot >= GENESIS_SLOT
|
||||
assert state.slot < attestation.data.slot + SLOTS_PER_EPOCH
|
||||
# Can't submit attestations too quickly
|
||||
assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot
|
||||
# Verify that the justified epoch is correct, case 1: current epoch attestations
|
||||
if slot_to_epoch(attestation.data.slot) >= get_current_epoch(state):
|
||||
assert attestation.data.justified_epoch == state.justified_epoch
|
||||
# Case 2: previous epoch attestations
|
||||
else:
|
||||
assert attestation.data.justified_epoch == state.previous_justified_epoch
|
||||
# Check that the justified block root is correct
|
||||
assert attestation.data.justified_block_root == get_block_root(
|
||||
state, get_epoch_start_slot(attestation.data.justified_epoch)
|
||||
)
|
||||
# Check that the crosslink data is valid
|
||||
acceptable_crosslink_data = {
|
||||
# Case 1: Latest crosslink matches the one in the state
|
||||
attestation.data.latest_crosslink,
|
||||
# Case 2: State has already been updated, state's latest crosslink matches the crosslink
|
||||
# the attestation is trying to create
|
||||
Crosslink(
|
||||
crosslink_data_root=attestation.data.crosslink_data_root,
|
||||
epoch=slot_to_epoch(attestation.data.slot)
|
||||
)
|
||||
}
|
||||
assert state.latest_crosslinks[attestation.data.shard] in acceptable_crosslink_data
|
||||
# Attestation must be nonempty!
|
||||
assert attestation.aggregation_bitfield != b'\x00' * len(attestation.aggregation_bitfield)
|
||||
# Custody must be empty (to be removed in phase 1)
|
||||
assert attestation.custody_bitfield == b'\x00' * len(attestation.custody_bitfield)
|
||||
# Get the committee for the specific shard that this attestation is for
|
||||
crosslink_committee = [
|
||||
committee for committee, shard in get_crosslink_committees_at_slot(state, attestation.data.slot)
|
||||
if shard == attestation.data.shard
|
||||
][0]
|
||||
# Custody bitfield must be a subset of the attestation bitfield
|
||||
for i in range(len(crosslink_committee)):
|
||||
if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b0:
|
||||
assert get_bitfield_bit(attestation.custody_bitfield, i) == 0b0
|
||||
# Verify aggregate signature
|
||||
participants = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
custody_bit_1_participants = get_attestation_participants(state, attestation.data, attestation.custody_bitfield)
|
||||
custody_bit_0_participants = [i for i in participants if i not in custody_bit_1_participants]
|
||||
|
||||
assert bls_verify_multiple(
|
||||
pubkeys=[
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_participants]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_participants]),
|
||||
],
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
signature=attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(attestation.data.slot), DOMAIN_ATTESTATION),
|
||||
)
|
||||
# Crosslink data root is zero (to be removed in phase 1)
|
||||
assert attestation.data.crosslink_data_root == ZERO_HASH
|
||||
# Apply the attestation
|
||||
pending_attestation = PendingAttestation(
|
||||
data=attestation.data,
|
||||
aggregation_bitfield=attestation.aggregation_bitfield,
|
||||
custody_bitfield=attestation.custody_bitfield,
|
||||
inclusion_slot=state.slot
|
||||
)
|
||||
if slot_to_epoch(attestation.data.slot) == get_current_epoch(state):
|
||||
state.current_epoch_attestations.append(pending_attestation)
|
||||
elif slot_to_epoch(attestation.data.slot) == get_previous_epoch(state):
|
||||
state.previous_epoch_attestations.append(pending_attestation)
|
||||
```
|
||||
|
||||
##### Deposits
|
||||
|
||||
Verify that `len(block.body.deposits) <= MAX_DEPOSITS`.
|
||||
|
||||
For each `deposit` in `block.body.deposits`, run `process_deposit(state, deposit)`.
|
||||
|
||||
##### Voluntary exits
|
||||
|
||||
Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`.
|
||||
|
||||
For each `exit` in `block.body.voluntary_exits`, run the following function:
|
||||
|
||||
```python
|
||||
def process_exit(state: BeaconState, exit: VoluntaryExit) -> None:
|
||||
"""
|
||||
Process ``VoluntaryExit`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
validator = state.validator_registry[exit.validator_index]
|
||||
# Verify the validator has not yet exited
|
||||
assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state))
|
||||
# Exits must specify an epoch when they become valid; they are not valid before then
|
||||
assert get_current_epoch(state) >= exit.epoch
|
||||
# Verify signature
|
||||
assert bls_verify(
|
||||
pubkey=validator.pubkey,
|
||||
message_hash=signed_root(exit),
|
||||
signature=exit.signature,
|
||||
domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT)
|
||||
)
|
||||
# Run the exit
|
||||
initiate_validator_exit(state, exit.validator_index)
|
||||
```
|
||||
|
||||
##### Transfers
|
||||
|
||||
Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2.
|
||||
|
||||
Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct.
|
||||
|
||||
For each `transfer` in `block.body.transfers`, run the following function:
|
||||
|
||||
```python
|
||||
def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
"""
|
||||
Process ``Transfer`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Verify the amount and fee aren't individually too big (for anti-overflow purposes)
|
||||
assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee)
|
||||
# Verify that we have enough ETH to send, and that after the transfer the balance will be either
|
||||
# exactly zero or at least MIN_DEPOSIT_AMOUNT
|
||||
assert (
|
||||
state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or
|
||||
state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT
|
||||
)
|
||||
# A transfer is valid in only one slot
|
||||
assert state.slot == transfer.slot
|
||||
# Only withdrawn or not-yet-deposited accounts can transfer
|
||||
assert (
|
||||
get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or
|
||||
state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH
|
||||
)
|
||||
# Verify that the pubkey is valid
|
||||
assert (
|
||||
state.validator_registry[transfer.sender].withdrawal_credentials ==
|
||||
BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:]
|
||||
)
|
||||
# Verify that the signature is valid
|
||||
assert bls_verify(
|
||||
pubkey=transfer.pubkey,
|
||||
message_hash=signed_root(transfer),
|
||||
signature=transfer.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER)
|
||||
)
|
||||
# Process the transfer
|
||||
state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee
|
||||
state.validator_balances[transfer.recipient] += transfer.amount
|
||||
state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee
|
||||
```
|
||||
|
||||
#### State root verification
|
||||
|
||||
Verify the block's `state_root` by running the following function:
|
||||
|
||||
```python
|
||||
def verify_block_state_root(state: BeaconState, block: BeaconBlock) -> None:
|
||||
assert block.state_root == hash_tree_root(state)
|
||||
```
|
||||
|
||||
# References
|
||||
|
||||
|
|
Loading…
Reference in New Issue