From 36aae1d84877d7c34a8b3e02fdc058ec618708d3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 16 Mar 2022 13:35:29 -0600 Subject: [PATCH] add tests for process_withdrawals --- specs/capella/beacon-chain.md | 7 +- .../test_process_withdrawals.py | 242 ++++++++++++++++++ .../test/helpers/execution_payload.py | 4 + 3 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 001bac70a..e912f608e 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -256,11 +256,8 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: dequeued_withdrawals = state.withdrawals_queue[:num_withdrawals] assert len(dequeued_withdrawals) == len(payload.withdrawals) - for dequeued_receipt, withdrawal_transaction in zip(dequeued_withdrawals, payload.withdrawals): - assert dequeued_receipt == withdrawal_transaction - - # Ensure no withdrawal type transactions in the normal payload transactions - # assert no_withdrawal_type_transactions_in(payload.transactions) + for dequeued_receipt, withdrawal in zip(dequeued_withdrawals, payload.withdrawals): + assert dequeued_receipt == withdrawal # Remove dequeued receipts from state state.withdrawals_queue = state.withdrawals_queue[num_withdrawals:] diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py new file mode 100644 index 000000000..204816c99 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py @@ -0,0 +1,242 @@ +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) + +from eth2spec.test.context import spec_state_test, expect_assertion_error, with_capella_and_later + +from eth2spec.test.helpers.state import next_slot + + +def prepare_withdrawals_queue(spec, state, num_withdrawals): + pre_queue_len = len(state.withdrawals_queue) + + for i in range(num_withdrawals): + withdrawal = spec.Withdrawal( + index=i + 5, + address=b'\x42' * 20, + amount=200000 + i, + ) + state.withdrawals_queue.append(withdrawal) + + assert len(state.withdrawals_queue) == num_withdrawals + pre_queue_len + + +def run_withdrawals_processing(spec, state, execution_payload, valid=True): + """ + Run ``process_execution_payload``, yielding: + - pre-state ('pre') + - execution payload ('execution_payload') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + + pre_withdrawals_queue = state.withdrawals_queue.copy() + num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(pre_withdrawals_queue)) + + yield 'pre', state + yield 'execution_payload', execution_payload + + if not valid: + expect_assertion_error(lambda: spec.process_withdrawals(state, execution_payload)) + yield 'post', None + return + + spec.process_withdrawals(state, execution_payload) + + yield 'post', state + + if len(pre_withdrawals_queue) == 0: + assert len(state.withdrawals_queue) == 0 + elif len(pre_withdrawals_queue) <= num_withdrawals: + assert len(state.withdrawals_queue) == 0 + else: + assert state.withdrawals_queue == pre_withdrawals_queue[num_withdrawals:] + + +@with_capella_and_later +@spec_state_test +def test_success_empty_queue(spec, state): + assert len(state.withdrawals_queue) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_one_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_max_per_slot_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_success_a_lot_in_queue(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +# +# Failure cases in which the number of withdrawals in the execution_payload is incorrect +# + +@with_capella_and_later +@spec_state_test +def test_fail_empty_queue_non_empty_withdrawals(spec, state): + assert len(state.withdrawals_queue) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + withdrawal = spec.Withdrawal( + index=0, + address=b'\x30' * 20, + amount=420, + ) + execution_payload.withdrawals.append(withdrawal) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_in_queue_none_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = [] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_in_queue_two_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy()) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_max_per_slot_in_queue_one_less_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_a_lot_in_queue_too_few_in_withdrawals(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +# +# Failure cases in which the withdrawals in the execution_payload are incorrect +# + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_index(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].index += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_address(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].address = b'\xff' * 20 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_incorrect_dequeue_amount(spec, state): + prepare_withdrawals_queue(spec, state, 1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].amount += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_one_of_many_dequeued_incorrectly(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + num_withdrawals = len(execution_payload.withdrawals) + + # Pick withdrawal in middle of list and mutate + withdrawal = execution_payload.withdrawals[num_withdrawals // 2] + withdrawal.index += 1 + withdrawal.address = b'\x99' * 20 + withdrawal.amount += 4000000 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_capella_and_later +@spec_state_test +def test_fail_many_dequeued_incorrectly(spec, state): + prepare_withdrawals_queue(spec, state, spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + for i, withdrawal in enumerate(execution_payload.withdrawals): + if i % 3 == 0: + withdrawal.index += 1 + elif i % 3 == 1: + withdrawal.address = (i).to_bytes(20, 'big') + else: + withdrawal.amount += 1 + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 915883aac..5e7066632 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -28,6 +28,10 @@ def build_empty_execution_payload(spec, state, randao_mix=None): block_hash=spec.Hash32(), transactions=empty_txs, ) + if spec.fork not in FORKS_BEFORE_CAPELLA: + num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawals_queue)) + payload.withdrawals = state.withdrawals_queue[:num_withdrawals] + # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH"))