Merge pull request #1202 from ethereum/test_genesis

Add `test_genesis.py` and fix `is_genesis_trigger`
This commit is contained in:
Danny Ryan 2019-06-29 23:31:32 -05:00 committed by GitHub
commit 2f43f9c339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 260 additions and 141 deletions

View File

@ -17,6 +17,8 @@ MIN_PER_EPOCH_CHURN_LIMIT: 4
CHURN_LIMIT_QUOTIENT: 65536 CHURN_LIMIT_QUOTIENT: 65536
# See issue 563 # See issue 563
SHUFFLE_ROUND_COUNT: 90 SHUFFLE_ROUND_COUNT: 90
# `2**16` (= 65,536)
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 65536
# Deposit contract # Deposit contract

View File

@ -16,6 +16,8 @@ MIN_PER_EPOCH_CHURN_LIMIT: 4
CHURN_LIMIT_QUOTIENT: 65536 CHURN_LIMIT_QUOTIENT: 65536
# [customized] Faster, but unsecure. # [customized] Faster, but unsecure.
SHUFFLE_ROUND_COUNT: 10 SHUFFLE_ROUND_COUNT: 10
# [customized]
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# Deposit contract # Deposit contract

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes AMOUNT_LENGTH: constant(uint256) = 8 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
Deposit: event({ DepositEvent: event({
pubkey: bytes[48], pubkey: bytes[48],
withdrawal_credentials: bytes[32], withdrawal_credentials: bytes[32],
amount: bytes[8], amount: bytes[8],
@ -42,8 +42,9 @@ def to_little_endian_64(value: uint256) -> bytes[8]:
@public @public
@constant @constant
def get_deposit_root() -> bytes32: def get_hash_tree_root() -> bytes32:
node: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000 zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
node: bytes32 = zero_bytes32
size: uint256 = self.deposit_count size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH): for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1` if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
@ -51,7 +52,7 @@ def get_deposit_root() -> bytes32:
else: else:
node = sha256(concat(node, self.zero_hashes[height])) node = sha256(concat(node, self.zero_hashes[height]))
size /= 2 size /= 2
return node return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public @public
@ -75,11 +76,11 @@ def deposit(pubkey: bytes[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
# Emit `Deposit` log # Emit `DepositEvent` log
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)) log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
# Compute `DepositData` root # Compute `DepositData` hash tree root
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000 zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH))) pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
signature_root: bytes32 = sha256(concat( signature_root: bytes32 = sha256(concat(
@ -91,7 +92,7 @@ def deposit(pubkey: bytes[PUBKEY_LENGTH],
sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)), sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
)) ))
# Add `DepositData` root to Merkle tree (update a single `branch` node) # Add `DepositData` hash tree root to Merkle tree (update a single `branch` node)
self.deposit_count += 1 self.deposit_count += 1
size: uint256 = self.deposit_count size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH): for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):

View File

@ -15,26 +15,12 @@ from eth2spec.phase0.spec import (
DepositData, DepositData,
) )
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import List
from eth2spec.utils.ssz.ssz_impl import ( from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root, hash_tree_root,
) )
def compute_merkle_root(leaf_nodes):
assert len(leaf_nodes) >= 1
empty_node = b'\x00' * 32
child_nodes = leaf_nodes[:]
for _ in range(DEPOSIT_CONTRACT_TREE_DEPTH):
parent_nodes = []
if len(child_nodes) % 2 == 1:
child_nodes.append(empty_node)
for j in range(0, len(child_nodes), 2):
parent_nodes.append(hash(child_nodes[j] + child_nodes[j + 1]))
child_nodes = parent_nodes
empty_node = hash(empty_node + empty_node)
return child_nodes[0]
@pytest.fixture @pytest.fixture
def deposit_input(): def deposit_input():
""" """
@ -110,8 +96,8 @@ def test_deposit_inputs(registration_contract,
) )
def test_deposit_log(registration_contract, a0, w3, deposit_input): def test_deposit_event_log(registration_contract, a0, w3, deposit_input):
log_filter = registration_contract.events.Deposit.createFilter( log_filter = registration_contract.events.DepositEvent.createFilter(
fromBlock='latest', fromBlock='latest',
) )
@ -131,13 +117,14 @@ def test_deposit_log(registration_contract, a0, w3, deposit_input):
assert log['signature'] == deposit_input[2] assert log['signature'] == deposit_input[2]
assert log['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.DepositEvent.createFilter(
fromBlock='latest', fromBlock='latest',
) )
deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(10)] deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(10)]
leaf_nodes = [] deposit_data_list = []
for i in range(0, 10): for i in range(0, 10):
tx_hash = registration_contract.functions.deposit( tx_hash = registration_contract.functions.deposit(
*deposit_input, *deposit_input,
@ -151,13 +138,12 @@ def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input
assert log["index"] == i.to_bytes(8, 'little') assert log["index"] == i.to_bytes(8, 'little')
deposit_data = DepositData( deposit_data_list.append(DepositData(
pubkey=deposit_input[0], pubkey=deposit_input[0],
withdrawal_credentials=deposit_input[1], withdrawal_credentials=deposit_input[1],
amount=deposit_amount_list[i], amount=deposit_amount_list[i],
signature=deposit_input[2], signature=deposit_input[2],
) ))
hash_tree_root_result = hash_tree_root(deposit_data)
leaf_nodes.append(hash_tree_root_result) root = hash_tree_root(List[DepositData, 2**32](*deposit_data_list))
root = compute_merkle_root(leaf_nodes) assert root == registration_contract.functions.get_hash_tree_root().call()
assert root == registration_contract.functions.get_deposit_root().call()

View File

@ -95,7 +95,6 @@
- [`initiate_validator_exit`](#initiate_validator_exit) - [`initiate_validator_exit`](#initiate_validator_exit)
- [`slash_validator`](#slash_validator) - [`slash_validator`](#slash_validator)
- [Genesis](#genesis) - [Genesis](#genesis)
- [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)
@ -126,7 +125,6 @@
This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain. This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain.
At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0, the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior. At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0, the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior.
The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block and proof-of-stake votes for a beacon block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication. The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block and proof-of-stake votes for a beacon block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication.
## Notation ## Notation
@ -176,6 +174,7 @@ The following values are (non-configurable) constants used throughout the specif
| `ZERO_HASH` | `Hash(b'\x00' * 32)` | | `ZERO_HASH` | `Hash(b'\x00' * 32)` |
| `BASE_REWARDS_PER_EPOCH` | `5` | | `BASE_REWARDS_PER_EPOCH` | `5` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `SECONDS_PER_DAY` | `86400` |
## Configuration ## Configuration
@ -191,6 +190,8 @@ The following values are (non-configurable) constants used throughout the specif
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `90` | | `SHUFFLE_ROUND_COUNT` | `90` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**16` (= 65,536) |
| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) |
| `JUSTIFICATION_BITS_LENGTH` | `4` | | `JUSTIFICATION_BITS_LENGTH` | `4` |
* For the safety of crosslinks, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) * For the safety of crosslinks, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -446,7 +447,7 @@ class Attestation(Container):
```python ```python
class Deposit(Container): class Deposit(Container):
proof: Vector[Hash, DEPOSIT_CONTRACT_TREE_DEPTH] # Merkle path to deposit root proof: Vector[Hash, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit data list root
data: DepositData data: DepositData
``` ```
@ -1109,61 +1110,43 @@ def slash_validator(state: BeaconState,
## Genesis ## Genesis
### Genesis trigger
Before genesis has been triggered and whenever the deposit contract emits a `Deposit` log, call the function `is_genesis_trigger(deposits: Sequence[Deposit], timestamp: uint64) -> bool` where:
* `deposits` is the list of all deposits, ordered chronologically, up to and including the deposit triggering the latest `Deposit` log
* `timestamp` is the Unix timestamp in the Ethereum 1.0 block that emitted the latest `Deposit` log
When `is_genesis_trigger(deposits, timestamp) is True` for the first time, let:
* `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 upon by the community, and can be updated as necessary. We define the following testing placeholder:
```python
def is_genesis_trigger(deposits: Sequence[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.validators:
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, genesis_time, genesis_eth1_data)`. Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 block, call `initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` where:
* `eth1_block_hash` is the hash of the Ethereum 1.0 block
* `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash`
* `deposits` is the sequence of all deposits, ordered chronologically, up to the block with hash `eth1_block_hash`
The genesis state `genesis_state` is the return value of calling `initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` only if `is_valid_genesis_state(genesis_state) is True`.
Implementations can choose to support different (more optimized) variations of the below initialization approach:
- Build the `genesis_state` from a stream of deposits by incrementally updating the `state.eth1_data.deposit_root`.
- Compute deposit proofs for the final `state.eth1_data.deposit_root`, and process as a pre-determined collection.
*Note*: The two constants `MIN_GENESIS_TIME` and `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` have yet to be agreed upon by the community, and can be updated as necessary.
```python ```python
def get_genesis_beacon_state(deposits: Sequence[Deposit], genesis_time: int, eth1_data: Eth1Data) -> BeaconState: def initialize_beacon_state_from_eth1(eth1_block_hash: Hash,
eth1_timestamp: uint64,
deposits: Sequence[Deposit]) -> BeaconState:
state = BeaconState( state = BeaconState(
genesis_time=genesis_time, genesis_time=eth1_timestamp - eth1_timestamp % SECONDS_PER_DAY + 2 * SECONDS_PER_DAY,
eth1_data=eth1_data, eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
) )
# Process genesis deposits # Process deposits
for deposit in deposits: leaves = list(map(lambda deposit: deposit.data, deposits))
for index, deposit in enumerate(deposits):
state.eth1_data.deposit_root = hash_tree_root(
List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1])
)
process_deposit(state, deposit) process_deposit(state, deposit)
# Process genesis activations # Process activations
for validator in state.validators: for index, validator in enumerate(state.validators):
if validator.effective_balance >= MAX_EFFECTIVE_BALANCE: if state.balances[index] >= MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH validator.activation_epoch = GENESIS_EPOCH
@ -1174,6 +1157,16 @@ def get_genesis_beacon_state(deposits: Sequence[Deposit], genesis_time: int, eth
return state return state
``` ```
```python
def is_valid_genesis_state(state: BeaconState) -> bool:
if state.genesis_time < MIN_GENESIS_TIME:
return False
elif len(get_active_validator_indices(state, GENESIS_EPOCH)) < MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
return False
else:
return True
```
### Genesis block ### Genesis block
Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
@ -1455,7 +1448,7 @@ def process_registry_updates(state: BeaconState) -> None:
for index, validator in enumerate(state.validators): for index, validator in enumerate(state.validators):
if ( if (
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and
validator.effective_balance >= MAX_EFFECTIVE_BALANCE validator.effective_balance == MAX_EFFECTIVE_BALANCE
): ):
validator.activation_eligibility_epoch = get_current_epoch(state) validator.activation_eligibility_epoch = get_current_epoch(state)
@ -1695,7 +1688,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
assert verify_merkle_branch( assert verify_merkle_branch(
leaf=hash_tree_root(deposit.data), leaf=hash_tree_root(deposit.data),
proof=deposit.proof, proof=deposit.proof,
depth=DEPOSIT_CONTRACT_TREE_DEPTH, depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # add 1 for the SSZ length mix-in
index=state.eth1_deposit_index, index=state.eth1_deposit_index,
root=state.eth1_data.deposit_root, root=state.eth1_data.deposit_root,
) )

View File

@ -14,7 +14,7 @@
- [`deposit` function](#deposit-function) - [`deposit` function](#deposit-function)
- [Deposit amount](#deposit-amount) - [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials) - [Withdrawal credentials](#withdrawal-credentials)
- [`Deposit` log](#deposit-log) - [`DepositEvent` log](#depositevent-log)
- [Vyper code](#vyper-code) - [Vyper code](#vyper-code)
<!-- /TOC --> <!-- /TOC -->
@ -53,9 +53,9 @@ One of the `DepositData` fields is `withdrawal_credentials`. It is a commitment
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.
#### `Deposit` log #### `DepositEvent` log
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. Every Ethereum 1.0 deposit emits a `DepositEvent` 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.
## Vyper code ## Vyper code

View File

@ -221,7 +221,7 @@ epoch_signature = bls_sign(
##### Eth1 Data ##### Eth1 Data
The `block.eth1_data` field is for block proposers to vote on recent Eth 1.0 data. This recent data contains an Eth 1.0 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth 1.0 block. If over half of the block proposers in the current Eth 1.0 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`. The `block.eth1_data` field is for block proposers to vote on recent Eth 1.0 data. This recent data contains an Eth 1.0 block hash as well as the associated deposit root (as calculated by the `get_hash_tree_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth 1.0 block. If over half of the block proposers in the current Eth 1.0 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
Let `get_eth1_data(distance: int) -> Eth1Data` be the (subjective) function that returns the Eth 1.0 data at distance `distance` relative to the Eth 1.0 head at the start of the current Eth 1.0 voting period. Let `previous_eth1_distance` be the distance relative to the Eth 1.0 block corresponding to `state.eth1_data.block_hash` at the start of the current Eth 1.0 voting period. An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where: Let `get_eth1_data(distance: int) -> Eth1Data` be the (subjective) function that returns the Eth 1.0 data at distance `distance` relative to the Eth 1.0 head at the start of the current Eth 1.0 voting period. Let `previous_eth1_distance` be the distance relative to the Eth 1.0 block corresponding to `state.eth1_data.block_hash` at the start of the current Eth 1.0 voting period. An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where:

View File

@ -27,9 +27,13 @@ def with_state(fn):
DEFAULT_BLS_ACTIVE = False DEFAULT_BLS_ACTIVE = False
def spectest_with_bls_switch(fn):
return bls_switch(spectest()(fn))
# shorthand for decorating @with_state @spectest() # shorthand for decorating @with_state @spectest()
def spec_state_test(fn): def spec_state_test(fn):
return with_state(bls_switch(spectest()(fn))) return with_state(spectest_with_bls_switch(fn))
def expect_assertion_error(fn): def expect_assertion_error(fn):

View File

@ -0,0 +1,30 @@
from eth2spec.test.context import spectest_with_bls_switch, with_phases
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
@with_phases(['phase0'])
@spectest_with_bls_switch
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)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
yield 'eth1_block_hash', eth1_block_hash
yield 'eth1_timestamp', eth1_timestamp
yield 'deposits', deposits
# initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY
assert len(state.validators) == deposit_count
assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == deposit_count
assert state.eth1_data.block_hash == eth1_block_hash
# yield state
yield 'state', state

View File

@ -0,0 +1,86 @@
from eth2spec.test.context import spectest_with_bls_switch, with_phases
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
def create_valid_beacon_state(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
return spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
def run_is_valid_genesis_state(spec, state, valid=True):
"""
Run ``is_valid_genesis_state``, yielding:
- state ('state')
- is_valid ('is_valid')
If ``valid == False``, run expecting ``AssertionError``
"""
yield state
is_valid = spec.is_valid_genesis_state(state)
yield 'is_valid', is_valid
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_true(spec):
state = create_valid_beacon_state(spec)
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_false_invalid_timestamp(spec):
state = create_valid_beacon_state(spec)
state.genesis_time = spec.MIN_GENESIS_TIME - 1
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
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
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_false_not_enough_balance(spec):
state = create_valid_beacon_state(spec)
state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1
yield from run_is_valid_genesis_state(spec, state, valid=False)
@with_phases(['phase0'])
@spectest_with_bls_switch
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)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
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)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
yield from run_is_valid_genesis_state(spec, state, valid=False)

View File

@ -1,66 +1,88 @@
from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.test.helpers.keys import pubkeys, privkeys
from eth2spec.utils.bls import bls_sign from eth2spec.utils.bls import bls_sign
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_root, get_merkle_proof from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
from eth2spec.utils.ssz.ssz_impl import signing_root from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
from eth2spec.utils.ssz.ssz_typing import List
def build_deposit_data(spec, state, pubkey, privkey, amount, withdrawal_credentials, signed=False): def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=None, signed=False):
deposit_data = spec.DepositData( deposit_data = spec.DepositData(
pubkey=pubkey, pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,
amount=amount, amount=amount,
) )
if signed: if signed:
sign_deposit_data(spec, state, deposit_data, privkey) sign_deposit_data(spec, deposit_data, privkey, state)
return deposit_data return deposit_data
def sign_deposit_data(spec, state, deposit_data, privkey): def sign_deposit_data(spec, deposit_data, privkey, state=None):
signature = bls_sign( if state is None:
message_hash=signing_root(deposit_data), # Genesis
privkey=privkey, domain = spec.bls_domain(spec.DOMAIN_DEPOSIT)
domain=spec.get_domain( else:
domain = spec.get_domain(
state, state,
spec.DOMAIN_DEPOSIT, spec.DOMAIN_DEPOSIT,
) )
signature = bls_sign(
message_hash=signing_root(deposit_data),
privkey=privkey,
domain=domain,
) )
deposit_data.signature = signature deposit_data.signature = signature
def build_deposit(spec, def build_deposit(spec,
state, state,
deposit_data_leaves, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
amount, amount,
withdrawal_credentials, withdrawal_credentials,
signed): signed):
deposit_data = build_deposit_data(spec, state, pubkey, privkey, amount, withdrawal_credentials, signed) deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=state, signed=signed)
index = len(deposit_data_list)
deposit_data_list.append(deposit_data)
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index)) + [(index + 1).to_bytes(32, 'little')]
leaf = deposit_data.hash_tree_root()
assert spec.verify_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root)
deposit = spec.Deposit(proof=proof, data=deposit_data)
item = deposit_data.hash_tree_root() return deposit, root, deposit_data_list
index = len(deposit_data_leaves)
deposit_data_leaves.append(item)
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
root = get_merkle_root((tuple(deposit_data_leaves)))
proof = list(get_merkle_proof(tree, item_index=index))
assert spec.verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
deposit = spec.Deposit(
proof=list(proof),
index=index,
data=deposit_data,
)
return deposit, root, deposit_data_leaves def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False):
deposit_data_list = []
genesis_deposits = []
for validator_index in range(genesis_validator_count):
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
# insecurely use pubkey as withdrawal key if no credentials provided
withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(pubkey)[1:]
deposit, root, deposit_data_list = build_deposit(
spec,
None,
deposit_data_list,
pubkey,
privkey,
amount,
withdrawal_credentials,
signed,
)
genesis_deposits.append(deposit)
return genesis_deposits, root
def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False): def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False):
""" """
Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount. Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount.
""" """
pre_validator_count = len(state.validators) deposit_data_list = []
# fill previous deposits with zero-hash
deposit_data_leaves = [spec.ZERO_HASH] * pre_validator_count
pubkey = pubkeys[validator_index] pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index] privkey = privkeys[validator_index]
@ -69,10 +91,10 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
if withdrawal_credentials is None: if withdrawal_credentials is None:
withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + 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_list = build_deposit(
spec, spec,
state, state,
deposit_data_leaves, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
amount, amount,
@ -80,6 +102,7 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
signed, signed,
) )
state.eth1_deposit_index = 0
state.eth1_data.deposit_root = root state.eth1_data.deposit_root = root
state.eth1_data.deposit_count = len(deposit_data_leaves) state.eth1_data.deposit_count = len(deposit_data_list)
return deposit return deposit

View File

@ -147,7 +147,7 @@ def test_invalid_withdrawal_credentials_top_up(spec, state):
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
def test_wrong_deposit_for_deposit_count(spec, state): def test_wrong_deposit_for_deposit_count(spec, state):
deposit_data_leaves = [spec.ZERO_HASH] * len(state.validators) deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
# build root for deposit_1 # build root for deposit_1
index_1 = len(deposit_data_leaves) index_1 = len(deposit_data_leaves)
@ -197,6 +197,6 @@ def test_bad_merkle_proof(spec, state):
# mess up merkle branch # mess up merkle branch
deposit.proof[5] = spec.ZERO_HASH deposit.proof[5] = spec.ZERO_HASH
sign_deposit_data(spec, state, deposit.data, privkeys[validator_index]) sign_deposit_data(spec, deposit.data, privkeys[validator_index], state=state)
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False) yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False)

View File

@ -1,4 +1,5 @@
from .hash_function import hash from .hash_function import hash
from math import log2
ZERO_BYTES32 = b'\x00' * 32 ZERO_BYTES32 = b'\x00' * 32
@ -8,11 +9,10 @@ for layer in range(1, 100):
zerohashes.append(hash(zerohashes[layer - 1] + zerohashes[layer - 1])) zerohashes.append(hash(zerohashes[layer - 1] + zerohashes[layer - 1]))
# Compute a Merkle root of a right-zerobyte-padded 2**32 sized tree def calc_merkle_tree_from_leaves(values, layer_count=32):
def calc_merkle_tree_from_leaves(values):
values = list(values) values = list(values)
tree = [values[::]] tree = [values[::]]
for h in range(32): for h in range(layer_count):
if len(values) % 2 == 1: if len(values) % 2 == 1:
values.append(zerohashes[h]) values.append(zerohashes[h])
values = [hash(values[i] + values[i + 1]) for i in range(0, len(values), 2)] values = [hash(values[i] + values[i + 1]) for i in range(0, len(values), 2)]
@ -20,8 +20,11 @@ def calc_merkle_tree_from_leaves(values):
return tree return tree
def get_merkle_root(values): def get_merkle_root(values, pad_to=1):
return calc_merkle_tree_from_leaves(values)[-1][0] layer_count = int(log2(pad_to))
if len(values) == 0:
return zerohashes[layer_count]
return calc_merkle_tree_from_leaves(values, layer_count)[-1][0]
def get_merkle_proof(tree, item_index): def get_merkle_proof(tree, item_index):
@ -32,19 +35,7 @@ def get_merkle_proof(tree, item_index):
return proof return proof
def next_power_of_two(v: int) -> int: def merkleize_chunks(chunks, pad_to: int=1):
"""
Get the next power of 2. (for 64 bit range ints).
0 is a special case, to have non-empty defaults.
Examples:
0 -> 1, 1 -> 1, 2 -> 2, 3 -> 4, 32 -> 32, 33 -> 64
"""
if v == 0:
return 1
return 1 << (v - 1).bit_length()
def merkleize_chunks(chunks, pad_to: int = 1):
count = len(chunks) count = len(chunks)
depth = max(count - 1, 0).bit_length() depth = max(count - 1, 0).bit_length()
max_depth = max(depth, (pad_to - 1).bit_length()) max_depth = max(depth, (pad_to - 1).bit_length())

View File

@ -1,5 +1,5 @@
import pytest import pytest
from .merkle_minimal import zerohashes, merkleize_chunks from .merkle_minimal import zerohashes, merkleize_chunks, get_merkle_root
from .hash_function import hash from .hash_function import hash
@ -53,6 +53,7 @@ cases = [
'depth,count,pow2,value', 'depth,count,pow2,value',
cases, cases,
) )
def test_merkleize_chunks(depth, count, pow2, value): def test_merkleize_chunks_and_get_merkle_root(depth, count, pow2, value):
chunks = [e(i) for i in range(count)] chunks = [e(i) for i in range(count)]
assert merkleize_chunks(chunks, pad_to=pow2) == value assert merkleize_chunks(chunks, pad_to=pow2) == value
assert get_merkle_root(chunks, pad_to=pow2) == value