Skip to content

Commit

Permalink
Add deposits transition tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mkalinin committed Apr 5, 2023
1 parent 502745e commit 80e6b0d
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.context import (
spec_state_test,
with_phases,
EIP6110,
expect_assertion_error,
)
from eth2spec.test.helpers.deposits import (
build_deposit_data,
deposit_from_context,
prepare_deposit_receipt,
)
from eth2spec.test.helpers.execution_payload import (
compute_el_block_hash,
)
from eth2spec.test.helpers.keys import privkeys, pubkeys


def run_deposit_transition_block(spec, state, block, top_up_keys=[], valid=True):
"""
Run ``process_block``, yielding:
- pre-state ('pre')
- block ('block')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
yield 'pre', state
yield 'block', block

if not valid:
expect_assertion_error(lambda: spec.process_block(state, block))
yield 'post', None
return

spec.process_block(state, block)
yield 'post', state

# Check that deposits are applied
expected_pubkeys = [d.data.pubkey for d in block.body.deposits]
deposit_receipts = block.body.execution_payload.deposit_receipts
expected_pubkeys = expected_pubkeys + [d.pubkey for d in deposit_receipts if (d.pubkey not in top_up_keys)]
actual_pubkeys = [v.pubkey for v in state.validators[len(state.validators) - len(expected_pubkeys):]]

assert actual_pubkeys == expected_pubkeys


def prepare_state_and_block(spec,
state,
deposit_cnt,
deposit_receipt_cnt,
first_deposit_receipt_index=0,
deposit_receipts_start_index=None):
deposits = []
deposit_receipts = []
keypair_index = len(state.validators)

# Prepare deposits
deposit_data_list = []
for index in range(deposit_cnt):
deposit_data = build_deposit_data(spec,
pubkeys[keypair_index],
privkeys[keypair_index],
# use max effective balance
spec.MAX_EFFECTIVE_BALANCE,
# insecurely use pubkey as withdrawal key
spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkeys[keypair_index])[1:],
signed=True)
deposit_data_list.append(deposit_data)
keypair_index += 1

deposit_root = None
for index in range(deposit_cnt):
deposit, deposit_root, _ = deposit_from_context(spec, deposit_data_list, index)
deposits.append(deposit)

if deposit_root:
state.eth1_deposit_index = 0
state.eth1_data = spec.Eth1Data(deposit_root=deposit_root,
deposit_count=deposit_cnt,
block_hash=state.eth1_data.block_hash)

# Prepare deposit receipts
for offset in range(deposit_receipt_cnt):
deposit_receipt = prepare_deposit_receipt(spec,
keypair_index,
# use max effective balance
spec.MAX_EFFECTIVE_BALANCE,
first_deposit_receipt_index + offset,
signed=True)
deposit_receipts.append(deposit_receipt)
keypair_index += 1

# Set start index if defined
if deposit_receipts_start_index:
state.deposit_receipts_start_index = deposit_receipts_start_index

block = build_empty_block_for_next_slot(spec, state)

# Assign deposits and deposit receipts
block.body.deposits = deposits
block.body.execution_payload.deposit_receipts = deposit_receipts
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)

# Advance a slot
spec.process_slots(state, block.slot)

return state, block


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__start_index_is_set(spec, state):
# 0 deposits, 2 deposit receipts, unset deposit_receipts_start_index
state, block = prepare_state_and_block(spec, state,
deposit_cnt=0,
deposit_receipt_cnt=2,
first_deposit_receipt_index=state.eth1_data.deposit_count + 11)

yield from run_deposit_transition_block(spec, state, block)

# deposit_receipts_start_index must be set to the index of the first receipt
assert state.deposit_receipts_start_index == block.body.execution_payload.deposit_receipts[0].index


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__process_eth1_deposits(spec, state):
# 3 deposits, 1 deposit receipt, state.eth1_data.deposit_count < state.deposit_receipts_start_index
state, block = prepare_state_and_block(spec, state,
deposit_cnt=3,
deposit_receipt_cnt=1,
first_deposit_receipt_index=11,
deposit_receipts_start_index=7)

yield from run_deposit_transition_block(spec, state, block)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__process_max_eth1_deposits(spec, state):
# spec.MAX_DEPOSITS deposits, 1 deposit receipt, state.eth1_data.deposit_count > state.deposit_receipts_start_index
# state.deposit_receipts_start_index == spec.MAX_DEPOSITS
state, block = prepare_state_and_block(spec, state,
deposit_cnt=spec.MAX_DEPOSITS,
deposit_receipt_cnt=1,
first_deposit_receipt_index=spec.MAX_DEPOSITS + 1,
deposit_receipts_start_index=spec.MAX_DEPOSITS)
state.eth1_data = spec.Eth1Data(deposit_root=state.eth1_data.deposit_root,
deposit_count=23,
block_hash=state.eth1_data.block_hash)

yield from run_deposit_transition_block(spec, state, block)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__process_eth1_deposits_up_to_start_index(spec, state):
# 3 deposits, 1 deposit receipt, state.eth1_data.deposit_count == state.deposit_receipts_start_index
state, block = prepare_state_and_block(spec, state,
deposit_cnt=3,
deposit_receipt_cnt=1,
first_deposit_receipt_index=7,
deposit_receipts_start_index=3)

yield from run_deposit_transition_block(spec, state, block)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__invalid_not_enough_eth1_deposits(spec, state):
# 3 deposits, 1 deposit receipt, state.eth1_data.deposit_count < state.deposit_receipts_start_index
state, block = prepare_state_and_block(spec, state,
deposit_cnt=3,
deposit_receipt_cnt=1,
first_deposit_receipt_index=29,
deposit_receipts_start_index=23)
state.eth1_data = spec.Eth1Data(deposit_root=state.eth1_data.deposit_root,
deposit_count=17,
block_hash=state.eth1_data.block_hash)

yield from run_deposit_transition_block(spec, state, block, valid=False)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__invalid_too_many_eth1_deposits(spec, state):
# 3 deposits, 1 deposit receipt, state.eth1_data.deposit_count < state.eth1_data_index
state, block = prepare_state_and_block(spec, state,
deposit_cnt=3,
deposit_receipt_cnt=1,
first_deposit_receipt_index=11,
deposit_receipts_start_index=7)
state.eth1_data = spec.Eth1Data(deposit_root=state.eth1_data.deposit_root,
deposit_count=2,
block_hash=state.eth1_data.block_hash)

yield from run_deposit_transition_block(spec, state, block, valid=False)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__invalid_eth1_deposits_overlap_in_protocol_deposits(spec, state):
# spec.MAX_DEPOSITS deposits, 1 deposit receipt, state.eth1_data.deposit_count > state.deposit_receipts_start_index
# state.deposit_receipts_start_index == spec.MAX_DEPOSITS - 1
state, block = prepare_state_and_block(spec, state,
deposit_cnt=spec.MAX_DEPOSITS,
deposit_receipt_cnt=1,
first_deposit_receipt_index=spec.MAX_DEPOSITS,
deposit_receipts_start_index=spec.MAX_DEPOSITS - 1)
state.eth1_data = spec.Eth1Data(deposit_root=state.eth1_data.deposit_root,
deposit_count=23,
block_hash=state.eth1_data.block_hash)

yield from run_deposit_transition_block(spec, state, block, valid=False)


@with_phases([EIP6110])
@spec_state_test
def test_deposit_transition__deposit_and_top_up_same_block(spec, state):
# 1 deposit, 1 deposit receipt that top ups deposited validator
state, block = prepare_state_and_block(spec, state,
deposit_cnt=1,
deposit_receipt_cnt=1,
first_deposit_receipt_index=11,
deposit_receipts_start_index=7)

# Artificially assign deposit's pubkey to a deposit receipt of the same block
top_up_keys = [block.body.deposits[0].data.pubkey]
block.body.execution_payload.deposit_receipts[0].pubkey = top_up_keys[0]
block.body.execution_payload.block_hash = compute_el_block_hash(spec, block.body.execution_payload)

yield from run_deposit_transition_block(spec, state, block, top_up_keys=top_up_keys)

# Check the top up
expected_balance = block.body.deposits[0].data.amount + block.body.execution_payload.deposit_receipts[0].amount
assert state.balances[len(state.balances) - 1] == expected_balance
34 changes: 29 additions & 5 deletions tests/core/pyspec/eth2spec/test/helpers/execution_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ def compute_el_header_block_hash(spec,
# excess_data_gas
execution_payload_header_rlp.append((big_endian_int, payload_header.excess_data_gas))
if is_post_eip6110(spec):
# TODO: RLP or SSZ for `deposit_receipts_root`
# FIXME: if using RLP, we need to implement `get_deposit_receipt_rlp` helper
...
# deposit_receipts_root
assert deposit_receipts_trie_root is not None
execution_payload_header_rlp.append((Binary(32, 32), deposit_receipts_trie_root))

sedes = List([schema for schema, _ in execution_payload_header_rlp])
values = [value for _, value in execution_payload_header_rlp]
Expand All @@ -129,14 +129,37 @@ def get_withdrawal_rlp(spec, withdrawal):
return encode(values, sedes)


def get_deposit_receipt_rlp(spec, deposit_receipt):
deposit_receipt_rlp = [
# pubkey
(Binary(48, 48), deposit_receipt.pubkey),
# withdrawal_credentials
(Binary(32, 32), deposit_receipt.withdrawal_credentials),
# amount
(big_endian_int, deposit_receipt.amount),
# pubkey
(Binary(96, 96), deposit_receipt.signature),
# index
(big_endian_int, deposit_receipt.index),
]

sedes = List([schema for schema, _ in deposit_receipt_rlp])
values = [value for _, value in deposit_receipt_rlp]
return encode(values, sedes)


def compute_el_block_hash(spec, payload):
transactions_trie_root = compute_trie_root_from_indexed_data(payload.transactions)

withdrawals_trie_root = None
deposit_receipts_trie_root = None

if is_post_capella(spec):
withdrawals_encoded = [get_withdrawal_rlp(spec, withdrawal) for withdrawal in payload.withdrawals]
withdrawals_trie_root = compute_trie_root_from_indexed_data(withdrawals_encoded)
else:
withdrawals_trie_root = None
if is_post_eip6110(spec):
deposit_receipts_encoded = [get_deposit_receipt_rlp(spec, receipt) for receipt in payload.deposit_receipts]
deposit_receipts_trie_root = compute_trie_root_from_indexed_data(deposit_receipts_encoded)

payload_header = get_execution_payload_header(spec, payload)

Expand All @@ -145,6 +168,7 @@ def compute_el_block_hash(spec, payload):
payload_header,
transactions_trie_root,
withdrawals_trie_root,
deposit_receipts_trie_root,
)


Expand Down
7 changes: 5 additions & 2 deletions tests/core/pyspec/eth2spec/test/helpers/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,20 @@ def get_sample_genesis_execution_payload_header(spec,
)

transactions_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
withdrawals_trie_root = None
deposit_receipts_trie_root = None

if is_post_capella(spec):
withdrawals_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
else:
withdrawals_trie_root = None
if is_post_eip6110(spec):
deposit_receipts_trie_root = bytes.fromhex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

payload_header.block_hash = compute_el_header_block_hash(
spec,
payload_header,
transactions_trie_root,
withdrawals_trie_root,
deposit_receipts_trie_root,
)
return payload_header

Expand Down

0 comments on commit 80e6b0d

Please sign in to comment.