Backport master (v0.9.1) to dev (#1482)
* p2p-interface: clarify that signing_root is used for block requests * hash cleanups * one more hash tree root gone for blocks - block hashes are always signing roots! * use simple serialize data types consistently * Describe which finalized root/epoch to use * remove custody_bits from attestation * remove AttestationDataAndCustodyBit * Specify inclusive range for genesis deposits * add initial fork choice bounce prevention and tests * PR feedback * further test bounce attack * wipe queued justified after epoch transition * remove extra var * minor fmt * only allow attestatiosn to be considered from current and previous epoch * use best_justified_checkpoint instead of queued_justified_checkpoints * use helper for slots since epoch start * be explicit about use of genesis epoch for previous epoch in fork choice on_block * pr feedback * add note aboutgenesis attestations * cleanup get_eth1_vote * make eth1_follow_distance clearer * Update the expected proposer period Since `SECONDS_PER_SLOT` is now `12` * minor fix to comment in mainnet config * Update 0_beacon-chain.md
This commit is contained in:
parent
40cb72ec11
commit
b15669b7a5
|
@ -62,7 +62,6 @@ The following are the broad design goals for Ethereum 2.0:
|
|||
|
||||
## For spec contributors
|
||||
|
||||
|
||||
Documentation on the different components used during spec writing can be found here:
|
||||
* [YAML Test Generators](test_generators/README.md)
|
||||
* [Executable Python Spec, with Py-tests](test_libs/pyspec/README.md)
|
||||
|
|
|
@ -23,6 +23,11 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 65536
|
|||
MIN_GENESIS_TIME: 1578009600
|
||||
|
||||
|
||||
# Fork Choice
|
||||
# ---------------------------------------------------------------
|
||||
# 2**3 (= 8)
|
||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8
|
||||
|
||||
|
||||
# Deposit contract
|
||||
# ---------------------------------------------------------------
|
||||
|
@ -53,7 +58,7 @@ BLS_WITHDRAWAL_PREFIX: 0x00
|
|||
# ---------------------------------------------------------------
|
||||
# 12 seconds
|
||||
SECONDS_PER_SLOT: 12
|
||||
# 2**0 (= 1) slots 6 seconds
|
||||
# 2**0 (= 1) slots 12 seconds
|
||||
MIN_ATTESTATION_INCLUSION_DELAY: 1
|
||||
# 2**5 (= 32) slots 6.4 minutes
|
||||
SLOTS_PER_EPOCH: 32
|
||||
|
|
|
@ -21,6 +21,12 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
|
|||
# Jan 3, 2020
|
||||
MIN_GENESIS_TIME: 1578009600
|
||||
|
||||
#
|
||||
#
|
||||
# Fork Choice
|
||||
# ---------------------------------------------------------------
|
||||
# 2**1 (= 1)
|
||||
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2
|
||||
|
||||
|
||||
# Deposit contract
|
||||
|
|
|
@ -24,14 +24,13 @@ from eth2spec.utils.ssz.ssz_impl import (
|
|||
signing_root,
|
||||
)
|
||||
from eth2spec.utils.ssz.ssz_typing import (
|
||||
bit, boolean, Container, List, Vector, uint64,
|
||||
boolean, Container, List, Vector, uint64,
|
||||
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
|
||||
)
|
||||
from eth2spec.utils.bls import (
|
||||
bls_aggregate_signatures,
|
||||
bls_aggregate_pubkeys,
|
||||
bls_verify,
|
||||
bls_verify_multiple,
|
||||
bls_sign,
|
||||
)
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
- [`Checkpoint`](#checkpoint)
|
||||
- [`Validator`](#validator)
|
||||
- [`AttestationData`](#attestationdata)
|
||||
- [`AttestationDataAndCustodyBit`](#attestationdataandcustodybit)
|
||||
- [`IndexedAttestation`](#indexedattestation)
|
||||
- [`PendingAttestation`](#pendingattestation)
|
||||
- [`Eth1Data`](#eth1data)
|
||||
|
@ -55,7 +54,6 @@
|
|||
- [`hash_tree_root`](#hash_tree_root)
|
||||
- [`signing_root`](#signing_root)
|
||||
- [`bls_verify`](#bls_verify)
|
||||
- [`bls_verify_multiple`](#bls_verify_multiple)
|
||||
- [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys)
|
||||
- [Predicates](#predicates)
|
||||
- [`is_active_validator`](#is_active_validator)
|
||||
|
@ -308,20 +306,11 @@ class AttestationData(Container):
|
|||
target: Checkpoint
|
||||
```
|
||||
|
||||
#### `AttestationDataAndCustodyBit`
|
||||
|
||||
```python
|
||||
class AttestationDataAndCustodyBit(Container):
|
||||
data: AttestationData
|
||||
custody_bit: bit # Challengeable bit (SSZ-bool, 1 byte) for the custody of shard data
|
||||
```
|
||||
|
||||
#### `IndexedAttestation`
|
||||
|
||||
```python
|
||||
class IndexedAttestation(Container):
|
||||
custody_bit_0_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 0
|
||||
custody_bit_1_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] # Indices with custody bit equal to 1
|
||||
attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
@ -399,7 +388,6 @@ class AttesterSlashing(Container):
|
|||
class Attestation(Container):
|
||||
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: AttestationData
|
||||
custody_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
|
@ -553,10 +541,6 @@ def bytes_to_int(data: bytes) -> uint64:
|
|||
|
||||
`bls_verify` is a function for verifying a BLS signature, as defined in the [BLS Signature spec](../bls_signature.md#bls_verify).
|
||||
|
||||
#### `bls_verify_multiple`
|
||||
|
||||
`bls_verify_multiple` is a function for verifying a BLS signature constructed from multiple messages, as defined in the [BLS Signature spec](../bls_signature.md#bls_verify_multiple).
|
||||
|
||||
#### `bls_aggregate_pubkeys`
|
||||
|
||||
`bls_aggregate_pubkeys` is a function for aggregating multiple BLS public keys into a single aggregate key, as defined in the [BLS Signature spec](../bls_signature.md#bls_aggregate_pubkeys).
|
||||
|
@ -605,31 +589,18 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
|||
"""
|
||||
Check if ``indexed_attestation`` has valid indices and signature.
|
||||
"""
|
||||
bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||
bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
indices = indexed_attestation.attesting_indices
|
||||
|
||||
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
|
||||
if not len(bit_1_indices) == 0: # [to be removed in phase 1]
|
||||
return False # [to be removed in phase 1]
|
||||
# Verify max number of indices
|
||||
if not len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE:
|
||||
return False
|
||||
# Verify index sets are disjoint
|
||||
if not len(set(bit_0_indices).intersection(bit_1_indices)) == 0:
|
||||
if not len(indices) <= MAX_VALIDATORS_PER_COMMITTEE:
|
||||
return False
|
||||
# Verify indices are sorted
|
||||
if not (bit_0_indices == sorted(bit_0_indices) and bit_1_indices == sorted(bit_1_indices)):
|
||||
if not indices == sorted(indices):
|
||||
return False
|
||||
# Verify aggregate signature
|
||||
if not bls_verify_multiple(
|
||||
pubkeys=[
|
||||
bls_aggregate_pubkeys([state.validators[i].pubkey for i in bit_0_indices]),
|
||||
bls_aggregate_pubkeys([state.validators[i].pubkey for i in bit_1_indices]),
|
||||
],
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
if not bls_verify(
|
||||
pubkey=bls_aggregate_pubkeys([state.validators[i].pubkey for i in indices]),
|
||||
message_hash=hash_tree_root(indexed_attestation.data),
|
||||
signature=indexed_attestation.signature,
|
||||
domain=get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch),
|
||||
):
|
||||
|
@ -922,13 +893,9 @@ def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> Ind
|
|||
Return the indexed attestation corresponding to ``attestation``.
|
||||
"""
|
||||
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bits)
|
||||
assert custody_bit_1_indices.issubset(attesting_indices)
|
||||
custody_bit_0_indices = attesting_indices.difference(custody_bit_1_indices)
|
||||
|
||||
return IndexedAttestation(
|
||||
custody_bit_0_indices=sorted(custody_bit_0_indices),
|
||||
custody_bit_1_indices=sorted(custody_bit_1_indices),
|
||||
attesting_indices=sorted(attesting_indices),
|
||||
data=attestation.data,
|
||||
signature=attestation.signature,
|
||||
)
|
||||
|
@ -1026,7 +993,7 @@ Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 b
|
|||
|
||||
- `eth1_block_hash` is the hash of the Ethereum 1.0 block
|
||||
- `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash`
|
||||
- `deposits` is the sequence of all deposits, ordered chronologically, up to the block with hash `eth1_block_hash`
|
||||
- `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash`
|
||||
|
||||
```python
|
||||
def initialize_beacon_state_from_eth1(eth1_block_hash: Hash,
|
||||
|
@ -1460,9 +1427,8 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla
|
|||
assert is_valid_indexed_attestation(state, attestation_2)
|
||||
|
||||
slashed_any = False
|
||||
attesting_indices_1 = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
|
||||
attesting_indices_2 = attestation_2.custody_bit_0_indices + attestation_2.custody_bit_1_indices
|
||||
for index in sorted(set(attesting_indices_1).intersection(attesting_indices_2)):
|
||||
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
|
||||
for index in sorted(indices):
|
||||
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
|
||||
slash_validator(state, index)
|
||||
slashed_any = True
|
||||
|
@ -1479,7 +1445,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
|
||||
|
||||
committee = get_beacon_committee(state, data.slot, data.index)
|
||||
assert len(attestation.aggregation_bits) == len(attestation.custody_bits) == len(committee)
|
||||
assert len(attestation.aggregation_bits) == len(committee)
|
||||
|
||||
pending_attestation = PendingAttestation(
|
||||
data=data,
|
||||
|
|
|
@ -43,6 +43,12 @@ The head block root associated with a `store` is defined as `get_head(store)`. A
|
|||
4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`.
|
||||
5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost).
|
||||
|
||||
### Configuration
|
||||
|
||||
| Name | Value | Unit | Duration |
|
||||
| - | - | :-: | :-: |
|
||||
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
|
||||
|
||||
### Helpers
|
||||
|
||||
#### `LatestMessage`
|
||||
|
@ -60,8 +66,10 @@ class LatestMessage(object):
|
|||
@dataclass
|
||||
class Store(object):
|
||||
time: uint64
|
||||
genesis_time: uint64
|
||||
justified_checkpoint: Checkpoint
|
||||
finalized_checkpoint: Checkpoint
|
||||
best_justified_checkpoint: Checkpoint
|
||||
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
|
||||
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
|
||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||
|
@ -78,14 +86,30 @@ def get_genesis_store(genesis_state: BeaconState) -> Store:
|
|||
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
|
||||
return Store(
|
||||
time=genesis_state.genesis_time,
|
||||
genesis_time=genesis_state.genesis_time,
|
||||
justified_checkpoint=justified_checkpoint,
|
||||
finalized_checkpoint=finalized_checkpoint,
|
||||
best_justified_checkpoint=justified_checkpoint,
|
||||
blocks={root: genesis_block},
|
||||
block_states={root: genesis_state.copy()},
|
||||
checkpoint_states={justified_checkpoint: genesis_state.copy()},
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_current_slot`
|
||||
|
||||
```python
|
||||
def get_current_slot(store: Store) -> Slot:
|
||||
return Slot((store.time - store.genesis_time) // SECONDS_PER_SLOT)
|
||||
```
|
||||
|
||||
#### `compute_slots_since_epoch_start`
|
||||
|
||||
```python
|
||||
def compute_slots_since_epoch_start(slot: Slot) -> int:
|
||||
return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot))
|
||||
```
|
||||
|
||||
#### `get_ancestor`
|
||||
|
||||
```python
|
||||
|
@ -130,13 +154,50 @@ def get_head(store: Store) -> Hash:
|
|||
head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root))
|
||||
```
|
||||
|
||||
#### `should_update_justified_checkpoint`
|
||||
|
||||
```python
|
||||
def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool:
|
||||
"""
|
||||
To address the bouncing attack, only update conflicting justified
|
||||
checkpoints in the fork choice if in the early slots of the epoch.
|
||||
Otherwise, delay incorporation of new justified checkpoint until next epoch boundary.
|
||||
|
||||
See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion.
|
||||
"""
|
||||
if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED:
|
||||
return True
|
||||
|
||||
new_justified_block = store.blocks[new_justified_checkpoint.root]
|
||||
if new_justified_block.slot <= compute_start_slot_at_epoch(store.justified_checkpoint.epoch):
|
||||
return False
|
||||
if not (
|
||||
get_ancestor(store, new_justified_checkpoint.root, store.blocks[store.justified_checkpoint.root].slot) ==
|
||||
store.justified_checkpoint.root
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
### Handlers
|
||||
|
||||
#### `on_tick`
|
||||
|
||||
```python
|
||||
def on_tick(store: Store, time: uint64) -> None:
|
||||
previous_slot = get_current_slot(store)
|
||||
|
||||
# update store time
|
||||
store.time = time
|
||||
|
||||
current_slot = get_current_slot(store)
|
||||
# Not a new epoch, return
|
||||
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
||||
return
|
||||
# Update store.justified_checkpoint if a better checkpoint is known
|
||||
if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.justified_checkpoint = store.best_justified_checkpoint
|
||||
```
|
||||
|
||||
#### `on_block`
|
||||
|
@ -164,6 +225,8 @@ def on_block(store: Store, block: BeaconBlock) -> None:
|
|||
|
||||
# Update justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||
store.best_justified_checkpoint = state.current_justified_checkpoint
|
||||
if should_update_justified_checkpoint(store, state.current_justified_checkpoint):
|
||||
store.justified_checkpoint = state.current_justified_checkpoint
|
||||
|
||||
# Update finalized checkpoint
|
||||
|
@ -177,6 +240,11 @@ def on_block(store: Store, block: BeaconBlock) -> None:
|
|||
def on_attestation(store: Store, attestation: Attestation) -> None:
|
||||
target = attestation.data.target
|
||||
|
||||
# Attestations must be from the current or previous epoch
|
||||
current_epoch = compute_epoch_at_slot(get_current_slot(store))
|
||||
# Use GENESIS_EPOCH for previous when genesis to avoid underflow
|
||||
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH
|
||||
assert target.epoch in [current_epoch, previous_epoch]
|
||||
# Cannot calculate the current shuffling if have not seen the target
|
||||
assert target.root in store.blocks
|
||||
|
||||
|
@ -199,7 +267,7 @@ def on_attestation(store: Store, attestation: Attestation) -> None:
|
|||
assert is_valid_indexed_attestation(target_state, indexed_attestation)
|
||||
|
||||
# Update latest messages
|
||||
for i in indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_indices:
|
||||
for i in indexed_attestation.attesting_indices:
|
||||
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
|
||||
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=attestation.data.beacon_block_root)
|
||||
```
|
||||
|
|
|
@ -320,7 +320,7 @@ Here, `result` represents the 1-byte response code.
|
|||
|
||||
The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time:
|
||||
|
||||
- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `HashTreeRoots`'s.
|
||||
- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Bytes32`'s.
|
||||
- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet.
|
||||
|
||||
#### SSZ-encoding strategy (with or without Snappy)
|
||||
|
@ -344,20 +344,20 @@ constituents individually as `response_chunk`s. For example, the
|
|||
Request, Response Content:
|
||||
```
|
||||
(
|
||||
head_fork_version: bytes4
|
||||
finalized_root: bytes32
|
||||
head_fork_version: Bytes4
|
||||
finalized_root: Bytes32
|
||||
finalized_epoch: uint64
|
||||
head_root: bytes32
|
||||
head_root: Bytes32
|
||||
head_slot: uint64
|
||||
)
|
||||
```
|
||||
The fields are:
|
||||
The fields are, as seen by the client at the time of sending the message:
|
||||
|
||||
- `head_fork_version`: The beacon_state `Fork` version.
|
||||
- `finalized_root`: The latest finalized root the node knows about.
|
||||
- `finalized_epoch`: The latest finalized epoch the node knows about.
|
||||
- `head_root`: The block hash tree root corresponding to the head of the chain as seen by the sending node.
|
||||
- `head_slot`: The slot corresponding to the `head_root`.
|
||||
- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block.
|
||||
- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block.
|
||||
- `head_root`: The signing root of the current head block.
|
||||
- `head_slot`: The slot of the block corresponding to the `head_root`.
|
||||
|
||||
The dialing client MUST send a `Status` request upon connection.
|
||||
|
||||
|
@ -403,7 +403,7 @@ The response MUST consist of a single `response_chunk`.
|
|||
Request Content:
|
||||
```
|
||||
(
|
||||
head_block_root: HashTreeRoot
|
||||
head_block_root: Bytes32
|
||||
start_slot: uint64
|
||||
count: uint64
|
||||
step: uint64
|
||||
|
@ -417,7 +417,7 @@ Response Content:
|
|||
)
|
||||
```
|
||||
|
||||
Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root`. The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`.
|
||||
Requests count beacon blocks from the peer starting from `start_slot` on the chain defined by `head_block_root` (= `signing_root(BeaconBlock)`). The response MUST contain no more than count blocks. `step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at [2, 4, 6, …]. In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A step value of 1 returns all blocks on the range `[start_slot, start_slot + count)`.
|
||||
|
||||
The request MUST be encoded as an SSZ-container.
|
||||
|
||||
|
@ -441,7 +441,7 @@ Request Content:
|
|||
|
||||
```
|
||||
(
|
||||
[]HashTreeRoot
|
||||
[]Bytes32
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -453,7 +453,7 @@ Response Content:
|
|||
)
|
||||
```
|
||||
|
||||
Requests blocks by their block roots. The response is a list of `BeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks.
|
||||
Requests blocks by block root (= `signing_root(BeaconBlock)`). The response is a list of `BeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks.
|
||||
|
||||
`BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown).
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
- [Construct attestation](#construct-attestation)
|
||||
- [Data](#data)
|
||||
- [Aggregation bits](#aggregation-bits)
|
||||
- [Custody bits](#custody-bits)
|
||||
- [Aggregate signature](#aggregate-signature)
|
||||
- [Broadcast attestation](#broadcast-attestation)
|
||||
- [Attestation aggregation](#attestation-aggregation)
|
||||
|
@ -53,7 +52,6 @@
|
|||
- [Construct aggregate](#construct-aggregate)
|
||||
- [Data](#data-1)
|
||||
- [Aggregation bits](#aggregation-bits-1)
|
||||
- [Custody bits](#custody-bits-1)
|
||||
- [Aggregate signature](#aggregate-signature-1)
|
||||
- [Broadcast aggregate](#broadcast-aggregate)
|
||||
- [`AggregateAndProof`](#aggregateandproof)
|
||||
|
@ -197,7 +195,7 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
|
|||
|
||||
A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function).
|
||||
|
||||
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~3 weeks).
|
||||
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks).
|
||||
|
||||
#### Block header
|
||||
|
||||
|
@ -231,18 +229,22 @@ def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) ->
|
|||
|
||||
The `block.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
|
||||
|
||||
Let `get_eth1_data(distance: uint64) -> Eth1Data` be the (subjective) function that returns the Eth1 data at distance `distance` relative to the Eth1 head at the start of the current Eth1 voting period. Let `previous_eth1_distance` be the distance relative to the Eth1 block corresponding to `state.eth1_data.block_hash` at the start of the current Eth1 voting period. An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where:
|
||||
Let `get_eth1_data(distance: uint64) -> Eth1Data` be the (subjective) function that returns the Eth1 data at distance `distance` relative to the Eth1 head at the start of the current Eth1 voting period. Let `previous_eth1_distance` be the distance relative to the Eth1 block corresponding to `eth1_data.block_hash` found in the state at the _start_ of the current Eth1 voting period. Note that `eth1_data` can be updated in the middle of a voting period and thus the starting `eth1_data.block_hash` must be stored separately.
|
||||
|
||||
An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where:
|
||||
|
||||
```python
|
||||
def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Data:
|
||||
new_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, 2 * ETH1_FOLLOW_DISTANCE)]
|
||||
all_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, previous_eth1_distance)]
|
||||
|
||||
valid_votes = []
|
||||
for slot, vote in enumerate(state.eth1_data_votes):
|
||||
period_tail = slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_squareroot(SLOTS_PER_ETH1_VOTING_PERIOD)
|
||||
if vote in new_eth1_data or (period_tail and vote in all_eth1_data):
|
||||
valid_votes.append(vote)
|
||||
period_tail = state.slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_squareroot(SLOTS_PER_ETH1_VOTING_PERIOD)
|
||||
if period_tail:
|
||||
votes_to_consider = all_eth1_data
|
||||
else:
|
||||
votes_to_consider = new_eth1_data
|
||||
|
||||
valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider]
|
||||
|
||||
return max(
|
||||
valid_votes,
|
||||
|
@ -291,6 +293,8 @@ A validator is expected to create, sign, and broadcast an attestation during eac
|
|||
|
||||
A validator should create and broadcast the `attestation` to the associated attestation subnet one-third of the way through the `slot` during which the validator is assigned―that is, `SECONDS_PER_SLOT / 3` seconds after the start of `slot`.
|
||||
|
||||
*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded fork, and should be made.
|
||||
|
||||
#### Attestation data
|
||||
|
||||
First, the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot.
|
||||
|
@ -331,25 +335,14 @@ Set `attestation.data = attestation_data` where `attestation_data` is the `Attes
|
|||
|
||||
*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bits)` should return a list of length equal to 1, containing `validator_index`.
|
||||
|
||||
##### Custody bits
|
||||
|
||||
- Let `attestation.custody_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` filled with zeros of length `len(committee)`.
|
||||
|
||||
*Note*: This is a stub for Phase 0.
|
||||
|
||||
##### Aggregate signature
|
||||
|
||||
Set `attestation.signature = signed_attestation_data` where `signed_attestation_data` is obtained from:
|
||||
|
||||
```python
|
||||
def get_signed_attestation_data(state: BeaconState, attestation: IndexedAttestation, privkey: int) -> BLSSignature:
|
||||
attestation_data_and_custody_bit = AttestationDataAndCustodyBit(
|
||||
data=attestation.data,
|
||||
custody_bit=0b0,
|
||||
)
|
||||
|
||||
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
|
||||
return bls_sign(privkey, hash_tree_root(attestation_data_and_custody_bit), domain)
|
||||
return bls_sign(privkey, hash_tree_root(attestation.data), domain)
|
||||
```
|
||||
|
||||
#### Broadcast attestation
|
||||
|
@ -391,12 +384,6 @@ Set `aggregate_attestation.data = attestation_data` where `attestation_data` is
|
|||
|
||||
Let `aggregate_attestation.aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`.
|
||||
|
||||
##### Custody bits
|
||||
|
||||
- Let `aggregate_attestation.custody_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` filled with zeros of length `len(committee)`.
|
||||
|
||||
*Note*: This is a stub for Phase 0.
|
||||
|
||||
##### Aggregate signature
|
||||
|
||||
Set `aggregate_attestation.signature = aggregate_signature` where `aggregate_signature` is obtained from:
|
||||
|
|
|
@ -9,7 +9,7 @@ def test_decoder():
|
|||
rng = Random(123)
|
||||
|
||||
# check these types only, Block covers a lot of operation types already.
|
||||
for typ in [spec.AttestationDataAndCustodyBit, spec.BeaconState, spec.BeaconBlock]:
|
||||
for typ in [spec.Attestation, spec.BeaconState, spec.BeaconBlock]:
|
||||
# create a random pyspec value
|
||||
original = random_value.get_random_ssz_object(rng, typ, 100, 10,
|
||||
mode=random_value.RandomizationMode.mode_random,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
from eth2spec.test.context import with_all_phases, spec_state_test, with_phases
|
||||
|
||||
|
||||
from eth2spec.test.context import with_all_phases, spec_state_test
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
from eth2spec.test.helpers.state import state_transition_and_sign_block
|
||||
|
@ -19,7 +17,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
|
|||
indexed_attestation = spec.get_indexed_attestation(state, attestation)
|
||||
spec.on_attestation(store, attestation)
|
||||
assert (
|
||||
store.latest_messages[indexed_attestation.custody_bit_0_indices[0]] ==
|
||||
store.latest_messages[indexed_attestation.attesting_indices[0]] ==
|
||||
spec.LatestMessage(
|
||||
epoch=attestation.data.target.epoch,
|
||||
root=attestation.data.beacon_block_root,
|
||||
|
@ -29,10 +27,9 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_attestation(spec, state):
|
||||
def test_on_attestation_current_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * 2)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
@ -41,9 +38,53 @@ def test_on_attestation(spec, state):
|
|||
spec.on_block(store, block)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
assert attestation.data.target.epoch == spec.GENESIS_EPOCH
|
||||
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH
|
||||
|
||||
run_on_attestation(spec, state, store, attestation)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_attestation_previous_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
|
||||
# store block in store
|
||||
spec.on_block(store, block)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
assert attestation.data.target.epoch == spec.GENESIS_EPOCH
|
||||
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH + 1
|
||||
|
||||
run_on_attestation(spec, state, store, attestation)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_attestation_past_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
|
||||
# move time forward 2 epochs
|
||||
time = store.time + 2 * spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
spec.on_tick(store, time)
|
||||
|
||||
# create and store block from 3 epochs ago
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
spec.on_block(store, block)
|
||||
|
||||
# create attestation for past block
|
||||
attestation = get_valid_attestation(spec, state, slot=state.slot)
|
||||
assert attestation.data.target.epoch == spec.GENESIS_EPOCH
|
||||
assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == spec.GENESIS_EPOCH + 2
|
||||
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_attestation_target_not_in_store(spec, state):
|
||||
|
@ -77,8 +118,7 @@ def test_on_attestation_future_epoch(spec, state):
|
|||
spec.on_block(store, block)
|
||||
|
||||
# move state forward but not store
|
||||
attestation_slot = block.slot + spec.SLOTS_PER_EPOCH
|
||||
state.slot = attestation_slot
|
||||
state.slot = block.slot + spec.SLOTS_PER_EPOCH
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=state.slot)
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
@ -100,7 +140,7 @@ def test_on_attestation_same_slot(spec, state):
|
|||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_phases(['phase0'])
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_attestation_invalid_attestation(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
|
@ -113,6 +153,7 @@ def test_on_attestation_invalid_attestation(spec, state):
|
|||
spec.on_block(store, block)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, slot=block.slot)
|
||||
# make attestation invalid by setting a phase1-only custody bit
|
||||
attestation.custody_bits[0] = 1
|
||||
# make invalid by using an invalid committee index
|
||||
attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
|
|
@ -132,3 +132,74 @@ def test_on_block_before_finalized(spec, state):
|
|||
block = build_empty_block_for_next_slot(spec, state)
|
||||
state_transition_and_sign_block(spec, state, block)
|
||||
run_on_block(spec, store, block, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
next_epoch(spec, state)
|
||||
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
|
||||
state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
|
||||
next_epoch(spec, state)
|
||||
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
|
||||
last_block_root = signing_root(last_block)
|
||||
|
||||
# Mock the justified checkpoint
|
||||
just_state = store.block_states[last_block_root]
|
||||
new_justified = spec.Checkpoint(
|
||||
epoch=just_state.current_justified_checkpoint.epoch + 1,
|
||||
root=b'\x77' * 32,
|
||||
)
|
||||
just_state.current_justified_checkpoint = new_justified
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, just_state)
|
||||
state_transition_and_sign_block(spec, deepcopy(just_state), block)
|
||||
assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
|
||||
run_on_block(spec, store, block)
|
||||
|
||||
assert store.justified_checkpoint == new_justified
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_on_block_outside_safe_slots_and_old_block(spec, state):
|
||||
# Initialization
|
||||
store = spec.get_genesis_store(state)
|
||||
time = 100
|
||||
spec.on_tick(store, time)
|
||||
|
||||
next_epoch(spec, state)
|
||||
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
|
||||
state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
|
||||
next_epoch(spec, state)
|
||||
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
|
||||
last_block_root = signing_root(last_block)
|
||||
|
||||
# Mock justified block in store
|
||||
just_block = build_empty_block_for_next_slot(spec, state)
|
||||
# Slot is same as justified checkpoint so does not trigger an override in the store
|
||||
just_block.slot = spec.compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
|
||||
store.blocks[just_block.hash_tree_root()] = just_block
|
||||
|
||||
# Mock the justified checkpoint
|
||||
just_state = store.block_states[last_block_root]
|
||||
new_justified = spec.Checkpoint(
|
||||
epoch=just_state.current_justified_checkpoint.epoch + 1,
|
||||
root=just_block.hash_tree_root(),
|
||||
)
|
||||
just_state.current_justified_checkpoint = new_justified
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, just_state)
|
||||
state_transition_and_sign_block(spec, deepcopy(just_state), block)
|
||||
|
||||
spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.SECONDS_PER_SLOT)
|
||||
assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED
|
||||
run_on_block(spec, store, block)
|
||||
|
||||
assert store.justified_checkpoint != new_justified
|
||||
assert store.best_justified_checkpoint == new_justified
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
from eth2spec.test.context import with_all_phases, spec_state_test
|
||||
|
||||
|
||||
def run_on_tick(spec, store, time, new_justified_checkpoint=False):
|
||||
previous_justified_checkpoint = store.justified_checkpoint
|
||||
|
||||
spec.on_tick(store, time)
|
||||
|
||||
assert store.time == time
|
||||
|
||||
if new_justified_checkpoint:
|
||||
assert store.justified_checkpoint == store.best_justified_checkpoint
|
||||
assert store.justified_checkpoint.epoch > previous_justified_checkpoint.epoch
|
||||
assert store.justified_checkpoint.root != previous_justified_checkpoint.root
|
||||
else:
|
||||
assert store.justified_checkpoint == previous_justified_checkpoint
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_basic(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
run_on_tick(spec, store, store.time + 1)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_update_justified_single(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
store.best_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch, True)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_no_update_same_slot_at_epoch_boundary(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
store.best_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
# set store time to already be at epoch boundary
|
||||
store.time = seconds_per_epoch
|
||||
|
||||
run_on_tick(spec, store, store.time + 1)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_no_update_not_epoch_boundary(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
|
||||
store.best_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
run_on_tick(spec, store, store.time + spec.SECONDS_PER_SLOT)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_no_update_new_justified_equal_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
store.best_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
store.justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.best_justified_checkpoint.epoch,
|
||||
root=b'\44' * 32,
|
||||
)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_no_update_new_justified_later_epoch(spec, state):
|
||||
store = spec.get_genesis_store(state)
|
||||
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
|
||||
|
||||
store.best_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.justified_checkpoint.epoch + 1,
|
||||
root=b'\x55' * 32,
|
||||
)
|
||||
|
||||
store.justified_checkpoint = spec.Checkpoint(
|
||||
epoch=store.best_justified_checkpoint.epoch + 1,
|
||||
root=b'\44' * 32,
|
||||
)
|
||||
|
||||
run_on_tick(spec, store, store.time + seconds_per_epoch)
|
|
@ -54,11 +54,9 @@ def get_valid_attestation(spec, state, slot=None, index=None, signed=False):
|
|||
|
||||
committee_size = len(beacon_committee)
|
||||
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
|
||||
custody_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
|
||||
attestation = spec.Attestation(
|
||||
aggregation_bits=aggregation_bits,
|
||||
data=attestation_data,
|
||||
custody_bits=custody_bits,
|
||||
)
|
||||
fill_aggregate_attestation(spec, state, attestation)
|
||||
if signed:
|
||||
|
@ -83,7 +81,7 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
|
|||
|
||||
|
||||
def sign_indexed_attestation(spec, state, indexed_attestation):
|
||||
participants = indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_indices
|
||||
participants = indexed_attestation.attesting_indices
|
||||
indexed_attestation.signature = sign_aggregate_attestation(spec, state, indexed_attestation.data, participants)
|
||||
|
||||
|
||||
|
@ -97,14 +95,9 @@ def sign_attestation(spec, state, attestation):
|
|||
attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants)
|
||||
|
||||
|
||||
def get_attestation_signature(spec, state, attestation_data, privkey, custody_bit=0b0):
|
||||
message_hash = spec.AttestationDataAndCustodyBit(
|
||||
data=attestation_data,
|
||||
custody_bit=custody_bit,
|
||||
).hash_tree_root()
|
||||
|
||||
def get_attestation_signature(spec, state, attestation_data, privkey):
|
||||
return bls_sign(
|
||||
message_hash=message_hash,
|
||||
message_hash=attestation_data.hash_tree_root(),
|
||||
privkey=privkey,
|
||||
domain=spec.get_domain(
|
||||
state=state,
|
||||
|
|
|
@ -2,7 +2,7 @@ from eth2spec.test.context import (
|
|||
spec_state_test,
|
||||
expect_assertion_error,
|
||||
always_bls, never_bls,
|
||||
with_all_phases, with_phases,
|
||||
with_all_phases,
|
||||
spec_test,
|
||||
low_balances,
|
||||
with_custom_state,
|
||||
|
@ -274,35 +274,6 @@ def test_bad_source_root(spec, state):
|
|||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_inconsistent_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
custody_bits = attestation.custody_bits[:]
|
||||
custody_bits.append(False)
|
||||
|
||||
attestation.custody_bits = custody_bits
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_phases(['phase0'])
|
||||
@spec_state_test
|
||||
def test_non_empty_custody_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.custody_bits = attestation.aggregation_bits[:]
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_empty_aggregation_bits(spec, state):
|
||||
|
@ -344,32 +315,3 @@ def test_too_few_aggregation_bits(spec, state):
|
|||
attestation.aggregation_bits = attestation.aggregation_bits[:-1]
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_too_many_custody_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
# one too many bits
|
||||
attestation.custody_bits.append(0b0)
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_too_few_custody_bits(spec, state):
|
||||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
attestation.custody_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](
|
||||
*([0b1] + [0b0] * (len(attestation.custody_bits) - 1)))
|
||||
|
||||
sign_attestation(spec, state, attestation)
|
||||
|
||||
# one too few bits
|
||||
attestation.custody_bits = attestation.custody_bits[:-1]
|
||||
|
||||
yield from run_attestation_processing(spec, state, attestation, False)
|
||||
|
|
|
@ -25,10 +25,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
|
|||
yield 'post', None
|
||||
return
|
||||
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
slashed_indices = attester_slashing.attestation_1.attesting_indices
|
||||
|
||||
proposer_index = spec.get_beacon_proposer_index(state)
|
||||
pre_proposer_balance = get_balance(state, proposer_index)
|
||||
|
@ -112,10 +109,7 @@ def test_success_surround(spec, state):
|
|||
@always_bls
|
||||
def test_success_already_exited_recent(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
slashed_indices = attester_slashing.attestation_1.attesting_indices
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
|
||||
|
@ -127,10 +121,7 @@ def test_success_already_exited_recent(spec, state):
|
|||
@always_bls
|
||||
def test_success_already_exited_long_ago(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
slashed_indices = (
|
||||
attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices
|
||||
)
|
||||
slashed_indices = attester_slashing.attestation_1.attesting_indices
|
||||
for index in slashed_indices:
|
||||
spec.initiate_validator_exit(state, index)
|
||||
state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2
|
||||
|
@ -190,38 +181,23 @@ def test_participants_already_slashed(spec, state):
|
|||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
# 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
|
||||
validator_indices = attester_slashing.attestation_1.attesting_indices
|
||||
for index in validator_indices:
|
||||
state.validators[index].slashed = True
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_custody_bit_0_and_1_intersect(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
|
||||
attester_slashing.attestation_1.custody_bit_1_indices.append(
|
||||
attester_slashing.attestation_1.custody_bit_0_indices[0]
|
||||
)
|
||||
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_att1_bad_extra_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_1.attesting_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
|
||||
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
|
||||
attester_slashing.attestation_1.attesting_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad extra index is spotted, and slashing is aborted.
|
||||
|
||||
|
@ -234,10 +210,10 @@ def test_att1_bad_extra_index(spec, state):
|
|||
def test_att1_bad_replaced_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_1.attesting_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
|
||||
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
|
||||
attester_slashing.attestation_1.attesting_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad replaced index is spotted, and slashing is aborted.
|
||||
|
||||
|
@ -250,10 +226,10 @@ def test_att1_bad_replaced_index(spec, state):
|
|||
def test_att2_bad_extra_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_2.attesting_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
|
||||
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
|
||||
attester_slashing.attestation_2.attesting_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad extra index is spotted, and slashing is aborted.
|
||||
|
||||
|
@ -266,10 +242,10 @@ def test_att2_bad_extra_index(spec, state):
|
|||
def test_att2_bad_replaced_index(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_2.attesting_indices
|
||||
options = list(set(range(len(state.validators))) - set(indices))
|
||||
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
|
||||
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
|
||||
attester_slashing.attestation_2.attesting_indices = sorted(indices)
|
||||
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
|
||||
# see if the bad replaced index is spotted, and slashing is aborted.
|
||||
|
||||
|
@ -278,10 +254,10 @@ def test_att2_bad_replaced_index(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_unsorted_att_1_bit0(spec, state):
|
||||
def test_unsorted_att_1(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
|
||||
|
||||
indices = attester_slashing.attestation_1.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_1.attesting_indices
|
||||
assert len(indices) >= 3
|
||||
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
|
||||
|
@ -291,15 +267,12 @@ def test_unsorted_att_1_bit0(spec, state):
|
|||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_unsorted_att_2_bit0(spec, state):
|
||||
def test_unsorted_att_2(spec, state):
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)
|
||||
|
||||
indices = attester_slashing.attestation_2.custody_bit_0_indices
|
||||
indices = attester_slashing.attestation_2.attesting_indices
|
||||
assert len(indices) >= 3
|
||||
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
|
||||
sign_indexed_attestation(spec, state, attester_slashing.attestation_2)
|
||||
|
||||
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
|
||||
|
||||
|
||||
# note: unsorted indices for custody bit 0 are to be introduced in phase 1 testing.
|
||||
|
|
|
@ -141,7 +141,7 @@ def test_duplicate_attestation(spec, state):
|
|||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
indexed_attestation = spec.get_indexed_attestation(state, attestation)
|
||||
participants = indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_indices
|
||||
participants = indexed_attestation.attesting_indices
|
||||
|
||||
assert len(participants) > 0
|
||||
|
||||
|
|
|
@ -188,8 +188,7 @@ def test_attester_slashing(spec, state):
|
|||
pre_state = deepcopy(state)
|
||||
|
||||
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
|
||||
validator_index = (attester_slashing.attestation_1.custody_bit_0_indices
|
||||
+ attester_slashing.attestation_1.custody_bit_1_indices)[0]
|
||||
validator_index = attester_slashing.attestation_1.attesting_indices[0]
|
||||
|
||||
assert not state.validators[validator_index].slashed
|
||||
|
||||
|
|
Loading…
Reference in New Issue