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 processing](#beacon-chain-processing)
|
||||||
- [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule)
|
- [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule)
|
||||||
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
|
||||||
- [Per-slot processing](#per-slot-processing)
|
- [State-root caching](#state-root-caching)
|
||||||
- [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)
|
|
||||||
- [Per-epoch processing](#per-epoch-processing)
|
- [Per-epoch processing](#per-epoch-processing)
|
||||||
- [Helper functions](#helper-functions-1)
|
- [Helper functions](#helper-functions-1)
|
||||||
- [Justification](#justification)
|
- [Justification](#justification)
|
||||||
|
@ -135,6 +124,18 @@
|
||||||
- [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data)
|
- [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data)
|
||||||
- [Slashings and exit queue](#slashings-and-exit-queue)
|
- [Slashings and exit queue](#slashings-and-exit-queue)
|
||||||
- [Final updates](#final-updates)
|
- [Final updates](#final-updates)
|
||||||
|
- [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)
|
- [State root verification](#state-root-verification)
|
||||||
- [References](#references)
|
- [References](#references)
|
||||||
- [Normative](#normative)
|
- [Normative](#normative)
|
||||||
|
@ -1659,327 +1660,39 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock)
|
||||||
|
|
||||||
## Beacon chain state transition function
|
## 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.
|
1. State-root caching, which happens at the start of every slot.
|
||||||
2. The per-block transitions, which happens at every block.
|
2. The per-epoch transitions, which happens at the start of the first slot of every epoch.
|
||||||
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`).
|
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.
|
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:
|
At every `slot > GENESIS_SLOT` run the following function:
|
||||||
|
|
||||||
```python
|
```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.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
|
### 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
|
#### Helper functions
|
||||||
|
|
||||||
We define some helper functions:
|
We define some helper functions utilized when processing an epoch transition:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_current_total_balance(state: BeaconState) -> Gwei:
|
def get_current_total_balance(state: BeaconState) -> Gwei:
|
||||||
|
@ -2007,24 +1720,24 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat
|
||||||
```python
|
```python
|
||||||
def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||||
return [
|
return [
|
||||||
a for a in state.current_epoch_attestations if
|
a for a in state.current_epoch_attestations
|
||||||
a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state)))
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||||
return [
|
return [
|
||||||
a for a in state.previous_epoch_attestations if
|
a for a in state.previous_epoch_attestations
|
||||||
a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]:
|
def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]:
|
||||||
return [
|
return [
|
||||||
a for a in state.previous_epoch_attestations if
|
a for a in state.previous_epoch_attestations
|
||||||
a.data.beacon_block_root == get_block_root(state, a.data.slot)
|
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:
|
if len(all_roots) == 0:
|
||||||
return ZERO_HASH, []
|
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]
|
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
|
# Winning crosslink root is the root with the most votes for it, ties broken in favor of
|
||||||
|
@ -2119,7 +1832,7 @@ Run the following function:
|
||||||
```python
|
```python
|
||||||
def process_crosslinks(state: BeaconState) -> None:
|
def process_crosslinks(state: BeaconState) -> None:
|
||||||
current_epoch = get_current_epoch(state)
|
current_epoch = get_current_epoch(state)
|
||||||
previous_epoch = get_previous_epoch(state)
|
previous_epoch = current_epoch - 1
|
||||||
next_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 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):
|
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:
|
if 3 * participating_balance >= 2 * total_balance:
|
||||||
state.latest_crosslinks[shard] = Crosslink(
|
state.latest_crosslinks[shard] = Crosslink(
|
||||||
epoch=slot_to_epoch(slot),
|
epoch=slot_to_epoch(slot),
|
||||||
crosslink_data_root=winning_root,
|
crosslink_data_root=winning_root
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2313,7 +2026,7 @@ def apply_rewards(state: BeaconState) -> None:
|
||||||
|
|
||||||
#### Ejections
|
#### Ejections
|
||||||
|
|
||||||
* Run `process_ejections(state)`.
|
Run `process_ejections(state)`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def process_ejections(state: BeaconState) -> None:
|
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)
|
total_balance = get_total_balance(state, active_validator_indices)
|
||||||
|
|
||||||
# Compute `total_penalties`
|
# Compute `total_penalties`
|
||||||
epoch_index = current_epoch % LATEST_SLASHED_EXIT_LENGTH
|
total_at_start = state.latest_slashed_balances[(current_epoch + 1) % 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[current_epoch % LATEST_SLASHED_EXIT_LENGTH]
|
||||||
total_at_end = state.latest_slashed_balances[epoch_index]
|
|
||||||
total_penalties = total_at_end - total_at_start
|
total_penalties = total_at_end - total_at_start
|
||||||
|
|
||||||
for index, validator in enumerate(state.validator_registry):
|
for index, validator in enumerate(state.validator_registry):
|
||||||
|
@ -2500,9 +2212,315 @@ def finish_epoch_update(state: BeaconState) -> None:
|
||||||
state.current_epoch_attestations = []
|
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
|
# References
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue