Merge branch 'dev' into proto-merge-test-gen
This commit is contained in:
commit
51ed40916f
|
@ -11,6 +11,7 @@ This repo hosts the current eth2.0 specifications. Discussions about design rati
|
|||
|
||||
Core specifications for eth2.0 client validation can be found in [specs/core](specs/core). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are:
|
||||
* [Phase 0 -- The Beacon Chain](specs/core/0_beacon-chain.md)
|
||||
* [Phase 1 -- Custody game](specs/core/1_custody-game.md)
|
||||
* [Phase 1 -- Shard Data Chains](specs/core/1_shard-data-chains.md)
|
||||
|
||||
Accompanying documents can be found in [specs](specs) and include
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
from copy import deepcopy
|
||||
import pytest
|
||||
|
||||
import pyspec.phase0.spec as spec
|
||||
from pyspec.phase0.spec import (
|
||||
get_balance,
|
||||
get_beacon_proposer_index,
|
||||
process_attester_slashing,
|
||||
)
|
||||
from phase0.helpers import (
|
||||
get_valid_attester_slashing,
|
||||
)
|
||||
|
||||
# mark entire file as 'attester_slashing'
|
||||
pytestmark = pytest.mark.attester_slashings
|
||||
|
||||
|
||||
def run_attester_slashing_processing(state, attester_slashing, valid=True):
|
||||
"""
|
||||
Run ``process_attester_slashing`` returning the pre and post state.
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
post_state = deepcopy(state)
|
||||
|
||||
if not valid:
|
||||
with pytest.raises(AssertionError):
|
||||
process_attester_slashing(post_state, attester_slashing)
|
||||
return state, None
|
||||
|
||||
process_attester_slashing(post_state, attester_slashing)
|
||||
|
||||
slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
slashed_validator = post_state.validator_registry[slashed_index]
|
||||
assert not slashed_validator.initiated_exit
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
# lost whistleblower reward
|
||||
assert (
|
||||
get_balance(post_state, slashed_index) <
|
||||
get_balance(state, slashed_index)
|
||||
)
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
# gained whistleblower reward
|
||||
assert (
|
||||
get_balance(post_state, proposer_index) >
|
||||
get_balance(state, proposer_index)
|
||||
)
|
||||
|
||||
return state, post_state
|
||||
|
||||
|
||||
def test_success_double(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
||||
|
||||
|
||||
def test_success_surround(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
# set attestion1 to surround attestation 2
|
||||
attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1
|
||||
attester_slashing.attestation_1.data.slot = attester_slashing.attestation_2.data.slot + spec.SLOTS_PER_EPOCH
|
||||
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
||||
|
||||
|
||||
def test_same_data(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
attester_slashing.attestation_1.data = attester_slashing.attestation_2.data
|
||||
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
||||
|
||||
|
||||
def test_no_double_or_surround(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
attester_slashing.attestation_1.data.slot += spec.SLOTS_PER_EPOCH
|
||||
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
||||
|
||||
|
||||
def test_participants_already_slashed(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
# set all indices to slashed
|
||||
attestation_1 = attester_slashing.attestation_1
|
||||
validator_indices = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
|
||||
for index in validator_indices:
|
||||
state.validator_registry[index].slashed = True
|
||||
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
||||
|
||||
|
||||
def test_custody_bit_0_and_1(state):
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
|
||||
attester_slashing.attestation_1.custody_bit_1_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
)
|
||||
pre_state, post_state = run_attester_slashing_processing(state, attester_slashing, False)
|
||||
|
||||
return pre_state, attester_slashing, post_state
|
|
@ -12,6 +12,7 @@ from pyspec.phase0.spec import (
|
|||
Attestation,
|
||||
AttestationData,
|
||||
AttestationDataAndCustodyBit,
|
||||
AttesterSlashing,
|
||||
BeaconBlockHeader,
|
||||
Deposit,
|
||||
DepositData,
|
||||
|
@ -19,6 +20,7 @@ from pyspec.phase0.spec import (
|
|||
ProposerSlashing,
|
||||
VoluntaryExit,
|
||||
# functions
|
||||
convert_to_indexed,
|
||||
get_active_validator_indices,
|
||||
get_attestation_participants,
|
||||
get_block_root,
|
||||
|
@ -244,6 +246,17 @@ def get_valid_proposer_slashing(state):
|
|||
)
|
||||
|
||||
|
||||
def get_valid_attester_slashing(state):
|
||||
attestation_1 = get_valid_attestation(state)
|
||||
attestation_2 = deepcopy(attestation_1)
|
||||
attestation_2.data.target_root = b'\x01'*32
|
||||
|
||||
return AttesterSlashing(
|
||||
attestation_1=convert_to_indexed(state, attestation_1),
|
||||
attestation_2=convert_to_indexed(state, attestation_2),
|
||||
)
|
||||
|
||||
|
||||
def get_valid_attestation(state, slot=None):
|
||||
if slot is None:
|
||||
slot = state.slot
|
||||
|
|
|
@ -17,6 +17,7 @@ from pyspec.phase0.spec import (
|
|||
# functions
|
||||
get_active_validator_indices,
|
||||
get_balance,
|
||||
get_beacon_proposer_index,
|
||||
get_block_root,
|
||||
get_current_epoch,
|
||||
get_domain,
|
||||
|
@ -40,6 +41,7 @@ from .helpers import (
|
|||
build_empty_block_for_next_slot,
|
||||
force_registry_change_at_next_epoch,
|
||||
get_valid_attestation,
|
||||
get_valid_attester_slashing,
|
||||
get_valid_proposer_slashing,
|
||||
privkeys,
|
||||
pubkeys,
|
||||
|
@ -140,6 +142,39 @@ def test_proposer_slashing(state):
|
|||
return state, [block], test_state
|
||||
|
||||
|
||||
def test_attester_slashing(state):
|
||||
test_state = deepcopy(state)
|
||||
attester_slashing = get_valid_attester_slashing(state)
|
||||
validator_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
|
||||
#
|
||||
# Add to state via block transition
|
||||
#
|
||||
block = build_empty_block_for_next_slot(test_state)
|
||||
block.body.attester_slashings.append(attester_slashing)
|
||||
state_transition(test_state, block)
|
||||
|
||||
assert not state.validator_registry[validator_index].initiated_exit
|
||||
assert not state.validator_registry[validator_index].slashed
|
||||
|
||||
slashed_validator = test_state.validator_registry[validator_index]
|
||||
assert not slashed_validator.initiated_exit
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
# lost whistleblower reward
|
||||
assert get_balance(test_state, validator_index) < get_balance(state, validator_index)
|
||||
|
||||
proposer_index = get_beacon_proposer_index(test_state, test_state.slot)
|
||||
# gained whistleblower reward
|
||||
assert (
|
||||
get_balance(test_state, proposer_index) >
|
||||
get_balance(state, proposer_index)
|
||||
)
|
||||
|
||||
return state, [block], test_state
|
||||
|
||||
|
||||
def test_deposit_in_block(state):
|
||||
pre_state = deepcopy(state)
|
||||
test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Ethereum 2.0 Phase 0 -- The Beacon Chain
|
||||
|
||||
**NOTICE**: This document is a work-in-progress for researchers and implementers. It reflects recent spec changes and takes precedence over the Python proof-of-concept implementation [[python-poc]](#ref-python-poc).
|
||||
**NOTICE**: This document is a work in progress for researchers and implementers. It reflects recent spec changes and takes precedence over the Python proof-of-concept implementation [[python-poc]](#ref-python-poc).
|
||||
|
||||
## Table of contents
|
||||
<!-- TOC -->
|
||||
|
@ -18,7 +18,7 @@
|
|||
- [Time parameters](#time-parameters)
|
||||
- [State list lengths](#state-list-lengths)
|
||||
- [Reward and penalty quotients](#reward-and-penalty-quotients)
|
||||
- [Max transactions per block](#max-transactions-per-block)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Signature domains](#signature-domains)
|
||||
- [Data structures](#data-structures)
|
||||
- [Misc dependencies](#misc-dependencies)
|
||||
|
@ -34,7 +34,7 @@
|
|||
- [`Validator`](#validator)
|
||||
- [`PendingAttestation`](#pendingattestation)
|
||||
- [`HistoricalBatch`](#historicalbatch)
|
||||
- [Beacon transactions](#beacon-transactions)
|
||||
- [Beacon operations](#beacon-operations)
|
||||
- [`ProposerSlashing`](#proposerslashing)
|
||||
- [`AttesterSlashing`](#attesterslashing)
|
||||
- [`Attestation`](#attestation)
|
||||
|
@ -132,7 +132,7 @@
|
|||
- [Block header](#block-header)
|
||||
- [RANDAO](#randao)
|
||||
- [Eth1 data](#eth1-data-1)
|
||||
- [Transactions](#transactions)
|
||||
- [Operations](#operations)
|
||||
- [Proposer slashings](#proposer-slashings)
|
||||
- [Attester slashings](#attester-slashings)
|
||||
- [Attestations](#attestations)
|
||||
|
@ -151,9 +151,9 @@
|
|||
|
||||
This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain.
|
||||
|
||||
At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0 the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and after a queuing process. Exit is either voluntary or done forcibly as a penalty for misbehavior.
|
||||
At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0, the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior.
|
||||
|
||||
The primary source of load on the beacon chain is "attestations". Attestations are availability votes for a shard block, and simultaneously proof of stake votes for a beacon block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication.
|
||||
The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block and proof-of-stake votes for a beacon block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication.
|
||||
|
||||
## Notation
|
||||
|
||||
|
@ -161,20 +161,20 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
|
|||
|
||||
## Terminology
|
||||
|
||||
* **Validator** <a id="dfn-validator"></a> - a registered participant in the beacon chain. You can become one by sending Ether into the Ethereum 1.0 deposit contract.
|
||||
* **Validator** <a id="dfn-validator"></a> - a registered participant in the beacon chain. You can become one by sending ether into the Ethereum 1.0 deposit contract.
|
||||
* **Active validator** <a id="dfn-active-validator"></a> - an active participant in the Ethereum 2.0 consensus invited to, among other things, propose and attest to blocks and vote for crosslinks.
|
||||
* **Committee** - a (pseudo-) randomly sampled subset of [active validators](#dfn-active-validator). When a committee is referred to collectively, as in "this committee attests to X", this is assumed to mean "some subset of that committee that contains enough [validators](#dfn-validator) that the protocol recognizes it as representing the committee".
|
||||
* **Proposer** - the [validator](#dfn-validator) that creates a beacon chain block
|
||||
* **Proposer** - the [validator](#dfn-validator) that creates a beacon chain block.
|
||||
* **Attester** - a [validator](#dfn-validator) that is part of a committee that needs to sign off on a beacon chain block while simultaneously creating a link (crosslink) to a recent shard block on a particular shard chain.
|
||||
* **Beacon chain** - the central PoS chain that is the base of the sharding system.
|
||||
* **Shard chain** - one of the chains on which user transactions take place and account data is stored.
|
||||
* **Block root** - a 32-byte Merkle root of a beacon chain block or shard chain block. Previously called "block hash".
|
||||
* **Crosslink** - a set of signatures from a committee attesting to a block in a shard chain, which can be included into the beacon chain. Crosslinks are the main means by which the beacon chain "learns about" the updated state of shard chains.
|
||||
* **Slot** - a period during which one proposer has the ability to create a beacon chain block and some attesters have the ability to make attestations
|
||||
* **Epoch** - an aligned span of slots during which all [validators](#dfn-validator) get exactly one chance to make an attestation
|
||||
* **Finalized**, **justified** - see Casper FFG finalization [[casper-ffg]](#ref-casper-ffg)
|
||||
* **Withdrawal period** - the number of slots between a [validator](#dfn-validator) exit and the [validator](#dfn-validator) balance being withdrawable
|
||||
* **Genesis time** - the Unix time of the genesis beacon chain block at slot 0
|
||||
* **Crosslink** - a set of signatures from a committee attesting to a block in a shard chain that can be included into the beacon chain. Crosslinks are the main means by which the beacon chain "learns about" the updated state of shard chains.
|
||||
* **Slot** - a period during which one proposer has the ability to create a beacon chain block and some attesters have the ability to make attestations.
|
||||
* **Epoch** - an aligned span of slots during which all [validators](#dfn-validator) get exactly one chance to make an attestation.
|
||||
* **Finalized**, **justified** - see Casper FFG finalization [[casper-ffg]](#ref-casper-ffg).
|
||||
* **Withdrawal period** - the number of slots between a [validator](#dfn-validator) exit and the [validator](#dfn-validator) balance being withdrawable.
|
||||
* **Genesis time** - the Unix time of the genesis beacon chain block at slot 0.
|
||||
|
||||
## Constants
|
||||
|
||||
|
@ -253,16 +253,15 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
|
|||
| Name | Value |
|
||||
| - | - |
|
||||
| `BASE_REWARD_QUOTIENT` | `2**5` (= 32) |
|
||||
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `ATTESTATION_INCLUSION_REWARD_QUOTIENT` | `2**3` (= 8) |
|
||||
| `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) |
|
||||
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
|
||||
| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) |
|
||||
| `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) |
|
||||
|
||||
* The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch.
|
||||
* The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`.
|
||||
|
||||
|
||||
### Max transactions per block
|
||||
### Max operations per block
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
|
@ -460,7 +459,7 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
}
|
||||
```
|
||||
|
||||
### Beacon transactions
|
||||
### Beacon operations
|
||||
|
||||
#### `ProposerSlashing`
|
||||
|
||||
|
@ -872,7 +871,7 @@ def compute_committee(validator_indices: List[ValidatorIndex],
|
|||
]
|
||||
```
|
||||
|
||||
**Note**: this definition and the next few definitions are highly inefficient as algorithms as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work.
|
||||
**Note**: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work.
|
||||
|
||||
### `get_current_epoch_committee_count`
|
||||
|
||||
|
@ -1187,6 +1186,9 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA
|
|||
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
|
||||
# ensure no duplicate indices across custody bits
|
||||
assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0
|
||||
|
||||
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
||||
return False
|
||||
|
||||
|
@ -1398,21 +1400,25 @@ def exit_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
|||
#### `slash_validator`
|
||||
|
||||
```python
|
||||
def slash_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex=None) -> None:
|
||||
"""
|
||||
Slash the validator with index ``index``.
|
||||
Slash the validator with index ``slashed_index``.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
validator = state.validator_registry[index]
|
||||
exit_validator(state, index)
|
||||
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, index)
|
||||
exit_validator(state, slashed_index)
|
||||
state.validator_registry[slashed_index].slashed = True
|
||||
state.validator_registry[slashed_index].withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH
|
||||
slashed_balance = get_effective_balance(state, slashed_index)
|
||||
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance
|
||||
|
||||
whistleblower_index = get_beacon_proposer_index(state, state.slot)
|
||||
whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
increase_balance(state, whistleblower_index, whistleblower_reward)
|
||||
decrease_balance(state, index, whistleblower_reward)
|
||||
validator.slashed = True
|
||||
validator.withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
if whistleblower_index is None:
|
||||
whistleblower_index = proposer_index
|
||||
whistleblowing_reward = slashed_balance // WHISTLEBLOWING_REWARD_QUOTIENT
|
||||
proposer_reward = whistleblowing_reward // PROPOSER_REWARD_QUOTIENT
|
||||
increase_balance(state, proposer_index, proposer_reward)
|
||||
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
|
||||
decrease_balance(state, slashed_index, whistleblowing_reward)
|
||||
```
|
||||
|
||||
#### `prepare_validator_for_withdrawal`
|
||||
|
@ -1451,7 +1457,7 @@ Every Ethereum 1.0 deposit, of size between `MIN_DEPOSIT_AMOUNT` and `MAX_DEPOSI
|
|||
|
||||
### `Eth2Genesis` log
|
||||
|
||||
When sufficiently many full deposits have been made the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined below) where:
|
||||
When a sufficient amount of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined below) where:
|
||||
|
||||
* `genesis_time` equals `time` in the `Eth2Genesis` log
|
||||
* `latest_eth1_data.deposit_root` equals `deposit_root` in the `Eth2Genesis` log
|
||||
|
@ -1582,13 +1588,13 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
|
|||
|
||||
## Beacon chain processing
|
||||
|
||||
The beacon chain is the system chain for Ethereum 2.0. The main responsibilities of the beacon chain are:
|
||||
The beacon chain is the system chain for Ethereum 2.0. The main responsibilities of the beacon chain are as follows:
|
||||
|
||||
* Store and maintain the registry of [validators](#dfn-validator)
|
||||
* Process crosslinks (see above)
|
||||
* Process its per-block consensus, as well as the finality gadget
|
||||
|
||||
Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks, and maintain a view of what is the current "canonical chain", terminating at the current "head". However, because of the beacon chain's relationship with Ethereum 1.0, and because it is a proof-of-stake chain, there are differences.
|
||||
Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks and maintain a view of what is the current "canonical chain", terminating at the current "head". However, because of the beacon chain's relationship with Ethereum 1.0, and because it is a proof-of-stake chain, there are differences.
|
||||
|
||||
For a beacon chain block, `block`, to be processed by a node, the following conditions must be met:
|
||||
|
||||
|
@ -1598,7 +1604,7 @@ For a beacon chain block, `block`, to be processed by a node, the following cond
|
|||
|
||||
If these conditions are not met, the client should delay processing the beacon block until the conditions are all satisfied.
|
||||
|
||||
Beacon block production is significantly different because of the proof of stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block, and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (i.e. within `SECONDS_PER_SLOT` seconds) synchronized with the other nodes.
|
||||
Beacon block production is significantly different because of the proof-of-stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (i.e. within `SECONDS_PER_SLOT` seconds) synchronized with the other nodes.
|
||||
|
||||
### Beacon chain fork choice rule
|
||||
|
||||
|
@ -1660,7 +1666,7 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock)
|
|||
|
||||
## Beacon chain state transition function
|
||||
|
||||
We now define the state transition function. At a high level the state transition is made up of four parts:
|
||||
We now define the state transition function. At a high level, the state transition is made up of four parts:
|
||||
|
||||
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.
|
||||
|
@ -1668,7 +1674,7 @@ We now define the state transition function. At a high level the state transitio
|
|||
4. The per-block transitions, which happens at every block.
|
||||
|
||||
Transition section notes:
|
||||
* The state caching, caches the state root of the previous slot.
|
||||
* 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`.
|
||||
|
@ -1901,7 +1907,7 @@ def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_sin
|
|||
return get_base_reward(state, index) + extra_penalty
|
||||
```
|
||||
|
||||
Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow.
|
||||
Note: When applying penalties in the following balance recalculations, implementers should make sure the `uint64` does not underflow.
|
||||
|
||||
##### Justification and finalization
|
||||
|
||||
|
@ -1951,7 +1957,7 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[
|
|||
# Proposer bonus
|
||||
if index in get_attesting_indices(state, state.previous_epoch_attestations):
|
||||
proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index))
|
||||
rewards[proposer_index] += base_reward // ATTESTATION_INCLUSION_REWARD_QUOTIENT
|
||||
rewards[proposer_index] += base_reward // PROPOSER_REWARD_QUOTIENT
|
||||
# Take away max rewards if we're not finalizing
|
||||
if epochs_since_finality > 4:
|
||||
penalties[index] += base_reward * 4
|
||||
|
@ -2230,7 +2236,7 @@ def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None:
|
|||
state.eth1_data_votes.append(Eth1DataVote(eth1_data=block.body.eth1_data, vote_count=1))
|
||||
```
|
||||
|
||||
#### Transactions
|
||||
#### Operations
|
||||
|
||||
##### Proposer slashings
|
||||
|
||||
|
@ -2242,7 +2248,7 @@ For each `proposer_slashing` in `block.body.proposer_slashings`, run the followi
|
|||
def process_proposer_slashing(state: BeaconState,
|
||||
proposer_slashing: ProposerSlashing) -> None:
|
||||
"""
|
||||
Process ``ProposerSlashing`` transaction.
|
||||
Process ``ProposerSlashing`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
proposer = state.validator_registry[proposer_slashing.proposer_index]
|
||||
|
@ -2273,7 +2279,7 @@ For each `attester_slashing` in `block.body.attester_slashings`, run the followi
|
|||
def process_attester_slashing(state: BeaconState,
|
||||
attester_slashing: AttesterSlashing) -> None:
|
||||
"""
|
||||
Process ``AttesterSlashing`` transaction.
|
||||
Process ``AttesterSlashing`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
attestation1 = attester_slashing.attestation_1
|
||||
|
@ -2287,10 +2293,12 @@ def process_attester_slashing(state: BeaconState,
|
|||
|
||||
assert verify_indexed_attestation(state, attestation1)
|
||||
assert verify_indexed_attestation(state, attestation2)
|
||||
attesting_indices_1 = attestation1.custody_bit_0_indices + attestation1.custody_bit_1_indices
|
||||
attesting_indices_2 = attestation2.custody_bit_0_indices + attestation2.custody_bit_1_indices
|
||||
slashable_indices = [
|
||||
index for index in attestation1.validator_indices
|
||||
index for index in attesting_indices_1
|
||||
if (
|
||||
index in attestation2.validator_indices and
|
||||
index in attesting_indices_2 and
|
||||
is_slashable_validator(state.validator_registry[index], get_current_epoch(state))
|
||||
)
|
||||
]
|
||||
|
@ -2308,7 +2316,7 @@ For each `attestation` in `block.body.attestations`, run the following function:
|
|||
```python
|
||||
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||
"""
|
||||
Process ``Attestation`` transaction.
|
||||
Process ``Attestation`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
assert max(GENESIS_SLOT, state.slot - SLOTS_PER_EPOCH) <= attestation.data.slot
|
||||
|
@ -2363,7 +2371,7 @@ 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.
|
||||
Process ``VoluntaryExit`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
validator = state.validator_registry[exit.validator_index]
|
||||
|
@ -2399,7 +2407,7 @@ For each `transfer` in `block.body.transfers`, run the following function:
|
|||
```python
|
||||
def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
||||
"""
|
||||
Process ``Transfer`` transaction.
|
||||
Process ``Transfer`` operation.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Verify the amount and fee aren't individually too big (for anti-overflow purposes)
|
||||
|
@ -2446,7 +2454,7 @@ def verify_block_state_root(state: BeaconState, block: BeaconBlock) -> None:
|
|||
|
||||
# References
|
||||
|
||||
This section is divided into Normative and Informative references. Normative references are those that must be read in order to implement this specification, while Informative references are merely that, information. An example of the former might be the details of a required consensus algorithm, and an example of the latter might be a pointer to research that demonstrates why a particular consensus algorithm might be better suited for inclusion in the standard than another.
|
||||
This section is divided into Normative and Informative references. Normative references are those that must be read in order to implement this specification, while Informative references are merely helpful information. An example of the former might be the details of a required consensus algorithm, and an example of the latter might be a pointer to research that demonstrates why a particular consensus algorithm might be better suited for inclusion in the standard than another.
|
||||
|
||||
## Normative
|
||||
|
||||
|
|
|
@ -0,0 +1,499 @@
|
|||
# Ethereum 2.0 Phase 1 -- Custody Game
|
||||
|
||||
**NOTICE**: This spec is a work-in-progress for researchers and implementers.
|
||||
|
||||
## Table of contents
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [Ethereum 2.0 Phase 1 -- Custody Game](#ethereum-20-phase-1----custody-game)
|
||||
- [Table of contents](#table-of-contents)
|
||||
- [Introduction](#introduction)
|
||||
- [Terminology](#terminology)
|
||||
- [Constants](#constants)
|
||||
- [Misc](#misc)
|
||||
- [Time parameters](#time-parameters)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Signature domains](#signature-domains)
|
||||
- [Data structures](#data-structures)
|
||||
- [Custody objects](#custody-objects)
|
||||
- [`CustodyChunkChallenge`](#custodychunkchallenge)
|
||||
- [`CustodyBitChallenge`](#custodybitchallenge)
|
||||
- [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord)
|
||||
- [`CustodyBitChallengeRecord`](#custodybitchallengerecord)
|
||||
- [`CustodyResponse`](#custodyresponse)
|
||||
- [`CustodyKeyReveal`](#custodykeyreveal)
|
||||
- [Phase 0 container updates](#phase-0-container-updates)
|
||||
- [`Validator`](#validator)
|
||||
- [`BeaconState`](#beaconstate)
|
||||
- [`BeaconBlockBody`](#beaconblockbody)
|
||||
- [Helpers](#helpers)
|
||||
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
|
||||
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
|
||||
- [`epoch_to_custody_period`](#epoch_to_custody_period)
|
||||
- [`verify_custody_key`](#verify_custody_key)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Operations](#operations)
|
||||
- [Custody reveals](#custody-reveals)
|
||||
- [Chunk challenges](#chunk-challenges)
|
||||
- [Bit challenges](#bit-challenges)
|
||||
- [Custody responses](#custody-responses)
|
||||
- [Per-epoch processing](#per-epoch-processing)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [phase 0](0_beacon-chain.md) specification.
|
||||
|
||||
## Terminology
|
||||
|
||||
* **Custody game**:
|
||||
* **Custody period**:
|
||||
* **Custody chunk**:
|
||||
* **Custody chunk bit**:
|
||||
* **Custody chunk challenge**:
|
||||
* **Custody bit**:
|
||||
* **Custody bit challenge**:
|
||||
* **Custody key**:
|
||||
* **Custody key reveal**:
|
||||
* **Custody key mask**:
|
||||
* **Custody response**:
|
||||
* **Custody response deadline**:
|
||||
|
||||
## Constants
|
||||
|
||||
### Misc
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) |
|
||||
| `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) |
|
||||
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
|
||||
|
||||
### Time parameters
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
|
||||
| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
|
||||
|
||||
### Max operations per block
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `MAX_CUSTODY_KEY_REVEALS` | `2**4` (= 16) |
|
||||
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) |
|
||||
| `MAX_CUSTODY_BIT_CHALLENGES` | `2**2` (= 4) |
|
||||
| `MAX_CUSTODY_RESPONSES` | `2**5` (= 32) |
|
||||
|
||||
### Signature domains
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_CUSTODY_KEY_REVEAL` | `6` |
|
||||
| `DOMAIN_CUSTODY_BIT_CHALLENGE` | `7` |
|
||||
|
||||
## Data structures
|
||||
|
||||
### Custody objects
|
||||
|
||||
#### `CustodyChunkChallenge`
|
||||
|
||||
```python
|
||||
{
|
||||
'responder_index': ValidatorIndex,
|
||||
'attestation': Attestation,
|
||||
'chunk_index': 'uint64',
|
||||
}
|
||||
```
|
||||
|
||||
#### `CustodyBitChallenge`
|
||||
|
||||
```python
|
||||
{
|
||||
'responder_index': ValidatorIndex,
|
||||
'attestation': Attestation,
|
||||
'challenger_index': ValidatorIndex,
|
||||
'responder_key': BLSSignature,
|
||||
'chunk_bits': Bitfield,
|
||||
'signature': BLSSignature,
|
||||
}
|
||||
```
|
||||
|
||||
#### `CustodyChunkChallengeRecord`
|
||||
|
||||
```python
|
||||
{
|
||||
'challenge_index': 'uint64',
|
||||
'challenger_index': ValidatorIndex,
|
||||
'responder_index': ValidatorIndex,
|
||||
'deadline': Epoch,
|
||||
'crosslink_data_root': Hash,
|
||||
'depth': 'uint64',
|
||||
'chunk_index': 'uint64',
|
||||
}
|
||||
```
|
||||
|
||||
#### `CustodyBitChallengeRecord`
|
||||
|
||||
```python
|
||||
{
|
||||
'challenge_index': 'uint64',
|
||||
'challenger_index': ValidatorIndex,
|
||||
'responder_index': ValidatorIndex,
|
||||
'deadline': Epoch,
|
||||
'crosslink_data_root': Hash,
|
||||
'chunk_bits': Bitfield,
|
||||
'responder_key': BLSSignature,
|
||||
}
|
||||
```
|
||||
|
||||
#### `CustodyResponse`
|
||||
|
||||
```python
|
||||
{
|
||||
'challenge_index': 'uint64',
|
||||
'chunk_index': 'uint64',
|
||||
'chunk': ['byte', BYTES_PER_CUSTODY_CHUNK],
|
||||
'branch': [Hash],
|
||||
}
|
||||
```
|
||||
|
||||
#### `CustodyKeyReveal`
|
||||
|
||||
```python
|
||||
{
|
||||
'revealer_index': ValidatorIndex,
|
||||
'period': 'uint64',
|
||||
'key': BLSSignature,
|
||||
'masker_index': ValidatorIndex,
|
||||
'mask': Hash,
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 0 container updates
|
||||
|
||||
Add the following fields to the end of the specified container objects. Fields with underlying type `uint64` are initialized to `0` and list fields are initialized to `[]`.
|
||||
|
||||
#### `Validator`
|
||||
|
||||
```python
|
||||
'custody_reveal_index': 'uint64',
|
||||
'max_reveal_lateness': 'uint64',
|
||||
```
|
||||
|
||||
#### `BeaconState`
|
||||
|
||||
```python
|
||||
'custody_chunk_challenge_records': [CustodyChunkChallengeRecord],
|
||||
'custody_bit_challenge_records': [CustodyBitChallengeRecord],
|
||||
'custody_challenge_index': 'uint64',
|
||||
```
|
||||
|
||||
#### `BeaconBlockBody`
|
||||
|
||||
```python
|
||||
'custody_key_reveals': [CustodyKeyReveal],
|
||||
'custody_chunk_challenges': [CustodyChunkChallenge],
|
||||
'custody_bit_challenges': [CustodyBitChallenge],
|
||||
'custody_responses': [CustodyResponse],
|
||||
```
|
||||
|
||||
## Helpers
|
||||
|
||||
### `get_crosslink_chunk_count`
|
||||
|
||||
```python
|
||||
def get_custody_chunk_count(attestation: Attestation) -> int:
|
||||
crosslink_start_epoch = attestation.data.latest_crosslink.epoch
|
||||
crosslink_end_epoch = slot_to_epoch(attestation.data.slot)
|
||||
crosslink_crosslink_length = min(MAX_CROSSLINK_EPOCHS, end_epoch - start_epoch)
|
||||
chunks_per_epoch = 2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK
|
||||
return crosslink_crosslink_length * chunks_per_epoch
|
||||
```
|
||||
|
||||
### `get_custody_chunk_bit`
|
||||
|
||||
```python
|
||||
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
|
||||
# TODO: Replace with something MPC-friendly, e.g. the Legendre symbol
|
||||
return get_bitfield_bit(hash(challenge.responder_key + chunk), 0)
|
||||
```
|
||||
|
||||
### `epoch_to_custody_period`
|
||||
|
||||
```python
|
||||
def epoch_to_custody_period(epoch: Epoch) -> int:
|
||||
return epoch // EPOCHS_PER_CUSTODY_PERIOD
|
||||
```
|
||||
|
||||
### `verify_custody_key`
|
||||
|
||||
```python
|
||||
def verify_custody_key(state: BeaconState, reveal: CustodyKeyReveal) -> bool:
|
||||
# Case 1: non-masked non-punitive non-early reveal
|
||||
pubkeys = [state.validator_registry[reveal.revealer_index].pubkey]
|
||||
message_hashes = [hash_tree_root(reveal.period)]
|
||||
|
||||
# Case 2: masked punitive early reveal
|
||||
# Masking prevents proposer stealing the whistleblower reward
|
||||
# Secure under the aggregate extraction infeasibility assumption
|
||||
# See pages 11-12 of https://crypto.stanford.edu/~dabo/pubs/papers/aggreg.pdf
|
||||
if reveal.mask != ZERO_HASH:
|
||||
pubkeys.append(state.validator_registry[reveal.masker_index].pubkey)
|
||||
message_hashes.append(reveal.mask)
|
||||
|
||||
return bls_verify_multiple(
|
||||
pubkeys=pubkeys,
|
||||
message_hashes=message_hashes,
|
||||
signature=reveal.key,
|
||||
domain=get_domain(
|
||||
fork=state.fork,
|
||||
epoch=reveal.period * EPOCHS_PER_CUSTODY_PERIOD,
|
||||
domain_type=DOMAIN_CUSTODY_KEY_REVEAL,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## Per-block processing
|
||||
|
||||
### Operations
|
||||
|
||||
Add the following operations to the per-block processing, in order the given below and after all other operations in phase 0.
|
||||
|
||||
#### Custody reveals
|
||||
|
||||
Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`.
|
||||
|
||||
For each `reveal` in `block.body.custody_key_reveals`, run the following function:
|
||||
|
||||
```python
|
||||
def process_custody_reveal(state: BeaconState,
|
||||
reveal: CustodyKeyReveal) -> None:
|
||||
assert verify_custody_key(state, reveal)
|
||||
revealer = state.validator_registry[reveal.revealer_index]
|
||||
current_custody_period = epoch_to_custody_period(get_current_epoch(state))
|
||||
|
||||
# Case 1: non-masked non-punitive non-early reveal
|
||||
if reveal.mask == ZERO_HASH:
|
||||
assert reveal.period == epoch_to_custody_period(revealer.activation_epoch) + revealer.custody_reveal_index
|
||||
# Revealer is active or exited
|
||||
assert is_active_validator(revealer, get_current_epoch(state)) or revealer.exit_epoch > get_current_epoch(state)
|
||||
revealer.custody_reveal_index += 1
|
||||
revealer.max_reveal_lateness = max(revealer.max_reveal_lateness, current_custody_period - reveal.period)
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
increase_balance(state, proposer_index, base_reward(state, index) // MINOR_REWARD_QUOTIENT)
|
||||
|
||||
# Case 2: masked punitive early reveal
|
||||
else:
|
||||
assert reveal.period > current_custody_period
|
||||
assert revealer.slashed is False
|
||||
slash_validator(state, reveal.revealer_index, reveal.masker_index)
|
||||
```
|
||||
|
||||
#### Chunk challenges
|
||||
|
||||
Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`.
|
||||
|
||||
For each `challenge` in `block.body.custody_chunk_challenges`, run the following function:
|
||||
|
||||
```python
|
||||
def process_chunk_challenge(state: BeaconState,
|
||||
challenge: CustodyChunkChallenge) -> None:
|
||||
# Verify the attestation
|
||||
assert verify_standalone_attestation(state, convert_to_standalone(state, challenge.attestation))
|
||||
# Verify it is not too late to challenge
|
||||
assert slot_to_epoch(challenge.attestation.data.slot) >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
|
||||
responder = state.validator_registry[challenge.responder_index]
|
||||
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
|
||||
# Verify the responder participated in the attestation
|
||||
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
assert challenge.responder_index in attesters
|
||||
# Verify the challenge is not a duplicate
|
||||
for record in state.custody_chunk_challenge_records:
|
||||
assert (
|
||||
record.crosslink_data_root != challenge.attestation.data.crosslink_data_root or
|
||||
record.chunk_index != challenge.chunk_index
|
||||
)
|
||||
# Verify depth
|
||||
depth = math.log2(next_power_of_two(get_custody_chunk_count(challenge.attestation)))
|
||||
assert challenge.chunk_index < 2**depth
|
||||
# Add new chunk challenge record
|
||||
state.custody_chunk_challenge_records.append(CustodyChunkChallengeRecord(
|
||||
challenge_index=state.custody_challenge_index,
|
||||
challenger_index=get_beacon_proposer_index(state, state.slot),
|
||||
responder_index=challenge.responder_index
|
||||
deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE,
|
||||
crosslink_data_root=challenge.attestation.data.crosslink_data_root,
|
||||
depth=depth,
|
||||
chunk_index=challenge.chunk_index,
|
||||
))
|
||||
state.custody_challenge_index += 1
|
||||
# Postpone responder withdrawability
|
||||
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
|
||||
```
|
||||
|
||||
#### Bit challenges
|
||||
|
||||
Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGES`.
|
||||
|
||||
For each `challenge` in `block.body.custody_bit_challenges`, run the following function:
|
||||
|
||||
```python
|
||||
def process_bit_challenge(state: BeaconState,
|
||||
challenge: CustodyBitChallenge) -> None:
|
||||
# Verify challenge signature
|
||||
challenger = state.validator_registry[challenge.challenger_index]
|
||||
assert bls_verify(
|
||||
pubkey=challenger.pubkey,
|
||||
message_hash=signed_root(challenge),
|
||||
signature=challenge.signature,
|
||||
domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_BIT_CHALLENGE),
|
||||
)
|
||||
# Verify the challenger is not slashed
|
||||
assert challenger.slashed is False
|
||||
# Verify the attestation
|
||||
assert verify_standalone_attestation(state, convert_to_standalone(state, challenge.attestation))
|
||||
# Verify the attestation is eligible for challenging
|
||||
responder = state.validator_registry[challenge.responder_index]
|
||||
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
|
||||
assert min_challengeable_epoch <= slot_to_epoch(challenge.attestation.data.slot)
|
||||
# Verify the responder participated in the attestation
|
||||
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
assert challenge.responder_index in attesters
|
||||
# A validator can be the challenger or responder for at most one challenge at a time
|
||||
for record in state.custody_bit_challenge_records:
|
||||
assert record.challenger_index != challenge.challenger_index
|
||||
assert record.responder_index != challenge.responder_index
|
||||
# Verify the responder key
|
||||
assert verify_custody_key(state, CustodyKeyReveal(
|
||||
revealer_index=challenge.responder_index,
|
||||
period=epoch_to_custody_period(slot_to_epoch(attestation.data.slot)),
|
||||
key=challenge.responder_key,
|
||||
masker_index=0,
|
||||
mask=ZERO_HASH,
|
||||
))
|
||||
# Verify the chunk count
|
||||
chunk_count = get_custody_chunk_count(challenge.attestation)
|
||||
assert verify_bitfield(challenge.chunk_bits, chunk_count)
|
||||
# Verify the xor of the chunk bits does not equal the custody bit
|
||||
chunk_bits_xor = 0b0
|
||||
for i in range(chunk_count):
|
||||
chunk_bits_xor ^ get_bitfield_bit(challenge.chunk_bits, i)
|
||||
custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(responder_index))
|
||||
assert custody_bit != chunk_bits_xor
|
||||
# Add new bit challenge record
|
||||
state.custody_bit_challenge_records.append(CustodyBitChallengeRecord(
|
||||
challenge_index=state.custody_challenge_index,
|
||||
challenger_index=challenge.challenger_index,
|
||||
responder_index=challenge.responder_index,
|
||||
deadline=get_current_epoch(state) + CUSTODY_RESPONSE_DEADLINE
|
||||
crosslink_data_root=challenge.attestation.crosslink_data_root,
|
||||
chunk_bits=challenge.chunk_bits,
|
||||
responder_key=challenge.responder_key,
|
||||
))
|
||||
state.custody_challenge_index += 1
|
||||
# Postpone responder withdrawability
|
||||
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
|
||||
```
|
||||
|
||||
#### Custody responses
|
||||
|
||||
Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`.
|
||||
|
||||
For each `response` in `block.body.custody_responses`, run the following function:
|
||||
|
||||
```python
|
||||
def process_custody_response(state: BeaconState,
|
||||
response: CustodyResponse) -> None:
|
||||
chunk_challenge = next(record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index, None)
|
||||
if chunk_challenge is not None:
|
||||
return process_chunk_challenge_response(state, response, chunk_challenge)
|
||||
|
||||
bit_challenge = next(record for record in state.custody_bit_challenge_records if record.challenge_index == response.challenge_index, None)
|
||||
if bit_challenge is not None:
|
||||
return process_bit_challenge_response(state, response, bit_challenge)
|
||||
|
||||
assert False
|
||||
```
|
||||
|
||||
```python
|
||||
def process_chunk_challenge_response(state: BeaconState,
|
||||
response: CustodyResponse,
|
||||
challenge: CustodyChunkChallengeRecord) -> None:
|
||||
# Verify chunk index
|
||||
assert response.chunk_index == challenge.chunk_index
|
||||
# Verify the chunk matches the crosslink data root
|
||||
assert verify_merkle_branch(
|
||||
leaf=hash_tree_root(response.chunk),
|
||||
branch=response.branch,
|
||||
depth=challenge.depth,
|
||||
index=response.chunk_index,
|
||||
root=challenge.crosslink_data_root,
|
||||
)
|
||||
# Clear the challenge
|
||||
state.custody_chunk_challenge_records.remove(challenge)
|
||||
# Reward the proposer
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
increase_balance(state, proposer_index, base_reward(state, index) // MINOR_REWARD_QUOTIENT)
|
||||
```
|
||||
|
||||
```python
|
||||
def process_bit_challenge_response(state: BeaconState,
|
||||
response: CustodyResponse,
|
||||
challenge: CustodyBitChallengeRecord) -> None:
|
||||
# Verify chunk index
|
||||
assert response.chunk_index < len(challenge.chunk_bits)
|
||||
# Verify the chunk matches the crosslink data root
|
||||
assert verify_merkle_branch(
|
||||
leaf=hash_tree_root(response.chunk),
|
||||
branch=response.branch,
|
||||
depth=math.log2(next_power_of_two(len(challenge.chunk_bits))),
|
||||
index=response.chunk_index,
|
||||
root=challenge.crosslink_data_root,
|
||||
)
|
||||
# Verify the chunk bit does not match the challenge chunk bit
|
||||
assert get_custody_chunk_bit(challenge.responder_key, response.chunk) != get_bitfield_bit(challenge.chunk_bits, response.chunk_index)
|
||||
# Clear the challenge
|
||||
state.custody_bit_challenge_records.remove(challenge)
|
||||
# Slash challenger
|
||||
slash_validator(state, challenge.challenger_index, challenge.responder_index)
|
||||
```
|
||||
|
||||
## Per-epoch processing
|
||||
|
||||
Run `process_challenge_deadlines(state)` immediately after `process_ejections(state)`:
|
||||
|
||||
```python
|
||||
def process_challenge_deadlines(state: BeaconState) -> None:
|
||||
for challenge in state.custody_chunk_challenge_records:
|
||||
if get_current_epoch(state) > challenge.deadline:
|
||||
slash_validator(state, challenge.responder_index, challenge.challenger_index)
|
||||
state.custody_chunk_challenge_records.remove(challenge)
|
||||
|
||||
for challenge in state.custody_bit_challenge_records:
|
||||
if get_current_epoch(state) > challenge.deadline:
|
||||
slash_validator(state, challenge.responder_index, challenge.challenger_index)
|
||||
state.custody_bit_challenge_records.remove(challenge)
|
||||
```
|
||||
|
||||
In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope):
|
||||
|
||||
```python
|
||||
def eligible(index):
|
||||
validator = state.validator_registry[index]
|
||||
# Cannot exit if there are still open chunk challenges
|
||||
if len([record for record in state.custody_chunk_challenge_records if record.responder_index == index]) > 0:
|
||||
return False
|
||||
# Cannot exit if you have not revealed all of your custody keys
|
||||
elif epoch_to_custody_period(revealer.activation_epoch) + validator.custody_reveal_index <= epoch_to_custody_period(validator.exit_epoch):
|
||||
return False
|
||||
# Cannot exit if you already have
|
||||
elif validator.withdrawable_epoch < FAR_FUTURE_EPOCH:
|
||||
return False
|
||||
# Return minimum time
|
||||
else:
|
||||
return current_epoch >= validator.exit_epoch + MIN_VALIDATOR_WITHDRAWAL_EPOCHS
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -20,31 +20,31 @@ def expected_deposit_count(state: BeaconState) -> int:
|
|||
)
|
||||
|
||||
|
||||
def process_transaction_type(state: BeaconState,
|
||||
transactions: List[Any],
|
||||
max_transactions: int,
|
||||
def process_operation_type(state: BeaconState,
|
||||
operations: List[Any],
|
||||
max_operations: int,
|
||||
tx_fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
assert len(transactions) <= max_transactions
|
||||
for transaction in transactions:
|
||||
tx_fn(state, transaction)
|
||||
assert len(operations) <= max_operations
|
||||
for operation in operations:
|
||||
tx_fn(state, operation)
|
||||
|
||||
|
||||
def process_transactions(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_transaction_type(
|
||||
def process_operations(state: BeaconState, block: BeaconBlock) -> None:
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.proposer_slashings,
|
||||
spec.MAX_PROPOSER_SLASHINGS,
|
||||
spec.process_proposer_slashing,
|
||||
)
|
||||
|
||||
process_transaction_type(
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.attester_slashings,
|
||||
spec.MAX_ATTESTER_SLASHINGS,
|
||||
spec.process_attester_slashing,
|
||||
)
|
||||
|
||||
process_transaction_type(
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.attestations,
|
||||
spec.MAX_ATTESTATIONS,
|
||||
|
@ -52,14 +52,14 @@ def process_transactions(state: BeaconState, block: BeaconBlock) -> None:
|
|||
)
|
||||
|
||||
assert len(block.body.deposits) == expected_deposit_count(state)
|
||||
process_transaction_type(
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.deposits,
|
||||
spec.MAX_DEPOSITS,
|
||||
spec.process_deposit,
|
||||
)
|
||||
|
||||
process_transaction_type(
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.voluntary_exits,
|
||||
spec.MAX_VOLUNTARY_EXITS,
|
||||
|
@ -67,7 +67,7 @@ def process_transactions(state: BeaconState, block: BeaconBlock) -> None:
|
|||
)
|
||||
|
||||
assert len(block.body.transfers) == len(set(block.body.transfers))
|
||||
process_transaction_type(
|
||||
process_operation_type(
|
||||
state,
|
||||
block.body.transfers,
|
||||
spec.MAX_TRANSFERS,
|
||||
|
@ -82,7 +82,7 @@ def process_block(state: BeaconState,
|
|||
spec.process_randao(state, block)
|
||||
spec.process_eth1_data(state, block)
|
||||
|
||||
process_transactions(state, block)
|
||||
process_operations(state, block)
|
||||
if verify_state_root:
|
||||
spec.verify_block_state_root(state, block)
|
||||
|
||||
|
|
Loading…
Reference in New Issue