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
# See issue 563
SHUFFLE_ROUND_COUNT: 90
# `2**16` (= 65,536)
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 65536
# Deposit contract

View File

@ -16,6 +16,8 @@ MIN_PER_EPOCH_CHURN_LIMIT: 4
CHURN_LIMIT_QUOTIENT: 65536
# [customized] Faster, but unsecure.
SHUFFLE_ROUND_COUNT: 10
# [customized]
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# 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
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
Deposit: event({
DepositEvent: event({
pubkey: bytes[48],
withdrawal_credentials: bytes[32],
amount: bytes[8],
@ -42,8 +42,9 @@ def to_little_endian_64(value: uint256) -> bytes[8]:
@public
@constant
def get_deposit_root() -> bytes32:
node: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
def get_hash_tree_root() -> bytes32:
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
node: bytes32 = zero_bytes32
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
@ -51,7 +52,7 @@ def get_deposit_root() -> bytes32:
else:
node = sha256(concat(node, self.zero_hashes[height]))
size /= 2
return node
return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public
@ -75,11 +76,11 @@ def deposit(pubkey: bytes[PUBKEY_LENGTH],
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH
# Emit `Deposit` log
# Emit `DepositEvent` log
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
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
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)),
))
# 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
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):

View File

@ -15,26 +15,12 @@ from eth2spec.phase0.spec import (
DepositData,
)
from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import List
from eth2spec.utils.ssz.ssz_impl import (
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
def deposit_input():
"""
@ -110,8 +96,8 @@ def test_deposit_inputs(registration_contract,
)
def test_deposit_log(registration_contract, a0, w3, deposit_input):
log_filter = registration_contract.events.Deposit.createFilter(
def test_deposit_event_log(registration_contract, a0, w3, deposit_input):
log_filter = registration_contract.events.DepositEvent.createFilter(
fromBlock='latest',
)
@ -131,13 +117,14 @@ def test_deposit_log(registration_contract, a0, w3, deposit_input):
assert log['signature'] == deposit_input[2]
assert log['index'] == i.to_bytes(8, 'little')
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',
)
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):
tx_hash = registration_contract.functions.deposit(
*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')
deposit_data = DepositData(
deposit_data_list.append(DepositData(
pubkey=deposit_input[0],
withdrawal_credentials=deposit_input[1],
amount=deposit_amount_list[i],
signature=deposit_input[2],
)
hash_tree_root_result = hash_tree_root(deposit_data)
leaf_nodes.append(hash_tree_root_result)
root = compute_merkle_root(leaf_nodes)
assert root == registration_contract.functions.get_deposit_root().call()
))
root = hash_tree_root(List[DepositData, 2**32](*deposit_data_list))
assert root == registration_contract.functions.get_hash_tree_root().call()

View File

@ -95,7 +95,6 @@
- [`initiate_validator_exit`](#initiate_validator_exit)
- [`slash_validator`](#slash_validator)
- [Genesis](#genesis)
- [Genesis trigger](#genesis-trigger)
- [Genesis state](#genesis-state)
- [Genesis block](#genesis-block)
- [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.
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.
## Notation
@ -176,6 +174,7 @@ The following values are (non-configurable) constants used throughout the specif
| `ZERO_HASH` | `Hash(b'\x00' * 32)` |
| `BASE_REWARDS_PER_EPOCH` | `5` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `SECONDS_PER_DAY` | `86400` |
## Configuration
@ -191,6 +190,8 @@ The following values are (non-configurable) constants used throughout the specif
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `90` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**16` (= 65,536) |
| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) |
| `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.)
@ -446,7 +447,7 @@ class Attestation(Container):
```python
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
```
@ -1109,61 +1110,43 @@ def slash_validator(state: BeaconState,
## 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
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
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(
genesis_time=genesis_time,
eth1_data=eth1_data,
genesis_time=eth1_timestamp - eth1_timestamp % SECONDS_PER_DAY + 2 * SECONDS_PER_DAY,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
)
# Process genesis deposits
for deposit in deposits:
# Process 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 genesis activations
for validator in state.validators:
if validator.effective_balance >= MAX_EFFECTIVE_BALANCE:
# Process activations
for index, validator in enumerate(state.validators):
if state.balances[index] >= MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_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
```
```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
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):
if (
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)
@ -1695,7 +1688,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
assert verify_merkle_branch(
leaf=hash_tree_root(deposit.data),
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,
root=state.eth1_data.deposit_root,
)

View File

@ -14,7 +14,7 @@
- [`deposit` function](#deposit-function)
- [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials)
- [`Deposit` log](#deposit-log)
- [`DepositEvent` log](#depositevent-log)
- [Vyper code](#vyper-code)
<!-- /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.
#### `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

View File

@ -221,7 +221,7 @@ epoch_signature = bls_sign(
##### 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:

View File

@ -27,9 +27,13 @@ def with_state(fn):
DEFAULT_BLS_ACTIVE = False
def spectest_with_bls_switch(fn):
return bls_switch(spectest()(fn))
# shorthand for decorating @with_state @spectest()
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):

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.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.ssz.ssz_impl import signing_root
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
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(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
amount=amount,
)
if signed:
sign_deposit_data(spec, state, deposit_data, privkey)
sign_deposit_data(spec, deposit_data, privkey, state)
return deposit_data
def sign_deposit_data(spec, state, deposit_data, privkey):
signature = bls_sign(
message_hash=signing_root(deposit_data),
privkey=privkey,
domain=spec.get_domain(
def sign_deposit_data(spec, deposit_data, privkey, state=None):
if state is None:
# Genesis
domain = spec.bls_domain(spec.DOMAIN_DEPOSIT)
else:
domain = spec.get_domain(
state,
spec.DOMAIN_DEPOSIT,
)
signature = bls_sign(
message_hash=signing_root(deposit_data),
privkey=privkey,
domain=domain,
)
deposit_data.signature = signature
def build_deposit(spec,
state,
deposit_data_leaves,
deposit_data_list,
pubkey,
privkey,
amount,
withdrawal_credentials,
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()
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)
return deposit, root, deposit_data_list
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):
"""
Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount.
"""
pre_validator_count = len(state.validators)
# fill previous deposits with zero-hash
deposit_data_leaves = [spec.ZERO_HASH] * pre_validator_count
deposit_data_list = []
pubkey = pubkeys[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:
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,
state,
deposit_data_leaves,
deposit_data_list,
pubkey,
privkey,
amount,
@ -80,6 +102,7 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
signed,
)
state.eth1_deposit_index = 0
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

View File

@ -147,7 +147,7 @@ def test_invalid_withdrawal_credentials_top_up(spec, state):
@with_all_phases
@spec_state_test
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
index_1 = len(deposit_data_leaves)
@ -197,6 +197,6 @@ def test_bad_merkle_proof(spec, state):
# mess up merkle branch
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)

View File

@ -1,4 +1,5 @@
from .hash_function import hash
from math import log2
ZERO_BYTES32 = b'\x00' * 32
@ -8,11 +9,10 @@ for layer in range(1, 100):
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):
def calc_merkle_tree_from_leaves(values, layer_count=32):
values = list(values)
tree = [values[::]]
for h in range(32):
for h in range(layer_count):
if len(values) % 2 == 1:
values.append(zerohashes[h])
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
def get_merkle_root(values):
return calc_merkle_tree_from_leaves(values)[-1][0]
def get_merkle_root(values, pad_to=1):
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):
@ -32,19 +35,7 @@ def get_merkle_proof(tree, item_index):
return proof
def next_power_of_two(v: int) -> int:
"""
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):
def merkleize_chunks(chunks, pad_to: int=1):
count = len(chunks)
depth = max(count - 1, 0).bit_length()
max_depth = max(depth, (pad_to - 1).bit_length())

View File

@ -1,5 +1,5 @@
import pytest
from .merkle_minimal import zerohashes, merkleize_chunks
from .merkle_minimal import zerohashes, merkleize_chunks, get_merkle_root
from .hash_function import hash
@ -53,6 +53,7 @@ cases = [
'depth,count,pow2,value',
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)]
assert merkleize_chunks(chunks, pad_to=pow2) == value
assert get_merkle_root(chunks, pad_to=pow2) == value