Merge pull request #1504 from ethereum/phase1rebase

Phase 1 rebase
This commit is contained in:
Danny Ryan 2020-01-23 12:09:17 -07:00 committed by GitHub
commit 6a26b76858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1877 additions and 2170 deletions

View File

@ -118,7 +118,9 @@ $(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(PHASE0_SPEC_DIR)/validator.md $@
$(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(SSZ_DIR)/merkle-proofs.md $(PHASE1_SPEC_DIR)/custody-game.md $(PHASE1_SPEC_DIR)/shard-data-chains.md $(PHASE1_SPEC_DIR)/beacon-chain-misc.md $@
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(PHASE1_SPEC_DIR)/custody-game.md $(PHASE1_SPEC_DIR)/beacon-chain.md $(PHASE1_SPEC_DIR)/fraud-proofs.md $(PHASE1_SPEC_DIR)/fork-choice.md $(PHASE1_SPEC_DIR)/phase1-fork.md $@
# TODO: also build validator spec and light-client-sync
CURRENT_DIR = ${CURDIR}

View File

@ -16,11 +16,13 @@ Core specifications for Eth2 clients be found in [specs/](specs/). These are div
* [Fork Choice](specs/phase0/fork-choice.md)
* [Deposit Contract](specs/phase0/deposit-contract.md)
* [Honest Validator](specs/phase0/validator.md)
* [P2P Networking](specs/phase0/p2p-interface.md)
### Phase 1
* [From Phase 0 to Phase 1](specs/phase1/phase1-fork.md)
* [The Beacon Chain for Shards](specs/phase1/beacon-chain.md)
* [Custody Game](specs/phase1/custody-game.md)
* [Shard Data Chains](specs/phase1/shard-data-chains.md)
* [Misc beacon chain updates](specs/phase1/beacon-chain-misc.md)
* [Shard Transition and Fraud Proofs](specs/phase1/fraud-proofs.md)
* [Light client syncing protocol](specs/phase1/light-client-sync.md)
### Phase 2

View File

@ -94,9 +94,6 @@ PERSISTENT_COMMITTEE_PERIOD: 2048
MAX_EPOCHS_PER_CROSSLINK: 64
# 2**2 (= 4) epochs 25.6 minutes
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# 2**14 (= 16,384) epochs ~73 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384
# State vector lengths
@ -146,6 +143,73 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
DOMAIN_RANDAO: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000
DOMAIN_SHARD_PROPOSER: 0x80000000
DOMAIN_SHARD_ATTESTER: 0x81000000
# Phase 1
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
# Phase 1: Upgrade from Phase 0
# ---------------------------------------------------------------
PHASE_1_FORK_VERSION: 0x01000000
INITIAL_ACTIVE_SHARDS: 64
# Placeholder
INITIAL_GASPRICE: 10
# Phase 1: General
# ---------------------------------------------------------------
# 2**10` (= 1024)
MAX_SHARDS: 1024
# 2**3 (= 8) | online epochs | ~51 min
ONLINE_PERIOD: 8
# 2**7 (= 128)
LIGHT_CLIENT_COMMITTEE_SIZE: 128
# 2**8 (= 256) | epochs | ~27 hours
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
# 2**8 (= 256) | epochs | ~27 hours
SHARD_COMMITTEE_PERIOD: 256
# 2**18 (= 262,144)
SHARD_BLOCK_CHUNK_SIZE: 262144
# 2**2 (= 4)
MAX_SHARD_BLOCK_CHUNKS: 4
# 3 * 2**16` (= 196,608)
TARGET_SHARD_BLOCK_SIZE: 196608
# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length.
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
# len(SHARD_BLOCK_OFFSETS)
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
# 2**14 (= 16,384) Gwei
MAX_GASPRICE: 16384
# 2**5 (= 32) Gwei
MIN_GASPRICE: 32
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Phase 1: Custody Game
# ---------------------------------------------------------------
# Time parameters
# 2**1 (= 2) epochs, 12.8 minutes
RANDAO_PENALTY_EPOCHS: 2
# 2**14 (= 16,384) epochs ~73 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384
# 2**11 (= 2,048) epochs, ~9 days
EPOCHS_PER_CUSTODY_PERIOD: 2048
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**7 (= 128) epochs, ~14 hours
MAX_REVEAL_LATENESS_DECREMENT: 128
# Max operations
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256

View File

@ -21,14 +21,13 @@ MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# Jan 3, 2020
MIN_GENESIS_TIME: 1578009600
#
#
# Fork Choice
# ---------------------------------------------------------------
# 2**1 (= 1)
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2
#
# Validator
# ---------------------------------------------------------------
# [customized] process deposits more quickly, but insecure
@ -88,18 +87,12 @@ SLOTS_PER_ETH1_VOTING_PERIOD: 16
SLOTS_PER_HISTORICAL_ROOT: 64
# 2**8 (= 256) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**11 (= 2,048) epochs
PERSISTENT_COMMITTEE_PERIOD: 2048
# [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit
PERSISTENT_COMMITTEE_PERIOD: 128
# [customized] fast catchup crosslinks
MAX_EPOCHS_PER_CROSSLINK: 4
# 2**2 (= 4) epochs
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# [customized] 2**12 (= 4,096) epochs
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
# 2**2 (= 4) epochs
EPOCHS_PER_CUSTODY_PERIOD: 4
# 2**2 (= 4) epochs
CUSTODY_PERIOD_TO_RANDAO_PADDING: 4
# State vector lengths
@ -149,16 +142,75 @@ DOMAIN_BEACON_ATTESTER: 0x01000000
DOMAIN_RANDAO: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000
DOMAIN_SHARD_PROPOSER: 0x80000000
DOMAIN_SHARD_ATTESTER: 0x81000000
# Phase 1
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
# Phase 1: Upgrade from Phase 0
# ---------------------------------------------------------------
SHARD_SLOTS_PER_BEACON_SLOT: 2
EPOCHS_PER_SHARD_PERIOD: 4
# PHASE_1_FORK_EPOCH >= EPOCHS_PER_SHARD_PERIOD * 2
PHASE_1_FORK_EPOCH: 8
# PHASE_1_FORK_SLOT = PHASE_1_FORK_EPOCH * SLOTS_PER_EPOCH
PHASE_1_FORK_SLOT: 64
# [customized] for testnet distinction
PHASE_1_FORK_VERSION: 0x01000001
# [customized] reduced for testing
INITIAL_ACTIVE_SHARDS: 4
# Placeholder
INITIAL_GASPRICE: 10
# Phase 1: General
# ---------------------------------------------------------------
# [customized] reduced for testing
MAX_SHARDS: 8
# 2**3 (= 8) | online epochs
ONLINE_PERIOD: 8
# 2**7 (= 128)
LIGHT_CLIENT_COMMITTEE_SIZE: 128
# 2**8 (= 256) | epochs
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
# 2**8 (= 256) | epochs
SHARD_COMMITTEE_PERIOD: 256
# 2**18 (= 262,144)
SHARD_BLOCK_CHUNK_SIZE: 262144
# 2**2 (= 4)
MAX_SHARD_BLOCK_CHUNKS: 4
# 3 * 2**16` (= 196,608)
TARGET_SHARD_BLOCK_SIZE: 196608
# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length.
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
# len(SHARD_BLOCK_OFFSETS)
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
# 2**14 (= 16,384) Gwei
MAX_GASPRICE: 16384
# 2**5 (= 32) Gwei
MIN_GASPRICE: 32
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Phase 1: Custody Game
# ---------------------------------------------------------------
# Time parameters
# 2**1 (= 2) epochs
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
# 2**11 (= 2,048) epochs
EPOCHS_PER_CUSTODY_PERIOD: 2048
# 2**11 (= 2,048) epochs
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**7 (= 128) epochs
MAX_REVEAL_LATENESS_DECREMENT: 128
# Max operations
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256

View File

@ -9,9 +9,13 @@ from typing import (
Optional,
)
CONFIG_LOADER = '''
apply_constants_preset(globals())
'''
PHASE0_IMPORTS = '''from typing import (
Any, Dict, Set, Sequence, Tuple, Optional, TypeVar
PHASE0_IMPORTS = '''from eth2spec.config.apply_config import apply_constants_preset
from typing import (
Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar
)
from dataclasses import (
@ -30,11 +34,10 @@ from eth2spec.utils.hash_function import hash
SSZObject = TypeVar('SSZObject', bound=SSZType)
'''
PHASE1_IMPORTS = '''from typing import (
Any, Dict, Set, Sequence, MutableSequence, NewType, Tuple, Union, TypeVar
)
from math import (
log2,
PHASE1_IMPORTS = '''from eth2spec.phase0 import spec as phase0
from eth2spec.config.apply_config import apply_constants_preset
from typing import (
Any, Callable, Dict, Set, Sequence, NewType, Tuple, TypeVar
)
from dataclasses import (
@ -42,15 +45,11 @@ from dataclasses import (
field,
)
from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root,
is_zero,
)
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import (
BasicValue, Elements, BaseBytes, BaseList, SSZType,
Container, List, Vector, ByteList, ByteVector, Bitlist, Bitvector, Bits,
SSZType, Container, List, Vector, ByteList, ByteVector, Bitlist, Bitvector,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96,
uint64, bit, boolean, byte,
uint64, uint8, bit, boolean,
)
from eth2spec.utils import bls
@ -94,51 +93,15 @@ def compute_committee(indices: Sequence[ValidatorIndex], # type: ignore
if param_hash not in committee_cache:
committee_cache[param_hash] = _compute_committee(indices, seed, index, count)
return committee_cache[param_hash]
# Access to overwrite spec constants based on configuration
def apply_constants_preset(preset: Dict[str, Any]) -> None:
global_vars = globals()
for k, v in preset.items():
if k.startswith('DOMAIN_'):
global_vars[k] = DomainType(v) # domain types are defined as bytes in the configs
else:
global_vars[k] = v
# Deal with derived constants
global_vars['GENESIS_EPOCH'] = compute_epoch_at_slot(GENESIS_SLOT)
# Initialize SSZ types again, to account for changed lengths
init_SSZ_types()
'''
def remove_for_phase1(functions: Dict[str, str]):
for key, value in functions.items():
lines = value.split("\n")
lines = filter(lambda s: "[to be removed in phase 1]" not in s, lines)
functions[key] = "\n".join(lines)
def strip_comments(raw: str) -> str:
comment_line_regex = re.compile(r'^\s+# ')
lines = raw.split('\n')
out = []
for line in lines:
if not comment_line_regex.match(line):
if ' #' in line:
line = line[:line.index(' #')]
out.append(line)
return '\n'.join(out)
return committee_cache[param_hash]'''
def objects_to_spec(functions: Dict[str, str],
custom_types: Dict[str, str],
constants: Dict[str, str],
ssz_objects: Dict[str, str],
inserts: Dict[str, str],
imports: Dict[str, str],
version: str,
) -> str:
"""
Given all the objects that constitute a spec, combine them into a single pyfile.
@ -160,27 +123,18 @@ def objects_to_spec(functions: Dict[str, str],
constants[k] += " # noqa: E501"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants))
ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values())
ssz_objects_reinitialization_spec = (
'def init_SSZ_types() -> None:\n global_vars = globals()\n\n '
+ '\n\n '.join([strip_comments(re.sub(r'(?!\n\n)\n', r'\n ', value[:-1]))
for value in ssz_objects.values()])
+ '\n\n'
+ '\n'.join(map(lambda x: ' global_vars[\'%s\'] = %s' % (x, x), ssz_objects.keys()))
)
spec = (
imports
+ '\n\n' + f"version = \'{version}\'\n"
+ '\n\n' + new_type_definitions
+ '\n' + SUNDRY_CONSTANTS_FUNCTIONS
+ '\n\n' + constants_spec
+ '\n\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + CONFIG_LOADER
+ '\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + functions_spec
+ '\n' + SUNDRY_FUNCTIONS
+ '\n\n' + ssz_objects_reinitialization_spec
+ '\n'
)
# Handle @inserts
for key, value in inserts.items():
spec = re.sub('[ ]*# %s\\n' % key, value, spec)
return spec
@ -197,10 +151,10 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st
ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'ByteList', 'ByteVector'
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature',
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteVector' # to be removed after updating spec doc
'bytes', 'byte', 'ByteList', 'ByteVector'
]
@ -233,32 +187,26 @@ def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str]
and returns the newer versions of the objects in dependency order.
"""
for key, value in new_objects.items():
if key in old_objects:
# remove trailing newline
old_objects[key] = old_objects[key]
# remove leading variable name
value = re.sub(r'^class [\w]*\(Container\):\n', '', value)
old_objects[key] = old_objects.get(key, '') + value
dependency_order_ssz_objects(old_objects, custom_types)
old_objects[key] = value
return old_objects
# inserts are handled the same way as functions
combine_inserts = combine_functions
def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject:
"""
Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function.
"""
functions0, custom_types0, constants0, ssz_objects0, inserts0 = spec0
functions1, custom_types1, constants1, ssz_objects1, inserts1 = spec1
functions0, custom_types0, constants0, ssz_objects0 = spec0
functions1, custom_types1, constants1, ssz_objects1 = spec1
functions = combine_functions(functions0, functions1)
custom_types = combine_constants(custom_types0, custom_types1)
constants = combine_constants(constants0, constants1)
ssz_objects = combine_ssz_objects(ssz_objects0, ssz_objects1, custom_types)
inserts = combine_inserts(inserts0, inserts1)
return functions, custom_types, constants, ssz_objects, inserts
return functions, custom_types, constants, ssz_objects
def dependency_order_spec(objs: SpecObject):
functions, custom_types, constants, ssz_objects = objs
dependency_order_ssz_objects(ssz_objects, custom_types)
def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str,
@ -269,7 +217,8 @@ def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str,
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)
dependency_order_spec(spec_objects)
spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS, 'phase0')
if outfile is not None:
with open(outfile, 'w') as out:
out.write(spec)
@ -278,26 +227,27 @@ def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str,
def build_phase1_spec(phase0_beacon_sourcefile: str,
phase0_fork_choice_sourcefile: str,
merkle_proofs_sourcefile: str,
phase1_custody_sourcefile: str,
phase1_shard_sourcefile: str,
phase1_beacon_misc_sourcefile: str,
phase1_beacon_sourcefile: str,
phase1_fraud_sourcefile: str,
phase1_fork_choice_sourcefile: str,
phase1_fork_sourcefile: str,
outfile: str=None) -> Optional[str]:
all_sourcefiles = (
phase0_beacon_sourcefile,
phase0_fork_choice_sourcefile,
merkle_proofs_sourcefile,
phase1_custody_sourcefile,
phase1_shard_sourcefile,
phase1_beacon_misc_sourcefile,
phase1_beacon_sourcefile,
phase1_fraud_sourcefile,
phase1_fork_choice_sourcefile,
phase1_fork_sourcefile,
)
all_spescs = [get_spec(spec) for spec in all_sourcefiles]
for spec in all_spescs:
remove_for_phase1(spec[0])
spec_objects = all_spescs[0]
for value in all_spescs[1:]:
spec_objects = combine_spec_objects(spec_objects, value)
spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS)
dependency_order_spec(spec_objects)
spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS, 'phase1')
if outfile is not None:
with open(outfile, 'w') as out:
out.write(spec)
@ -316,11 +266,12 @@ If building phase 0:
If building phase 1:
1st argument is input phase0/beacon-chain.md
2nd argument is input phase0/fork-choice.md
3rd argument is input ssz/merkle-proofs.md
4th argument is input phase1/custody-game.md
5th argument is input phase1/shard-data-chains.md
6th argument is input phase1/beacon-chain-misc.md
7th argument is output spec.py
3rd argument is input phase1/custody-game.md
4th argument is input phase1/beacon-chain.md
5th argument is input phase1/fraud-proofs.md
6th argument is input phase1/fork-choice.md
7th argument is input phase1/phase1-fork.md
8th argument is output spec.py
'''
parser = ArgumentParser(description=description)
parser.add_argument("-p", "--phase", dest="phase", type=int, default=0, help="Build for phase #")
@ -333,14 +284,13 @@ If building phase 1:
else:
print(" Phase 0 requires spec, forkchoice, and v-guide inputs as well as an output file.")
elif args.phase == 1:
if len(args.files) == 7:
if len(args.files) == 8:
build_phase1_spec(*args.files)
else:
print(
" Phase 1 requires input files as well as an output file:\n"
"\t phase0: (beacon-chain.md, fork-choice.md)\n"
"\t ssz: (merkle-proofs.md)\n"
"\t phase1: (custody-game.md, shard-data-chains.md, beacon-chain-misc.md)\n"
"\t phase1: (custody-game.md, beacon-chain.md, fraud-proofs.md, fork-choice.md, phase1-fork.md)\n"
"\t and output.py"
)
else:

View File

@ -3,8 +3,6 @@ from typing import Dict, Tuple, NewType
FUNCTION_REGEX = r'^def [\w_]*'
BEGIN_INSERT_REGEX = r'# begin insert '
END_INSERT_REGEX = r'# end insert'
SpecObject = NewType('SpecObjects', Tuple[Dict[str, str], Dict[str, str], Dict[str, str], Dict[str, str]])
@ -15,22 +13,18 @@ def get_spec(file_name: str) -> SpecObject:
functions = {function_name: function_code}
constants= {constant_name: constant_code}
ssz_objects= {object_name: object}
inserts= {insert_tag: code to be inserted}
Note: This function makes heavy use of the inherent ordering of dicts,
if this is not supported by your python version, it will not work.
"""
pulling_from = None # line number of start of latest object
current_name = None # most recent section title
insert_name = None # stores the label of the current insert object
functions = {}
constants = {}
ssz_objects = {}
inserts = {}
functions: Dict[str, str] = {}
constants: Dict[str, str] = {}
ssz_objects: Dict[str, str] = {}
function_matcher = re.compile(FUNCTION_REGEX)
inserts_matcher = re.compile(BEGIN_INSERT_REGEX)
is_ssz = False
custom_types = {}
custom_types: Dict[str, str] = {}
for linenum, line in enumerate(open(file_name).readlines()):
line = line.rstrip()
if pulling_from is None and len(line) > 0 and line[0] == '#' and line[-1] == '`':
@ -40,15 +34,6 @@ def get_spec(file_name: str) -> SpecObject:
pulling_from = linenum + 1
elif line[:3] == '```':
pulling_from = None
elif inserts_matcher.match(line) is not None:
# Find @insert names
insert_name = re.search(r'@[\w]*', line).group(0)
elif insert_name is not None:
# In insert mode, either the next line is more code, or the end of the insert
if re.match(END_INSERT_REGEX, line) is not None:
insert_name = None
else:
inserts[insert_name] = inserts.get(insert_name, '') + line + '\n'
else:
# Handle function definitions & ssz_objects
if pulling_from is not None:
@ -84,4 +69,4 @@ def get_spec(file_name: str) -> SpecObject:
constants[row[0]] = row[1].replace('**TBD**', '2**32')
elif row[1].startswith('uint') or row[1].startswith('Bytes'):
custom_types[row[0]] = row[1]
return functions, custom_types, constants, ssz_objects, inserts
return SpecObject((functions, custom_types, constants, ssz_objects))

View File

@ -1189,19 +1189,13 @@ def process_slot(state: BeaconState) -> None:
### Epoch processing
*Note*: The `# @LabelHere` lines below are placeholders to show that code will be inserted here in a future phase.
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
# @process_reveal_deadlines
# @process_challenge_deadlines
process_slashings(state)
# @update_period_committee
process_final_updates(state)
# @after_process_final_updates
```
#### Helper functions
@ -1480,16 +1474,15 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# Verify that outstanding deposits are processed up to the maximum number of deposits
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
for operations, function in (
(body.proposer_slashings, process_proposer_slashing),
(body.attester_slashings, process_attester_slashing),
(body.attestations, process_attestation),
(body.deposits, process_deposit),
(body.voluntary_exits, process_voluntary_exit),
# @process_shard_receipt_proofs
):
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
function(state, operation)
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
for_ops(body.attestations, process_attestation)
for_ops(body.deposits, process_deposit)
for_ops(body.voluntary_exits, process_voluntary_exit)
```
##### Proposer slashings

View File

@ -14,7 +14,7 @@
- [Helpers](#helpers)
- [`LatestMessage`](#latestmessage)
- [`Store`](#store)
- [`get_genesis_store`](#get_genesis_store)
- [`get_forkchoice_store`](#get_forkchoice_store)
- [`get_slots_since_genesis`](#get_slots_since_genesis)
- [`get_current_slot`](#get_current_slot)
- [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start)
@ -24,6 +24,10 @@
- [`get_filtered_block_tree`](#get_filtered_block_tree)
- [`get_head`](#get_head)
- [`should_update_justified_checkpoint`](#should_update_justified_checkpoint)
- [`on_attestation` helpers](#on_attestation-helpers)
- [`validate_on_attestation`](#validate_on_attestation)
- [`store_target_checkpoint_state`](#store_target_checkpoint_state)
- [`update_latest_messages`](#update_latest_messages)
- [Handlers](#handlers)
- [`on_tick`](#on_tick)
- [`on_block`](#on_block)
@ -38,7 +42,7 @@ This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0
## Fork choice
The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_genesis_store(genesis_state)` and update `store` by running:
The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_checkpoint_store(genesis_state)` and update `store` by running:
- `on_tick(time)` whenever `time > store.time` where `time` is the current Unix time
- `on_block(block)` whenever a block `block: SignedBeaconBlock` is received
@ -79,29 +83,35 @@ class Store(object):
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
blocks: Dict[Root, BeaconBlockHeader] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
```
#### `get_genesis_store`
#### `get_forkchoice_store`
The provided anchor-state will be regarded as a trusted state, to not roll back beyond.
This should be the genesis state for a full client.
```python
def get_genesis_store(genesis_state: BeaconState) -> Store:
genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))
root = hash_tree_root(genesis_block)
justified_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
def get_forkchoice_store(anchor_state: BeaconState) -> Store:
anchor_block_header = anchor_state.latest_block_header.copy()
if anchor_block_header.state_root == Bytes32():
anchor_block_header.state_root = hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block_header)
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
return Store(
time=genesis_state.genesis_time,
genesis_time=genesis_state.genesis_time,
time=anchor_state.genesis_time,
genesis_time=anchor_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()},
blocks={anchor_root: anchor_block_header},
block_states={anchor_root: anchor_state.copy()},
checkpoint_states={justified_checkpoint: anchor_state.copy()},
)
```
@ -251,6 +261,59 @@ def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: C
return True
```
#### `on_attestation` helpers
##### `validate_on_attestation`
```python
def validate_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]
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
# Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
assert target.root in store.blocks
# Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives
assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch)
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
assert attestation.data.beacon_block_root in store.blocks
# Attestations must not be for blocks in the future. If not, the attestation should not be considered
assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot
# Attestations can only affect the fork choice of subsequent slots.
# Delay consideration in the fork choice until their slot is in the past.
assert get_current_slot(store) >= attestation.data.slot + 1
```
##### `store_target_checkpoint_state`
```python
def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None:
# Store target checkpoint state if not yet seen
if target not in store.checkpoint_states:
base_state = store.block_states[target.root].copy()
process_slots(base_state, compute_start_slot_at_epoch(target.epoch))
store.checkpoint_states[target] = base_state
```
##### `update_latest_messages`
```python
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
for i in 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=beacon_block_root)
```
### Handlers
#### `on_tick`
@ -317,42 +380,14 @@ def on_attestation(store: Store, attestation: Attestation) -> None:
An ``attestation`` that is asserted as invalid may be valid at a later time,
consider scheduling it for later processing in such case.
"""
target = attestation.data.target
validate_on_attestation(store, attestation)
store_target_checkpoint_state(store, 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]
assert target.epoch == compute_epoch_at_slot(attestation.data.slot)
# Attestations target be for a known block. If target block is unknown, delay consideration until the block is found
assert target.root in store.blocks
# Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives
base_state = store.block_states[target.root].copy()
assert get_current_slot(store) >= compute_start_slot_at_epoch(target.epoch)
# Attestations must be for a known block. If block is unknown, delay consideration until the block is found
assert attestation.data.beacon_block_root in store.blocks
# Attestations must not be for blocks in the future. If not, the attestation should not be considered
assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot
# Store target checkpoint state if not yet seen
if target not in store.checkpoint_states:
process_slots(base_state, compute_start_slot_at_epoch(target.epoch))
store.checkpoint_states[target] = base_state
target_state = store.checkpoint_states[target]
# Attestations can only affect the fork choice of subsequent slots.
# Delay consideration in the fork choice until their slot is in the past.
assert get_current_slot(store) >= attestation.data.slot + 1
# Get state at the `target` to validate attestation and calculate the committees
# Get state at the `target` to fully validate attestation
target_state = store.checkpoint_states[attestation.data.target]
indexed_attestation = get_indexed_attestation(target_state, attestation)
assert is_valid_indexed_attestation(target_state, indexed_attestation)
# Update latest messages
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)
# Update latest messages for attesting indices
update_latest_messages(store, indexed_attestation.attesting_indices, attestation)
```

View File

@ -1,254 +0,0 @@
# Phase 1 miscellaneous beacon chain changes
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Configuration](#configuration)
- [Containers](#containers)
- [`CompactCommittee`](#compactcommittee)
- [`ShardReceiptDelta`](#shardreceiptdelta)
- [`ShardReceiptProof`](#shardreceiptproof)
- [Helper functions](#helper-functions)
- [`pack_compact_validator`](#pack_compact_validator)
- [`unpack_compact_validator`](#unpack_compact_validator)
- [`committee_to_compact_committee`](#committee_to_compact_committee)
- [`verify_merkle_proof`](#verify_merkle_proof)
- [`compute_historical_state_generalized_index`](#compute_historical_state_generalized_index)
- [`get_generalized_index_of_crosslink_header`](#get_generalized_index_of_crosslink_header)
- [`process_shard_receipt_proof`](#process_shard_receipt_proof)
- [Changes](#changes)
- [Phase 0 container updates](#phase-0-container-updates)
- [`BeaconState`](#beaconstate)
- [`BeaconBlockBody`](#beaconblockbody)
- [Persistent committees](#persistent-committees)
- [Shard receipt processing](#shard-receipt-processing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Configuration
| Name | Value | Unit | Duration
| - | - | - | - |
| `MAX_SHARD_RECEIPT_PROOFS` | `2**0` (= 1) | - | - |
| `PERIOD_COMMITTEE_ROOT_LENGTH` | `2**8` (= 256) | periods | ~9 months |
| `MINOR_REWARD_QUOTIENT` | `2**8` (=256) | - | - |
| `REWARD_COEFFICIENT_BASE` | **TBD** | - | - |
## Containers
#### `CompactCommittee`
```python
class CompactCommittee(Container):
pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE]
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
```
#### `ShardReceiptDelta`
```python
class ShardReceiptDelta(Container):
index: ValidatorIndex
reward_coefficient: uint64
block_fee: Gwei
```
#### `ShardReceiptProof`
```python
class ShardReceiptProof(Container):
shard: Shard
proof: List[Bytes32, PLACEHOLDER]
receipt: List[ShardReceiptDelta, PLACEHOLDER]
```
## Helper functions
#### `pack_compact_validator`
```python
def pack_compact_validator(index: int, slashed: bool, balance_in_increments: int) -> int:
"""
Creates a compact validator object representing index, slashed status, and compressed balance.
Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with
the unpacking function.
"""
return (index << 16) + (slashed << 15) + balance_in_increments
```
#### `unpack_compact_validator`
```python
def unpack_compact_validator(compact_validator: int) -> Tuple[int, bool, int]:
"""
Returns validator index, slashed, balance // EFFECTIVE_BALANCE_INCREMENT
"""
return compact_validator >> 16, bool((compact_validator >> 15) % 2), compact_validator & (2**15 - 1)
```
#### `committee_to_compact_committee`
```python
def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee:
"""
Given a state and a list of validator indices, outputs the CompactCommittee representing them.
"""
validators = [state.validators[i] for i in committee]
compact_validators = [
pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT)
for i, v in zip(committee, validators)
]
pubkeys = [v.pubkey for v in validators]
return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators)
```
#### `verify_merkle_proof`
```python
def verify_merkle_proof(leaf: Bytes32, proof: Sequence[Bytes32], index: GeneralizedIndex, root: Root) -> bool:
assert len(proof) == get_generalized_index_length(index)
for i, h in enumerate(proof):
if get_generalized_index_bit(index, i):
leaf = hash(h + leaf)
else:
leaf = hash(leaf + h)
return leaf == root
```
#### `compute_historical_state_generalized_index`
```python
def compute_historical_state_generalized_index(earlier: ShardSlot, later: ShardSlot) -> GeneralizedIndex:
"""
Computes the generalized index of the state root of slot `earlier` based on the state root of slot `later`.
Relies on the `history_accumulator` in the `ShardState`, where `history_accumulator[i]` maintains the most
recent 2**i'th slot state. Works by tracing a `log(later-earlier)` step path from `later` to `earlier`
through intermediate blocks at the next available multiples of descending powers of two.
"""
o = GeneralizedIndex(1)
for i in range(HISTORY_ACCUMULATOR_DEPTH - 1, -1, -1):
if (later - 1) & 2**i > (earlier - 1) & 2**i:
later = later - ((later - 1) % 2**i) - 1
gindex = GeneralizedIndex(get_generalized_index(ShardState, ['history_accumulator', i]))
o = concat_generalized_indices(o, gindex)
return o
```
#### `get_generalized_index_of_crosslink_header`
```python
def get_generalized_index_of_crosslink_header(index: int) -> GeneralizedIndex:
"""
Gets the generalized index for the root of the index'th header in a crosslink.
"""
MAX_CROSSLINK_SIZE = (
MAX_SHARD_BLOCK_SIZE * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
)
assert MAX_CROSSLINK_SIZE == get_previous_power_of_two(MAX_CROSSLINK_SIZE)
return GeneralizedIndex(MAX_CROSSLINK_SIZE // SHARD_HEADER_SIZE + index)
```
#### `process_shard_receipt_proof`
```python
def process_shard_receipt_proof(state: BeaconState, receipt_proof: ShardReceiptProof) -> None:
"""
Processes a ShardReceipt object.
"""
receipt_slot = (
state.next_shard_receipt_period[receipt_proof.shard] *
SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD
)
first_slot_in_last_crosslink = state.current_crosslinks[receipt_proof.shard].start_epoch * SHARD_SLOTS_PER_EPOCH
gindex = concat_generalized_indices(
get_generalized_index_of_crosslink_header(0),
GeneralizedIndex(get_generalized_index(ShardBlockHeader, 'state_root')),
compute_historical_state_generalized_index(receipt_slot, first_slot_in_last_crosslink),
GeneralizedIndex(get_generalized_index(ShardState, 'receipt_root'))
)
assert verify_merkle_proof(
leaf=hash_tree_root(receipt_proof.receipt),
proof=receipt_proof.proof,
index=gindex,
root=state.current_crosslinks[receipt_proof.shard].data_root
)
for delta in receipt_proof.receipt:
if get_current_epoch(state) < state.validators[delta.index].withdrawable_epoch:
increase_amount = (
state.validators[delta.index].effective_balance * delta.reward_coefficient // REWARD_COEFFICIENT_BASE
)
increase_balance(state, delta.index, increase_amount)
decrease_balance(state, delta.index, delta.block_fee)
state.next_shard_receipt_period[receipt_proof.shard] += 1
proposer_index = get_beacon_proposer_index(state)
increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT))
```
## Changes
### Phase 0 container updates
Add the following fields to the end of the specified container objects.
#### `BeaconState`
```python
class BeaconState(Container):
# Period committees
period_committee_roots: Vector[Root, PERIOD_COMMITTEE_ROOT_LENGTH]
next_shard_receipt_period: Vector[uint64, SHARD_COUNT]
```
`period_committee_roots` values are initialized to `Bytes32()` (empty bytes value).
`next_shard_receipt_period` values are initialized to `compute_epoch_at_slot(PHASE_1_FORK_SLOT) // EPOCHS_PER_SHARD_PERIOD`.
#### `BeaconBlockBody`
```python
class BeaconBlockBody(Container):
shard_receipt_proofs: List[ShardReceiptProof, MAX_SHARD_RECEIPT_PROOFS]
```
`shard_receipt_proofs` is initialized to `[]`.
### Persistent committees
Run `update_period_committee` immediately before `process_final_updates`:
```python
# begin insert @update_period_committee
update_period_committee(state)
# end insert @update_period_committee
def update_period_committee(state: BeaconState) -> None:
"""
Updates period committee roots at boundary blocks.
"""
if (get_current_epoch(state) + 1) % EPOCHS_PER_SHARD_PERIOD != 0:
return
period = (get_current_epoch(state) + 1) // EPOCHS_PER_SHARD_PERIOD
committees = Vector[CompactCommittee, SHARD_COUNT]([
committee_to_compact_committee(
state,
get_period_committee(state, Shard(shard), Epoch(get_current_epoch(state) + 1)),
)
for shard in range(SHARD_COUNT)
])
state.period_committee_roots[period % PERIOD_COMMITTEE_ROOT_LENGTH] = hash_tree_root(committees)
```
### Shard receipt processing
Run `process_shard_receipt_proof` on each `ShardReceiptProof` during block processing.
```python
# begin insert @process_shard_receipt_proofs
(body.shard_receipt_proofs, process_shard_receipt_proof),
# end insert @process_shard_receipt_proofs
```

View File

@ -0,0 +1,932 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards](#ethereum-20-phase-1----the-beacon-chain-for-shards)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Configuration](#configuration)
- [Misc](#misc)
- [Updated containers](#updated-containers)
- [Extended `AttestationData`](#extended-attestationdata)
- [Extended `Attestation`](#extended-attestation)
- [Extended `PendingAttestation`](#extended-pendingattestation)
- [`IndexedAttestation`](#indexedattestation)
- [Extended `AttesterSlashing`](#extended-attesterslashing)
- [Extended `Validator`](#extended-validator)
- [Extended `BeaconBlockBody`](#extended-beaconblockbody)
- [Extended `BeaconBlock`](#extended-beaconblock)
- [Extended `SignedBeaconBlock`](#extended-signedbeaconblock)
- [Extended `BeaconState`](#extended-beaconstate)
- [New containers](#new-containers)
- [`ShardBlockWrapper`](#shardblockwrapper)
- [`ShardSignableHeader`](#shardsignableheader)
- [`ShardState`](#shardstate)
- [`ShardTransition`](#shardtransition)
- [`CompactCommittee`](#compactcommittee)
- [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper)
- [Helper functions](#helper-functions)
- [Misc](#misc-1)
- [`get_previous_slot`](#get_previous_slot)
- [`pack_compact_validator`](#pack_compact_validator)
- [`committee_to_compact_committee`](#committee_to_compact_committee)
- [`chunks_to_body_root`](#chunks_to_body_root)
- [`compute_shard_from_committee_index`](#compute_shard_from_committee_index)
- [Beacon state accessors](#beacon-state-accessors)
- [`get_active_shard_count`](#get_active_shard_count)
- [`get_online_validator_indices`](#get_online_validator_indices)
- [`get_shard_committee`](#get_shard_committee)
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [`get_light_client_committee`](#get_light_client_committee)
- [`get_indexed_attestation`](#get_indexed_attestation)
- [`get_updated_gasprice`](#get_updated_gasprice)
- [`get_start_shard`](#get_start_shard)
- [`get_shard`](#get_shard)
- [`get_next_slot_for_shard`](#get_next_slot_for_shard)
- [`get_offset_slots`](#get_offset_slots)
- [Predicates](#predicates)
- [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation)
- [Block processing](#block-processing)
- [Operations](#operations)
- [New Attestation processing](#new-attestation-processing)
- [`validate_attestation`](#validate_attestation)
- [`apply_shard_transition`](#apply_shard_transition)
- [`process_crosslink_for_shard`](#process_crosslink_for_shard)
- [`process_crosslinks`](#process_crosslinks)
- [`process_attestations`](#process_attestations)
- [New Attester slashing processing](#new-attester-slashing-processing)
- [Shard transition false positives](#shard-transition-false-positives)
- [Light client processing](#light-client-processing)
- [Epoch transition](#epoch-transition)
- [Custody game updates](#custody-game-updates)
- [Online-tracking](#online-tracking)
- [Light client committee updates](#light-client-committee-updates)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
TODO
<!-- /TOC -->
## Introduction
This document describes the extensions made to the Phase 0 design of The Beacon Chain
to facilitate the new shards as part of Phase 1 of Eth2.
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `Shard` | `uint64` | a shard number |
| `OnlineEpochs` | `uint8` | online countdown epochs |
## Configuration
Configuration is not namespaced. Instead it is strictly an extension;
no constants of phase 0 change, but new constants are adopted for changing behaviors.
### Misc
| Name | Value | Unit | Duration |
| - | - | - | - |
| `MAX_SHARDS` | `2**10` (= 1024) |
| `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 min |
| `LIGHT_CLIENT_COMMITTEE_SIZE` | `2**7` (= 128) |
| `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours |
| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours |
| `SHARD_BLOCK_CHUNK_SIZE` | `2**18` (= 262,144) | |
| `MAX_SHARD_BLOCK_CHUNKS` | `2**2` (= 4) | |
| `TARGET_SHARD_BLOCK_SIZE` | `3 * 2**16` (= 196,608) | |
| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | |
| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | |
| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | |
| `MIN_GASPRICE` | `Gwei(2**5)` (= 32) | Gwei | |
| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | |
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | |
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | |
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | |
## Updated containers
The following containers have updated definitions in Phase 1.
### Extended `AttestationData`
```python
class AttestationData(Container):
slot: Slot
index: CommitteeIndex
# LMD GHOST vote
beacon_block_root: Root
# FFG vote
source: Checkpoint
target: Checkpoint
# Current-slot shard block root
head_shard_root: Root
# Shard transition root
shard_transition_root: Root
```
### Extended `Attestation`
```python
class Attestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]
signature: BLSSignature
```
### Extended `PendingAttestation`
```python
class PendingAttestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: AttestationData
inclusion_delay: Slot
proposer_index: ValidatorIndex
crosslink_success: boolean
```
### `IndexedAttestation`
```python
class IndexedAttestation(Container):
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]
attestation: Attestation
```
#### Extended `AttesterSlashing`
Note that the `attestation_1` and `attestation_2` have a new `IndexedAttestation` definition.
```python
class AttesterSlashing(Container):
attestation_1: IndexedAttestation
attestation_2: IndexedAttestation
```
### Extended `Validator`
```python
class Validator(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
effective_balance: Gwei # Balance at stake
slashed: boolean
# Status epochs
activation_eligibility_epoch: Epoch # When criteria for activation were met
activation_epoch: Epoch
exit_epoch: Epoch
withdrawable_epoch: Epoch # When validator can withdraw funds
# Custody game
# next_custody_secret_to_reveal is initialised to the custody period
# (of the particular validator) in which the validator is activated
# = get_custody_period_for_validator(...)
next_custody_secret_to_reveal: uint64
max_reveal_lateness: Epoch
```
### Extended `BeaconBlockBody`
```python
class BeaconBlockBody(Container):
randao_reveal: BLSSignature
eth1_data: Eth1Data # Eth1 data vote
graffiti: Bytes32 # Arbitrary data
# Slashings
proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
# Attesting
attestations: List[Attestation, MAX_ATTESTATIONS]
# Entry & exit
deposits: List[Deposit, MAX_DEPOSITS]
voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
# Custody game
custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS]
custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS]
early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS]
# Shards
shard_transitions: Vector[ShardTransition, MAX_SHARDS]
# Light clients
light_client_signature_bitfield: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]
light_client_signature: BLSSignature
```
### Extended `BeaconBlock`
Note that the `body` has a new `BeaconBlockBody` definition.
```python
class BeaconBlock(Container):
slot: Slot
parent_root: Root
state_root: Root
body: BeaconBlockBody
```
#### Extended `SignedBeaconBlock`
Note that the `message` has a new `BeaconBlock` definition.
```python
class SignedBeaconBlock(Container):
message: BeaconBlock
signature: BLSSignature
```
### Extended `BeaconState`
Note that aside from the new additions, `Validator` and `PendingAttestation` have new definitions.
```python
class BeaconState(Container):
# Versioning
genesis_time: uint64
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, SLOTS_PER_ETH1_VOTING_PERIOD]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Root, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Attestations
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint # Previous epoch snapshot
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Phase 1
shard_states: List[ShardState, MAX_SHARDS]
online_countdown: List[OnlineEpochs, VALIDATOR_REGISTRY_LIMIT] # not a raw byte array, considered its large size.
current_light_committee: CompactCommittee
next_light_committee: CompactCommittee
# Custody game
# Future derived secrets already exposed; contains the indices of the exposed validator
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH],
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
```
## New containers
The following containers are new in Phase 1.
### `ShardBlockWrapper`
_Wrapper for being broadcasted over the network._
```python
class ShardBlockWrapper(Container):
shard_parent_root: Root
beacon_parent_root: Root
slot: Slot
body: ByteList[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE]
signature: BLSSignature
```
### `ShardSignableHeader`
```python
class ShardSignableHeader(Container):
shard_parent_root: Root
beacon_parent_root: Root
slot: Slot
body_root: Root
```
### `ShardState`
```python
class ShardState(Container):
slot: Slot
gasprice: Gwei
data: Bytes32
latest_block_root: Root
```
### `ShardTransition`
```python
class ShardTransition(Container):
# Starting from slot
start_slot: Slot
# Shard block lengths
shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION]
# Shard data roots
shard_data_roots: List[List[Bytes32, MAX_SHARD_BLOCK_CHUNKS], MAX_SHARD_BLOCKS_PER_ATTESTATION]
# Intermediate shard states
shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION]
# Proposer signature aggregate
proposer_signature_aggregate: BLSSignature
```
### `CompactCommittee`
```python
class CompactCommittee(Container):
pubkeys: List[BLSPubkey, MAX_VALIDATORS_PER_COMMITTEE]
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
```
### `AttestationCustodyBitWrapper`
```python
class AttestationCustodyBitWrapper(Container):
attestation_data_root: Root
block_index: uint64
bit: boolean
```
## Helper functions
### Misc
#### `get_previous_slot`
```python
def get_previous_slot(slot: Slot) -> Slot:
if slot > 0:
return Slot(slot - 1)
else:
return Slot(0)
```
#### `pack_compact_validator`
```python
def pack_compact_validator(index: int, slashed: bool, balance_in_increments: int) -> int:
"""
Creates a compact validator object representing index, slashed status, and compressed balance.
Takes as input balance-in-increments (// EFFECTIVE_BALANCE_INCREMENT) to preserve symmetry with
the unpacking function.
"""
return (index << 16) + (slashed << 15) + balance_in_increments
```
#### `committee_to_compact_committee`
```python
def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee:
"""
Given a state and a list of validator indices, outputs the CompactCommittee representing them.
"""
validators = [state.validators[i] for i in committee]
compact_validators = [
pack_compact_validator(i, v.slashed, v.effective_balance // EFFECTIVE_BALANCE_INCREMENT)
for i, v in zip(committee, validators)
]
pubkeys = [v.pubkey for v in validators]
return CompactCommittee(pubkeys=pubkeys, compact_validators=compact_validators)
```
#### `chunks_to_body_root`
```python
def chunks_to_body_root(chunks: List[Bytes32, MAX_SHARD_BLOCK_CHUNKS]) -> Root:
empty_chunk_root = hash_tree_root(ByteList[SHARD_BLOCK_CHUNK_SIZE]())
return hash_tree_root(Vector[Bytes32, MAX_SHARD_BLOCK_CHUNKS](
chunks + [empty_chunk_root] * (MAX_SHARD_BLOCK_CHUNKS - len(chunks))
))
```
#### `compute_shard_from_committee_index`
```python
def compute_shard_from_committee_index(state: BeaconState, index: CommitteeIndex, slot: Slot) -> Shard:
active_shards = get_active_shard_count(state)
return Shard((index + get_start_shard(state, slot)) % active_shards)
```
### Beacon state accessors
#### `get_active_shard_count`
```python
def get_active_shard_count(state: BeaconState) -> uint64:
return len(state.shard_states) # May adapt in the future, or change over time.
```
#### `get_online_validator_indices`
```python
def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]:
active_validators = get_active_validator_indices(state, get_current_epoch(state))
return set([i for i in active_validators if state.online_countdown[i] != 0])
```
#### `get_shard_committee`
```python
def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]:
source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD
if source_epoch > 0:
source_epoch -= SHARD_COMMITTEE_PERIOD
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
return compute_committee(active_validator_indices, seed, shard, get_active_shard_count(beacon_state))
```
#### `get_shard_proposer_index`
```python
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard)
r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8])
return committee[r % len(committee)]
```
#### `get_light_client_committee`
```python
def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]:
source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD
if source_epoch > 0:
source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT)
active_shards = get_active_shard_count(beacon_state)
return compute_committee(active_validator_indices, seed, 0, active_shards)[:TARGET_COMMITTEE_SIZE]
```
#### `get_indexed_attestation`
```python
def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation:
committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index)
return IndexedAttestation(
committee=committee,
attestation=attestation,
)
```
#### `get_updated_gasprice`
```python
def get_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei:
if length > TARGET_SHARD_BLOCK_SIZE:
delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE)
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
return min(prev_gasprice + delta, MAX_GASPRICE)
else:
delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length)
// TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT)
return max(prev_gasprice, MIN_GASPRICE + delta) - delta
```
#### `get_start_shard`
```python
def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
# TODO: implement start shard logic
return Shard(0)
```
#### `get_shard`
```python
def get_shard(state: BeaconState, attestation: Attestation) -> Shard:
return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
```
#### `get_next_slot_for_shard`
```python
def get_next_slot_for_shard(state: BeaconState, shard: Shard) -> Slot:
return Slot(state.shard_states[shard].slot + 1)
```
#### `get_offset_slots`
```python
def get_offset_slots(state: BeaconState, start_slot: Slot) -> Sequence[Slot]:
return [Slot(start_slot + x) for x in SHARD_BLOCK_OFFSETS if start_slot + x < state.slot]
```
### Predicates
#### Updated `is_valid_indexed_attestation`
Note that this replaces the Phase 0 `is_valid_indexed_attestation`.
```python
def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool:
"""
Check if ``indexed_attestation`` has valid indices and signature.
"""
# Verify aggregate signature
all_pubkeys = []
all_signing_roots = []
attestation = indexed_attestation.attestation
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
aggregation_bits = attestation.aggregation_bits
assert len(aggregation_bits) == len(indexed_attestation.committee)
if len(attestation.custody_bits_blocks) == 0:
# fall back on phase0 behavior if there is no shard data.
for participant, abit in zip(indexed_attestation.committee, aggregation_bits):
if abit:
all_pubkeys.append(state.validators[participant].pubkey)
signing_root = compute_signing_root(indexed_attestation.attestation.data, domain)
return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature)
else:
for i, custody_bits in enumerate(attestation.custody_bits_blocks):
assert len(custody_bits) == len(indexed_attestation.committee)
for participant, abit, cbit in zip(indexed_attestation.committee, aggregation_bits, custody_bits):
if abit:
all_pubkeys.append(state.validators[participant].pubkey)
# Note: only 2N distinct message hashes
all_signing_roots.append(compute_signing_root(
AttestationCustodyBitWrapper(hash_tree_root(attestation.data), i, cbit), domain))
else:
assert not cbit
return bls.AggregateVerify(zip(all_pubkeys, all_signing_roots), signature=attestation.signature)
```
### Block processing
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_randao(state, block.body)
process_eth1_data(state, block.body)
verify_shard_transition_false_positives(state, block.body)
process_light_client_signatures(state, block.body)
process_operations(state, block.body)
```
#### Operations
```python
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# Verify that outstanding deposits are processed up to the maximum number of deposits
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
# New attestation processing
process_attestations(state, body, body.attestations)
for_ops(body.deposits, process_deposit)
for_ops(body.voluntary_exits, process_voluntary_exit)
# See custody game spec.
process_custody_game_operations(state, body)
# TODO process_operations(body.shard_receipt_proofs, process_shard_receipt_proofs)
```
##### New Attestation processing
###### `validate_attestation`
```python
def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.index < get_committee_count_at_slot(state, data.slot)
assert data.index < get_active_shard_count(state)
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
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(committee)
if attestation.data.target.epoch == get_current_epoch(state):
assert attestation.data.source == state.current_justified_checkpoint
else:
assert attestation.data.source == state.previous_justified_checkpoint
shard = get_shard(state, attestation)
shard_start_slot = get_next_slot_for_shard(state, shard)
# Signature check
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Type 1: on-time attestations
if attestation.custody_bits_blocks != []:
# Correct slot
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot
# Correct data root count
assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard_start_slot))
# Correct parent block root
assert data.beacon_block_root == get_block_root_at_slot(state, get_previous_slot(state.slot))
# Type 2: no shard transition, no custody bits # TODO: could only allow for older attestations.
else:
# assert state.slot - compute_start_slot_at_epoch(compute_epoch_at_slot(data.slot)) < SLOTS_PER_EPOCH
assert data.shard_transition_root == Root()
```
###### `apply_shard_transition`
```python
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
# Slot the attestation starts counting from
start_slot = get_next_slot_for_shard(state, shard)
# Correct data root count
offset_slots = get_offset_slots(state, start_slot)
assert (
len(transition.shard_data_roots)
== len(transition.shard_states)
== len(transition.shard_block_lengths)
== len(offset_slots)
)
assert transition.start_slot == start_slot
# Reconstruct shard headers
headers = []
proposers = []
shard_parent_root = state.shard_states[shard].latest_block_root
for i in range(len(offset_slots)):
if any(transition.shard_data_roots):
headers.append(ShardSignableHeader(
shard_parent_root=shard_parent_root,
parent_hash=get_block_root_at_slot(state, get_previous_slot(state.slot)),
slot=offset_slots[i],
body_root=chunks_to_body_root(transition.shard_data_roots[i])
))
proposers.append(get_shard_proposer_index(state, shard, offset_slots[i]))
shard_parent_root = hash_tree_root(headers[-1])
# Verify correct calculation of gas prices and slots and chunk roots
prev_gasprice = state.shard_states[shard].gasprice
for i in range(len(offset_slots)):
shard_state = transition.shard_states[i]
block_length = transition.shard_block_lengths[i]
chunks = transition.shard_data_roots[i]
assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, block_length)
assert shard_state.slot == offset_slots[i]
assert len(chunks) == block_length // SHARD_BLOCK_CHUNK_SIZE
prev_gasprice = shard_state.gasprice
pubkeys = [state.validators[proposer].pubkey for proposer in proposers]
signing_roots = [
compute_signing_root(header, get_domain(state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(header.slot)))
for header in headers
]
# Verify combined proposer signature
assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate)
# Save updated state
state.shard_states[shard] = transition.shard_states[-1]
state.shard_states[shard].slot = state.slot - 1
```
###### `process_crosslink_for_shard`
```python
def process_crosslink_for_shard(state: BeaconState,
shard: Shard,
shard_transition: ShardTransition,
attestations: Sequence[Attestation]) -> Root:
committee = get_beacon_committee(state, get_current_epoch(state), shard)
online_indices = get_online_validator_indices(state)
# Loop over all shard transition roots
shard_transition_roots = set([a.data.shard_transition_root for a in attestations])
for shard_transition_root in sorted(shard_transition_roots):
transition_attestations = [a for a in attestations if a.data.shard_transition_root == shard_transition_root]
transition_participants: Set[ValidatorIndex] = set()
for attestation in transition_attestations:
participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
transition_participants = transition_participants.union(participants)
enough_online_stake = (
get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >=
get_total_balance(state, online_indices.intersection(committee)) * 2
)
# If not enough stake, try next transition root
if not enough_online_stake:
continue
# Attestation <-> shard transition consistency
assert shard_transition_root == hash_tree_root(shard_transition)
assert (
attestation.data.head_shard_root
== chunks_to_body_root(shard_transition.shard_data_roots[-1])
)
# Apply transition
apply_shard_transition(state, shard, shard_transition)
# Apply proposer reward and cost
beacon_proposer_index = get_beacon_proposer_index(state)
estimated_attester_reward = sum([get_base_reward(state, attester) for attester in transition_participants])
proposer_reward = Gwei(estimated_attester_reward // PROPOSER_REWARD_QUOTIENT)
increase_balance(state, beacon_proposer_index, proposer_reward)
states_slots_lengths = zip(
shard_transition.shard_states,
get_offset_slots(state, get_next_slot_for_shard(state, shard)),
shard_transition.shard_block_lengths
)
for shard_state, slot, length in states_slots_lengths:
proposer_index = get_shard_proposer_index(state, shard, slot)
decrease_balance(state, proposer_index, shard_state.gasprice * length)
# Return winning transition root
return shard_transition_root
# No winning transition root, ensure empty and return empty root
assert shard_transition == ShardTransition()
return Root()
```
###### `process_crosslinks`
```python
def process_crosslinks(state: BeaconState,
block_body: BeaconBlockBody,
attestations: Sequence[Attestation]) -> Set[Tuple[Shard, Root]]:
winners: Set[Tuple[Shard, Root]] = set()
committee_count = get_committee_count_at_slot(state, state.slot)
for committee_index in map(CommitteeIndex, range(committee_count)):
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
# All attestations in the block for this shard
shard_attestations = [
attestation for attestation in attestations
if get_shard(state, attestation) == shard and attestation.data.slot == state.slot
]
shard_transition = block_body.shard_transitions[shard]
winning_root = process_crosslink_for_shard(state, shard, shard_transition, shard_attestations)
if winning_root != Root():
winners.add((shard, winning_root))
return winners
```
###### `process_attestations`
```python
def process_attestations(state: BeaconState, block_body: BeaconBlockBody, attestations: Sequence[Attestation]) -> None:
# Basic validation
for attestation in attestations:
validate_attestation(state, attestation)
# Process crosslinks
winners = process_crosslinks(state, block_body, attestations)
# Store pending attestations for epoch processing
for attestation in attestations:
is_winning_transition = (get_shard(state, attestation), attestation.data.shard_transition_root) in winners
pending_attestation = PendingAttestation(
aggregation_bits=attestation.aggregation_bits,
data=attestation.data,
inclusion_delay=state.slot - attestation.data.slot,
crosslink_success=is_winning_transition and attestation.data.slot == state.slot,
proposer_index=get_beacon_proposer_index(state),
)
if attestation.data.target.epoch == get_current_epoch(state):
state.current_epoch_attestations.append(pending_attestation)
else:
state.previous_epoch_attestations.append(pending_attestation)
```
##### New Attester slashing processing
```python
def get_indices_from_committee(
committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE],
bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]:
assert len(bits) == len(committee)
return List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE](
[validator_index for i, validator_index in enumerate(committee) if bits[i]]
)
```
```python
def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None:
indexed_attestation_1 = attester_slashing.attestation_1
indexed_attestation_2 = attester_slashing.attestation_2
assert is_slashable_attestation_data(
indexed_attestation_1.attestation.data,
indexed_attestation_2.attestation.data,
)
assert is_valid_indexed_attestation(state, indexed_attestation_1)
assert is_valid_indexed_attestation(state, indexed_attestation_2)
indices_1 = get_indices_from_committee(
indexed_attestation_1.committee,
indexed_attestation_1.attestation.aggregation_bits,
)
indices_2 = get_indices_from_committee(
indexed_attestation_2.committee,
indexed_attestation_2.attestation.aggregation_bits,
)
slashed_any = False
indices = set(indices_1).intersection(indices_2)
for index in sorted(indices):
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
slash_validator(state, index)
slashed_any = True
assert slashed_any
```
#### Shard transition false positives
```python
def verify_shard_transition_false_positives(state: BeaconState, block_body: BeaconBlockBody) -> None:
# Verify that a `shard_transition` in a block is empty if an attestation was not processed for it
for shard in range(get_active_shard_count(state)):
if state.shard_states[shard].slot != state.slot - 1:
assert block_body.shard_transitions[shard] == ShardTransition()
```
#### Light client processing
```python
def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockBody) -> None:
committee = get_light_client_committee(state, get_current_epoch(state))
total_reward = Gwei(0)
signer_pubkeys = []
for bit_index, participant_index in enumerate(committee):
if block_body.light_client_signature_bitfield[bit_index]:
signer_pubkeys.append(state.validators[participant_index].pubkey)
increase_balance(state, participant_index, get_base_reward(state, participant_index))
total_reward += get_base_reward(state, participant_index)
increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT))
slot = get_previous_slot(state.slot)
signing_root = compute_signing_root(get_block_root_at_slot(state, slot),
get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot)))
return bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature)
```
### Epoch transition
This epoch transition overrides the phase0 epoch transition:
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_reveal_deadlines(state)
process_slashings(state)
process_final_updates(state)
process_custody_final_updates(state)
process_online_tracking(state)
process_light_client_committee_updates(state)
```
#### Custody game updates
`process_reveal_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./1_custody-game.md),
#### Online-tracking
```python
def process_online_tracking(state: BeaconState) -> None:
# Slowly remove validators from the "online" set if they do not show up
for index in range(len(state.validators)):
if state.online_countdown[index] != 0:
state.online_countdown[index] = state.online_countdown[index] - 1
# Process pending attestations
for pending_attestation in state.current_epoch_attestations + state.previous_epoch_attestations:
for index in get_attesting_indices(state, pending_attestation.data, pending_attestation.aggregation_bits):
state.online_countdown[index] = ONLINE_PERIOD
```
#### Light client committee updates
```python
def process_light_client_committee_updates(state: BeaconState) -> None:
# Update light client committees
if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0:
state.current_light_committee = state.next_light_committee
new_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD)
state.next_light_committee = committee_to_compact_committee(state, new_committee)
```

View File

@ -4,107 +4,62 @@
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Terminology](#terminology)
- [Constants](#constants)
- [Misc](#misc)
- [Custody game parameters](#custody-game-parameters)
- [Configuration](#configuration)
- [Time parameters](#time-parameters)
- [Max operations per block](#max-operations-per-block)
- [Reward and penalty quotients](#reward-and-penalty-quotients)
- [Signature domain types](#signature-domain-types)
- [TODO PLACEHOLDER](#todo-placeholder)
- [Data structures](#data-structures)
- [Custody objects](#custody-objects)
- [`CustodyChunkChallenge`](#custodychunkchallenge)
- [`CustodyBitChallenge`](#custodybitchallenge)
- [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord)
- [`CustodyBitChallengeRecord`](#custodybitchallengerecord)
- [`CustodyResponse`](#custodyresponse)
- [New beacon operations](#new-beacon-operations)
- [New Beacon Chain operations](#new-beacon-chain-operations)
- [`CustodySlashing`](#custodyslashing)
- [`SignedCustodySlashing`](#signedcustodyslashing)
- [`CustodyKeyReveal`](#custodykeyreveal)
- [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal)
- [Phase 0 container updates](#phase-0-container-updates)
- [`Validator`](#validator)
- [`BeaconState`](#beaconstate)
- [`BeaconBlockBody`](#beaconblockbody)
- [Helpers](#helpers)
- [`ceillog2`](#ceillog2)
- [`is_valid_merkle_branch_with_mixin`](#is_valid_merkle_branch_with_mixin)
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
- [`legendre_bit`](#legendre_bit)
- [`custody_subchunkify`](#custody_subchunkify)
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
- [`get_chunk_bits_root`](#get_chunk_bits_root)
- [`custody_atoms`](#custody_atoms)
- [`compute_custody_bit`](#compute_custody_bit)
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
- [`get_custody_period_for_validator`](#get_custody_period_for_validator)
- [`replace_empty_or_append`](#replace_empty_or_append)
- [Per-block processing](#per-block-processing)
- [Operations](#operations)
- [Custody Game Operations](#custody-game-operations)
- [Custody key reveals](#custody-key-reveals)
- [Early derived secret reveals](#early-derived-secret-reveals)
- [Chunk challenges](#chunk-challenges)
- [Bit challenges](#bit-challenges)
- [Custody responses](#custody-responses)
- [Custody Slashings](#custody-slashings)
- [Per-epoch processing](#per-epoch-processing)
- [Handling of custody-related deadlines](#handling-of-custody-related-deadlines)
- [Handling of reveal deadlines](#handling-of-reveal-deadlines)
- [Final updates](#final-updates)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [Phase 0](../phase0/beacon-chain.md) specification.
## Terminology
- **Custody game**
- **Custody period**
- **Custody chunk**
- **Custody chunk bit**
- **Custody chunk challenge**
- **Custody bit**
- **Custody bit challenge**
- **Custody key**
- **Custody key reveal**
- **Custody key mask**
- **Custody response**
- **Custody response deadline**
## Constants
### Misc
| Name | Value |
| Name | Value | Unit |
| - | - |
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` |
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
| `MAX_EPOCHS_PER_CROSSLINK` | `2**6` (= 64) | epochs | ~7 hours |
| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes |
### Custody game parameters
| Name | Value |
| - | - |
| `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) |
| `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) |
| `BYTES_PER_CUSTODY_SUBCHUNK` | `48` |
| `CHUNKS_PER_EPOCH` | `2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK` |
| `MAX_CUSTODY_CHUNKS` | `MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH` |
| `CUSTODY_DATA_DEPTH` | `ceillog2(MAX_CUSTODY_CHUNKS) + 1` |
| `CUSTODY_CHUNK_BIT_DEPTH` | `ceillog2(MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH // 256) + 2` |
## Configuration
### Time parameters
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days |
| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
| `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` | epochs | ~73 days |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days |
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
| `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours |
@ -113,17 +68,16 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| Name | Value |
| - | - |
| `MAX_CUSTODY_KEY_REVEALS` | `2**4` (= 16) |
| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) |
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` |
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) |
| `MAX_CUSTODY_BIT_CHALLENGES` | `2**2` (= 4) |
| `MAX_CUSTODY_RESPONSES` | `2**5` (= 32) |
| `MAX_CUSTODY_SLASHINGS` | `1` |
### Reward and penalty quotients
| Name | Value |
| - | - |
| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) |
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
### Signature domain types
@ -131,79 +85,35 @@ The following types are defined, mapping into `DomainType` (little endian):
| Name | Value |
| - | - |
| `DOMAIN_CUSTODY_BIT_CHALLENGE` | `DomainType('0x06000000')` |
### TODO PLACEHOLDER
| Name | Value |
| - | - |
| `PLACEHOLDER` | `2**32` |
| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` |
## Data structures
### Custody objects
### New Beacon Chain operations
#### `CustodyChunkChallenge`
#### `CustodySlashing`
```python
class CustodyChunkChallenge(Container):
responder_index: ValidatorIndex
class CustodySlashing(Container):
# Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check.
# (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data.
data_index: uint64
malefactor_index: ValidatorIndex
malefactor_secret: BLSSignature
whistleblower_index: ValidatorIndex
shard_transition: ShardTransition
attestation: Attestation
chunk_index: uint64
data: ByteList[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE]
```
#### `CustodyBitChallenge`
#### `SignedCustodySlashing`
```python
class CustodyBitChallenge(Container):
responder_index: ValidatorIndex
attestation: Attestation
challenger_index: ValidatorIndex
responder_key: BLSSignature
chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]
class SignedCustodySlashing(Container):
message: CustodySlashing
signature: BLSSignature
```
#### `CustodyChunkChallengeRecord`
```python
class CustodyChunkChallengeRecord(Container):
challenge_index: uint64
challenger_index: ValidatorIndex
responder_index: ValidatorIndex
inclusion_epoch: Epoch
data_root: Root
depth: uint64
chunk_index: uint64
```
#### `CustodyBitChallengeRecord`
```python
class CustodyBitChallengeRecord(Container):
challenge_index: uint64
challenger_index: ValidatorIndex
responder_index: ValidatorIndex
inclusion_epoch: Epoch
data_root: Root
chunk_count: uint64
chunk_bits_merkle_root: Root
responder_key: BLSSignature
```
#### `CustodyResponse`
```python
class CustodyResponse(Container):
challenge_index: uint64
chunk_index: uint64
chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
data_branch: List[Bytes32, CUSTODY_DATA_DEPTH]
chunk_bits_branch: List[Bytes32, CUSTODY_CHUNK_BIT_DEPTH]
chunk_bits_leaf: Bitvector[256]
```
### New beacon operations
#### `CustodyKeyReveal`
@ -233,82 +143,9 @@ class EarlyDerivedSecretReveal(Container):
mask: Bytes32
```
### Phase 0 container updates
Add the following fields to the end of the specified container objects. Fields with underlying type `uint64` are initialized to `0` and list fields are initialized to `[]`.
#### `Validator`
```python
class Validator(Container):
# next_custody_secret_to_reveal is initialised to the custody period
# (of the particular validator) in which the validator is activated
# = get_custody_period_for_validator(...)
next_custody_secret_to_reveal: uint64
max_reveal_lateness: Epoch
```
#### `BeaconState`
```python
class BeaconState(Container):
custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, PLACEHOLDER]
custody_bit_challenge_records: List[CustodyBitChallengeRecord, PLACEHOLDER]
custody_challenge_index: uint64
# Future derived secrets already exposed; contains the indices of the exposed validator
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
exposed_derived_secrets: Vector[List[ValidatorIndex, PLACEHOLDER],
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
```
#### `BeaconBlockBody`
```python
class BeaconBlockBody(Container):
custody_chunk_challenges: List[CustodyChunkChallenge, PLACEHOLDER]
custody_bit_challenges: List[CustodyBitChallenge, PLACEHOLDER]
custody_responses: List[CustodyResponse, PLACEHOLDER]
custody_key_reveals: List[CustodyKeyReveal, PLACEHOLDER]
early_derived_secret_reveals: List[EarlyDerivedSecretReveal, PLACEHOLDER]
```
## Helpers
### `ceillog2`
```python
def ceillog2(x: uint64) -> int:
return (x - 1).bit_length()
```
### `is_valid_merkle_branch_with_mixin`
```python
def is_valid_merkle_branch_with_mixin(leaf: Bytes32,
branch: Sequence[Bytes32],
depth: uint64,
index: uint64,
root: Root,
mixin: uint64) -> bool:
value = leaf
for i in range(depth):
if index // (2**i) % 2:
value = hash(branch[i] + value)
else:
value = hash(value + branch[i])
value = hash(value + mixin.to_bytes(32, "little"))
return value == root
```
### `get_crosslink_chunk_count`
```python
def get_custody_chunk_count(crosslink: Crosslink) -> int:
crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch)
return crosslink_length * CHUNKS_PER_EPOCH
```
### `legendre_bit`
Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this.
@ -338,37 +175,27 @@ def legendre_bit(a: int, q: int) -> int:
return 0
```
### `custody_subchunkify`
### `custody_atoms`
Given one proof of custody chunk, returns the proof of custody subchunks of the correct sizes.
Given one set of data, return the custody atoms: each atom will be combined with one legendre bit.
```python
def custody_subchunkify(bytez: bytes) -> Sequence[bytes]:
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_SUBCHUNK)
return [bytez[i:i + BYTES_PER_CUSTODY_SUBCHUNK]
for i in range(0, len(bytez), BYTES_PER_CUSTODY_SUBCHUNK)]
def get_custody_atoms(bytez: bytes) -> Sequence[bytes]:
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_ATOM) # right-padding
return [bytez[i:i + BYTES_PER_CUSTODY_ATOM]
for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)]
```
### `get_custody_chunk_bit`
### `compute_custody_bit`
```python
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
def compute_custody_bit(key: BLSSignature, data: bytes) -> bit:
full_G2_element = bls.signature_to_G2(key)
s = full_G2_element[0].coeffs
bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q)
for i, subchunk in enumerate(custody_subchunkify(chunk))]
return bool(sum(bits) % 2)
```
### `get_chunk_bits_root`
```python
def get_chunk_bits_root(chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]) -> bit:
aggregated_bits = 0
for i, b in enumerate(chunk_bits):
aggregated_bits += 2**i * b
return legendre_bit(aggregated_bits, BLS12_381_Q)
bits = [legendre_bit(sum(s[i % 2]**i * int.from_bytes(atom, "little")), BLS12_381_Q)
for i, atom in enumerate(get_custody_atoms(data))]
# XOR all atom bits
return bit(sum(bits) % 2)
```
### `get_randao_epoch_for_custody_period`
@ -382,38 +209,31 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat
### `get_custody_period_for_validator`
```python
def get_custody_period_for_validator(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> int:
'''
Return the reveal period for a given validator.
'''
epoch = get_current_epoch(state) if epoch is None else epoch
return (epoch + validator_index % EPOCHS_PER_CUSTODY_PERIOD) // EPOCHS_PER_CUSTODY_PERIOD
```
### `replace_empty_or_append`
```python
def replace_empty_or_append(list: MutableSequence[Any], new_element: Any) -> int:
for i in range(len(list)):
if is_zero(list[i]):
list[i] = new_element
return i
list.append(new_element)
return len(list) - 1
```
## Per-block processing
### Operations
### Custody Game Operations
Add the following operations to the per-block processing, in the order given below and after all other operations in Phase 0.
```python
def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) -> None:
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.custody_key_reveals, process_custody_key_reveal)
for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal)
for_ops(body.custody_slashings, process_custody_slashing)
```
#### Custody key reveals
Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`.
For each `reveal` in `block.body.custody_key_reveals`, run the following function:
```python
def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> None:
"""
@ -423,7 +243,8 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
revealer = state.validators[reveal.revealer_index]
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index)
assert revealer.next_custody_secret_to_reveal < get_custody_period_for_validator(state, reveal.revealer_index)
custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state))
assert revealer.next_custody_secret_to_reveal < custody_reveal_period
# Revealed validator is active or exited, but not withdrawn
assert is_slashable_validator(revealer, get_current_epoch(state))
@ -448,7 +269,7 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
# Process reveal
revealer.next_custody_secret_to_reveal += 1
# Reward Block Preposer
# Reward Block Proposer
proposer_index = get_beacon_proposer_index(state)
increase_balance(
state,
@ -459,10 +280,6 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
#### Early derived secret reveals
Verify that `len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_SECRET_REVEALS`.
For each `reveal` in `block.body.early_derived_secret_reveals`, run the following function:
```python
def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None:
"""
@ -520,252 +337,95 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived
state.exposed_derived_secrets[derived_secret_location].append(reveal.revealed_index)
```
#### Chunk challenges
Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`.
For each `challenge` in `block.body.custody_chunk_challenges`, run the following function:
#### Custody Slashings
```python
def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None:
def process_custody_slashing(state: BeaconState, signed_custody_slashing: SignedCustodySlashing) -> None:
custody_slashing = signed_custody_slashing.message
attestation = custody_slashing.attestation
# Any signed custody-slashing should result in at least one slashing.
# If the custody bits are valid, then the claim itself is slashed.
malefactor = state.validators[custody_slashing.malefactor_index]
whistleblower = state.validators[custody_slashing.whistleblower_index]
domain = get_domain(state, DOMAIN_CUSTODY_BIT_SLASHING, get_current_epoch(state))
signing_root = compute_signing_root(custody_slashing, domain)
assert bls.Verify(whistleblower.pubkey, signing_root, signed_custody_slashing.signature)
# Verify that the whistleblower is slashable
assert is_slashable_validator(whistleblower, get_current_epoch(state))
# Verify that the claimed malefactor is slashable
assert is_slashable_validator(malefactor, get_current_epoch(state))
# Verify the attestation
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation))
# Verify it is not too late to challenge
assert (compute_epoch_at_slot(challenge.attestation.data.slot)
>= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY)
responder = state.validators[challenge.responder_index]
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
# Verify the responder participated in the attestation
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits)
assert challenge.responder_index in attesters
# Verify the challenge is not a duplicate
for record in state.custody_chunk_challenge_records:
assert (
record.data_root != challenge.attestation.data.crosslink.data_root or
record.chunk_index != challenge.chunk_index
)
# Verify depth
depth = ceillog2(get_custody_chunk_count(challenge.attestation.data.crosslink))
assert challenge.chunk_index < 2**depth
# Add new chunk challenge record
new_record = CustodyChunkChallengeRecord(
challenge_index=state.custody_challenge_index,
challenger_index=get_beacon_proposer_index(state),
responder_index=challenge.responder_index,
inclusion_epoch=get_current_epoch(state),
data_root=challenge.attestation.data.crosslink.data_root,
depth=depth,
chunk_index=challenge.chunk_index,
)
replace_empty_or_append(state.custody_chunk_challenge_records, new_record)
state.custody_challenge_index += 1
# Postpone responder withdrawability
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
```
#### Bit challenges
Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGES`.
For each `challenge` in `block.body.custody_bit_challenges`, run the following function:
```python
def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None:
attestation = challenge.attestation
epoch = attestation.data.target.epoch
shard = attestation.data.crosslink.shard
# Verify challenge signature
challenger = state.validators[challenge.challenger_index]
domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state))
# TODO incorrect hash-tree-root, but this changes with phase 1 PR #1483
assert bls.Verify(challenger.pubkey, compute_signing_root(challenge, domain), challenge.signature)
# Verify challenger is slashable
assert is_slashable_validator(challenger, get_current_epoch(state))
# Verify attestation
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Verify attestation is eligible for challenging
responder = state.validators[challenge.responder_index]
assert get_current_epoch(state) <= get_randao_epoch_for_custody_period(
get_custody_period_for_validator(state, challenge.responder_index, epoch),
challenge.responder_index
) + 2 * EPOCHS_PER_CUSTODY_PERIOD + responder.max_reveal_lateness
# Verify the responder participated in the attestation
# TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding.
# TODO: can do a single combined merkle proof of data being attested.
# Verify the shard transition is indeed attested by the attestation
shard_transition = custody_slashing.shard_transition
assert hash_tree_root(shard_transition) == attestation.shard_transition_root
# Verify that the provided data matches the shard-transition
shard_chunk_roots = shard_transition.shard_data_roots[custody_slashing.data_index]
assert hash_tree_root(custody_slashing.data) == chunks_to_body_root(shard_chunk_roots)
# Verify existence and participation of claimed malefactor
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
assert challenge.responder_index in attesters
# Verifier challenger is not already challenging
for record in state.custody_bit_challenge_records:
assert record.challenger_index != challenge.challenger_index
# Verify the responder custody key
assert custody_slashing.malefactor_index in attesters
# Verify the malefactor custody key
epoch_to_sign = get_randao_epoch_for_custody_period(
get_custody_period_for_validator(state, challenge.responder_index, epoch),
challenge.responder_index,
get_custody_period_for_validator(custody_slashing.malefactor_index, attestation.data.target.epoch),
custody_slashing.malefactor_index,
)
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
assert bls.Verify(responder.pubkey, compute_signing_root(epoch_to_sign, domain), challenge.responder_key)
# Verify the chunk count
chunk_count = get_custody_chunk_count(attestation.data.crosslink)
assert chunk_count == len(challenge.chunk_bits)
# Verify custody bit is incorrect
committee = get_beacon_committee(state, epoch, shard)
custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)]
assert custody_bit != get_chunk_bits_root(challenge.chunk_bits)
# Add new bit challenge record
new_record = CustodyBitChallengeRecord(
challenge_index=state.custody_challenge_index,
challenger_index=challenge.challenger_index,
responder_index=challenge.responder_index,
inclusion_epoch=get_current_epoch(state),
data_root=attestation.data.crosslink.data_root,
chunk_count=chunk_count,
chunk_bits_merkle_root=hash_tree_root(challenge.chunk_bits),
responder_key=challenge.responder_key,
)
replace_empty_or_append(state.custody_bit_challenge_records, new_record)
state.custody_challenge_index += 1
# Postpone responder withdrawability
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
signing_root = compute_signing_root(epoch_to_sign, domain)
assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret)
# Get the custody bit
custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index]
committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)]
# Compute the custody bit
computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data)
# Verify the claim
if claimed_custody_bit != computed_custody_bit:
# Slash the malefactor, reward the other committee members
slash_validator(state, custody_slashing.malefactor_index)
others_count = len(committee) - 1
whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count)
for attester_index in attesters:
if attester_index != custody_slashing.malefactor_index:
increase_balance(state, attester_index, whistleblower_reward)
# No special whisteblower reward: it is expected to be an attester. Others are free to slash too however.
else:
# The claim was false, the custody bit was correct. Slash the whistleblower that induced this work.
slash_validator(state, custody_slashing.whistleblower_index)
```
#### Custody responses
Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`.
For each `response` in `block.body.custody_responses`, run the following function:
```python
def process_custody_response(state: BeaconState, response: CustodyResponse) -> None:
chunk_challenge = next((record for record in state.custody_chunk_challenge_records
if record.challenge_index == response.challenge_index), None)
if chunk_challenge is not None:
return process_chunk_challenge_response(state, response, chunk_challenge)
bit_challenge = next((record for record in state.custody_bit_challenge_records
if record.challenge_index == response.challenge_index), None)
if bit_challenge is not None:
return process_bit_challenge_response(state, response, bit_challenge)
assert False
```
```python
def process_chunk_challenge_response(state: BeaconState,
response: CustodyResponse,
challenge: CustodyChunkChallengeRecord) -> None:
# Verify chunk index
assert response.chunk_index == challenge.chunk_index
# Verify bit challenge data is null
assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == Bytes32()
# Verify minimum delay
assert get_current_epoch(state) >= challenge.inclusion_epoch + MAX_SEED_LOOKAHEAD
# Verify the chunk matches the crosslink data root
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.data_branch,
depth=challenge.depth,
index=response.chunk_index,
root=challenge.data_root,
)
# Clear the challenge
records = state.custody_chunk_challenge_records
records[records.index(challenge)] = CustodyChunkChallengeRecord()
# Reward the proposer
proposer_index = get_beacon_proposer_index(state)
increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT))
```
```python
def process_bit_challenge_response(state: BeaconState,
response: CustodyResponse,
challenge: CustodyBitChallengeRecord) -> None:
# Verify chunk index
assert response.chunk_index < challenge.chunk_count
# Verify responder has not been slashed
responder = state.validators[challenge.responder_index]
assert not responder.slashed
# Verify the chunk matches the crosslink data root
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.data_branch,
depth=ceillog2(challenge.chunk_count),
index=response.chunk_index,
root=challenge.data_root,
)
# Verify the chunk bit leaf matches the challenge data
assert is_valid_merkle_branch_with_mixin(
leaf=hash_tree_root(response.chunk_bits_leaf),
branch=response.chunk_bits_branch,
depth=ceillog2(MAX_CUSTODY_CHUNKS // 256),
index=response.chunk_index // 256,
root=challenge.chunk_bits_merkle_root,
mixin=challenge.chunk_count,
)
# Verify the chunk bit does not match the challenge chunk bit
assert (get_custody_chunk_bit(challenge.responder_key, response.chunk)
!= response.chunk_bits_leaf[response.chunk_index % 256])
# Clear the challenge
records = state.custody_bit_challenge_records
records[records.index(challenge)] = CustodyBitChallengeRecord()
# Slash challenger
slash_validator(state, challenge.challenger_index, challenge.responder_index)
```
## Per-epoch processing
### Handling of custody-related deadlines
### Handling of reveal deadlines
Run `process_reveal_deadlines(state)` immediately after `process_registry_updates(state)`:
Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`:
```python
# begin insert @process_reveal_deadlines
process_reveal_deadlines(state)
# end insert @process_reveal_deadlines
def process_reveal_deadlines(state: BeaconState) -> None:
epoch = get_current_epoch(state)
for index, validator in enumerate(state.validators):
deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
if get_custody_period_for_validator(state, ValidatorIndex(index)) > deadline:
if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal:
slash_validator(state, ValidatorIndex(index))
```
Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`:
### Final updates
After `process_final_updates(state)`, additional updates are made for the custody game:
```python
# begin insert @process_challenge_deadlines
process_challenge_deadlines(state)
# end insert @process_challenge_deadlines
def process_challenge_deadlines(state: BeaconState) -> None:
for custody_chunk_challenge in state.custody_chunk_challenge_records:
if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index)
records = state.custody_chunk_challenge
records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord()
for custody_bit_challenge in state.custody_bit_challenge_records:
if get_current_epoch(state) > custody_bit_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, custody_bit_challenge.responder_index, custody_bit_challenge.challenger_index)
records = state.custody_bit_challenge_records
records[records.index(custody_bit_challenge)] = CustodyBitChallengeRecord()
```
Append this to `process_final_updates(state)`:
```python
# begin insert @after_process_final_updates
after_process_final_updates(state)
# end insert @after_process_final_updates
def after_process_final_updates(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
def process_custody_final_updates(state: BeaconState) -> None:
# Clean up exposed RANDAO key reveals
state.exposed_derived_secrets[current_epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = []
# Reset withdrawable epochs if challenge records are empty
records = state.custody_chunk_challenge_records + state.custody_bit_challenge_records
validator_indices_in_records = set(
[record.challenger_index for record in records] + [record.responder_index for record in records]
)
for index, validator in enumerate(state.validators):
if index not in validator_indices_in_records:
if validator.exit_epoch != FAR_FUTURE_EPOCH and validator.withdrawable_epoch == FAR_FUTURE_EPOCH:
validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = []
```

View File

@ -0,0 +1,52 @@
# Ethereum 2.0 Phase 1 -- Beacon Chain Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Handlers](#handlers)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
## Fork choice
Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible.
The rest of the fork choice remains stable.
### Handlers
```python
def on_attestation(store: Store, attestation: Attestation) -> None:
"""
Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire.
An ``attestation`` that is asserted as invalid may be valid at a later time,
consider scheduling it for later processing in such case.
"""
validate_on_attestation(store, attestation)
store_target_checkpoint_state(store, attestation.data.target)
# Get state at the `target` to fully validate attestation
target_state = store.checkpoint_states[attestation.data.target]
indexed_attestation = get_indexed_attestation(target_state, attestation)
assert is_valid_indexed_attestation(target_state, indexed_attestation)
# Update latest messages for attesting indices
attesting_indices = [
index for i, index in enumerate(indexed_attestation.committee)
if attestation.aggregation_bits[i]
]
update_latest_messages(store, attesting_indices, attestation)
```

View File

@ -0,0 +1,70 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs](#ethereum-20-phase-1----shard-transition-and-fraud-proofs)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Fraud proofs](#fraud-proofs)
- [Shard state transition function](#shard-state-transition-function)
- [Honest committee member behavior](#honest-committee-member-behavior)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
TODO
<!-- /TOC -->
## Introduction
This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0.
## Fraud proofs
TODO. The intent is to have a single universal fraud proof type, which contains the following parts:
1. An on-time attestation on some `shard` signing a `ShardTransition`
2. An index `i` of a particular position to focus on
3. The `ShardTransition` itself
4. The full body of the block
5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing
The proof verifies that one of the two conditions is false:
1. `custody_bits[i][j] != generate_custody_bit(subkey, block_contents)` for any `j`
2. `execute_state_transition(shard, slot, transition.shard_states[i-1].data, hash_tree_root(parent), get_shard_proposer_index(state, shard, slot), block_contents) != transition.shard_states[i].data` (if `i=0` then instead use `parent.shard_states[shard][-1].data`)
## Shard state transition function
```python
def shard_state_transition(shard: Shard,
slot: Slot,
pre_state: Root,
previous_beacon_root: Root,
proposer_pubkey: BLSPubkey,
block_data: ByteVector[MAX_SHARD_BLOCK_CHUNKS * SHARD_BLOCK_CHUNK_SIZE]) -> Root:
# We will add something more substantive in phase 2
return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(block_data))
```
## Honest committee member behavior
Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure:
* Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`.
* For `slot in get_offset_slots(state, start_slot)`, do the following:
* Look for all valid proposals for `slot`; that is, a Bytes `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover.
* If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))`
* If `len(choices) == 1`, do `proposals.append(choices[0])`
* If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`.
* If `proposals[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, shard, slot), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged.
Make an attestation using `shard_data_roots = [hash_tree_root(proposal) for proposal in proposals]` and `shard_state_roots = shard_states`.

View File

@ -49,7 +49,7 @@ We define the following Python custom types for type hinting and readability:
### `LightClientUpdate`
```python
class LightClientUpdate(container):
class LightClientUpdate(Container):
# Shard block root (and authenticating signature data)
shard_block_root: Root
fork_version: Version

121
specs/phase1/phase1-fork.md Normal file
View File

@ -0,0 +1,121 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1](#ethereum-20-phase-1----from-phase-0-to-phase-1)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Phase 1](#fork-to-phase-1)
- [Fork trigger.](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
TODO
<!-- /TOC -->
## Introduction
This document describes the process of moving from Phase 0 to Phase 1 of Ethereum 2.0.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
| `INITIAL_GASPRICE` | `Gwei(10)` |
## Fork to Phase 1
### Fork trigger.
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork.
### Upgrading the state
After `process_slots` of Phase 0 finishes, but before the first Phase 1 block is processed, an irregular state change is made to upgrade to Phase 1.
```python
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
epoch = get_current_epoch(pre)
post = BeaconState(
genesis_time=pre.genesis_time,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=PHASE_1_FORK_VERSION,
epoch=epoch,
),
# History
latest_block_header=pre.latest_block_header,
block_roots=pre.block_roots,
state_roots=pre.state_roots,
historical_roots=pre.historical_roots,
# Eth1
eth1_data=pre.eth1_data,
eth1_data_votes=pre.eth1_data_votes,
eth1_deposit_index=pre.eth1_deposit_index,
# Registry
validators=List[Validator, VALIDATOR_REGISTRY_LIMIT](
Validator(
pubkey=phase0_validator.pubkey,
withdrawal_credentials=phase0_validator.withdrawal_credentials,
effective_balance=phase0_validator.effective_balance,
slashed=phase0_validator.slashed,
activation_eligibility_epoch=phase0_validator.activation_eligibility_epoch,
activation_epoch=phase0_validator.activation_eligibility_epoch,
exit_epoch=phase0_validator.exit_epoch,
withdrawable_epoch=phase0_validator.withdrawable_epoch,
next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch),
max_reveal_lateness=0, # TODO custody refactor. Outdated?
) for i, phase0_validator in enumerate(pre.validators)
),
balances=pre.balances,
# Randomness
randao_mixes=pre.randao_mixes,
# Slashings
slashings=pre.slashings,
# Attestations
# previous_epoch_attestations is cleared on upgrade.
previous_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](),
# empty in pre state, since the upgrade is performed just after an epoch boundary.
current_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](),
# Finality
justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
# Phase 1
shard_states=List[ShardState, MAX_SHARDS](
ShardState(
slot=pre.slot,
gasprice=INITIAL_GASPRICE,
data=Root(),
latest_block_root=Root(),
) for i in range(INITIAL_ACTIVE_SHARDS)
),
online_countdown=[ONLINE_PERIOD] * len(pre.validators), # all online
current_light_committee=CompactCommittee(), # computed after state creation
next_light_committee=CompactCommittee(),
# Custody game
custody_challenge_index=0,
# exposed_derived_secrets will fully default to zeroes
)
next_epoch = Epoch(epoch + 1)
post.current_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, epoch))
post.next_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, next_epoch))
return post
```

View File

@ -1,444 +0,0 @@
# Ethereum 2.0 Phase 1 -- Shard Data Chains
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Configuration](#configuration)
- [Misc](#misc)
- [Initial values](#initial-values)
- [Time parameters](#time-parameters)
- [State list lengths](#state-list-lengths)
- [Rewards and penalties](#rewards-and-penalties)
- [Signature domain types](#signature-domain-types)
- [Containers](#containers)
- [`Crosslink`](#crosslink)
- [`ShardBlock`](#shardblock)
- [`ShardBlockHeader`](#shardblockheader)
- [`ShardState`](#shardstate)
- [`ShardAttestationData`](#shardattestationdata)
- [Helper functions](#helper-functions)
- [Misc](#misc-1)
- [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot)
- [`compute_shard_period_start_epoch`](#compute_shard_period_start_epoch)
- [Beacon state accessors](#beacon-state-accessors)
- [`get_period_committee`](#get_period_committee)
- [`get_shard_committee`](#get_shard_committee)
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [Shard state mutators](#shard-state-mutators)
- [`process_delta`](#process_delta)
- [Genesis](#genesis)
- [`get_genesis_shard_state`](#get_genesis_shard_state)
- [`get_genesis_shard_block`](#get_genesis_shard_block)
- [Shard state transition function](#shard-state-transition-function)
- [Period processing](#period-processing)
- [Block processing](#block-processing)
- [Block header](#block-header)
- [Attestations](#attestations)
- [Block body](#block-body)
- [Shard fork choice rule](#shard-fork-choice-rule)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document describes the shard transition function (data layer only) and the shard fork choice rule as part of Phase 1 of Ethereum 2.0.
## Custom types
| Name | SSZ equivalent | Description |
| - | - | - |
| `Shard` | `uint64` | a shard number |
| `ShardSlot` | `uint64` | a shard slot number |
## Configuration
### Misc
| Name | Value |
| - | - |
| `SHARD_COUNT` | `2**10` (= 1,024) |
| `MIN_BLOCK_BODY_PRICE` | `2**0` (= 1) |
| `MAX_PERIOD_COMMITTEE_SIZE` | `2**7` (= 128) |
| `SHARD_HEADER_SIZE` | `2**10` (= 1024) |
| `SHARD_BLOCK_SIZE_TARGET` | `2**14` (= 16,384) |
| `MAX_SHARD_BLOCK_SIZE` | `2**16` (= 65,536) |
### Initial values
| Name | Value | Unit |
| - | - |
| `SHARD_GENESIS_EPOCH` | **TBD** | Epoch |
### Time parameters
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `SHARD_SLOTS_PER_EPOCH` | `2**7` (= 128) | shard slots | 6.4 minutes |
| `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours |
### State list lengths
| Name | Value |
| - | - |
| `HISTORY_ACCUMULATOR_DEPTH` | `2**6` (= 64) |
### Rewards and penalties
| Name | Value |
| - | - |
| `BLOCK_BODY_PRICE_QUOTIENT` | `2**3` (= 8) |
### Signature domain types
| Name | Value |
| - | - |
| `DOMAIN_SHARD_PROPOSER` | `DomainType('0x80000000')` |
| `DOMAIN_SHARD_ATTESTER` | `DomainType('0x81000000')` |
## Containers
### `Crosslink`
```python
# Crosslink is a placeholder to appease the build script until phase 1 is reworked
class Crosslink(Container):
shard: Shard
```
### `ShardBlock`
```python
class ShardBlock(Container):
shard: Shard
slot: ShardSlot
beacon_block_root: Root
parent_root: Root
state_root: Root
body: List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE]
block_size_sum: uint64
aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE]
attestations: BLSSignature
signature: BLSSignature
```
### `ShardBlockHeader`
```python
class ShardBlockHeader(Container):
shard: Shard
slot: ShardSlot
beacon_block_root: Root
parent_root: Root
state_root: Root
body_root: Root
block_size_sum: uint64
aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE]
attestations: BLSSignature
signature: BLSSignature
```
### `ShardState`
```python
class ShardState(Container):
shard: Shard
slot: ShardSlot
history_accumulator: Vector[Bytes32, HISTORY_ACCUMULATOR_DEPTH]
latest_block_header: ShardBlockHeader
block_size_sum: uint64
# Fees and rewards
block_body_price: Gwei
older_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE]
older_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE]
newer_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE]
newer_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE]
```
### `ShardAttestationData`
```python
class ShardAttestationData(Container):
slot: ShardSlot
parent_root: Root
```
## Helper functions
### Misc
#### `compute_epoch_of_shard_slot`
```python
def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch:
return Epoch(slot // SHARD_SLOTS_PER_EPOCH)
```
#### `compute_shard_period_start_epoch`
```python
def compute_shard_period_start_epoch(epoch: Epoch, lookback: uint64) -> Epoch:
return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD)
```
### Beacon state accessors
#### `get_period_committee`
```python
def get_period_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]:
active_validator_indices = get_active_validator_indices(beacon_state, epoch)
seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_ATTESTER)
return compute_committee(active_validator_indices, seed, shard, SHARD_COUNT)[:MAX_PERIOD_COMMITTEE_SIZE]
```
#### `get_shard_committee`
```python
def get_shard_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]:
older_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 2))
newer_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 1))
# Every epoch cycle out validators from the older committee and cycle in validators from the newer committee
older_subcommittee = [i for i in older_committee if i % EPOCHS_PER_SHARD_PERIOD > epoch % EPOCHS_PER_SHARD_PERIOD]
newer_subcommittee = [i for i in newer_committee if i % EPOCHS_PER_SHARD_PERIOD <= epoch % EPOCHS_PER_SHARD_PERIOD]
return older_subcommittee + newer_subcommittee
```
#### `get_shard_proposer_index`
```python
def get_shard_proposer_index(beacon_state: BeaconState, shard: Shard, slot: ShardSlot) -> ValidatorIndex:
epoch = get_current_epoch(beacon_state)
shard_committee = get_shard_committee(beacon_state, shard, epoch)
active_indices = [i for i in shard_committee if is_active_validator(beacon_state.validators[i], epoch)]
assert any(active_indices)
epoch_seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER)
seed = hash(epoch_seed + int_to_bytes(slot, length=8) + int_to_bytes(shard, length=8))
return compute_proposer_index(beacon_state, active_indices, seed)
```
### Shard state mutators
#### `process_delta`
```python
def process_delta(beacon_state: BeaconState,
shard_state: ShardState,
index: ValidatorIndex,
delta: Gwei,
positive: bool=True) -> None:
epoch = compute_epoch_of_shard_slot(shard_state.slot)
older_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 2))
newer_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 1))
if index in older_committee:
if positive:
shard_state.older_committee_positive_deltas[older_committee.index(index)] += delta
else:
shard_state.older_committee_negative_deltas[older_committee.index(index)] += delta
elif index in newer_committee:
if positive:
shard_state.newer_committee_positive_deltas[newer_committee.index(index)] += delta
else:
shard_state.newer_committee_negative_deltas[newer_committee.index(index)] += delta
```
## Genesis
### `get_genesis_shard_state`
```python
def get_genesis_shard_state(shard: Shard) -> ShardState:
return ShardState(
shard=shard,
slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH),
latest_block_header=ShardBlockHeader(
shard=shard,
slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH),
body_root=hash_tree_root(List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE]()),
),
block_body_price=MIN_BLOCK_BODY_PRICE,
)
```
### `get_genesis_shard_block`
```python
def get_genesis_shard_block(shard: Shard) -> ShardBlock:
return ShardBlock(
shard=shard,
slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH),
state_root=hash_tree_root(get_genesis_shard_state(shard)),
)
```
## Shard state transition function
```python
def shard_state_transition(beacon_state: BeaconState,
shard_state: ShardState,
block: ShardBlock,
validate_state_root: bool=False) -> ShardState:
# Process slots (including those with no blocks) since block
process_shard_slots(shard_state, block.slot)
# Process block
process_shard_block(beacon_state, shard_state, block)
# Validate state root (`validate_state_root == True` in production)
if validate_state_root:
assert block.state_root == hash_tree_root(shard_state)
# Return post-state
return shard_state
```
```python
def process_shard_slots(shard_state: ShardState, slot: ShardSlot) -> None:
assert shard_state.slot <= slot
while shard_state.slot < slot:
process_shard_slot(shard_state)
# Process shard period on the start slot of the next shard period
if (shard_state.slot + 1) % (SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0:
process_shard_period(shard_state)
shard_state.slot += ShardSlot(1)
```
```python
def process_shard_slot(shard_state: ShardState) -> None:
# Cache state root
previous_state_root = hash_tree_root(shard_state)
if shard_state.latest_block_header.state_root == Bytes32():
shard_state.latest_block_header.state_root = previous_state_root
# Cache state root in history accumulator
depth = 0
while shard_state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_DEPTH:
shard_state.history_accumulator[depth] = previous_state_root
depth += 1
```
### Period processing
```python
def process_shard_period(shard_state: ShardState) -> None:
# Rotate committee deltas
shard_state.older_committee_positive_deltas = shard_state.newer_committee_positive_deltas
shard_state.older_committee_negative_deltas = shard_state.newer_committee_negative_deltas
shard_state.newer_committee_positive_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)]
shard_state.newer_committee_negative_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)]
```
### Block processing
```python
def process_shard_block(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None:
process_shard_block_header(beacon_state, shard_state, block)
process_shard_attestations(beacon_state, shard_state, block)
process_shard_block_body(beacon_state, shard_state, block)
```
#### Block header
```python
def process_shard_block_header(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None:
# Verify the shard number
assert block.shard == shard_state.shard
# Verify the slot number
assert block.slot == shard_state.slot
# Verify the beacon chain root
epoch = compute_epoch_of_shard_slot(shard_state.slot)
assert epoch * SLOTS_PER_EPOCH == beacon_state.slot
beacon_block_header = BeaconBlockHeader(
slot=beacon_state.latest_block_header.slot,
parent_root=beacon_state.latest_block_header.parent_root,
state_root=beacon_state.latest_block_header.state_root,
body_root=beacon_state.latest_block_header.body_root,
)
if beacon_block_header.state_root == Bytes32():
beacon_block_header.state_root = hash_tree_root(beacon_state)
assert block.beacon_block_root == hash_tree_root(beacon_block_header)
# Verify the parent root
assert block.parent_root == hash_tree_root(shard_state.latest_block_header)
# Save current block as the new latest block
shard_state.latest_block_header = ShardBlockHeader(
shard=block.shard,
slot=block.slot,
beacon_block_root=block.beacon_block_root,
parent_root=block.parent_root,
# `state_root` is zeroed and overwritten in the next `process_shard_slot` call
body_root=hash_tree_root(block.body),
block_size_sum=block.block_size_sum,
aggregation_bits=block.aggregation_bits,
attestations=block.attestations,
# `signature` is zeroed
)
# Verify the sum of the block sizes since genesis
shard_state.block_size_sum += SHARD_HEADER_SIZE + len(block.body)
assert block.block_size_sum == shard_state.block_size_sum
# Verify proposer is not slashed
proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
proposer = beacon_state.validators[proposer_index]
assert not proposer.slashed
# Verify proposer signature
domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot))
assert bls.Verify(proposer.pubkey, compute_signing_root(block, domain), block.signature)
```
#### Attestations
```python
def process_shard_attestations(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None:
pubkeys = []
attestation_count = 0
shard_committee = get_shard_committee(beacon_state, shard_state.shard, block.slot)
for i, validator_index in enumerate(shard_committee):
if block.aggregation_bits[i]:
pubkeys.append(beacon_state.validators[validator_index].pubkey)
process_delta(beacon_state, shard_state, validator_index, get_base_reward(beacon_state, validator_index))
attestation_count += 1
# Verify there are no extraneous bits set beyond the shard committee
for i in range(len(shard_committee), 2 * MAX_PERIOD_COMMITTEE_SIZE):
assert block.aggregation_bits[i] == 0b0
# Verify attester aggregate signature
domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot))
shard_attestation_data = ShardAttestationData(slot=shard_state.slot, parent_root=block.parent_root)
signing_root = compute_signing_root(shard_attestation_data, domain)
assert bls.FastAggregateVerify(pubkeys, signing_root, block.attestations)
# Proposer micro-reward
proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
reward = attestation_count * get_base_reward(beacon_state, proposer_index) // PROPOSER_REWARD_QUOTIENT
process_delta(beacon_state, shard_state, proposer_index, Gwei(reward))
```
#### Block body
```python
def process_shard_block_body(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None:
# Verify block body size is a multiple of the header size
assert len(block.body) % SHARD_HEADER_SIZE == 0
# Apply proposer block body fee
block_body_fee = shard_state.block_body_price * len(block.body) // MAX_SHARD_BLOCK_SIZE
proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee), positive=False) # Burn
process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee // PROPOSER_REWARD_QUOTIENT)) # Reward
# Calculate new block body price
block_size = SHARD_HEADER_SIZE + len(block.body)
QUOTIENT = MAX_SHARD_BLOCK_SIZE * BLOCK_BODY_PRICE_QUOTIENT
if block_size > SHARD_BLOCK_SIZE_TARGET:
price_delta = Gwei(shard_state.block_body_price * (block_size - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT)
# The maximum block body price caps the amount burnt on fees within a shard period
MAX_BLOCK_BODY_PRICE = MAX_EFFECTIVE_BALANCE // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_EPOCH
shard_state.block_body_price = Gwei(min(MAX_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta))
else:
price_delta = Gwei(shard_state.block_body_price * (SHARD_BLOCK_SIZE_TARGET - block_size) // QUOTIENT)
shard_state.block_body_price = Gwei(max(MIN_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta))
```
## Shard fork choice rule
The fork choice rule for any shard is LMD GHOST using the shard attestations of the shard committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (i.e. `beacon_state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_block_root` is the block in the main beacon chain at the specified `slot` should be considered. (If the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than that slot.)

View File

@ -18,7 +18,9 @@ def load_presets(configs_dir, presets_name) -> Dict[str, Any]:
loaded = yaml.load(path)
out = dict()
for k, v in loaded.items():
if v.startswith("0x"):
if isinstance(v, list):
out[k] = v
elif isinstance(v, str) and v.startswith("0x"):
out[k] = bytes.fromhex(v[2:])
else:
out[k] = int(v)

View File

@ -0,0 +1,22 @@
from preset_loader import loader
from typing import Dict, Any
presets: Dict[str, Any] = {}
# Access to overwrite spec constants based on configuration
# This is called by the spec module after declaring its globals, and applies the loaded presets.
def apply_constants_preset(spec_globals: Dict[str, Any]) -> None:
global presets
for k, v in presets.items():
if k.startswith('DOMAIN_'):
spec_globals[k] = spec_globals['DomainType'](v) # domain types are defined as bytes in the configs
else:
spec_globals[k] = v
# Load presets from a file. This does not apply the presets.
# To apply the presets, reload the spec module (it will re-initialize with the presets taken from here).
def load_presets(configs_path, config_name):
global presets
presets = loader.load_presets(configs_path, config_name)

View File

@ -1,5 +1,5 @@
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.config import apply_config
from eth2spec.test.context import reload_specs
# We import pytest only when it's present, i.e. when we are running tests.
# The test-cases themselves can be generated without installing pytest.
@ -33,7 +33,6 @@ def pytest_addoption(parser):
@fixture(autouse=True)
def config(request):
config_name = request.config.getoption("--config")
from preset_loader import loader
presets = loader.load_presets('../../../configs/', config_name)
spec_phase0.apply_constants_preset(presets)
spec_phase1.apply_constants_preset(presets)
apply_config.load_presets('../../../configs/', config_name)
# now that the presets are loaded, reload the specs to apply them
reload_specs()

View File

@ -1,29 +1,64 @@
from eth2spec.phase0 import spec as spec_phase0
# from eth2spec.phase1 import spec as spec_phase1
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.utils import bls
from .helpers.genesis import create_genesis_state
from .utils import vector_test, with_meta_tags
from typing import Any, Callable, Sequence
from typing import Any, Callable, Sequence, TypedDict, Protocol
from importlib import reload
def reload_specs():
reload(spec_phase0)
reload(spec_phase1)
# Some of the Spec module functionality is exposed here to deal with phase-specific changes.
# TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing.
class Spec(Protocol):
version: str
class Phase0(Spec):
...
class Phase1(Spec):
def upgrade_to_phase1(self, state: spec_phase0.BeaconState) -> spec_phase1.BeaconState:
...
# add transfer, bridge, etc. as the spec evolves
class SpecForks(TypedDict, total=False):
phase0: Phase0
phase1: Phase1
def with_custom_state(balances_fn: Callable[[Any], Sequence[int]],
threshold_fn: Callable[[Any], int]):
def deco(fn):
def entry(*args, **kw):
def entry(*args, spec: Spec, phases: SpecForks, **kw):
try:
spec = kw['spec']
p0 = phases["phase0"]
balances = balances_fn(p0)
activation_threshold = threshold_fn(p0)
balances = balances_fn(spec)
activation_threshold = threshold_fn(spec)
state = create_genesis_state(spec=p0, validator_balances=balances,
activation_threshold=activation_threshold)
if spec.version == 'phase1':
# TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper.
# Decide based on performance/consistency results later.
state = phases["phase1"].upgrade_to_phase1(state)
kw['state'] = create_genesis_state(spec=spec, validator_balances=balances,
activation_threshold=activation_threshold)
kw['state'] = state
except KeyError:
raise TypeError('Spec decorator must come within state decorator to inject spec into state.')
return fn(*args, **kw)
return fn(*args, spec=spec, phases=phases, **kw)
return entry
return deco
@ -69,6 +104,19 @@ def misc_balances(spec):
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + [spec.MIN_DEPOSIT_AMOUNT] * num_misc_validators
def single_phase(fn):
"""
Decorator that filters out the phases data.
most state tests only focus on behavior of a single phase (the "spec").
This decorator is applied as part of spec_state_test(fn).
"""
def entry(*args, **kw):
if 'phases' in kw:
kw.pop('phases')
return fn(*args, **kw)
return entry
# BLS is turned off by default *for performance purposes during TESTING*.
# The runner of the test can indicate the preferred setting (test generators prefer BLS to be ON).
# - Some tests are marked as BLS-requiring, and ignore this setting.
@ -88,9 +136,9 @@ def spec_test(fn):
return vector_test()(bls_switch(fn))
# shorthand for decorating @spectest() @with_state
# shorthand for decorating @spectest() @with_state @single_phase
def spec_state_test(fn):
return spec_test(with_state(fn))
return spec_test(with_state(single_phase(fn)))
def expect_assertion_error(fn):
@ -169,15 +217,12 @@ def with_all_phases_except(exclusion_phases):
return decorator
def with_phases(phases):
def with_phases(phases, other_phases=None):
"""
Decorator factory that returns a decorator that runs a test for the appropriate phases
Decorator factory that returns a decorator that runs a test for the appropriate phases.
Additional phases that do not initially run, but are made available through the test, are optional.
"""
def decorator(fn):
def run_with_spec_version(spec, *args, **kw):
kw['spec'] = spec
return fn(*args, **kw)
def wrapper(*args, **kw):
run_phases = phases
@ -188,12 +233,25 @@ def with_phases(phases):
return
run_phases = [phase]
available_phases = set(run_phases)
if other_phases is not None:
available_phases += set(other_phases)
# TODO: test state is dependent on phase0 but is immediately transitioned to phase1.
# A new state-creation helper for phase 1 may be in place, and then phase1+ tests can run without phase0
available_phases.add('phase0')
phase_dir = {}
if 'phase0' in available_phases:
phase_dir['phase0'] = spec_phase0
if 'phase1' in available_phases:
phase_dir['phase1'] = spec_phase1
# return is ignored whenever multiple phases are ran. If
if 'phase0' in run_phases:
ret = run_with_spec_version(spec_phase0, *args, **kw)
ret = fn(spec=spec_phase0, phases=phase_dir, *args, **kw)
if 'phase1' in run_phases:
# temporarily disable phase 1 tests
return
# ret = run_with_spec_version(spec_phase1, *args, **kw)
ret = fn(spec=spec_phase1, phases=phase_dir, *args, **kw)
return ret
return wrapper
return decorator

View File

@ -30,22 +30,29 @@ def add_attestation_to_store(spec, store, attestation):
spec.on_attestation(store, attestation)
def get_anchor_root(spec, state):
anchor_block_header = state.latest_block_header.copy()
if anchor_block_header.state_root == spec.Bytes32():
anchor_block_header.state_root = spec.hash_tree_root(state)
return spec.hash_tree_root(anchor_block_header)
@with_all_phases
@spec_state_test
def test_genesis(spec, state):
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
@with_all_phases
@spec_state_test
def test_chain_no_attestations(spec, state):
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
# On receiving a block of `GENESIS_SLOT + 1` slot
block_1 = build_empty_block_for_next_slot(spec, state)
@ -66,9 +73,9 @@ def test_split_tie_breaker_no_attestations(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
# block at slot 1
block_1_state = genesis_state.copy()
@ -94,9 +101,9 @@ def test_shorter_chain_but_heavier_weight(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
# build longer tree
long_state = genesis_state.copy()
@ -122,15 +129,14 @@ def test_shorter_chain_but_heavier_weight(spec, state):
@spec_state_test
def test_filtered_block_tree(spec, state):
# Initialization
genesis_state_root = state.hash_tree_root()
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=genesis_state_root)
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
# transition state past initial couple of epochs
next_epoch(spec, state)
next_epoch(spec, state)
assert spec.get_head(store) == spec.hash_tree_root(genesis_block)
assert spec.get_head(store) == anchor_root
# fill in attestations for entire epoch, justifying the recent epoch
prev_state, signed_blocks, state = next_epoch_with_attestations(spec, state, True, False)

View File

@ -15,8 +15,17 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
indexed_attestation = spec.get_indexed_attestation(state, attestation)
spec.on_attestation(store, attestation)
if spec.version == 'phase0':
sample_index = indexed_attestation.attesting_indices[0]
else:
attesting_indices = [
index for i, index in enumerate(indexed_attestation.committee)
if attestation.aggregation_bits[i]
]
sample_index = attesting_indices[0]
assert (
store.latest_messages[indexed_attestation.attesting_indices[0]] ==
store.latest_messages[sample_index] ==
spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
@ -27,7 +36,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
@with_all_phases
@spec_state_test
def test_on_attestation_current_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * 2)
block = build_empty_block_for_next_slot(spec, state)
@ -46,7 +55,7 @@ def test_on_attestation_current_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_previous_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_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)
@ -65,7 +74,7 @@ def test_on_attestation_previous_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_past_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
# move time forward 2 epochs
time = store.time + 2 * spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
@ -87,7 +96,7 @@ def test_on_attestation_past_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_mismatched_target_and_slot(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_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)
@ -110,7 +119,7 @@ def test_on_attestation_mismatched_target_and_slot(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_target_not_in_store(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
spec.on_tick(store, time)
@ -131,7 +140,7 @@ def test_on_attestation_target_not_in_store(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_beacon_block_not_in_store(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
spec.on_tick(store, time)
@ -159,7 +168,7 @@ def test_on_attestation_beacon_block_not_in_store(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_future_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
@ -179,7 +188,7 @@ def test_on_attestation_future_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_future_block(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = spec.SECONDS_PER_SLOT * 5
spec.on_tick(store, time)
@ -199,7 +208,7 @@ def test_on_attestation_future_block(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_same_slot(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 1 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
@ -215,7 +224,7 @@ def test_on_attestation_same_slot(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_invalid_attestation(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)

View File

@ -36,7 +36,7 @@ def apply_next_epoch_with_attestations(spec, state, store):
@spec_state_test
def test_basic(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)
assert store.time == time
@ -60,7 +60,7 @@ def test_basic(spec, state):
@spec_state_test
def test_on_block_checkpoints(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)
@ -86,7 +86,7 @@ def test_on_block_checkpoints(spec, state):
@spec_state_test
def test_on_block_future_block(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
# do not tick time
@ -100,7 +100,7 @@ def test_on_block_future_block(spec, state):
@spec_state_test
def test_on_block_bad_parent_root(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)
@ -120,7 +120,7 @@ def test_on_block_bad_parent_root(spec, state):
@spec_state_test
def test_on_block_before_finalized(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)
@ -139,7 +139,7 @@ def test_on_block_before_finalized(spec, state):
@spec_state_test
def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)
@ -170,7 +170,7 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
@spec_state_test
def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state):
# Initialization
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
time = 100
spec.on_tick(store, time)

View File

@ -19,14 +19,14 @@ def run_on_tick(spec, store, time, new_justified_checkpoint=False):
@with_all_phases
@spec_state_test
def test_basic(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_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)
store = spec.get_forkchoice_store(state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
@ -40,7 +40,7 @@ def test_update_justified_single(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_same_slot_at_epoch_boundary(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
@ -57,7 +57,7 @@ def test_no_update_same_slot_at_epoch_boundary(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_not_epoch_boundary(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
@ -70,7 +70,7 @@ def test_no_update_not_epoch_boundary(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_new_justified_equal_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
@ -89,7 +89,7 @@ def test_no_update_new_justified_equal_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_new_justified_later_epoch(spec, state):
store = spec.get_genesis_store(state)
store = spec.get_forkchoice_store(state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import spec_test, with_phases
from eth2spec.test.context import spec_test, with_phases, single_phase
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
@ -6,6 +6,7 @@ from eth2spec.test.helpers.deposits import (
@with_phases(['phase0'])
@spec_test
@single_phase
def test_initialize_beacon_state_from_eth1(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, deposit_root, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
@ -33,6 +34,7 @@ def test_initialize_beacon_state_from_eth1(spec):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_initialize_beacon_state_some_small_balances(spec):
main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
main_deposits, _, deposit_data_list = prepare_genesis_deposits(spec, main_deposit_count,

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import spec_test, with_phases
from eth2spec.test.context import spec_test, with_phases, single_phase
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
@ -27,6 +27,7 @@ def run_is_valid_genesis_state(spec, state, valid=True):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_is_valid_genesis_state_true(spec):
state = create_valid_beacon_state(spec)
@ -35,6 +36,7 @@ def test_is_valid_genesis_state_true(spec):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_is_valid_genesis_state_false_invalid_timestamp(spec):
state = create_valid_beacon_state(spec)
state.genesis_time = spec.MIN_GENESIS_TIME - 1
@ -44,6 +46,7 @@ def test_is_valid_genesis_state_false_invalid_timestamp(spec):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_is_valid_genesis_state_true_more_balance(spec):
state = create_valid_beacon_state(spec)
state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE + 1
@ -63,6 +66,7 @@ def test_is_valid_genesis_state_true_more_balance(spec):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_is_valid_genesis_state_true_one_more_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1
deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
@ -76,6 +80,7 @@ def test_is_valid_genesis_state_true_one_more_validator(spec):
@with_phases(['phase0'])
@spec_test
@single_phase
def test_is_valid_genesis_state_false_not_enough_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1
deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)

View File

@ -77,12 +77,22 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
privkey
)
)
# TODO: we should try signing custody bits if spec.version == 'phase1'
return bls.Aggregate(signatures)
def sign_indexed_attestation(spec, state, indexed_attestation):
participants = indexed_attestation.attesting_indices
indexed_attestation.signature = sign_aggregate_attestation(spec, state, indexed_attestation.data, participants)
if spec.version == 'phase0':
participants = indexed_attestation.attesting_indices
data = indexed_attestation.data
indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
else:
participants = spec.get_indices_from_committee(
indexed_attestation.committee,
indexed_attestation.attestation.aggregation_bits,
)
data = indexed_attestation.attestation.data
indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
def sign_attestation(spec, state, attestation):

View File

@ -16,3 +16,40 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
attestation_1=spec.get_indexed_attestation(state, attestation_1),
attestation_2=spec.get_indexed_attestation(state, attestation_2),
)
def get_indexed_attestation_participants(spec, indexed_att):
"""
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
"""
if spec.version == "phase1":
return list(spec.get_indices_from_committee(
indexed_att.committee,
indexed_att.attestation.aggregation_bits,
))
else:
return list(indexed_att.attesting_indices)
def set_indexed_attestation_participants(spec, indexed_att, participants):
"""
Wrapper around index-attestation to return the list of participant indices, regardless of spec phase.
"""
if spec.version == "phase1":
indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee]
else:
indexed_att.attesting_indices = participants
def get_attestation_1_data(spec, att_slashing):
if spec.version == "phase1":
return att_slashing.attestation_1.attestation.data
else:
return att_slashing.attestation_1.data
def get_attestation_2_data(spec, att_slashing):
if spec.version == "phase1":
return att_slashing.attestation_2.attestation.data
else:
return att_slashing.attestation_2.data

View File

@ -1,6 +1,5 @@
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector
from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
@ -21,7 +20,7 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
reveal = bls.Sign(privkeys[revealed_index], signing_root)
# Generate the mask (any random 32 bytes that don't reveal the masker's secret will do)
mask = hash(reveal)
mask = spec.hash(reveal)
# Generate masker's signature on the mask
signing_root = spec.compute_signing_root(mask, domain)
masker_signature = bls.Sign(privkeys[masker_index], signing_root)

View File

@ -1,152 +0,0 @@
import re
from eth_utils import (
to_tuple,
)
from eth2spec.test.context import (
expect_assertion_error,
spec_state_test,
with_all_phases_except,
)
from eth2spec.utils.ssz.ssz_typing import (
Bytes32,
Container,
List,
uint64,
)
class Foo(Container):
x: uint64
y: List[Bytes32, 2]
# Tree
# root
# / \
# x y_root
# / \
# y_data_root len(y)
# / \
# / \ / \
#
# Generalized indices
# 1
# / \
# 2 (x) 3 (y_root)
# / \
# 6 7
# / \
# 12 13
@to_tuple
def ssz_object_to_path(start, end):
is_len = False
len_findall = re.findall(r"(?<=len\().*(?=\))", end)
if len_findall:
is_len = True
end = len_findall[0]
route = ''
if end.startswith(start):
route = end[len(start):]
segments = route.split('.')
for word in segments:
index_match = re.match(r"(\w+)\[(\d+)]", word)
if index_match:
yield from index_match.groups()
elif len(word):
yield word
if is_len:
yield '__len__'
to_path_test_cases = [
('foo', 'foo.x', ('x',)),
('foo', 'foo.x[100].y', ('x', '100', 'y')),
('foo', 'foo.x[100].y[1].z[2]', ('x', '100', 'y', '1', 'z', '2')),
('foo', 'len(foo.x[100].y[1].z[2])', ('x', '100', 'y', '1', 'z', '2', '__len__')),
]
def test_to_path():
for test_case in to_path_test_cases:
start, end, expected = test_case
assert ssz_object_to_path(start, end) == expected
generalized_index_cases = [
(Foo, ('x',), 2),
(Foo, ('y',), 3),
(Foo, ('y', 0), 12),
(Foo, ('y', 1), 13),
(Foo, ('y', '__len__'), None),
]
@with_all_phases_except(['phase0'])
@spec_state_test
def test_get_generalized_index(spec, state):
for typ, path, generalized_index in generalized_index_cases:
if generalized_index is not None:
assert spec.get_generalized_index(
typ=typ,
path=path,
) == generalized_index
else:
expect_assertion_error(lambda: spec.get_generalized_index(typ=typ, path=path))
yield 'typ', typ
yield 'path', path
yield 'generalized_index', generalized_index
@with_all_phases_except(['phase0'])
@spec_state_test
def test_verify_merkle_proof(spec, state):
h = spec.hash
a = b'\x11' * 32
b = b'\x22' * 32
c = b'\x33' * 32
d = b'\x44' * 32
root = h(h(a + b) + h(c + d))
leaf = a
generalized_index = 4
proof = [b, h(c + d)]
is_valid = spec.verify_merkle_proof(
leaf=leaf,
proof=proof,
index=generalized_index,
root=root,
)
assert is_valid
yield 'proof', proof
yield 'is_valid', is_valid
@with_all_phases_except(['phase0'])
@spec_state_test
def test_verify_merkle_multiproof(spec, state):
h = spec.hash
a = b'\x11' * 32
b = b'\x22' * 32
c = b'\x33' * 32
d = b'\x44' * 32
root = h(h(a + b) + h(c + d))
leaves = [a, d]
generalized_indices = [4, 7]
proof = [c, b] # helper_indices = [6, 5]
is_valid = spec.verify_merkle_multiproof(
leaves=leaves,
proof=proof,
indices=generalized_indices,
root=root,
)
assert is_valid
yield 'proof', proof
yield 'is_valid', is_valid

View File

@ -6,7 +6,7 @@ from eth2spec.test.context import (
spec_test,
low_balances,
with_custom_state,
)
single_phase)
from eth2spec.test.helpers.attestations import (
get_valid_attestation,
sign_aggregate_attestation,
@ -66,6 +66,7 @@ def test_success(spec, state):
@with_all_phases
@spec_test
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.EJECTION_BALANCE)
@single_phase
def test_success_multi_proposer_index_iterations(spec, state):
state.slot += spec.SLOTS_PER_EPOCH * 2
attestation = get_valid_attestation(spec, state, signed=True)

View File

@ -1,6 +1,7 @@
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
from eth2spec.test.helpers.attestations import sign_indexed_attestation
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \
get_indexed_attestation_participants, get_attestation_2_data, get_attestation_1_data
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.helpers.state import (
get_balance,
@ -25,7 +26,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
yield 'post', None
return
slashed_indices = attester_slashing.attestation_1.attesting_indices
slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
proposer_index = spec.get_beacon_proposer_index(state)
pre_proposer_balance = get_balance(state, proposer_index)
@ -92,12 +93,12 @@ def test_success_surround(spec, state):
state.current_justified_checkpoint.epoch += 1
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
att_1_data = get_attestation_1_data(spec, attester_slashing)
att_2_data = get_attestation_2_data(spec, attester_slashing)
# set attestion1 to surround attestation 2
attestation_1.data.source.epoch = attestation_2.data.source.epoch - 1
attestation_1.data.target.epoch = attestation_2.data.target.epoch + 1
att_1_data.source.epoch = att_2_data.source.epoch - 1
att_1_data.target.epoch = att_2_data.target.epoch + 1
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
@ -109,7 +110,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.attesting_indices
slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
for index in slashed_indices:
spec.initiate_validator_exit(state, index)
@ -121,7 +122,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.attesting_indices
slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
for index in slashed_indices:
spec.initiate_validator_exit(state, index)
state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2
@ -158,7 +159,12 @@ def test_invalid_sig_1_and_2(spec, state):
def test_same_data(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attester_slashing.attestation_1.data = attester_slashing.attestation_2.data
indexed_att_1 = attester_slashing.attestation_1
att_2_data = get_attestation_2_data(spec, attester_slashing)
if spec.version == 'phase1':
indexed_att_1.attestation.data = att_2_data
else:
indexed_att_1.data = att_2_data
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@ -169,7 +175,9 @@ def test_same_data(spec, state):
def test_no_double_or_surround(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attester_slashing.attestation_1.data.target.epoch += 1
att_1_data = get_attestation_1_data(spec, attester_slashing)
att_1_data.target.epoch += 1
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@ -181,20 +189,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
validator_indices = attester_slashing.attestation_1.attesting_indices
validator_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
for index in validator_indices:
state.validators[index].slashed = True
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
# Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list.
@with_phases(['phase0'])
@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.attesting_indices
indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)
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.attesting_indices = sorted(indices)
@ -204,7 +215,7 @@ def test_att1_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att1_bad_replaced_index(spec, state):
@ -220,7 +231,7 @@ def test_att1_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att2_bad_extra_index(spec, state):
@ -236,7 +247,7 @@ def test_att2_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att2_bad_replaced_index(spec, state):
@ -252,7 +263,7 @@ def test_att2_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att1_duplicate_index_normal_signed(spec, state):
@ -272,7 +283,7 @@ def test_att1_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att2_duplicate_index_normal_signed(spec, state):
@ -292,7 +303,7 @@ def test_att2_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att1_duplicate_index_double_signed(spec, state):
@ -307,7 +318,7 @@ def test_att1_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
@always_bls
def test_att2_duplicate_index_double_signed(spec, state):
@ -322,7 +333,7 @@ def test_att2_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
def test_unsorted_att_1(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
@ -335,7 +346,7 @@ def test_unsorted_att_1(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@with_phases(['phase0'])
@spec_state_test
def test_unsorted_att_2(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)

View File

@ -1,7 +1,7 @@
from copy import deepcopy
from eth2spec.test.context import spec_state_test, with_all_phases, spec_test, \
misc_balances, with_custom_state, default_activation_threshold
misc_balances, with_custom_state, default_activation_threshold, single_phase
from eth2spec.test.helpers.state import (
next_epoch,
next_slot,
@ -10,6 +10,7 @@ from eth2spec.test.helpers.attestations import (
add_attestations_to_state,
get_valid_attestation,
)
from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
@ -96,6 +97,7 @@ def test_full_attestations(spec, state):
@with_all_phases
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold)
@single_phase
def test_full_attestations_misc_balances(spec, state):
attestations = prepare_state_with_full_attestations(spec, state)
@ -141,7 +143,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.attesting_indices
participants = get_indexed_attestation_participants(spec, indexed_attestation)
assert len(participants) > 0

View File

@ -1,350 +0,0 @@
from eth2spec.test.helpers.custody import (
get_valid_bit_challenge,
get_valid_custody_response,
get_custody_test_vector,
get_custody_merkle_root
)
from eth2spec.test.helpers.attestations import (
get_valid_attestation,
)
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.helpers.state import next_epoch, get_balance
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
expect_assertion_error,
)
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True):
"""
Run ``process_bit_challenge``, yielding:
- pre-state ('pre')
- CustodyBitChallenge ('custody_bit_challenge')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'custody_bit_challenge', custody_bit_challenge
if not valid:
expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge))
yield 'post', None
return
spec.process_bit_challenge(state, custody_bit_challenge)
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].chunk_bits_merkle_root == \
hash_tree_root(custody_bit_challenge.chunk_bits)
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].challenger_index == \
custody_bit_challenge.challenger_index
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].responder_index == \
custody_bit_challenge.responder_index
yield 'post', state
def run_custody_response_processing(spec, state, custody_response, valid=True):
"""
Run ``process_bit_challenge_response``, yielding:
- pre-state ('pre')
- CustodyResponse ('custody_response')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'custody_response', custody_response
if not valid:
expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
yield 'post', None
return
# TODO: Add capability to also process chunk challenges, not only bit challenges
challenge = state.custody_bit_challenge_records[custody_response.challenge_index]
pre_slashed_balance = get_balance(state, challenge.challenger_index)
spec.process_custody_response(state, custody_response)
slashed_validator = state.validators[challenge.challenger_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
assert get_balance(state, challenge.challenger_index) < pre_slashed_balance
yield 'post', state
@with_all_phases_except(['phase0'])
@spec_state_test
def test_challenge_appended(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_multiple_epochs_custody(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 3
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_many_epochs_custody(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 100
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_off_chain_attestation(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_invalid_custody_bit_challenge(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True)
yield from run_bit_challenge_processing(spec, state, challenge, valid=False)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_max_reveal_lateness_1(spec, state):
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
next_epoch(spec, state)
apply_empty_block(spec, state)
_, _, _ = run_attestation_processing(spec, state, attestation)
challenge = get_valid_bit_challenge(spec, state, attestation)
responder_index = challenge.responder_index
target_epoch = attestation.data.target.epoch
state.validators[responder_index].max_reveal_lateness = 3
latest_reveal_epoch = spec.get_randao_epoch_for_custody_period(
spec.get_custody_period_for_validator(state, responder_index, target_epoch),
responder_index
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness
while spec.get_current_epoch(state) < latest_reveal_epoch - 2:
next_epoch(spec, state)
apply_empty_block(spec, state)
yield from run_bit_challenge_processing(spec, state, challenge)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_max_reveal_lateness_2(spec, state):
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
next_epoch(spec, state)
apply_empty_block(spec, state)
_, _, _ = run_attestation_processing(spec, state, attestation)
challenge = get_valid_bit_challenge(spec, state, attestation)
responder_index = challenge.responder_index
state.validators[responder_index].max_reveal_lateness = 3
for i in range(spec.get_randao_epoch_for_custody_period(
spec.get_custody_period_for_validator(state, responder_index),
responder_index
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1):
next_epoch(spec, state)
apply_empty_block(spec, state)
yield from run_bit_challenge_processing(spec, state, challenge, False)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response(spec, state):
state.slot = spec.SLOTS_PER_EPOCH
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response_multiple_epochs(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 3
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)
@with_all_phases_except(['phase0'])
@spec_state_test
def test_custody_response_many_epochs(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 100
attestation = get_valid_attestation(spec, state, signed=True)
test_vector = get_custody_test_vector(
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
shard_root = get_custody_merkle_root(test_vector)
attestation.data.crosslink.data_root = shard_root
attestation.custody_bits[0] = 0
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
_, _, _ = run_attestation_processing(spec, state, attestation)
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
challenge = get_valid_bit_challenge(spec, state, attestation)
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
bit_challenge_index = state.custody_challenge_index - 1
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
yield from run_custody_response_processing(spec, state, custody_response)

View File

@ -55,8 +55,8 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_success(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
@ -65,8 +65,8 @@ def test_success(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_reveal_too_early(spec, state):
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
@ -74,8 +74,8 @@ def test_reveal_too_early(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_wrong_period(spec, state):
custody_key_reveal = get_valid_custody_key_reveal(spec, state, period=5)
@ -83,8 +83,8 @@ def test_wrong_period(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_late_reveal(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
@ -93,8 +93,8 @@ def test_late_reveal(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_double_reveal(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 2
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
@ -105,8 +105,8 @@ def test_double_reveal(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
@always_bls
def test_max_decrement(spec, state):
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
custody_key_reveal = get_valid_custody_key_reveal(spec, state)

View File

@ -1,177 +0,0 @@
from copy import deepcopy
from eth2spec.test.helpers.phase1.shard_block import (
build_empty_shard_block,
sign_shard_block,
)
from eth2spec.test.helpers.phase1.shard_state import (
configure_shard_state,
shard_state_transition_and_sign_block,
)
from eth2spec.test.context import (
always_bls,
expect_assertion_error,
spec_state_test,
with_all_phases_except,
)
@with_all_phases_except(['phase0'])
@spec_state_test
@always_bls
def test_process_empty_shard_block(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
block = build_empty_shard_block(
spec,
beacon_state,
shard_state,
slot=shard_state.slot + 1,
signed=True,
full_attestation=False,
)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block)
yield 'blocks', [block]
yield 'post', shard_state
@with_all_phases_except(['phase0'])
@spec_state_test
@always_bls
def test_process_full_attestation_shard_block(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
block = build_empty_shard_block(
spec,
beacon_state,
shard_state,
slot=shard_state.slot + 1,
signed=True,
full_attestation=True,
)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block)
yield 'blocks', [block]
yield 'post', shard_state
@with_all_phases_except(['phase0'])
@spec_state_test
def test_prev_slot_block_transition(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
# Go to clean slot
spec.process_shard_slots(shard_state, shard_state.slot + 1)
# Make a block for it
block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True)
# Transition to next slot, above block will not be invalid on top of new state.
spec.process_shard_slots(shard_state, shard_state.slot + 1)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
expect_assertion_error(
lambda: spec.shard_state_transition(beacon_state, shard_state, block)
)
yield 'blocks', [block]
yield 'post', None
@with_all_phases_except(['phase0'])
@spec_state_test
def test_same_slot_block_transition(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
# Same slot on top of pre-state, but move out of slot 0 first.
spec.process_shard_slots(shard_state, shard_state.slot + 1)
block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block)
yield 'blocks', [block]
yield 'post', shard_state
@with_all_phases_except(['phase0'])
@spec_state_test
def test_invalid_state_root(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
spec.process_shard_slots(shard_state, shard_state.slot + 1)
block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot)
block.state_root = b'\x36' * 32
sign_shard_block(spec, beacon_state, shard_state, block)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
expect_assertion_error(
lambda: spec.shard_state_transition(beacon_state, shard_state, block, validate_state_root=True)
)
yield 'blocks', [block]
yield 'post', None
@with_all_phases_except(['phase0'])
@spec_state_test
def test_skipped_slots(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot + 3, signed=True)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block)
yield 'blocks', [block]
yield 'post', shard_state
assert shard_state.slot == block.slot
latest_block_header = deepcopy(shard_state.latest_block_header)
latest_block_header.state_root = shard_state.hash_tree_root()
assert latest_block_header.hash_tree_root() == block.hash_tree_root()
@with_all_phases_except(['phase0'])
@spec_state_test
def test_empty_shard_period_transition(spec, state):
beacon_state, shard_state = configure_shard_state(spec, state)
# modify some of the deltas to ensure the period transition works properly
stub_delta = 10
shard_state.newer_committee_positive_deltas[0] = stub_delta
shard_state.newer_committee_negative_deltas[0] = stub_delta
slot = shard_state.slot + spec.SHARD_SLOTS_PER_EPOCH * spec.EPOCHS_PER_SHARD_PERIOD
beacon_state.slot = spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH - 4
spec.process_slots(beacon_state, spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH)
# all validators get slashed for not revealing keys
# undo this to allow for a block proposal
for index in range(len(beacon_state.validators)):
beacon_state.validators[index].slashed = False
block = build_empty_shard_block(spec, beacon_state, shard_state, slot=slot, signed=True)
yield 'pre', shard_state
yield 'beacon_state', beacon_state
shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block)
yield 'blocks', [block]
yield 'post', shard_state
shard_state.older_committee_positive_deltas[0] == stub_delta
shard_state.older_committee_negative_deltas[0] == stub_delta
shard_state.newer_committee_positive_deltas[0] == 0
shard_state.newer_committee_negative_deltas[0] == 0

View File

@ -6,7 +6,7 @@ from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_b
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block, \
transition_unsigned_block
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
@ -220,7 +220,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.attesting_indices[0]
validator_index = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)[0]
assert not state.validators[validator_index].slashed

View File

@ -2,7 +2,7 @@ from ..merkle_minimal import merkleize_chunks
from ..hash_function import hash
from .ssz_typing import (
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bits, boolean, Container, List, ByteList,
Bitlist, Bitvector, uint,
Bitlist, Bitvector, uint, Bytes32
)
# SSZ Serialization
@ -140,7 +140,7 @@ def chunk_count(typ: SSZType) -> int:
raise Exception(f"Type not supported: {typ}")
def hash_tree_root(obj: SSZValue):
def hash_tree_root(obj: SSZValue) -> Bytes32:
if isinstance(obj, Series):
if is_bottom_layer_kind(obj.type()):
leaves = chunkify(pack(obj))
@ -152,6 +152,6 @@ def hash_tree_root(obj: SSZValue):
raise Exception(f"Type not supported: {type(obj)}")
if isinstance(obj, (List, ByteList, Bitlist)):
return mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj))
return Bytes32(mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj)))
else:
return merkleize_chunks(leaves)
return Bytes32(merkleize_chunks(leaves))

View File

@ -119,6 +119,8 @@ def coerce_type_maybe(v, typ: SSZType, strict: bool = False):
return typ(v)
elif isinstance(v, GeneratorType):
return typ(v)
elif issubclass(typ, Container) and not isinstance(v, typ):
return typ(**{field_name: getattr(v, field_name) for field_name in typ.get_field_names()})
# just return as-is, Value-checkers will take care of it not being coerced, if we are not strict.
if strict and not isinstance(v, typ):
@ -192,7 +194,7 @@ class Container(Series, metaclass=SSZType):
return dict(cls.__annotations__)
@classmethod
def get_field_names(cls) -> Iterable[SSZType]:
def get_field_names(cls) -> Iterable[str]:
if not hasattr(cls, '__annotations__'): # no container fields
return ()
return list(cls.__annotations__.keys())

View File

@ -3,6 +3,7 @@ from setuptools import setup, find_packages
setup(
name='pyspec',
packages=find_packages(),
python_requires=">=3.8, <4",
tests_require=["pytest"],
install_requires=[
"eth-utils>=1.3.0,<2",