Merge branch 'dev' into vbuterin-patch-13
This commit is contained in:
commit
9c4e034bed
|
@ -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 -->
|
||||
|
@ -28,7 +28,7 @@
|
|||
- [`Eth1DataVote`](#eth1datavote)
|
||||
- [`AttestationData`](#attestationdata)
|
||||
- [`AttestationDataAndCustodyBit`](#attestationdataandcustodybit)
|
||||
- [`SlashableAttestation`](#slashableattestation)
|
||||
- [`IndexedAttestation`](#indexedattestation)
|
||||
- [`DepositData`](#depositdata)
|
||||
- [`BeaconBlockHeader`](#beaconblockheader)
|
||||
- [`Validator`](#validator)
|
||||
|
@ -77,6 +77,7 @@
|
|||
- [`generate_seed`](#generate_seed)
|
||||
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
||||
- [`verify_merkle_branch`](#verify_merkle_branch)
|
||||
- [`get_crosslink_committee_for_attestation`](#get_crosslink_committee_for_attestation)
|
||||
- [`get_attestation_participants`](#get_attestation_participants)
|
||||
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
|
||||
- [`bytes_to_int`](#bytes_to_int)
|
||||
|
@ -86,7 +87,8 @@
|
|||
- [`get_domain`](#get_domain)
|
||||
- [`get_bitfield_bit`](#get_bitfield_bit)
|
||||
- [`verify_bitfield`](#verify_bitfield)
|
||||
- [`verify_slashable_attestation`](#verify_slashable_attestation)
|
||||
- [`convert_to_indexed`](#convert_to_indexed)
|
||||
- [`verify_indexed_attestation`](#verify_indexed_attestation)
|
||||
- [`is_double_vote`](#is_double_vote)
|
||||
- [`is_surround_vote`](#is_surround_vote)
|
||||
- [`integer_squareroot`](#integer_squareroot)
|
||||
|
@ -147,9 +149,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
|
||||
|
||||
|
@ -157,20 +159,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
|
||||
|
||||
|
@ -180,7 +182,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code.
|
|||
| - | - |
|
||||
| `SHARD_COUNT` | `2**10` (= 1,024) |
|
||||
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
|
||||
| `MAX_SLASHABLE_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
|
||||
| `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
|
||||
| `MAX_EXIT_DEQUEUES_PER_EPOCH` | `2**2` (= 4) |
|
||||
| `SHUFFLE_ROUND_COUNT` | 90 |
|
||||
|
||||
|
@ -248,8 +250,8 @@ 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) |
|
||||
|
||||
|
@ -366,16 +368,15 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
}
|
||||
```
|
||||
|
||||
#### `SlashableAttestation`
|
||||
#### `IndexedAttestation`
|
||||
|
||||
```python
|
||||
{
|
||||
# Validator indices
|
||||
'validator_indices': ['uint64'],
|
||||
'custody_bit_0_indices': ['uint64'],
|
||||
'custody_bit_1_indices': ['uint64'],
|
||||
# Attestation data
|
||||
'data': AttestationData,
|
||||
# Custody bitfield
|
||||
'custody_bitfield': 'bytes',
|
||||
# Aggregate signature
|
||||
'aggregate_signature': 'bytes96',
|
||||
}
|
||||
|
@ -475,10 +476,10 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
|
||||
```python
|
||||
{
|
||||
# First slashable attestation
|
||||
'slashable_attestation_1': SlashableAttestation,
|
||||
# Second slashable attestation
|
||||
'slashable_attestation_2': SlashableAttestation,
|
||||
# First attestation
|
||||
'attestation_1': IndexedAttestation,
|
||||
# Second attestation
|
||||
'attestation_2': IndexedAttestation,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -872,7 +873,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`
|
||||
|
||||
|
@ -1037,6 +1038,20 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index:
|
|||
return value == root
|
||||
```
|
||||
|
||||
### `get_crosslink_committee_for_attestation`
|
||||
|
||||
```python
|
||||
def get_crosslink_committee_for_attestation(state: BeaconState,
|
||||
attestation_data: AttestationData) -> List[ValidatorIndex]:
|
||||
# Find the committee in the list with the desired shard
|
||||
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
|
||||
|
||||
assert attestation_data.shard in [shard for _, shard in crosslink_committees]
|
||||
crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]
|
||||
|
||||
return crosslink_committee
|
||||
```
|
||||
|
||||
### `get_attestation_participants`
|
||||
|
||||
```python
|
||||
|
@ -1044,13 +1059,9 @@ def get_attestation_participants(state: BeaconState,
|
|||
attestation_data: AttestationData,
|
||||
bitfield: bytes) -> List[ValidatorIndex]:
|
||||
"""
|
||||
Return the participant indices corresponding to ``attestation_data`` and ``bitfield``.
|
||||
Return the sorted participant indices corresponding to ``attestation_data`` and ``bitfield``.
|
||||
"""
|
||||
# Find the committee in the list with the desired shard
|
||||
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
|
||||
|
||||
assert attestation_data.shard in [shard for _, shard in crosslink_committees]
|
||||
crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]
|
||||
crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data)
|
||||
|
||||
assert verify_bitfield(bitfield, len(crosslink_committee))
|
||||
|
||||
|
@ -1060,7 +1071,7 @@ def get_attestation_participants(state: BeaconState,
|
|||
aggregation_bit = get_bitfield_bit(bitfield, i)
|
||||
if aggregation_bit == 0b1:
|
||||
participants.append(validator_index)
|
||||
return participants
|
||||
return sorted(participants)
|
||||
```
|
||||
|
||||
### `int_to_bytes1`, `int_to_bytes2`, ...
|
||||
|
@ -1148,33 +1159,47 @@ def verify_bitfield(bitfield: bytes, committee_size: int) -> bool:
|
|||
return True
|
||||
```
|
||||
|
||||
### `verify_slashable_attestation`
|
||||
### `convert_to_indexed`
|
||||
|
||||
```python
|
||||
def verify_slashable_attestation(state: BeaconState, slashable_attestation: SlashableAttestation) -> bool:
|
||||
def convert_to_indexed(state: BeaconState, attestation: Attestation):
|
||||
"""
|
||||
Verify validity of ``slashable_attestation`` fields.
|
||||
Convert an attestation to (almost) indexed-verifiable form
|
||||
"""
|
||||
if slashable_attestation.custody_bitfield != b'\x00' * len(slashable_attestation.custody_bitfield): # [TO BE REMOVED IN PHASE 1]
|
||||
attesting_indices = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
custody_bit_1_indices = get_attestation_participants(state, attestation.data, attestation.custody_bitfield)
|
||||
custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices]
|
||||
|
||||
return IndexedAttestation(
|
||||
custody_bit_0_indices=custody_bit_0_indices,
|
||||
custody_bit_1_indices=custody_bit_1_indices,
|
||||
data=attestation.data,
|
||||
aggregate_signature=attestation.aggregate_signature
|
||||
)
|
||||
```
|
||||
|
||||
### `verify_indexed_attestation`
|
||||
|
||||
```python
|
||||
def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
|
||||
"""
|
||||
Verify validity of ``indexed_attestation`` fields.
|
||||
"""
|
||||
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
|
||||
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
||||
return False
|
||||
|
||||
if not (1 <= len(slashable_attestation.validator_indices) <= MAX_SLASHABLE_ATTESTATION_PARTICIPANTS):
|
||||
total_attesting_indices = len(custody_bit_0_indices + custody_bit_1_indices)
|
||||
if not (1 <= total_attesting_indices <= MAX_ATTESTATION_PARTICIPANTS):
|
||||
return False
|
||||
|
||||
for i in range(len(slashable_attestation.validator_indices) - 1):
|
||||
if slashable_attestation.validator_indices[i] >= slashable_attestation.validator_indices[i + 1]:
|
||||
return False
|
||||
|
||||
if not verify_bitfield(slashable_attestation.custody_bitfield, len(slashable_attestation.validator_indices)):
|
||||
if custody_bit_0_indices != sorted(custody_bit_0_indices):
|
||||
return False
|
||||
|
||||
custody_bit_0_indices = []
|
||||
custody_bit_1_indices = []
|
||||
for i, validator_index in enumerate(slashable_attestation.validator_indices):
|
||||
if get_bitfield_bit(slashable_attestation.custody_bitfield, i) == 0b0:
|
||||
custody_bit_0_indices.append(validator_index)
|
||||
else:
|
||||
custody_bit_1_indices.append(validator_index)
|
||||
if custody_bit_1_indices != sorted(custody_bit_1_indices):
|
||||
return False
|
||||
|
||||
return bls_verify_multiple(
|
||||
pubkeys=[
|
||||
|
@ -1182,11 +1207,11 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas
|
|||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_indices]),
|
||||
],
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=slashable_attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=slashable_attestation.data, custody_bit=0b1)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
signature=slashable_attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(slashable_attestation.data.slot), DOMAIN_ATTESTATION),
|
||||
signature=indexed_attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(indexed_attestation.data.slot), DOMAIN_ATTESTATION),
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -1372,21 +1397,25 @@ def initiate_validator_exit(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]
|
||||
initiate_validator_exit(state, index)
|
||||
state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += get_effective_balance(state, 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)
|
||||
```
|
||||
|
||||
## Ethereum 1.0 deposit contract
|
||||
|
@ -1412,7 +1441,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
|
||||
|
@ -1547,13 +1576,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:
|
||||
|
||||
|
@ -1563,7 +1592,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
|
||||
|
||||
|
@ -1625,7 +1654,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.
|
||||
|
@ -1633,7 +1662,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`.
|
||||
|
@ -1866,7 +1895,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
|
||||
|
||||
|
@ -1916,7 +1945,7 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[
|
|||
# Proposer bonus
|
||||
if index in get_unslashed_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
|
||||
|
@ -2182,16 +2211,17 @@ def process_attester_slashing(state: BeaconState,
|
|||
Process ``AttesterSlashing`` transaction.
|
||||
Note that this function mutates ``state``.
|
||||
"""
|
||||
attestation1 = attester_slashing.slashable_attestation_1
|
||||
attestation2 = attester_slashing.slashable_attestation_2
|
||||
attestation1 = attester_slashing.attestation_1
|
||||
attestation2 = attester_slashing.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)
|
||||
|
||||
assert verify_indexed_attestation(state, attestation1)
|
||||
assert verify_indexed_attestation(state, attestation2)
|
||||
slashable_indices = [
|
||||
index for index in attestation1.validator_indices
|
||||
if (
|
||||
|
@ -2237,18 +2267,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
),
|
||||
}
|
||||
|
||||
# Check custody bits [to be generalised in phase 1]
|
||||
assert attestation.custody_bitfield == b'\x00' * len(attestation.custody_bitfield)
|
||||
|
||||
# Check aggregate signature [to be generalised in phase 1]
|
||||
participants = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
||||
assert len(participants) != 0
|
||||
assert bls_verify(
|
||||
pubkey=bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in participants]),
|
||||
message_hash=hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)),
|
||||
signature=attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, target_epoch, DOMAIN_ATTESTATION),
|
||||
)
|
||||
# Check signature and bitfields
|
||||
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||
|
||||
# Cache pending attestation
|
||||
pending_attestation = PendingAttestation(
|
||||
|
@ -2359,7 +2379,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 transactions per block](#max-transactions-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)
|
||||
- [Transactions](#transactions)
|
||||
- [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 transactions 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
|
||||
|
||||
### Transactions
|
||||
|
||||
Add the following transactions to the per-block processing, in order the given below and after all other transactions 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
|
@ -0,0 +1,46 @@
|
|||
ETH 2.0 Networking Spec - Messaging
|
||||
===
|
||||
|
||||
# Abstract
|
||||
|
||||
This specification describes how individual Ethereum 2.0 messages are represented on the wire.
|
||||
|
||||
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL”, NOT", “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
|
||||
|
||||
# Motivation
|
||||
|
||||
This specification seeks to define a messaging protocol that is flexible enough to be changed easily as the ETH 2.0 specification evolves.
|
||||
|
||||
Note that while `libp2p` is the chosen networking stack for Ethereum 2.0, as of this writing some clients do not have workable `libp2p` implementations. To allow those clients to communicate, we define a message envelope that includes the body's compression, encoding, and body length. Once `libp2p` is available across all implementations, this message envelope will be removed because `libp2p` will negotiate the values defined in the envelope upfront.
|
||||
|
||||
# Specification
|
||||
|
||||
## Message Structure
|
||||
|
||||
An ETH 2.0 message consists of an envelope that defines the message's compression, encoding, and length followed by the body itself.
|
||||
|
||||
Visually, a message looks like this:
|
||||
|
||||
```
|
||||
+--------------------------+
|
||||
| compression nibble |
|
||||
+--------------------------+
|
||||
| encoding nibble |
|
||||
+--------------------------+
|
||||
| body length (uint64) |
|
||||
+--------------------------+
|
||||
| |
|
||||
| body |
|
||||
| |
|
||||
+--------------------------+
|
||||
```
|
||||
|
||||
Clients MUST ignore messages with mal-formed bodies. The compression/encoding nibbles MUST be one of the following values:
|
||||
|
||||
## Compression Nibble Values
|
||||
|
||||
- `0x0`: no compression
|
||||
|
||||
## Encoding Nibble Values
|
||||
|
||||
- `0x1`: SSZ
|
|
@ -0,0 +1,32 @@
|
|||
ETH 2.0 Networking Spec - Node Identification
|
||||
===
|
||||
|
||||
# Abstract
|
||||
|
||||
This specification describes how Ethereum 2.0 nodes identify and address each other on the network.
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL", NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
||||
|
||||
# Specification
|
||||
|
||||
Clients use Ethereum Node Records (as described in [EIP-778](http://eips.ethereum.org/EIPS/eip-778)) to discover one another. Each ENR includes, among other things, the following keys:
|
||||
|
||||
- The node's IP.
|
||||
- The node's TCP port.
|
||||
- The node's public key.
|
||||
|
||||
For clients to be addressable, their ENR responses MUST contain all of the above keys. Client MUST verify the signature of any received ENRs, and disconnect from peers whose ENR signatures are invalid. Each node's public key MUST be unique.
|
||||
|
||||
The keys above are enough to construct a [multiaddr](https://github.com/multiformats/multiaddr) for use with the rest of the `libp2p` stack.
|
||||
|
||||
It is RECOMMENDED that clients set their TCP port to the default of `9000`.
|
||||
|
||||
## Peer ID Generation
|
||||
|
||||
The `libp2p` networking stack identifies peers via a "peer ID." Simply put, a node's Peer ID is the SHA2-256 `multihash` of the node's public key struct (serialized in protobuf, refer to the [Peer ID spec](https://github.com/libp2p/specs/pull/100)). `go-libp2p-crypto` contains the canonical implementation of how to hash `secp256k1` keys for use as a peer ID.
|
||||
|
||||
# See Also
|
||||
|
||||
- [multiaddr](https://github.com/multiformats/multiaddr)
|
||||
- [multihash](https://multiformats.io/multihash/)
|
||||
- [go-libp2p-crypto](https://github.com/libp2p/go-libp2p-crypto)
|
|
@ -0,0 +1,292 @@
|
|||
ETH 2.0 Networking Spec - RPC Interface
|
||||
===
|
||||
|
||||
# Abstract
|
||||
|
||||
The Ethereum 2.0 networking stack uses two modes of communication: a broadcast protocol that gossips information to interested parties via GossipSub, and an RPC protocol that retrieves information from specific clients. This specification defines the RPC protocol.
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL", NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
||||
|
||||
# Dependencies
|
||||
|
||||
This specification assumes familiarity with the [Messaging](./messaging.md), [Node Identification](./node-identification), and [Beacon Chain](../core/0_beacon-chain.md) specifications.
|
||||
|
||||
# Specification
|
||||
|
||||
## Message Schemas
|
||||
|
||||
Message body schemas are notated like this:
|
||||
|
||||
```
|
||||
(
|
||||
field_name_1: type
|
||||
field_name_2: type
|
||||
)
|
||||
```
|
||||
|
||||
Embedded types are serialized as SSZ Containers unless otherwise noted.
|
||||
|
||||
All referenced data structures can be found in the [0-beacon-chain](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/core/0_beacon-chain.md#data-structures) specification.
|
||||
|
||||
## `libp2p` Protocol Names
|
||||
|
||||
A "Protocol ID" in `libp2p` parlance refers to a human-readable identifier `libp2p` uses in order to identify sub-protocols and stream messages of different types over the same connection. Peers exchange supported protocol IDs via the `Identify` protocol upon connection. When opening a new stream, peers pin a particular protocol ID to it, and the stream remains contextualised thereafter. Since messages are sent inside a stream, they do not need to bear the protocol ID.
|
||||
|
||||
## RPC-Over-`libp2p`
|
||||
|
||||
To facilitate RPC-over-`libp2p`, a single protocol name is used: `/eth/serenity/beacon/rpc/1`. The version number in the protocol name is neither backwards or forwards compatible, and will be incremented whenever changes to the below structures are required.
|
||||
|
||||
Remote method calls are wrapped in a "request" structure:
|
||||
|
||||
```
|
||||
(
|
||||
id: uint64
|
||||
method_id: uint16
|
||||
body: Request
|
||||
)
|
||||
```
|
||||
|
||||
and their corresponding responses are wrapped in a "response" structure:
|
||||
|
||||
```
|
||||
(
|
||||
id: uint64
|
||||
response_code: uint16
|
||||
result: bytes
|
||||
)
|
||||
```
|
||||
|
||||
If an error occurs, a variant of the response structure is returned:
|
||||
|
||||
```
|
||||
(
|
||||
id: uint64
|
||||
response_code: uint16
|
||||
result: bytes
|
||||
)
|
||||
```
|
||||
|
||||
The details of the RPC-Over-`libp2p` protocol are similar to [JSON-RPC 2.0](https://www.jsonrpc.org/specification). Specifically:
|
||||
|
||||
1. The `id` member is REQUIRED.
|
||||
2. The `id` member in the response MUST be the same as the value of the `id` in the request.
|
||||
3. The `id` member MUST be unique within the context of a single connection. Monotonically increasing `id`s are RECOMMENDED.
|
||||
4. The `method_id` member is REQUIRED.
|
||||
5. The `result` member is REQUIRED on success.
|
||||
6. The `result` member is OPTIONAL on errors, and MAY contain additional information about the error.
|
||||
7. `response_code` MUST be `0` on success.
|
||||
|
||||
Structuring RPC requests in this manner allows multiple calls and responses to be multiplexed over the same stream without switching. Note that this implies that responses MAY arrive in a different order than requests.
|
||||
|
||||
The "method ID" fields in the below messages refer to the `method` field in the request structure above.
|
||||
|
||||
The first 1,000 values in `response_code` are reserved for system use. The following response codes are predefined:
|
||||
|
||||
1. `0`: No error.
|
||||
2. `10`: Parse error.
|
||||
2. `20`: Invalid request.
|
||||
3. `30`: Method not found.
|
||||
4. `40`: Server error.
|
||||
|
||||
### Alternative for Non-`libp2p` Clients
|
||||
|
||||
Since some clients are waiting for `libp2p` implementations in their respective languages. As such, they MAY listen for raw TCP messages on port `9000`. To distinguish RPC messages from other messages on that port, a byte prefix of `ETH` (`0x455448`) MUST be prepended to all messages. This option will be removed once `libp2p` is ready in all supported languages.
|
||||
|
||||
## Messages
|
||||
|
||||
### Hello
|
||||
|
||||
**Method ID:** `0`
|
||||
|
||||
**Body**:
|
||||
|
||||
```
|
||||
(
|
||||
network_id: uint8
|
||||
chain_id: uint64
|
||||
latest_finalized_root: bytes32
|
||||
latest_finalized_epoch: uint64
|
||||
best_root: bytes32
|
||||
best_slot: uint64
|
||||
)
|
||||
```
|
||||
|
||||
Clients exchange `hello` messages upon connection, forming a two-phase handshake. The first message the initiating client sends MUST be the `hello` message. In response, the receiving client MUST respond with its own `hello` message.
|
||||
|
||||
Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions:
|
||||
|
||||
1. If `network_id` belongs to a different chain, since the client definitionally cannot sync with this client.
|
||||
2. If the `latest_finalized_root` shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 in the diagram below has `(root, epoch)` of `(A, 5)` and Peer 2 has `(B, 3)`, Peer 1 would disconnect because it knows that `B` is not the root in their chain at epoch 3:
|
||||
|
||||
```
|
||||
Root A
|
||||
|
||||
+---+
|
||||
|xxx| +----+ Epoch 5
|
||||
+-+-+
|
||||
^
|
||||
|
|
||||
+-+-+
|
||||
| | +----+ Epoch 4
|
||||
+-+-+
|
||||
Root B ^
|
||||
|
|
||||
+---+ +-+-+
|
||||
|xxx+<---+--->+ | +----+ Epoch 3
|
||||
+---+ | +---+
|
||||
|
|
||||
+-+-+
|
||||
| | +-----------+ Epoch 2
|
||||
+-+-+
|
||||
^
|
||||
|
|
||||
+-+-+
|
||||
| | +-----------+ Epoch 1
|
||||
+---+
|
||||
```
|
||||
|
||||
Once the handshake completes, the client with the higher `latest_finalized_epoch` or `best_slot` (if the clients have equal `latest_finalized_epoch`s) SHOULD request beacon block roots from its counterparty via `beacon_block_roots` (i.e., RPC method `10`).
|
||||
|
||||
### Goodbye
|
||||
|
||||
**Method ID:** `1`
|
||||
|
||||
**Body:**
|
||||
|
||||
```
|
||||
(
|
||||
reason: uint64
|
||||
)
|
||||
```
|
||||
|
||||
Client MAY send `goodbye` messages upon disconnection. The reason field MAY be one of the following values:
|
||||
|
||||
- `1`: Client shut down.
|
||||
- `2`: Irrelevant network.
|
||||
- `3`: Fault/error.
|
||||
|
||||
Clients MAY define custom goodbye reasons as long as the value is larger than `1000`.
|
||||
|
||||
### Get Status
|
||||
|
||||
**Method ID:** `2`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```
|
||||
(
|
||||
sha: bytes32
|
||||
user_agent: bytes
|
||||
timestamp: uint64
|
||||
)
|
||||
```
|
||||
|
||||
**Response Body:**
|
||||
|
||||
```
|
||||
(
|
||||
sha: bytes32
|
||||
user_agent: bytes
|
||||
timestamp: uint64
|
||||
)
|
||||
```
|
||||
|
||||
Returns metadata about the remote node.
|
||||
|
||||
### Request Beacon Block Roots
|
||||
|
||||
**Method ID:** `10`
|
||||
|
||||
**Request Body**
|
||||
|
||||
```
|
||||
(
|
||||
start_slot: uint64
|
||||
count: uint64
|
||||
)
|
||||
```
|
||||
|
||||
**Response Body:**
|
||||
|
||||
```
|
||||
# BlockRootSlot
|
||||
(
|
||||
block_root: bytes32
|
||||
slot: uint64
|
||||
)
|
||||
|
||||
(
|
||||
roots: []BlockRootSlot
|
||||
)
|
||||
```
|
||||
|
||||
Requests a list of block roots and slots from the peer. The `count` parameter MUST be less than or equal to `32768`. The slots MUST be returned in ascending slot order.
|
||||
|
||||
### Beacon Block Headers
|
||||
|
||||
**Method ID:** `11`
|
||||
|
||||
**Request Body**
|
||||
|
||||
```
|
||||
(
|
||||
start_root: HashTreeRoot
|
||||
start_slot: uint64
|
||||
max_headers: uint64
|
||||
skip_slots: uint64
|
||||
)
|
||||
```
|
||||
|
||||
**Response Body:**
|
||||
|
||||
```
|
||||
(
|
||||
headers: []BeaconBlockHeader
|
||||
)
|
||||
```
|
||||
|
||||
Requests beacon block headers from the peer starting from `(start_root, start_slot)`. The response MUST contain no more than `max_headers` headers. `skip_slots` defines the maximum number of slots to skip between blocks. For example, requesting blocks starting at slots `2` a `skip_slots` value of `1` would return the blocks at `[2, 4, 6, 8, 10]`. In cases where a slot is empty for a given slot number, the closest previous block MUST be returned. For example, if slot `4` were empty in the previous example, the returned array would contain `[2, 3, 6, 8, 10]`. If slot three were further empty, the array would contain `[2, 6, 8, 10]` - i.e., duplicate blocks MUST be collapsed. A `skip_slots` value of `0` returns all blocks.
|
||||
|
||||
The function of the `skip_slots` parameter helps facilitate light client sync - for example, in [#459](https://github.com/ethereum/eth2.0-specs/issues/459) - and allows clients to balance the peers from whom they request headers. Clients could, for instance, request every 10th block from a set of peers where each per has a different starting block in order to populate block data.
|
||||
|
||||
### Beacon Block Bodies
|
||||
|
||||
**Method ID:** `12`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```
|
||||
(
|
||||
block_roots: []HashTreeRoot
|
||||
)
|
||||
```
|
||||
|
||||
**Response Body:**
|
||||
|
||||
```
|
||||
(
|
||||
block_bodies: []BeaconBlockBody
|
||||
)
|
||||
```
|
||||
|
||||
Requests the `block_bodies` associated with the provided `block_roots` from the peer. Responses MUST return `block_roots` in the order provided in the request. If the receiver does not have a particular `block_root`, it must return a zero-value `block_body` (i.e., a `block_body` container with all zero fields).
|
||||
|
||||
### Beacon Chain State
|
||||
|
||||
**Note:** This section is preliminary, pending the definition of the data structures to be transferred over the wire during fast sync operations.
|
||||
|
||||
**Method ID:** `13`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```
|
||||
(
|
||||
hashes: []HashTreeRoot
|
||||
)
|
||||
```
|
||||
|
||||
**Response Body:** TBD
|
||||
|
||||
Requests contain the hashes of Merkle tree nodes that when merkelized yield the block's `state_root`.
|
||||
|
||||
The response will contain the values that, when hashed, yield the hashes inside the request body.
|
|
@ -152,7 +152,7 @@ _Note:_ there might be "skipped" slots between the `parent` and `block`. These s
|
|||
|
||||
##### Parent root
|
||||
|
||||
Set `block.previous_block_root = hash_tree_root(parent)`.
|
||||
Set `block.previous_block_root = signed_root(parent)`.
|
||||
|
||||
##### State root
|
||||
|
||||
|
@ -255,11 +255,11 @@ Set `attestation_data.shard = shard` where `shard` is the shard associated with
|
|||
|
||||
##### Beacon block root
|
||||
|
||||
Set `attestation_data.beacon_block_root = hash_tree_root(head_block)`.
|
||||
Set `attestation_data.beacon_block_root = signed_root(head_block)`.
|
||||
|
||||
##### Target root
|
||||
|
||||
Set `attestation_data.target_root = hash_tree_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
|
||||
Set `attestation_data.target_root = signed_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
|
||||
|
||||
_Note:_ This can be looked up in the state using:
|
||||
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
|
||||
|
|
|
@ -135,7 +135,7 @@ def test_non_empty_custody_bitfield(state):
|
|||
attestation = get_valid_attestation(state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.custody_bitfield = b'\x01' + attestation.custody_bitfield[1:]
|
||||
attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield)
|
||||
|
||||
pre_state, post_state = run_attestation_processing(state, attestation, False)
|
||||
|
||||
|
|
|
@ -22,12 +22,14 @@ from build.phase0.spec import (
|
|||
get_active_validator_indices,
|
||||
get_attestation_participants,
|
||||
get_block_root,
|
||||
get_crosslink_committee_for_attestation,
|
||||
get_crosslink_committees_at_slot,
|
||||
get_current_epoch,
|
||||
get_domain,
|
||||
get_empty_block,
|
||||
get_epoch_start_slot,
|
||||
get_genesis_beacon_state,
|
||||
slot_to_epoch,
|
||||
verify_merkle_branch,
|
||||
hash,
|
||||
)
|
||||
|
@ -248,12 +250,11 @@ def get_valid_attestation(state, slot=None):
|
|||
shard = state.latest_start_shard
|
||||
attestation_data = build_attestation_data(state, slot, shard)
|
||||
|
||||
crosslink_committees = get_crosslink_committees_at_slot(state, slot)
|
||||
crosslink_committee = [committee for committee, _shard in crosslink_committees if _shard == attestation_data.shard][0]
|
||||
crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data)
|
||||
|
||||
committee_size = len(crosslink_committee)
|
||||
bitfield_length = (committee_size + 7) // 8
|
||||
aggregation_bitfield = b'\x01' + b'\x00' * (bitfield_length - 1)
|
||||
aggregation_bitfield = b'\xC0' + b'\x00' * (bitfield_length - 1)
|
||||
custody_bitfield = b'\x00' * bitfield_length
|
||||
attestation = Attestation(
|
||||
aggregation_bitfield=aggregation_bitfield,
|
||||
|
@ -266,23 +267,35 @@ def get_valid_attestation(state, slot=None):
|
|||
attestation.data,
|
||||
attestation.aggregation_bitfield,
|
||||
)
|
||||
assert len(participants) == 1
|
||||
assert len(participants) == 2
|
||||
|
||||
validator_index = participants[0]
|
||||
privkey = privkeys[validator_index]
|
||||
signatures = []
|
||||
for validator_index in participants:
|
||||
privkey = privkeys[validator_index]
|
||||
signatures.append(
|
||||
get_attestation_signature(
|
||||
state,
|
||||
attestation.data,
|
||||
privkey
|
||||
)
|
||||
)
|
||||
|
||||
attestation.aggregation_signature = bls.aggregate_signatures(signatures)
|
||||
return attestation
|
||||
|
||||
|
||||
def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0):
|
||||
message_hash = AttestationDataAndCustodyBit(
|
||||
data=attestation.data,
|
||||
custody_bit=0b0,
|
||||
data=attestation_data,
|
||||
custody_bit=custody_bit,
|
||||
).hash_tree_root()
|
||||
|
||||
attestation.aggregation_signature = bls.sign(
|
||||
return bls.sign(
|
||||
message_hash=message_hash,
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
fork=state.fork,
|
||||
epoch=get_current_epoch(state),
|
||||
epoch=slot_to_epoch(attestation_data.slot),
|
||||
domain_type=spec.DOMAIN_ATTESTATION,
|
||||
)
|
||||
)
|
||||
return attestation
|
||||
|
|
Loading…
Reference in New Issue