From 2c93627154573b8dda3ff385d7ff6e79c47a73f1 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 30 Jan 2025 17:32:52 +0600 Subject: [PATCH 1/6] Add deposit request same pubkey to electra block tests --- .../test/electra/sanity/blocks/test_blocks.py | 41 +++++++++++++++++++ .../sanity/blocks/test_deposit_transition.py | 8 ++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index 2640e1aec7..9ab446a754 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -21,6 +21,9 @@ set_eth1_withdrawal_credential_with_balance, set_compounding_withdrawal_credential_with_balance, ) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request +) @with_electra_and_later @@ -327,3 +330,41 @@ def test_withdrawal_and_switch_to_compounding_request_same_validator(spec, state assert spec.is_compounding_withdrawal_credential(state.validators[validator_index].withdrawal_credentials) # Ensure there was no excess balance pending deposit assert len(state.pending_deposits) == 0 + + +@with_electra_and_later +@spec_state_test +def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, state): + # signify the transition + state.deposit_requests_start_index = state.eth1_deposit_index + + # prepare three deposit requests, where + # 1st and 3rd have the same pubkey but different withdrawal credentials + deposit_request_0 = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index, signed=True) + deposit_request_1 = prepare_deposit_request( + spec, len(state.validators) + 1, spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 1, signed=True) + deposit_request_2 = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 2, signed=True, + withdrawal_credentials=(spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x11' * 20) + ) + + # build a block with deposit requests + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.deposits = [deposit_request_0, deposit_request_1, deposit_request_2] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'pre', state + yield 'blocks', [signed_block] + yield 'post', state + + # check deposit requests are processed correctly + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + assert state.pending_deposits[i] == spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=signed_block.message.slot, + ) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py index f2d9a6f11a..b021eebe05 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py @@ -93,8 +93,8 @@ def prepare_state_and_block(spec, deposit_data = build_deposit_data(spec, pubkeys[keypair_index], privkeys[keypair_index], - # use max effective balance - spec.MAX_EFFECTIVE_BALANCE, + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, # insecurely use pubkey as withdrawal key spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkeys[keypair_index])[1:], signed=True) @@ -118,8 +118,8 @@ def prepare_state_and_block(spec, for offset in range(deposit_request_cnt): deposit_request = prepare_deposit_request(spec, keypair_index, - # use max effective balance - spec.MAX_EFFECTIVE_BALANCE, + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, first_deposit_request_index + offset, signed=True) deposit_requests.append(deposit_request) From 8231c6e05980ab5f34e51b0d76df644ace8a367a Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 30 Jan 2025 21:13:11 +0600 Subject: [PATCH 2/6] Add tests with validator created on two forks --- .../test/electra/sanity/blocks/test_blocks.py | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index 9ab446a754..de1d0a0dd4 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -1,5 +1,7 @@ from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot + build_empty_block_for_next_slot, + apply_empty_block, + sign_block, ) from eth2spec.test.context import ( spec_state_test, @@ -16,13 +18,18 @@ ) from eth2spec.test.helpers.state import ( state_transition_and_sign_block, + next_epoch_with_full_participation, + next_epoch_via_signed_block, + set_full_participation, + next_epoch, ) from eth2spec.test.helpers.withdrawals import ( set_eth1_withdrawal_credential_with_balance, set_compounding_withdrawal_credential_with_balance, ) from eth2spec.test.helpers.deposits import ( - prepare_deposit_request + prepare_deposit_request, + prepare_pending_deposit, ) @@ -335,7 +342,7 @@ def test_withdrawal_and_switch_to_compounding_request_same_validator(spec, state @with_electra_and_later @spec_state_test def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, state): - # signify the transition + # signify the eth1 bridge deprecation state.deposit_requests_start_index = state.eth1_deposit_index # prepare three deposit requests, where @@ -353,9 +360,11 @@ def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, block = build_empty_block_for_next_slot(spec, state) block.body.execution_requests.deposits = [deposit_request_0, deposit_request_1, deposit_request_2] block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) - signed_block = state_transition_and_sign_block(spec, state, block) yield 'pre', state + + signed_block = state_transition_and_sign_block(spec, state, block) + yield 'blocks', [signed_block] yield 'post', state @@ -368,3 +377,51 @@ def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, signature=deposit_request.signature, slot=signed_block.message.slot, ) + + +@with_electra_and_later +@spec_state_test +def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # (1) create pending deposit for a new validator and finalize it + pending_deposit = prepare_pending_deposit( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, signed=True, slot=spec.Slot(1)) + state.pending_deposits.append(pending_deposit) + + # do required state transitions and fill participation to get Slot(1) finalized + next_epoch_with_full_participation(spec, state) + next_epoch_with_full_participation(spec, state) + next_epoch_with_full_participation(spec, state) + set_full_participation(spec, state) + + # pending deposit is not yet processed + assert state.pending_deposits == [pending_deposit] + + yield 'pre', state + + # (2) create and apply a block for the next epoch so the new validator gets created + block_a_state = state.copy() + signed_block_a = next_epoch_via_signed_block(spec, block_a_state) + + # check that the validator has been created + assert block_a_state.pending_deposits == [] + new_validator = block_a_state.validators[len(block_a_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + # (3) create and apply a block conflicting with "block_a" + # so the epoch processing will be triggered once again + # and the validator will be created on another branch of the block tree + next_epoch(spec, state) + block_b = apply_empty_block(spec, state, state.slot + 1) + signed_block_b = sign_block(spec, state, block_b) + + # check that the validator has been created in the "block_b" fork + new_validator = state.validators[len(state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + yield 'blocks', [signed_block_a, signed_block_b] + yield 'post', state From 2cc9fe8235f4a59280be4f6d357dcb8d4032c4b4 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 30 Jan 2025 23:37:40 +0600 Subject: [PATCH 3/6] Fix lint --- .../eth2spec/test/electra/sanity/blocks/test_blocks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index de1d0a0dd4..891bb69728 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -348,12 +348,12 @@ def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, # prepare three deposit requests, where # 1st and 3rd have the same pubkey but different withdrawal credentials deposit_request_0 = prepare_deposit_request( - spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index, signed=True) + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index, signed=True) deposit_request_1 = prepare_deposit_request( - spec, len(state.validators) + 1, spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 1, signed=True) + spec, len(state.validators) + 1, spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 1, signed=True) deposit_request_2 = prepare_deposit_request( - spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 2, signed=True, - withdrawal_credentials=(spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x11' * 20) + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, state.eth1_deposit_index + 2, signed=True, + withdrawal_credentials=(spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x11' * 20) ) # build a block with deposit requests From c2bc028a62980ad094ebd44178d97a9500ad2eb6 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 31 Jan 2025 20:59:31 +0600 Subject: [PATCH 4/6] Move deposit with reorg test to fork_choice tests --- .../test/electra/fork_choice/__init__.py | 0 .../fork_choice/test_deposit_with_reorg.py | 90 +++++++++++++++++++ .../test/electra/sanity/blocks/test_blocks.py | 48 ---------- tests/generators/fork_choice/main.py | 5 +- 4 files changed, 94 insertions(+), 49 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py new file mode 100644 index 0000000000..69cf97469c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py @@ -0,0 +1,90 @@ +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.context import ( + with_presets, + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, + next_slot, +) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, +) +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + tick_and_add_block, + apply_next_slots_with_attestations, +) +from eth2spec.test.helpers.constants import ( + MINIMAL, +) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # yield anchor state and block + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + + test_steps = [] + + # (1) create deposit request for a new validator + deposit_request = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, signed=True) + deposit_block = build_empty_block_for_next_slot(spec, state) + deposit_block.body.execution_requests.deposits = [deposit_request] + deposit_block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, deposit_block) + signed_deposit_block = state_transition_and_sign_block(spec, state, deposit_block) + + pending_deposit = spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=deposit_block.slot + ) + + assert state.pending_deposits == [pending_deposit] + + yield from tick_and_add_block(spec, store, signed_deposit_block, test_steps) + + # (2) finalize and process pending deposit on one fork + slots = 4 * spec.SLOTS_PER_EPOCH - state.slot + post_state, _, latest_block = yield from apply_next_slots_with_attestations( + spec, state, store, slots, True, True, test_steps) + + # check new validator has been created + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + # (3) create a conflicting block that triggers deposit processing on another fork + prev_epoch_ancestor = store.blocks[latest_block.message.parent_root] + another_fork_state = store.block_states[prev_epoch_ancestor.hash_tree_root()].copy() + assert another_fork_state.pending_deposits == [pending_deposit] + + # skip a slot to create and process a fork block + next_slot(spec, another_fork_state) + post_state, _, _ = yield from apply_next_slots_with_attestations( + spec, another_fork_state, store, 1, True, True, test_steps) + + # check new validator has been created on another fork + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + yield 'steps', test_steps diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index 891bb69728..51db0e4955 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -377,51 +377,3 @@ def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, signature=deposit_request.signature, slot=signed_block.message.slot, ) - - -@with_electra_and_later -@spec_state_test -def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): - # signify the eth1 bridge deprecation - state.deposit_requests_start_index = state.eth1_deposit_index - - # (1) create pending deposit for a new validator and finalize it - pending_deposit = prepare_pending_deposit( - spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, signed=True, slot=spec.Slot(1)) - state.pending_deposits.append(pending_deposit) - - # do required state transitions and fill participation to get Slot(1) finalized - next_epoch_with_full_participation(spec, state) - next_epoch_with_full_participation(spec, state) - next_epoch_with_full_participation(spec, state) - set_full_participation(spec, state) - - # pending deposit is not yet processed - assert state.pending_deposits == [pending_deposit] - - yield 'pre', state - - # (2) create and apply a block for the next epoch so the new validator gets created - block_a_state = state.copy() - signed_block_a = next_epoch_via_signed_block(spec, block_a_state) - - # check that the validator has been created - assert block_a_state.pending_deposits == [] - new_validator = block_a_state.validators[len(block_a_state.validators) - 1] - assert new_validator.pubkey == pending_deposit.pubkey - assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials - - # (3) create and apply a block conflicting with "block_a" - # so the epoch processing will be triggered once again - # and the validator will be created on another branch of the block tree - next_epoch(spec, state) - block_b = apply_empty_block(spec, state, state.slot + 1) - signed_block_b = sign_block(spec, state, block_b) - - # check that the validator has been created in the "block_b" fork - new_validator = state.validators[len(state.validators) - 1] - assert new_validator.pubkey == pending_deposit.pubkey - assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials - - yield 'blocks', [signed_block_a, signed_block_b] - yield 'post', state diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py index 6aa581460a..37a1d66f06 100644 --- a/tests/generators/fork_choice/main.py +++ b/tests/generators/fork_choice/main.py @@ -28,7 +28,10 @@ ]} deneb_mods = combine_mods(_new_deneb_mods, capella_mods) - electra_mods = deneb_mods # No additional Electra specific fork choice tests + _new_electra_mods = {key: 'eth2spec.test.electra.fork_choice.test_' + key for key in [ + 'deposit_with_reorg', + ]} + electra_mods = combine_mods(_new_electra_mods, deneb_mods) # Fulu adds new `is_data_available` tests _new_fulu_mods = {key: 'eth2spec.test.fulu.fork_choice.test_' + key for key in [ From 19f2166200642252d542e59b1abc0cfc5948b5a8 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 31 Jan 2025 21:01:46 +0600 Subject: [PATCH 5/6] Remove unused imports --- .../eth2spec/test/electra/sanity/blocks/test_blocks.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py index 51db0e4955..bad569b4b7 100644 --- a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -1,7 +1,5 @@ from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, - apply_empty_block, - sign_block, ) from eth2spec.test.context import ( spec_state_test, @@ -18,10 +16,6 @@ ) from eth2spec.test.helpers.state import ( state_transition_and_sign_block, - next_epoch_with_full_participation, - next_epoch_via_signed_block, - set_full_participation, - next_epoch, ) from eth2spec.test.helpers.withdrawals import ( set_eth1_withdrawal_credential_with_balance, @@ -29,7 +23,6 @@ ) from eth2spec.test.helpers.deposits import ( prepare_deposit_request, - prepare_pending_deposit, ) From da97a988bf58ca3ddfa32bd7ac57a7de4a5edc40 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Sat, 1 Feb 2025 17:39:46 +0600 Subject: [PATCH 6/6] Improve test --- .../test/electra/fork_choice/test_deposit_with_reorg.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py index 69cf97469c..74c418ad60 100644 --- a/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py +++ b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py @@ -73,7 +73,11 @@ def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): # (3) create a conflicting block that triggers deposit processing on another fork prev_epoch_ancestor = store.blocks[latest_block.message.parent_root] + # important to skip last block of the epoch to make client do the epoch processing + # otherwise, client can read the post-epoch from cache + prev_epoch_ancestor = store.blocks[prev_epoch_ancestor.parent_root] another_fork_state = store.block_states[prev_epoch_ancestor.hash_tree_root()].copy() + assert another_fork_state.pending_deposits == [pending_deposit] # skip a slot to create and process a fork block