Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Executable Validator Guide #1236

Merged
merged 10 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
23 changes: 17 additions & 6 deletions scripts/build_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -237,10 +242,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 @@ -273,7 +282,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 @@ -288,14 +298,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))
88 changes: 48 additions & 40 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,7 +135,7 @@ 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) -> Tuple[Sequence[ValidatorIndex], Shard, Slot]:
"""
Return the committee assignment in the ``epoch`` for ``validator_index``.
``assignment`` returned is a tuple of the following form:
Expand All @@ -151,10 +152,14 @@ 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
break
else:
continue
break
return committee, shard, Slot(slot)
```

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 +210,19 @@ 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(slot_to_epoch(block.slot)),
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot `block.slot`
epoch=slot_to_epoch(block.slot),
domain_type=DOMAIN_RANDAO,
def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature:
return bls_sign(
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
privkey=privkey, # privkey stored locally
message_hash=hash_tree_root(slot_to_epoch(block.slot)),
domain=get_domain(
CarlBeek marked this conversation as resolved.
Show resolved Hide resolved
state=state,
domain_type=DOMAIN_RANDAO,
message_epoch=slot_to_epoch(block.slot),
)
)
)
```

##### Eth1 Data
Expand All @@ -232,30 +238,32 @@ 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=slot_to_epoch(block.slot),
domain_type=DOMAIN_BEACON_BLOCK,
def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature:
return bls_sign(
privkey=privkey, # privkey stored locally
message_hash=signing_root(header),
domain=get_domain(
state=state,
domain_type=DOMAIN_BEACON_PROPOSER,
message_epoch=slot_to_epoch(header.slot),
)
)
)
```

#### Block body
Expand Down Expand Up @@ -344,24 +352,24 @@ 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=slot_to_epoch(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,
)

return bls_sign(
privkey=privkey, # privkey stored locally
message_hash=hash_tree_root(attestation_data_and_custody_bit),
domain=get_domain(
state=state,
domain_type=DOMAIN_ATTESTATION,
message_epoch=attestation.data.target.epoch,
)
)
)
```

## How to avoid slashing
Expand Down