mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-13 12:14:19 +00:00
Merge branch 'dev' into JustinDrake-patch-4
This commit is contained in:
commit
7b829227ee
116
LICENSE
Normal file
116
LICENSE
Normal file
@ -0,0 +1,116 @@
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
@ -34,6 +34,7 @@
|
||||
- [`BeaconBlockHeader`](#beaconblockheader)
|
||||
- [`Validator`](#validator)
|
||||
- [`PendingAttestation`](#pendingattestation)
|
||||
- [`HistoricalBatch`](#historicalbatch)
|
||||
- [Beacon transactions](#beacon-transactions)
|
||||
- [`ProposerSlashing`](#proposerslashing)
|
||||
- [`AttesterSlashing`](#attesterslashing)
|
||||
@ -73,7 +74,6 @@
|
||||
- [`get_active_index_root`](#get_active_index_root)
|
||||
- [`generate_seed`](#generate_seed)
|
||||
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
||||
- [`merkle_root`](#merkle_root)
|
||||
- [`verify_merkle_branch`](#verify_merkle_branch)
|
||||
- [`get_attestation_participants`](#get_attestation_participants)
|
||||
- [`is_power_of_two`](#is_power_of_two)
|
||||
@ -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 caching](#state-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)
|
||||
- [Attester slashings](#attester-slashings)
|
||||
- [Attestations](#attestations)
|
||||
- [Deposits](#deposits)
|
||||
- [Voluntary exits](#voluntary-exits)
|
||||
- [Transfers](#transfers)
|
||||
- [State root verification](#state-root-verification)
|
||||
- [References](#references)
|
||||
- [Normative](#normative)
|
||||
- [Informative](#informative)
|
||||
@ -228,8 +229,9 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
|
||||
| `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes |
|
||||
| `ACTIVATION_EXIT_DELAY` | `2**2` (= 4) | epochs | 25.6 minutes |
|
||||
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**4` (= 16) | epochs | ~1.7 hours |
|
||||
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours |
|
||||
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours |
|
||||
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
|
||||
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
|
||||
|
||||
### State list lengths
|
||||
|
||||
@ -452,6 +454,17 @@ The types are defined topologically to aid in facilitating an executable version
|
||||
}
|
||||
```
|
||||
|
||||
#### `HistoricalBatch`
|
||||
|
||||
```python
|
||||
{
|
||||
# Block roots
|
||||
'block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||
# State roots
|
||||
'state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||
}
|
||||
```
|
||||
|
||||
### Beacon transactions
|
||||
|
||||
#### `ProposerSlashing`
|
||||
@ -1005,20 +1018,6 @@ def get_beacon_proposer_index(state: BeaconState,
|
||||
return first_committee[slot % len(first_committee)]
|
||||
```
|
||||
|
||||
### `merkle_root`
|
||||
|
||||
```python
|
||||
def merkle_root(values: List[Bytes32]) -> Bytes32:
|
||||
"""
|
||||
Merkleize ``values`` (where ``len(values)`` is a power of two) and return the Merkle root.
|
||||
Note that the leaves are not hashed.
|
||||
"""
|
||||
o = [0] * len(values) + values
|
||||
for i in range(len(values) - 1, 0, -1):
|
||||
o[i] = hash(o[i * 2] + o[i * 2 + 1])
|
||||
return o[1]
|
||||
```
|
||||
|
||||
### `verify_merkle_branch`
|
||||
|
||||
```python
|
||||
@ -1288,7 +1287,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
serialized_deposit_data = serialize(deposit.deposit_data)
|
||||
# Deposits must be processed in order
|
||||
assert deposit.index == state.deposit_index
|
||||
|
||||
|
||||
# Verify the Merkle branch
|
||||
merkle_branch_is_valid = verify_merkle_branch(
|
||||
leaf=hash(serialized_deposit_data),
|
||||
@ -1298,27 +1297,12 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
root=state.latest_eth1_data.deposit_root,
|
||||
)
|
||||
assert merkle_branch_is_valid
|
||||
|
||||
|
||||
# Increment the next deposit index we are expecting. Note that this
|
||||
# needs to be done here because while the deposit contract will never
|
||||
# create an invalid Merkle branch, it may admit an invalid deposit
|
||||
# object, and we need to be able to skip over it
|
||||
state.deposit_index += 1
|
||||
|
||||
# Verify the proof of possession
|
||||
proof_is_valid = bls_verify(
|
||||
pubkey=deposit_input.pubkey,
|
||||
message_hash=signed_root(deposit_input),
|
||||
signature=deposit_input.proof_of_possession,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
get_current_epoch(state),
|
||||
DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
|
||||
if not proof_is_valid:
|
||||
return
|
||||
|
||||
validator_pubkeys = [v.pubkey for v in state.validator_registry]
|
||||
pubkey = deposit_input.pubkey
|
||||
@ -1326,6 +1310,20 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
withdrawal_credentials = deposit_input.withdrawal_credentials
|
||||
|
||||
if pubkey not in validator_pubkeys:
|
||||
# Verify the proof of possession
|
||||
proof_is_valid = bls_verify(
|
||||
pubkey=deposit_input.pubkey,
|
||||
message_hash=signed_root(deposit_input),
|
||||
signature=deposit_input.proof_of_possession,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
get_current_epoch(state),
|
||||
DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
if not proof_is_valid:
|
||||
return
|
||||
|
||||
# Add new validator
|
||||
validator = Validator(
|
||||
pubkey=pubkey,
|
||||
@ -1342,10 +1340,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
||||
state.validator_balances.append(amount)
|
||||
else:
|
||||
# Increase balance by deposit amount
|
||||
index = validator_pubkeys.index(pubkey)
|
||||
assert state.validator_registry[index].withdrawal_credentials == withdrawal_credentials
|
||||
|
||||
state.validator_balances[index] += amount
|
||||
state.validator_balances[validator_pubkeys.index(pubkey)] += amount
|
||||
```
|
||||
|
||||
### Routines for updating validator status
|
||||
@ -1653,332 +1648,54 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock)
|
||||
children = get_children(store, head)
|
||||
if len(children) == 0:
|
||||
return head
|
||||
head = max(children, key=get_vote_count)
|
||||
head = max(children, key=lambda x: (get_vote_count(x), hash_tree_root(x)))
|
||||
```
|
||||
|
||||
## 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 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 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-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 caching
|
||||
|
||||
At every `slot > GENESIS_SLOT` run the following function:
|
||||
|
||||
```python
|
||||
def advance_slot(state: BeaconState) -> None:
|
||||
state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state)
|
||||
state.slot += 1
|
||||
def cache_state(state: BeaconState) -> None:
|
||||
previous_slot_state_root = hash_tree_root(state)
|
||||
|
||||
# store the previous slot's post state transition root
|
||||
state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_slot_state_root
|
||||
|
||||
# cache state root in stored latest_block_header if empty
|
||||
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)
|
||||
```
|
||||
state.latest_block_header.state_root = previous_slot_state_root
|
||||
|
||||
### 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
|
||||
# store latest known block for previous slot
|
||||
state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header)
|
||||
```
|
||||
|
||||
### 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:
|
||||
@ -2006,24 +1723,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)
|
||||
]
|
||||
```
|
||||
|
||||
@ -2041,7 +1758,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
|
||||
@ -2089,7 +1806,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)
|
||||
@ -2102,10 +1819,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
|
||||
@ -2118,7 +1835,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):
|
||||
@ -2128,7 +1845,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
|
||||
)
|
||||
```
|
||||
|
||||
@ -2161,8 +1878,7 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
||||
```
|
||||
|
||||
```python
|
||||
def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
||||
epochs_since_finality = get_current_epoch(state) + 1 - state.finalized_epoch
|
||||
def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei:
|
||||
return (
|
||||
get_base_reward(state, index) +
|
||||
get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2
|
||||
@ -2222,13 +1938,14 @@ def compute_normal_justification_and_finalization_deltas(state: BeaconState) ->
|
||||
else:
|
||||
deltas[1][index] += get_base_reward(state, index)
|
||||
# Proposer bonus
|
||||
proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index))
|
||||
deltas[0][proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT
|
||||
if index in get_attesting_indices(state, state.previous_epoch_attestations):
|
||||
proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index))
|
||||
deltas[0][proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT
|
||||
return deltas
|
||||
```
|
||||
|
||||
When blocks are not finalizing normally...
|
||||
|
||||
|
||||
```python
|
||||
def compute_inactivity_leak_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||
# deltas[0] for rewards
|
||||
@ -2312,7 +2029,7 @@ def apply_rewards(state: BeaconState) -> None:
|
||||
|
||||
#### Ejections
|
||||
|
||||
* Run `process_ejections(state)`.
|
||||
Run `process_ejections(state)`.
|
||||
|
||||
```python
|
||||
def process_ejections(state: BeaconState) -> None:
|
||||
@ -2434,9 +2151,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):
|
||||
@ -2493,15 +2209,326 @@ def finish_epoch_update(state: BeaconState) -> None:
|
||||
state.latest_randao_mixes[next_epoch % LATEST_RANDAO_MIXES_LENGTH] = get_randao_mix(state, current_epoch)
|
||||
# Set historical root accumulator
|
||||
if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0:
|
||||
state.historical_roots.append(merkle_root(state.latest_block_roots + state.latest_state_roots))
|
||||
historical_batch = HistoricalBatch(
|
||||
block_roots=state.latest_block_roots,
|
||||
state_roots=state.latest_state_roots,
|
||||
)
|
||||
state.historical_roots.append(hash_tree_root(historical_batch))
|
||||
# Rotate current/previous epoch attestations
|
||||
state.previous_epoch_attestations = 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:
|
||||
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 epoch is the same
|
||||
assert slot_to_epoch(proposer_slashing.header_1.slot) == slot_to_epoch(proposer_slashing.header_2.slot)
|
||||
# But the headers are different
|
||||
assert proposer_slashing.header_1 != 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_voluntary_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 == FAR_FUTURE_EPOCH
|
||||
# Verify the validator has not initiated an exit
|
||||
assert validator.initiated_exit is False
|
||||
# Exits must specify an epoch when they become valid; they are not valid before then
|
||||
assert get_current_epoch(state) >= exit.epoch
|
||||
# Must have been in the validator set long enough
|
||||
assert get_current_epoch(state) - validator.activation_epoch >= PERSISTENT_COMMITTEE_PERIOD
|
||||
# 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…
x
Reference in New Issue
Block a user