Merge branch 'dev' into container-cleanup

This commit is contained in:
Danny Ryan 2019-06-14 10:36:41 -06:00
commit a6230425b8
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
24 changed files with 234 additions and 341 deletions

View File

@ -48,7 +48,7 @@ GENESIS_FORK_VERSION: 0x00000000
GENESIS_SLOT: 0 GENESIS_SLOT: 0
# 2**64 - 1 # 2**64 - 1
FAR_FUTURE_EPOCH: 18446744073709551615 FAR_FUTURE_EPOCH: 18446744073709551615
BLS_WITHDRAWAL_PREFIX_BYTE: 0x00 BLS_WITHDRAWAL_PREFIX: 0
# Time parameters # Time parameters
@ -90,7 +90,7 @@ SLASHED_EXIT_LENGTH: 8192
# Reward and penalty quotients # Reward and penalty quotients
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 2**5 (= 32) # 2**5 (= 32)
BASE_REWARD_QUOTIENT: 32 BASE_REWARD_FACTOR: 32
# 2**9 (= 512) # 2**9 (= 512)
WHISTLEBLOWING_REWARD_QUOTIENT: 512 WHISTLEBLOWING_REWARD_QUOTIENT: 512
# 2**3 (= 8) # 2**3 (= 8)

View File

@ -47,7 +47,7 @@ GENESIS_FORK_VERSION: 0x00000000
GENESIS_SLOT: 0 GENESIS_SLOT: 0
# 2**64 - 1 # 2**64 - 1
FAR_FUTURE_EPOCH: 18446744073709551615 FAR_FUTURE_EPOCH: 18446744073709551615
BLS_WITHDRAWAL_PREFIX_BYTE: 0x00 BLS_WITHDRAWAL_PREFIX: 0
# Time parameters # Time parameters
@ -58,23 +58,23 @@ SECONDS_PER_SLOT: 6
MIN_ATTESTATION_INCLUSION_DELAY: 2 MIN_ATTESTATION_INCLUSION_DELAY: 2
# [customized] fast epochs # [customized] fast epochs
SLOTS_PER_EPOCH: 8 SLOTS_PER_EPOCH: 8
# 2**0 (= 1) epochs 6.4 minutes # 2**0 (= 1) epochs
MIN_SEED_LOOKAHEAD: 1 MIN_SEED_LOOKAHEAD: 1
# 2**2 (= 4) epochs 25.6 minutes # 2**2 (= 4) epochs
ACTIVATION_EXIT_DELAY: 4 ACTIVATION_EXIT_DELAY: 4
# [customized] higher frequency new deposits from eth1 for testing # [customized] higher frequency new deposits from eth1 for testing
SLOTS_PER_ETH1_VOTING_PERIOD: 16 SLOTS_PER_ETH1_VOTING_PERIOD: 16
# [customized] smaller state # [customized] smaller state
SLOTS_PER_HISTORICAL_ROOT: 64 SLOTS_PER_HISTORICAL_ROOT: 64
# 2**8 (= 256) epochs ~27 hours # 2**8 (= 256) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**11 (= 2,048) epochs 9 days # 2**11 (= 2,048) epochs
PERSISTENT_COMMITTEE_PERIOD: 2048 PERSISTENT_COMMITTEE_PERIOD: 2048
# 2**6 (= 64) epochs ~7 hours # 2**6 (= 64) epochs
MAX_EPOCHS_PER_CROSSLINK: 64 MAX_EPOCHS_PER_CROSSLINK: 64
# 2**2 (= 4) epochs 25.6 minutes # 2**2 (= 4) epochs
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# [customized] 2**12 (= 4,096) epochs 18 days # [customized] 2**12 (= 4,096) epochs
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
@ -91,7 +91,7 @@ SLASHED_EXIT_LENGTH: 64
# Reward and penalty quotients # Reward and penalty quotients
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 2**5 (= 32) # 2**5 (= 32)
BASE_REWARD_QUOTIENT: 32 BASE_REWARD_FACTOR: 32
# 2**9 (= 512) # 2**9 (= 512)
WHISTLEBLOWING_REWARD_QUOTIENT: 512 WHISTLEBLOWING_REWARD_QUOTIENT: 512
# 2**3 (= 8) # 2**3 (= 8)

File diff suppressed because one or more lines are too long

View File

@ -1,140 +1,103 @@
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
FULL_DEPOSIT_AMOUNT: constant(uint256) = 32000000000 # Gwei
CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = 65536 # 2**16
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32 DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
SECONDS_PER_DAY: constant(uint256) = 86400 MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
MAX_64_BIT_VALUE: constant(uint256) = 18446744073709551615 # 2**64 - 1
PUBKEY_LENGTH: constant(uint256) = 48 # bytes PUBKEY_LENGTH: constant(uint256) = 48 # bytes
WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
Deposit: event({ Deposit: event({
pubkey: bytes[48], pubkey: bytes[48],
withdrawal_credentials: bytes[32], withdrawal_credentials: bytes[32],
amount: bytes[8], amount: bytes[8],
signature: bytes[96], signature: bytes[96],
merkle_tree_index: bytes[8], index: bytes[8],
}) })
Eth2Genesis: event({deposit_root: bytes32, deposit_count: bytes[8], time: bytes[8]})
zerohashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
deposit_count: uint256 deposit_count: uint256
full_deposit_count: uint256
chainStarted: public(bool)
# Compute hashes in empty sparse Merkle tree
zero_hashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
@public @public
def __init__(): def __init__():
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1): for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1):
self.zerohashes[i+1] = sha256(concat(self.zerohashes[i], self.zerohashes[i])) self.zero_hashes[i + 1] = sha256(concat(self.zero_hashes[i], self.zero_hashes[i]))
@public @private
@constant @constant
def to_little_endian_64(value: uint256) -> bytes[8]: def to_little_endian_64(value: uint256) -> bytes[8]:
assert value <= MAX_64_BIT_VALUE # Reversing bytes using bitwise uint256 manipulations
# Note: array accesses of bytes[] are not currently supported in Vyper
# array access for bytes[] not currently supported in vyper so # Note: this function is only called when `value < 2**64`
# reversing bytes using bitwise uint256 manipulations
y: uint256 = 0 y: uint256 = 0
x: uint256 = value x: uint256 = value
for i in range(8): for _ in range(8):
y = shift(y, 8) y = shift(y, 8)
y = y + bitwise_and(x, 255) y = y + bitwise_and(x, 255)
x = shift(x, -8) x = shift(x, -8)
return slice(convert(y, bytes32), start=24, len=8) return slice(convert(y, bytes32), start=24, len=8)
@public @public
@constant @constant
def get_deposit_root() -> bytes32: def get_deposit_root() -> bytes32:
root: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000 node: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
size: uint256 = self.deposit_count size: uint256 = self.deposit_count
for h in range(DEPOSIT_CONTRACT_TREE_DEPTH): for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
root = sha256(concat(self.branch[h], root)) node = sha256(concat(self.branch[height], node))
else: else:
root = sha256(concat(root, self.zerohashes[h])) node = sha256(concat(node, self.zero_hashes[height]))
size /= 2 size /= 2
return root return node
@public @public
@constant @constant
def get_deposit_count() -> bytes[8]: def get_deposit_count() -> bytes[8]:
return self.to_little_endian_64(self.deposit_count) return self.to_little_endian_64(self.deposit_count)
@payable @payable
@public @public
def deposit(pubkey: bytes[PUBKEY_LENGTH], def deposit(pubkey: bytes[PUBKEY_LENGTH],
withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH], withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
signature: bytes[SIGNATURE_LENGTH]): signature: bytes[SIGNATURE_LENGTH]):
# Prevent edge case in computing `self.branch` when `self.deposit_count == MAX_DEPOSIT_COUNT` # Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
# NOTE: reaching this point with the constants as currently defined is impossible due to the
# uni-directional nature of transfers from eth1 to eth2 and the total ether supply (< 130M).
assert self.deposit_count < MAX_DEPOSIT_COUNT assert self.deposit_count < MAX_DEPOSIT_COUNT
# Validate deposit data
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
assert len(pubkey) == PUBKEY_LENGTH assert len(pubkey) == PUBKEY_LENGTH
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH assert len(signature) == SIGNATURE_LENGTH
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei") # Emit `Deposit` log
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
amount: bytes[8] = self.to_little_endian_64(deposit_amount) amount: bytes[8] = self.to_little_endian_64(deposit_amount)
log.Deposit(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
index: uint256 = self.deposit_count # Compute `DepositData` root
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
# add deposit to merkle tree pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
i: int128 = 0
size: uint256 = index + 1
for _ in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1:
break
i += 1
size /= 2
zero_bytes_32: bytes32
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes_32, start=0, len=16)))
signature_root: bytes32 = sha256(concat( signature_root: bytes32 = sha256(concat(
sha256(slice(signature, start=0, len=64)), sha256(slice(signature, start=0, len=64)),
sha256(concat(slice(signature, start=64, len=32), zero_bytes_32)) sha256(concat(slice(signature, start=64, len=SIGNATURE_LENGTH - 64), zero_bytes32)),
)) ))
value: bytes32 = sha256(concat( node: bytes32 = sha256(concat(
sha256(concat(pubkey_root, withdrawal_credentials)), sha256(concat(pubkey_root, withdrawal_credentials)),
sha256(concat( sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
amount,
slice(zero_bytes_32, start=0, len=24),
signature_root,
))
)) ))
for j in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if j < i:
value = sha256(concat(self.branch[j], value))
else:
break
self.branch[i] = value
# Add `DepositData` root to Merkle tree (update a single `branch` node)
self.deposit_count += 1 self.deposit_count += 1
log.Deposit( size: uint256 = self.deposit_count
pubkey, for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
withdrawal_credentials, if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
amount, self.branch[height] = node
signature, break
self.to_little_endian_64(index), node = sha256(concat(self.branch[height], node))
) size /= 2
if deposit_amount >= FULL_DEPOSIT_AMOUNT:
self.full_deposit_count += 1
if self.full_deposit_count == CHAIN_START_FULL_DEPOSIT_THRESHOLD:
timestamp_day_boundary: uint256 = (
as_unitless_number(block.timestamp) -
as_unitless_number(block.timestamp) % SECONDS_PER_DAY +
2 * SECONDS_PER_DAY
)
new_deposit_root: bytes32 = self.get_deposit_root()
log.Eth2Genesis(new_deposit_root,
self.to_little_endian_64(self.deposit_count),
self.to_little_endian_64(timestamp_day_boundary))
self.chainStarted = True

View File

@ -26,7 +26,6 @@ from .utils import (
# Constants # Constants
MIN_DEPOSIT_AMOUNT = 1000000000 # Gwei MIN_DEPOSIT_AMOUNT = 1000000000 # Gwei
FULL_DEPOSIT_AMOUNT = 32000000000 # Gwei FULL_DEPOSIT_AMOUNT = 32000000000 # Gwei
CHAIN_START_FULL_DEPOSIT_THRESHOLD = 65536 # 2**16
DEPOSIT_CONTRACT_TREE_DEPTH = 32 DEPOSIT_CONTRACT_TREE_DEPTH = 32
TWO_TO_POWER_OF_TREE_DEPTH = 2**DEPOSIT_CONTRACT_TREE_DEPTH TWO_TO_POWER_OF_TREE_DEPTH = 2**DEPOSIT_CONTRACT_TREE_DEPTH
@ -63,45 +62,6 @@ def registration_contract(w3, tester):
return registration_deployed return registration_deployed
@pytest.fixture(scope="session")
def chain_start_full_deposit_thresholds():
return [randint(1, 5), randint(6, 10), randint(11, 15)]
@pytest.fixture(params=[0, 1, 2])
def modified_registration_contract(
request,
w3,
tester,
chain_start_full_deposit_thresholds):
# Set CHAIN_START_FULL_DEPOSIT_THRESHOLD to different threshold t
registration_code = get_deposit_contract_code()
t = str(chain_start_full_deposit_thresholds[request.param])
modified_registration_code = re.sub(
r'CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant\(uint256\) = [0-9]+',
'CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = ' + t,
registration_code,
)
assert modified_registration_code != registration_code
contract_bytecode = compiler.compile_code(modified_registration_code)['bytecode']
contract_abi = compiler.mk_full_signature(modified_registration_code)
registration = w3.eth.contract(
abi=contract_abi,
bytecode=contract_bytecode)
tx_hash = registration.constructor().transact()
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
registration_deployed = w3.eth.contract(
address=tx_receipt.contractAddress,
abi=contract_abi
)
setattr(
registration_deployed,
'chain_start_full_deposit_threshold',
chain_start_full_deposit_thresholds[request.param]
)
return registration_deployed
@pytest.fixture @pytest.fixture
def assert_tx_failed(tester): def assert_tx_failed(tester):
def assert_tx_failed(function_to_test, exception=eth_tester.exceptions.TransactionFailed): def assert_tx_failed(function_to_test, exception=eth_tester.exceptions.TransactionFailed):

View File

@ -49,28 +49,6 @@ def deposit_input():
) )
@pytest.mark.parametrize(
'value,success',
[
(0, True),
(10, True),
(55555, True),
(2**64 - 1, True),
(2**64, False),
]
)
def test_to_little_endian_64(registration_contract, value, success, assert_tx_failed):
call = registration_contract.functions.to_little_endian_64(value)
if success:
little_endian_64 = call.call()
assert little_endian_64 == (value).to_bytes(8, 'little')
else:
assert_tx_failed(
lambda: call.call()
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
'success,deposit_amount', 'success,deposit_amount',
[ [
@ -151,8 +129,7 @@ def test_deposit_log(registration_contract, a0, w3, deposit_input):
assert log['withdrawal_credentials'] == deposit_input[1] assert log['withdrawal_credentials'] == deposit_input[1]
assert log['amount'] == deposit_amount_list[i].to_bytes(8, 'little') assert log['amount'] == deposit_amount_list[i].to_bytes(8, 'little')
assert log['signature'] == deposit_input[2] assert log['signature'] == deposit_input[2]
assert log['merkle_tree_index'] == i.to_bytes(8, 'little') assert log['index'] == i.to_bytes(8, 'little')
def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input): def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input):
log_filter = registration_contract.events.Deposit.createFilter( log_filter = registration_contract.events.Deposit.createFilter(
@ -172,7 +149,7 @@ def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input
assert len(logs) == 1 assert len(logs) == 1
log = logs[0]['args'] log = logs[0]['args']
assert log["merkle_tree_index"] == i.to_bytes(8, 'little') assert log["index"] == i.to_bytes(8, 'little')
deposit_data = DepositData( deposit_data = DepositData(
pubkey=deposit_input[0], pubkey=deposit_input[0],
@ -184,53 +161,3 @@ def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input
leaf_nodes.append(hash_tree_root_result) leaf_nodes.append(hash_tree_root_result)
root = compute_merkle_root(leaf_nodes) root = compute_merkle_root(leaf_nodes)
assert root == registration_contract.functions.get_deposit_root().call() assert root == registration_contract.functions.get_deposit_root().call()
def test_chain_start(modified_registration_contract, w3, assert_tx_failed, deposit_input):
t = getattr(modified_registration_contract, 'chain_start_full_deposit_threshold')
# CHAIN_START_FULL_DEPOSIT_THRESHOLD is set to t
min_deposit_amount = MIN_DEPOSIT_AMOUNT * eth_utils.denoms.gwei # in wei
full_deposit_amount = FULL_DEPOSIT_AMOUNT * eth_utils.denoms.gwei
log_filter = modified_registration_contract.events.Eth2Genesis.createFilter(
fromBlock='latest',
)
index_not_full_deposit = randint(0, t - 1)
for i in range(t):
if i == index_not_full_deposit:
# Deposit with value below FULL_DEPOSIT_AMOUNT
modified_registration_contract.functions.deposit(
*deposit_input,
).transact({"value": min_deposit_amount})
logs = log_filter.get_new_entries()
# Eth2Genesis event should not be triggered
assert len(logs) == 0
else:
# Deposit with value FULL_DEPOSIT_AMOUNT
modified_registration_contract.functions.deposit(
*deposit_input,
).transact({"value": full_deposit_amount})
logs = log_filter.get_new_entries()
# Eth2Genesis event should not be triggered
assert len(logs) == 0
# Make 1 more deposit with value FULL_DEPOSIT_AMOUNT to trigger Eth2Genesis event
modified_registration_contract.functions.deposit(
*deposit_input,
).transact({"value": full_deposit_amount})
logs = log_filter.get_new_entries()
assert len(logs) == 1
timestamp = int(w3.eth.getBlock(w3.eth.blockNumber)['timestamp'])
timestamp_day_boundary = timestamp + (86400 - timestamp % 86400) + 86400
log = logs[0]['args']
assert log['deposit_root'] == modified_registration_contract.functions.get_deposit_root().call()
assert int.from_bytes(log['time'], byteorder='little') == timestamp_day_boundary
assert modified_registration_contract.functions.chainStarted().call() is True
# Make 1 deposit with value FULL_DEPOSIT_AMOUNT and
# check that Eth2Genesis event is not triggered
modified_registration_contract.functions.deposit(
*deposit_input,
).transact({"value": full_deposit_amount})
logs = log_filter.get_new_entries()
assert len(logs) == 0

View File

@ -95,7 +95,7 @@
- [`initiate_validator_exit`](#initiate_validator_exit) - [`initiate_validator_exit`](#initiate_validator_exit)
- [`slash_validator`](#slash_validator) - [`slash_validator`](#slash_validator)
- [Genesis](#genesis) - [Genesis](#genesis)
- [`Eth2Genesis`](#eth2genesis) - [Genesis trigger](#genesis-trigger)
- [Genesis state](#genesis-state) - [Genesis state](#genesis-state)
- [Genesis block](#genesis-block) - [Genesis block](#genesis-block)
- [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Beacon chain state transition function](#beacon-chain-state-transition-function)
@ -941,6 +941,7 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA
""" """
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield) attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bitfield) custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bitfield)
assert set(custody_bit_1_indices).issubset(attesting_indices)
custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices] custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices]
return IndexedAttestation( return IndexedAttestation(
@ -1106,20 +1107,45 @@ def slash_validator(state: BeaconState,
## Genesis ## Genesis
### `Eth2Genesis` ### Genesis trigger
When enough deposits of size `MAX_EFFECTIVE_BALANCE` have been made to the deposit contract an `Eth2Genesis` log is emitted triggering the genesis of the beacon chain. Let: Before genesis has been triggered and whenever the deposit contract emits a `Deposit` log, call the function `is_genesis_trigger(deposits: List[Deposit], timestamp: uint64) -> bool` where:
* `eth2genesis` be the object corresponding to `Eth2Genesis` * `deposits` is the list of all deposits, ordered chronologically, up to and including the deposit triggering the latest `Deposit` log
* `genesis_eth1_data` be object of type `Eth1Data` where * `timestamp` is the Unix timestamp in the Ethereum 1.0 block that emitted the latest `Deposit` log
* `genesis_eth1_data.deposit_root = eth2genesis.deposit_root`
* `genesis_eth1_data.deposit_count = eth2genesis.deposit_count` When `is_genesis_trigger(deposits, timestamp) is True` for the first time let:
* `genesis_eth1_data.block_hash` is the hash of the Ethereum 1.0 block that emitted the `Eth2Genesis` log
* `genesis_deposits` be the object of type `List[Deposit]` with deposits ordered chronologically up to and including the deposit that triggered the `Eth2Genesis` log * `genesis_deposits = deposits`
* `genesis_time = timestamp - timestamp % SECONDS_PER_DAY + 2 * SECONDS_PER_DAY` where `SECONDS_PER_DAY = 86400`
* `genesis_eth1_data` be the object of type `Eth1Data` where:
* `genesis_eth1_data.block_hash` is the Ethereum 1.0 block hash that emitted the log for the last deposit in `deposits`
* `genesis_eth1_data.deposit_root` is the deposit root for the last deposit in `deposits`
* `genesis_eth1_data.deposit_count = len(genesis_deposits)`
*Note*: The function `is_genesis_trigger` has yet to be agreed by the community, and can be updated as necessary. We define the following testing placeholder:
```python
def is_genesis_trigger(deposits: List[Deposit], timestamp: uint64) -> bool:
# Process deposits
state = BeaconState()
for deposit in deposits:
process_deposit(state, deposit)
# Count active validators at genesis
active_validator_count = 0
for validator in state.validator_registry:
if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
active_validator_count += 1
# Check effective balance to trigger genesis
GENESIS_ACTIVE_VALIDATOR_COUNT = 2**16
return active_validator_count == GENESIS_ACTIVE_VALIDATOR_COUNT
```
### Genesis state ### Genesis state
Let `genesis_state = get_genesis_beacon_state(genesis_deposits, eth2genesis.genesis_time, genesis_eth1_data)`. Let `genesis_state = get_genesis_beacon_state(genesis_deposits, genesis_time, genesis_eth1_data)`.
```python ```python
def get_genesis_beacon_state(deposits: List[Deposit], genesis_time: int, genesis_eth1_data: Eth1Data) -> BeaconState: def get_genesis_beacon_state(deposits: List[Deposit], genesis_time: int, genesis_eth1_data: Eth1Data) -> BeaconState:
@ -1196,7 +1222,7 @@ def process_slot(state: BeaconState) -> None:
### Epoch processing ### Epoch processing
Note: the `# @LabelHere` lines below are placeholders to show that code will be inserted here in a future phase. *Note*: the `# @LabelHere` lines below are placeholders to show that code will be inserted here in a future phase.
```python ```python
def process_epoch(state: BeaconState) -> None: def process_epoch(state: BeaconState) -> None:
@ -1647,6 +1673,10 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
Process ``Attestation`` operation. Process ``Attestation`` operation.
""" """
data = attestation.data data = attestation.data
assert data.crosslink.shard < SHARD_COUNT
assert data.target_epoch in (get_previous_epoch(state), get_current_epoch(state))
attestation_slot = get_attestation_data_slot(state, data) attestation_slot = get_attestation_data_slot(state, data)
assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH assert attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation_slot + SLOTS_PER_EPOCH
@ -1657,7 +1687,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
proposer_index=get_beacon_proposer_index(state), proposer_index=get_beacon_proposer_index(state),
) )
assert data.target_epoch in (get_previous_epoch(state), get_current_epoch(state))
if data.target_epoch == get_current_epoch(state): if data.target_epoch == get_current_epoch(state):
ffg_data = (state.current_justified_epoch, state.current_justified_root, get_current_epoch(state)) ffg_data = (state.current_justified_epoch, state.current_justified_root, get_current_epoch(state))
parent_crosslink = state.current_crosslinks[data.crosslink.shard] parent_crosslink = state.current_crosslinks[data.crosslink.shard]

View File

@ -9,15 +9,12 @@
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Introduction](#introduction) - [Introduction](#introduction)
- [Constants](#constants) - [Constants](#constants)
- [Gwei values](#gwei-values)
- [Contract](#contract) - [Contract](#contract)
- [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) - [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract)
- [Arguments](#arguments) - [`deposit` function](#deposit-function)
- [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials) - [Withdrawal credentials](#withdrawal-credentials)
- [Amount](#amount) - [`Deposit` log](#deposit-log)
- [Event logs](#event-logs)
- [`Deposit` logs](#deposit-logs)
- [`Eth2Genesis` log](#eth2genesis-log)
- [Vyper code](#vyper-code) - [Vyper code](#vyper-code)
<!-- /TOC --> <!-- /TOC -->
@ -28,66 +25,40 @@ This document represents the specification for the beacon chain deposit contract
## Constants ## Constants
### Gwei values
| Name | Value | Unit |
| - | - | - |
| `FULL_DEPOSIT_AMOUNT` | `32 * 10**9` | Gwei |
### Contract ### Contract
| Name | Value | | Name | Value |
| - | - | | - | - |
| `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | | `DEPOSIT_CONTRACT_ADDRESS` | **TBD** |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `CHAIN_START_FULL_DEPOSIT_THRESHOLD` | `2**16` (= 65,536) |
## Ethereum 1.0 deposit contract ## Ethereum 1.0 deposit contract
The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to Ethereum 1.0 for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2 (i.e. when the EVM 2.0 is deployed and the shards have state). The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to Ethereum 1.0 for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2 (i.e. when the EVM 2.0 is deployed and the shards have state).
### Arguments ### `deposit` function
The deposit contract has a `deposit` function which takes the amount in Ethereum 1.0 transaction, and arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96]` corresponding to `DepositData`. The deposit contract has a public `deposit` function to make deposits. It takes as arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96]` corresponding to a `DepositData` object.
#### Deposit amount
The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on Ethereum 1.0.
#### Withdrawal credentials #### Withdrawal credentials
One of the `DepositData` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawals to shards. The first byte of `withdrawal_credentials` is a version number. As of now, the only expected format is as follows: One of the `DepositData` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawing validator balance (e.g. to another validator, or to shards). The first byte of `withdrawal_credentials` is a version number. As of now, the only expected format is as follows:
* `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE` * `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE`
* `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]` where `withdrawal_pubkey` is a BLS pubkey * `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]` where `withdrawal_pubkey` is a BLS pubkey
The private key corresponding to `withdrawal_pubkey` will be required to initiate a withdrawal. It can be stored separately until a withdrawal is required, e.g. in cold storage. The private key corresponding to `withdrawal_pubkey` will be required to initiate a withdrawal. It can be stored separately until a withdrawal is required, e.g. in cold storage.
#### Amount #### `Deposit` log
* A valid deposit amount should be at least `MIN_DEPOSIT_AMOUNT` in Gwei. Every Ethereum 1.0 deposit emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
* A deposit with an amount greater than or equal to `FULL_DEPOSIT_AMOUNT` in Gwei is considered as a full deposit.
## Event logs
### `Deposit` logs
Every Ethereum 1.0 deposit, of size at least `MIN_DEPOSIT_AMOUNT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
### `Eth2Genesis` log
When `CHAIN_START_FULL_DEPOSIT_THRESHOLD` of full deposits have been made, the deposit contract emits the `Eth2Genesis` log. The beacon chain state may then be initialized by calling the `get_genesis_beacon_state` function (defined [here](./0_beacon-chain.md#genesis-state)) where:
* `genesis_time` equals `time` in the `Eth2Genesis` log
* `eth1_data.deposit_root` equals `deposit_root` in the `Eth2Genesis` log
* `eth1_data.deposit_count` equals `deposit_count` in the `Eth2Genesis` log
* `eth1_data.block_hash` equals the hash of the block that included the log
* `genesis_validator_deposits` is a list of `Deposit` objects built according to the `Deposit` logs up to the deposit that triggered the `Eth2Genesis` log, processed in the order in which they were emitted (oldest to newest)
## Vyper code ## Vyper code
The source for the Vyper contract lives [here](./../../deposit_contract/contracts/validator_registration.v.py). The deposit contract source code, written in Vyper, is available [here](https://github.com/ethereum/eth2.0-specs/blob/dev/deposit_contract/contracts/validator_registration.v.py).
*Note*: To save ~10x on gas, this contract uses a somewhat unintuitive progressive Merkle root calculation algo that requires only O(log(n)) storage. See https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py for an implementation of the same algo in Python tested for correctness. *Note*: To save on gas the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof.
For convenience, we provide the interface to the contract here:
* `__init__()`: initializes the contract
* `get_deposit_root() -> bytes32`: returns the current root of the deposit tree
* `deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])`: adds a deposit instance to the deposit tree, incorporating the input arguments and the value transferred in the given call. *Note*: The amount of value transferred *must* be at least `MIN_DEPOSIT_AMOUNT`. Each of these constants are specified in units of Gwei.

View File

@ -147,7 +147,7 @@ def get_committee_assignment(
committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH
epoch_start_slot = get_epoch_start_slot(epoch) epoch_start_slot = get_epoch_start_slot(epoch)
for slot in range(epoch_start_slot, epoch_start_slot + SLOTS_PER_EPOCH) for slot in range(epoch_start_slot, epoch_start_slot + SLOTS_PER_EPOCH):
offset = committees_per_slot * (slot % SLOTS_PER_EPOCH) offset = committees_per_slot * (slot % SLOTS_PER_EPOCH)
slot_start_shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT slot_start_shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT
for i in range(committees_per_slot): for i in range(committees_per_slot):
@ -297,11 +297,11 @@ Set `attestation_data.beacon_block_root = signing_root(head_block)`.
* Set `attestation_data.source_epoch = head_state.current_justified_epoch`. * Set `attestation_data.source_epoch = head_state.current_justified_epoch`.
* Set `attestation_data.source_root = head_state.current_justified_root`. * Set `attestation_data.source_root = head_state.current_justified_root`.
* Set `attestation_data.target_epoch = get_current_epoch(head_state)` * Set `attestation_data.target_epoch = get_current_epoch(head_state)`
* Set `attestation_data.target_root = signing_root(epoch_boundary_block)` where `epoch_boundary_block` is the block at the most recent epoch boundary. * Set `attestation_data.target_root = epoch_boundary_block_root` where `epoch_boundary_block_root` is the root of block at the most recent epoch boundary.
*Note*: `epoch_boundary_block` can be looked up in the state using: *Note*: `epoch_boundary_block_root` can be looked up in the state using:
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`. * Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
* Let `epoch_boundary_block = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`. * Let `epoch_boundary_block_root = signing_root(head_block) if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
##### Crosslink vote ##### Crosslink vote

View File

@ -2,7 +2,7 @@ from typing import Callable, Iterable
from eth2spec.phase0 import spec as spec_phase0 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.test.epoch_processing import ( from eth2spec.test.phase_0.epoch_processing import (
test_process_crosslinks, test_process_crosslinks,
test_process_registry_updates test_process_registry_updates
) )
@ -33,8 +33,10 @@ def create_suite(transition_name: str, config_name: str, get_cases: Callable[[],
if __name__ == "__main__": if __name__ == "__main__":
gen_runner.run_generator("epoch_processing", [ gen_runner.run_generator("epoch_processing", [
create_suite('crosslinks', 'minimal', lambda: generate_from_tests(test_process_crosslinks)), create_suite('crosslinks', 'minimal', lambda: generate_from_tests(test_process_crosslinks, 'phase0')),
create_suite('crosslinks', 'mainnet', lambda: generate_from_tests(test_process_crosslinks)), create_suite('crosslinks', 'mainnet', lambda: generate_from_tests(test_process_crosslinks, 'phase0')),
create_suite('registry_updates', 'minimal', lambda: generate_from_tests(test_process_registry_updates)), create_suite('registry_updates', 'minimal',
create_suite('registry_updates', 'mainnet', lambda: generate_from_tests(test_process_registry_updates)), lambda: generate_from_tests(test_process_registry_updates, 'phase0')),
create_suite('registry_updates', 'mainnet',
lambda: generate_from_tests(test_process_registry_updates, 'phase0')),
]) ])

View File

@ -1,13 +1,13 @@
from typing import Callable, Iterable from typing import Callable, Iterable
from eth2spec.test.block_processing import ( from eth2spec.test.phase_0.block_processing import (
test_process_attestation, test_process_attestation,
test_process_attester_slashing, test_process_attester_slashing,
test_process_block_header, test_process_block_header,
test_process_deposit, test_process_deposit,
test_process_proposer_slashing, test_process_proposer_slashing,
test_process_transfer, test_process_transfer,
test_process_voluntary_exit test_process_voluntary_exit,
) )
from gen_base import gen_runner, gen_suite, gen_typing from gen_base import gen_runner, gen_suite, gen_typing
@ -38,18 +38,18 @@ def create_suite(operation_name: str, config_name: str, get_cases: Callable[[],
if __name__ == "__main__": if __name__ == "__main__":
gen_runner.run_generator("operations", [ gen_runner.run_generator("operations", [
create_suite('attestation', 'minimal', lambda: generate_from_tests(test_process_attestation)), create_suite('attestation', 'minimal', lambda: generate_from_tests(test_process_attestation, 'phase0')),
create_suite('attestation', 'mainnet', lambda: generate_from_tests(test_process_attestation)), create_suite('attestation', 'mainnet', lambda: generate_from_tests(test_process_attestation, 'phase0')),
create_suite('attester_slashing', 'minimal', lambda: generate_from_tests(test_process_attester_slashing)), create_suite('attester_slashing', 'minimal', lambda: generate_from_tests(test_process_attester_slashing, 'phase0')),
create_suite('attester_slashing', 'mainnet', lambda: generate_from_tests(test_process_attester_slashing)), create_suite('attester_slashing', 'mainnet', lambda: generate_from_tests(test_process_attester_slashing, 'phase0')),
create_suite('block_header', 'minimal', lambda: generate_from_tests(test_process_block_header)), create_suite('block_header', 'minimal', lambda: generate_from_tests(test_process_block_header, 'phase0')),
create_suite('block_header', 'mainnet', lambda: generate_from_tests(test_process_block_header)), create_suite('block_header', 'mainnet', lambda: generate_from_tests(test_process_block_header, 'phase0')),
create_suite('deposit', 'minimal', lambda: generate_from_tests(test_process_deposit)), create_suite('deposit', 'minimal', lambda: generate_from_tests(test_process_deposit, 'phase0')),
create_suite('deposit', 'mainnet', lambda: generate_from_tests(test_process_deposit)), create_suite('deposit', 'mainnet', lambda: generate_from_tests(test_process_deposit, 'phase0')),
create_suite('proposer_slashing', 'minimal', lambda: generate_from_tests(test_process_proposer_slashing)), create_suite('proposer_slashing', 'minimal', lambda: generate_from_tests(test_process_proposer_slashing, 'phase0')),
create_suite('proposer_slashing', 'mainnet', lambda: generate_from_tests(test_process_proposer_slashing)), create_suite('proposer_slashing', 'mainnet', lambda: generate_from_tests(test_process_proposer_slashing, 'phase0')),
create_suite('transfer', 'minimal', lambda: generate_from_tests(test_process_transfer)), create_suite('transfer', 'minimal', lambda: generate_from_tests(test_process_transfer, 'phase0')),
create_suite('transfer', 'mainnet', lambda: generate_from_tests(test_process_transfer)), create_suite('transfer', 'mainnet', lambda: generate_from_tests(test_process_transfer, 'phase0')),
create_suite('voluntary_exit', 'minimal', lambda: generate_from_tests(test_process_voluntary_exit)), create_suite('voluntary_exit', 'minimal', lambda: generate_from_tests(test_process_voluntary_exit, 'phase0')),
create_suite('voluntary_exit', 'mainnet', lambda: generate_from_tests(test_process_voluntary_exit)), create_suite('voluntary_exit', 'mainnet', lambda: generate_from_tests(test_process_voluntary_exit, 'phase0')),
]) ])

View File

@ -16,7 +16,7 @@ def create_suite(handler_name: str, config_name: str, get_cases: Callable[[], It
spec_phase0.apply_constants_preset(presets) spec_phase0.apply_constants_preset(presets)
spec_phase1.apply_constants_preset(presets) spec_phase1.apply_constants_preset(presets)
return ("%sanity_s_%s" % (handler_name, config_name), handler_name, gen_suite.render_suite( return ("sanity_%s_%s" % (handler_name, config_name), handler_name, gen_suite.render_suite(
title="sanity testing", title="sanity testing",
summary="Sanity test suite, %s type, generated from pytests" % handler_name, summary="Sanity test suite, %s type, generated from pytests" % handler_name,
forks_timeline="testing", forks_timeline="testing",
@ -30,8 +30,8 @@ def create_suite(handler_name: str, config_name: str, get_cases: Callable[[], It
if __name__ == "__main__": if __name__ == "__main__":
gen_runner.run_generator("sanity", [ gen_runner.run_generator("sanity", [
create_suite('blocks', 'minimal', lambda: generate_from_tests(test_blocks)), create_suite('blocks', 'minimal', lambda: generate_from_tests(test_blocks, 'phase0')),
create_suite('blocks', 'mainnet', lambda: generate_from_tests(test_blocks)), create_suite('blocks', 'mainnet', lambda: generate_from_tests(test_blocks, 'phase0')),
create_suite('slots', 'minimal', lambda: generate_from_tests(test_slots)), create_suite('slots', 'minimal', lambda: generate_from_tests(test_slots, 'phase0')),
create_suite('slots', 'mainnet', lambda: generate_from_tests(test_slots)), create_suite('slots', 'mainnet', lambda: generate_from_tests(test_slots, 'phase0')),
]) ])

View File

@ -1,5 +1,4 @@
from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase0 import spec as spec
from eth2spec.phase1 import spec as spec_phase1
from eth_utils import ( from eth_utils import (
to_dict, to_tuple to_dict, to_tuple
) )
@ -8,7 +7,7 @@ from preset_loader import loader
@to_dict @to_dict
def shuffling_case(seed: spec.Bytes32, count: int): def shuffling_case(seed, count):
yield 'seed', '0x' + seed.hex() yield 'seed', '0x' + seed.hex()
yield 'count', count yield 'count', count
yield 'shuffled', [spec.get_shuffled_index(i, count, seed) for i in range(count)] yield 'shuffled', [spec.get_shuffled_index(i, count, seed) for i in range(count)]
@ -23,8 +22,7 @@ def shuffling_test_cases():
def mini_shuffling_suite(configs_path: str) -> gen_typing.TestSuiteOutput: def mini_shuffling_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
presets = loader.load_presets(configs_path, 'minimal') presets = loader.load_presets(configs_path, 'minimal')
spec_phase0.apply_constants_preset(presets) spec.apply_constants_preset(presets)
spec_phase1.apply_constants_preset(presets)
return ("shuffling_minimal", "core", gen_suite.render_suite( return ("shuffling_minimal", "core", gen_suite.render_suite(
title="Swap-or-Not Shuffling tests with minimal config", title="Swap-or-Not Shuffling tests with minimal config",
@ -39,8 +37,7 @@ def mini_shuffling_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
def full_shuffling_suite(configs_path: str) -> gen_typing.TestSuiteOutput: def full_shuffling_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
presets = loader.load_presets(configs_path, 'mainnet') presets = loader.load_presets(configs_path, 'mainnet')
spec_phase0.apply_constants_preset(presets) spec.apply_constants_preset(presets)
spec_phase1.apply_constants_preset(presets)
return ("shuffling_full", "core", gen_suite.render_suite( return ("shuffling_full", "core", gen_suite.render_suite(
title="Swap-or-Not Shuffling tests with mainnet config", title="Swap-or-Not Shuffling tests with mainnet config",

View File

@ -1,7 +1,10 @@
from random import Random from random import Random
from inspect import getmembers, isclass
from eth2spec.debug import random_value, encode from eth2spec.debug import random_value, encode
from eth2spec.phase0 import spec from eth2spec.phase0 import spec
from eth2spec.utils.ssz.ssz_typing import Container
from eth2spec.utils.ssz.ssz_impl import ( from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root, hash_tree_root,
signing_root, signing_root,
@ -27,17 +30,23 @@ def create_test_case_contents(value, typ):
@to_dict @to_dict
def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool): def create_test_case(rng: Random, name: str, typ, mode: random_value.RandomizationMode, chaos: bool):
typ = spec.get_ssz_type_by_name(name)
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos) value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
yield name, create_test_case_contents(value, typ) yield name, create_test_case_contents(value, typ)
def get_spec_ssz_types():
return [
(name, value) for (name, value) in getmembers(spec, isclass)
if issubclass(value, Container) and value != Container # only the subclasses, not the imported base class
]
@to_tuple @to_tuple
def ssz_static_cases(rng: Random, mode: random_value.RandomizationMode, chaos: bool, count: int): def ssz_static_cases(rng: Random, mode: random_value.RandomizationMode, chaos: bool, count: int):
for type_name in spec.ssz_types: for (name, ssz_type) in get_spec_ssz_types():
for i in range(count): for i in range(count):
yield create_test_case(rng, type_name, mode, chaos) yield create_test_case(rng, name, ssz_type, mode, chaos)
def get_ssz_suite(seed: int, config_name: str, mode: random_value.RandomizationMode, chaos: bool, cases_if_random: int): def get_ssz_suite(seed: int, config_name: str, mode: random_value.RandomizationMode, chaos: bool, cases_if_random: int):
@ -81,8 +90,6 @@ if __name__ == "__main__":
settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5)) settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5))
seed += 1 seed += 1
print("Settings: %d, SSZ-types: %d" % (len(settings), len(spec.ssz_types)))
gen_runner.run_generator("ssz_static", [ gen_runner.run_generator("ssz_static", [
get_ssz_suite(seed, config_name, mode, chaos, cases_if_random) get_ssz_suite(seed, config_name, mode, chaos, cases_if_random)
for (seed, config_name, mode, chaos, cases_if_random) in settings for (seed, config_name, mode, chaos, cases_if_random) in settings

View File

@ -1,9 +1,10 @@
from inspect import getmembers, isfunction from inspect import getmembers, isfunction
def generate_from_tests(src, bls_active=True): def generate_from_tests(src, phase, bls_active=True):
""" """
Generate a list of test cases by running tests from the given src in generator-mode. Generate a list of test cases by running tests from the given src in generator-mode.
:param src: to retrieve tests from (discovered using inspect.getmembers) :param src: to retrieve tests from (discovered using inspect.getmembers).
:param phase: to run tests against particular phase.
:param bls_active: optional, to override BLS switch preference. Defaults to True. :param bls_active: optional, to override BLS switch preference. Defaults to True.
:return: the list of test cases. :return: the list of test cases.
""" """
@ -16,7 +17,7 @@ def generate_from_tests(src, bls_active=True):
for name in fn_names: for name in fn_names:
tfn = getattr(src, name) tfn = getattr(src, name)
try: try:
test_case = tfn(generator_mode=True, bls_active=bls_active) test_case = tfn(generator_mode=True, phase=phase, bls_active=bls_active)
# If no test case data is returned, the test is ignored. # If no test case data is returned, the test is ignored.
if test_case is not None: if test_case is not None:
out.append(test_case) out.append(test_case)

View File

@ -116,12 +116,22 @@ def with_phases(phases):
def decorator(fn): def decorator(fn):
def run_with_spec_version(spec, *args, **kw): def run_with_spec_version(spec, *args, **kw):
kw['spec'] = spec kw['spec'] = spec
fn(*args, **kw) return fn(*args, **kw)
def wrapper(*args, **kw): def wrapper(*args, **kw):
if 'phase0' in phases: run_phases = phases
run_with_spec_version(spec_phase0, *args, **kw)
if 'phase1' in phases: # limit phases if one explicitly specified
run_with_spec_version(spec_phase1, *args, **kw) if 'phase' in kw:
phase = kw.pop('phase')
if phase not in phases:
return
run_phases = [phase]
if 'phase0' in run_phases:
ret = run_with_spec_version(spec_phase0, *args, **kw)
if 'phase1' in run_phases:
ret = run_with_spec_version(spec_phase1, *args, **kw)
return ret
return wrapper return wrapper
return decorator return decorator

View File

@ -67,7 +67,7 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
# insecurely use pubkey as withdrawal key if no credentials provided # insecurely use pubkey as withdrawal key if no credentials provided
if withdrawal_credentials is None: if withdrawal_credentials is None:
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(pubkey)[1:] withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(pubkey)[1:]
deposit, root, deposit_data_leaves = build_deposit( deposit, root, deposit_data_leaves = build_deposit(
spec, spec,
@ -77,7 +77,7 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
privkey, privkey,
amount, amount,
withdrawal_credentials, withdrawal_credentials,
signed signed,
) )
state.eth1_data.deposit_root = root state.eth1_data.deposit_root = root

View File

@ -5,7 +5,7 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root
def build_mock_validator(spec, i: int, balance: int): def build_mock_validator(spec, i: int, balance: int):
pubkey = pubkeys[i] pubkey = pubkeys[i]
# insecurely use pubkey as withdrawal key as well # insecurely use pubkey as withdrawal key as well
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(pubkey)[1:] withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(pubkey)[1:]
return spec.Validator( return spec.Validator(
pubkey=pubkeys[i], pubkey=pubkeys[i],
withdrawal_credentials=withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,

View File

@ -32,7 +32,7 @@ def get_valid_transfer(spec, state, slot=None, sender_index=None, amount=None, f
# ensure withdrawal_credentials reproducible # ensure withdrawal_credentials reproducible
state.validators[transfer.sender].withdrawal_credentials = ( state.validators[transfer.sender].withdrawal_credentials = (
spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(transfer.pubkey)[1:] spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(transfer.pubkey)[1:]
) )
return transfer return transfer

View File

@ -101,7 +101,7 @@ def test_invalid_sig_top_up(spec, state):
def test_invalid_withdrawal_credentials_top_up(spec, state): def test_invalid_withdrawal_credentials_top_up(spec, state):
validator_index = 0 validator_index = 0
amount = spec.MAX_EFFECTIVE_BALANCE // 4 amount = spec.MAX_EFFECTIVE_BALANCE // 4
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(b"junk")[1:] withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(b"junk")[1:]
deposit = prepare_state_and_deposit( deposit = prepare_state_and_deposit(
spec, spec,
state, state,

View File

@ -5,7 +5,7 @@ from eth2spec.utils.ssz.ssz_impl import signing_root
from eth2spec.utils.bls import bls_sign from eth2spec.utils.bls import bls_sign
from eth2spec.test.helpers.state import get_balance from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.transfers import get_valid_transfer # from eth2spec.test.helpers.transfers import get_valid_transfer
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
from eth2spec.test.helpers.keys import privkeys, pubkeys 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
@ -303,38 +303,38 @@ def test_voluntary_exit(spec, state):
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases # @with_all_phases
@spec_state_test # @spec_state_test
def test_transfer(spec, state): # def test_transfer(spec, state):
# overwrite default 0 to test # overwrite default 0 to test
spec.MAX_TRANSFERS = 1 # spec.MAX_TRANSFERS = 1
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
amount = get_balance(state, sender_index) # amount = get_balance(state, sender_index)
transfer = get_valid_transfer(spec, state, state.slot + 1, sender_index, amount, signed=True) # transfer = get_valid_transfer(spec, state, state.slot + 1, sender_index, amount, signed=True)
recipient_index = transfer.recipient # recipient_index = transfer.recipient
pre_transfer_recipient_balance = get_balance(state, recipient_index) # pre_transfer_recipient_balance = get_balance(state, recipient_index)
# un-activate so validator can transfer # un-activate so validator can transfer
state.validators[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH # state.validators[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield 'pre', state # yield 'pre', state
# Add to state via block transition # Add to state via block transition
block = build_empty_block_for_next_slot(spec, state) # block = build_empty_block_for_next_slot(spec, state)
block.body.transfers.append(transfer) # block.body.transfers.append(transfer)
sign_block(spec, state, block) # sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock] # yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block) # spec.state_transition(state, block)
yield 'post', state # yield 'post', state
sender_balance = get_balance(state, sender_index) # sender_balance = get_balance(state, sender_index)
recipient_balance = get_balance(state, recipient_index) # recipient_balance = get_balance(state, recipient_index)
assert sender_balance == 0 # assert sender_balance == 0
assert recipient_balance == pre_transfer_recipient_balance + amount # assert recipient_balance == pre_transfer_recipient_balance + amount
@with_all_phases @with_all_phases

View File

@ -1,5 +1,6 @@
from typing import Dict, Any, Callable, Iterable from typing import Dict, Any, Callable, Iterable
from eth2spec.debug.encode import encode from eth2spec.debug.encode import encode
from eth2spec.utils.ssz.ssz_typing import Container
def spectest(description: str = None): def spectest(description: str = None):
@ -30,9 +31,13 @@ def spectest(description: str = None):
else: else:
# Otherwise, try to infer the type, but keep it as-is if it's not a SSZ container. # Otherwise, try to infer the type, but keep it as-is if it's not a SSZ container.
(key, value) = data (key, value) = data
if hasattr(value.__class__, 'fields'): if isinstance(value, Container):
out[key] = encode(value, value.__class__) out[key] = encode(value, value.__class__)
else: else:
# not a ssz value.
# It could be vector or bytes still, but it is a rare case,
# and lists can't be inferred fully (generics lose element type).
# In such cases, explicitly state the type of the yielded value as a third yielded object.
out[key] = value out[key] = value
if has_contents: if has_contents:
return out return out

View File

@ -1,5 +1,28 @@
from hashlib import sha256 from hashlib import sha256
ZERO_BYTES32 = b'\x00' * 32
def _hash(x):
return sha256(x).digest()
# Minimal collection of (key, value) pairs, for fast hash-retrieval, to save on repetitive computation cost.
# Key = the hash input
# Value = the hash output
hash_cache = []
def add_zero_hashes_to_cache():
zerohashes = [(None, ZERO_BYTES32)]
for layer in range(1, 32):
k = zerohashes[layer - 1][1] + zerohashes[layer - 1][1]
zerohashes.append((k, _hash(k)))
hash_cache.extend(zerohashes[1:])
def hash(x): def hash(x):
return sha256(x).digest() for (k, h) in hash_cache:
if x == k:
return h
return _hash(x)

View File

@ -513,13 +513,11 @@ def read_vector_elem_type(vector_typ: Type[Vector[T, L]]) -> T:
def read_elem_type(typ): def read_elem_type(typ):
if typ == bytes: if typ == bytes or (isinstance(typ, type) and issubclass(typ, bytes)): # bytes or bytesN
return byte return byte
elif is_list_type(typ): elif is_list_type(typ):
return read_list_elem_type(typ) return read_list_elem_type(typ)
elif is_vector_type(typ): elif is_vector_type(typ):
return read_vector_elem_type(typ) return read_vector_elem_type(typ)
elif issubclass(typ, bytes): # bytes or bytesN
return byte
else: else:
raise TypeError("Unexpected type: {}".format(typ)) raise TypeError("Unexpected type: {}".format(typ))