Skip to content

Commit

Permalink
Merge pull request #1236 from ethereum/exec_v_spec
Browse files Browse the repository at this point in the history
Executable Validator Guide
  • Loading branch information
djrtwo authored Jul 1, 2019
2 parents 350b9d7 + 2539406 commit 2855e43
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 53 deletions.
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html

all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS)

clean:
# deletes everything except the venvs
partial_clean:
rm -rf $(YAML_TEST_DIR)
rm -rf $(GENERATOR_VENVS)
rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(PY_SPEC_ALL_TARGETS)
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/$(COV_HTML_OUT)
rm -rf $(PY_SPEC_DIR)/.coverage
rm -rf $(PY_SPEC_DIR)/test-reports

clean: partial_clean
rm -rf $(PY_SPEC_DIR)/venv
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv

# "make gen_yaml_tests" to run generators
gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_TARGETS)

Expand Down Expand Up @@ -78,7 +83,7 @@ test_deposit_contract:
pyspec: $(PY_SPEC_ALL_TARGETS)

$(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/0_fork-choice.md $@
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/0_fork-choice.md $(SPEC_DIR)/validator/0_beacon-chain-validator.md $@

$(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/1_custody-game.md $(SPEC_DIR)/core/1_shard-data-chains.md $(SPEC_DIR)/core/0_fork-choice.md $@
Expand Down
25 changes: 18 additions & 7 deletions scripts/build_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


PHASE0_IMPORTS = '''from typing import (
Any, Dict, Set, Sequence, Tuple,
Any, Dict, Set, Sequence, Tuple, Optional
)
from dataclasses import (
Expand All @@ -31,6 +31,7 @@
bls_aggregate_pubkeys,
bls_verify,
bls_verify_multiple,
bls_sign,
)
from eth2spec.utils.hash_function import hash
Expand Down Expand Up @@ -68,6 +69,10 @@
hash_cache: Dict[bytes, Hash] = {}
def get_eth1_data(distance: uint64) -> Hash:
return hash(distance)
def hash(x: bytes) -> Hash:
if x not in hash_cache:
hash_cache[x] = Hash(_hash(x))
Expand Down Expand Up @@ -243,10 +248,14 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject:
return functions, custom_types, constants, ssz_objects, inserts


def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str, outfile: str=None) -> Optional[str]:
def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str,
v_guide_sourcefile: str, outfile: str=None) -> Optional[str]:
phase0_spec = get_spec(phase0_sourcefile)
fork_choice_spec = get_spec(fork_choice_sourcefile)
spec_objects = combine_spec_objects(phase0_spec, fork_choice_spec)
v_guide = get_spec(v_guide_sourcefile)
spec_objects = phase0_spec
for value in [fork_choice_spec, v_guide]:
spec_objects = combine_spec_objects(spec_objects, value)
spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS)
if outfile is not None:
with open(outfile, 'w') as out:
Expand Down Expand Up @@ -279,7 +288,8 @@ def build_phase1_spec(phase0_sourcefile: str,
If building phase 0:
1st argument is input spec.md
2nd argument is input fork_choice.md
3rd argument is output spec.py
3rd argument is input validator_guide.md
4th argument is output spec.py
If building phase 1:
1st argument is input spec_phase0.md
Expand All @@ -294,14 +304,15 @@ def build_phase1_spec(phase0_sourcefile: str,

args = parser.parse_args()
if args.phase == 0:
if len(args.files) == 3:
if len(args.files) == 4:
build_phase0_spec(*args.files)
else:
print(" Phase 0 requires an output as well as spec and forkchoice files.")
print(" Phase 0 requires spec, forkchoice, and v-guide inputs as well as an output file.")
elif args.phase == 1:
if len(args.files) == 5:
build_phase1_spec(*args.files)
else:
print(" Phase 1 requires an output as well as 4 input files (phase0.md and phase1.md, phase1.md, fork_choice.md)")
print(" Phase 1 requires 4 input files as well as an output file: "
+ "(phase0.md and phase1.md, phase1.md, fork_choice.md, output.py)")
else:
print("Invalid phase: {0}".format(args.phase))
69 changes: 27 additions & 42 deletions specs/validator/0_beacon-chain-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ In normal operation, the validator is quickly activated, at which point the vali
The function [`is_active_validator`](../core/0_beacon-chain.md#is_active_validator) can be used to check if a validator is active during a given epoch. Usage is as follows:

```python
validator = state.validators[validator_index]
is_active = is_active_validator(validator, get_current_epoch(state))
def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool:
validator = state.validators[validator_index]
return is_active_validator(validator, get_current_epoch(state))
```

Once a validator is activated, the validator is assigned [responsibilities](#beacon-chain-responsibilities) until exited.
Expand All @@ -134,13 +135,14 @@ A validator can get committee assignments for a given epoch using the following
```python
def get_committee_assignment(state: BeaconState,
epoch: Epoch,
validator_index: ValidatorIndex) -> Tuple[List[ValidatorIndex], Shard, Slot]:
validator_index: ValidatorIndex) -> Optional[Tuple[Sequence[ValidatorIndex], Shard, Slot]]:
"""
Return the committee assignment in the ``epoch`` for ``validator_index``.
``assignment`` returned is a tuple of the following form:
* ``assignment[0]`` is the list of validators in the committee
* ``assignment[1]`` is the shard to which the committee is assigned
* ``assignment[2]`` is the slot at which the committee is assigned
Return None if no assignment.
"""
next_epoch = get_current_epoch(state) + 1
assert epoch <= next_epoch
Expand All @@ -151,10 +153,11 @@ def get_committee_assignment(state: BeaconState,
offset = committees_per_slot * (slot % SLOTS_PER_EPOCH)
slot_start_shard = (get_start_shard(state, epoch) + offset) % SHARD_COUNT
for i in range(committees_per_slot):
shard = (slot_start_shard + i) % SHARD_COUNT
shard = Shard((slot_start_shard + i) % SHARD_COUNT)
committee = get_crosslink_committee(state, epoch, shard)
if validator_index in committee:
return committee, shard, slot
return committee, shard, Slot(slot)
return None
```

A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run with a `state` of the slot in question. Proposer selection is only stable within the context of the current epoch.
Expand Down Expand Up @@ -205,18 +208,12 @@ Set `block.state_root = hash_tree_root(state)` of the resulting `state` of the `

##### Randao reveal

Set `block.randao_reveal = epoch_signature` where `epoch_signature` is defined as:
Set `block.randao_reveal = epoch_signature` where `epoch_signature` is obtained from:

```python
epoch_signature = bls_sign(
privkey=validator.privkey, # privkey stored locally, not in state
message_hash=hash_tree_root(compute_epoch_of_slot(block.slot)),
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot `block.slot`
epoch=compute_epoch_of_slot(block.slot),
domain_type=DOMAIN_RANDAO,
)
)
def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_of_slot(block.slot))
return bls_sign(privkey, hash_tree_root(compute_epoch_of_slot(block.slot)), domain)
```

##### Eth1 Data
Expand All @@ -232,30 +229,25 @@ def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Dat

valid_votes = []
for slot, vote in enumerate(state.eth1_data_votes):
period_tail = slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_square_root(SLOTS_PER_ETH1_VOTING_PERIOD)
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)

return max(valid_votes,
return max(
valid_votes,
key=lambda v: (valid_votes.count(v), -all_eth1_data.index(v)), # Tiebreak by smallest distance
default=get_eth1_data(ETH1_FOLLOW_DISTANCE),
)
```

##### Signature

Set `block.signature = block_signature` where `block_signature` is defined as:
Set `header.signature = block_signature` where `block_signature` is obtained from:

```python
block_signature = bls_sign(
privkey=validator.privkey, # privkey store locally, not in state
message_hash=signing_root(block),
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot `block.slot`
epoch=compute_epoch_of_slot(block.slot),
domain_type=DOMAIN_BEACON_BLOCK,
)
)
def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_of_slot(header.slot))
return bls_sign(privkey, signing_root(header), domain)
```

#### Block body
Expand Down Expand Up @@ -344,24 +336,17 @@ Set `attestation.data = attestation_data` where `attestation_data` is the `Attes

##### Aggregate signature

Set `attestation.aggregate_signature = signed_attestation_data` where `signed_attestation_data` is defined as:
Set `attestation.signature = signed_attestation_data` where `signed_attestation_data` is obtained from:

```python
attestation_data_and_custody_bit = AttestationDataAndCustodyBit(
data=attestation.data,
custody_bit=0b0,
)
attestation_message = hash_tree_root(attestation_data_and_custody_bit)

signed_attestation_data = bls_sign(
privkey=validator.privkey, # privkey stored locally, not in state
message_hash=attestation_message,
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot, `attestation_data.slot`
epoch=compute_epoch_of_slot(attestation_data.slot),
domain_type=DOMAIN_ATTESTATION,
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_ATTESTATION, attestation.data.target.epoch)
return bls_sign(privkey, hash_tree_root(attestation_data_and_custody_bit), domain)
```

## How to avoid slashing
Expand Down

0 comments on commit 2855e43

Please sign in to comment.