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:
Diederik Loerakker 2019-11-15 22:27:04 +01:00 committed by vbuterin
parent 40cb72ec11
commit b15669b7a5
17 changed files with 374 additions and 220 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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,

View File

@ -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,7 +225,9 @@ def on_block(store: Store, block: BeaconBlock) -> None:
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.current_justified_checkpoint
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
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
@ -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)
```

View File

@ -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).

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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