-
Notifications
You must be signed in to change notification settings - Fork 998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge test vectors: enable phase0 tests for Merge + start on new testing #2380
Changes from 6 commits
f37f9a3
ab693c9
521cffc
25d0d67
36032fd
79fc411
e2be761
470c6dc
ff3a82e
865d7db
cc11328
00cd1c3
2ef6291
56bcb63
8ac59b7
76b5974
42733b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
def build_empty_execution_payload(spec, state): | ||
""" | ||
Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. | ||
protolambda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
latest = state.latest_execution_payload_header | ||
timestamp = spec.compute_time_at_slot(state, state.slot) | ||
empty_txs = spec.List[spec.OpaqueTransaction, spec.MAX_EXECUTION_TRANSACTIONS]() | ||
|
||
payload = spec.ExecutionPayload( | ||
block_hash=spec.Hash32(), | ||
parent_hash=latest.block_hash, | ||
coinbase=spec.Bytes20(), | ||
protolambda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
state_root=latest.state_root, # no changes to the state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this certain to be the case? Does the consensus not write anything to state automatically? The recent block_roots being included in state is something that comes to mind, like EIP 210. I know 210 isn't deployed but I'm unsure if there isn't anything else like that in place There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There may be something yes, although I'm not entirely sure either. The account tree definitely does not change (no block rewards on eth1 state anymore), and the storage/values/code/nonce of accounts only changes with transactions. Can't find anything in the yellow paper that would otherwise touch the eth1 world state. I'll open an issue to investigate this more. Maybe this new possibility of a duplicate state-root in eth1 blocks is untested/dangerous? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might be. at least it might not be expected I think we've touched on this a tiny bit. Peter mentioned that it isn't the case today because of issuance/coinbase but that it might change in this context. I think they can handle it but it's something to highlight as a design consideration/edge-case for execution engines |
||
number=latest.number + 1, | ||
gas_limit=latest.gas_limit, # retain same limit | ||
gas_used=0, # empty block, 0 gas | ||
timestamp=timestamp, | ||
receipt_root=b"no receipts here" + b"\x00"*16, # TODO: root of empty MPT may be better. | ||
logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? | ||
protolambda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
transactions_root=empty_txs, | ||
) | ||
# 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")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, it's just a short-cut to make a hash that is unique to the payload. Encoding and flat-hashing would work to. I think we may just want to do the real RLP thing though. The current thing works for now, but it's nice if it's correct, especially if we get eth1-eth2 combination clients that run these tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to decouple this shortcut and use it as a helper. |
||
|
||
return payload |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
from eth2spec.test.helpers.constants import PHASE0, ALTAIR | ||
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases_except | ||
|
||
with_merge_and_later = with_all_phases_except([PHASE0, ALTAIR]) | ||
|
||
|
||
def run_execution_payload_processing(spec, state, execution_payload, valid=True, execution_valid=True): | ||
""" | ||
Run ``process_execution_payload``, yielding: | ||
- pre-state ('pre') | ||
- execution payload ('execution_payload') | ||
- execution details, to mock EVM execution ('execution.yml', a dict with 'execution_valid' key and boolean value) | ||
- post-state ('post'). | ||
If ``valid == False``, run expecting ``AssertionError`` | ||
""" | ||
|
||
yield 'pre', state | ||
yield 'execution', {'execution_valid': execution_valid} | ||
yield 'execution_payload', execution_payload | ||
|
||
if not valid: | ||
expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload)) | ||
yield 'post', None | ||
return | ||
|
||
spec.process_execution_payload(state, execution_payload) | ||
|
||
yield 'post', state | ||
|
||
# TODO: any assertions to make? | ||
protolambda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_success_first_payload(spec, state): | ||
assert not spec.is_transition_completed(state) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe assert that it is a transition block too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no block processing here, just the execution payload, so as-is it cannot be asserted. Maybe we should start using the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this test case supposed to test the transition? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A nit: then we likely should not use transition spec helpers to check the pre-state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, but we want to test a range of tests/pre-states/inputs directly on this function. One of which when If we don't use that helper, we'd want to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I see. This is more likely to be done in a separate PR anyway. Though, I would rather set test case preliminaries in the test case itself than expect them from the outside. |
||
|
||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_success_regular_payload(spec, state): | ||
# TODO: setup state | ||
assert spec.is_transition_completed(state) | ||
|
||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_success_first_payload_with_gap_slot(spec, state): | ||
# TODO: transition gap slot | ||
|
||
assert not spec.is_transition_completed(state) | ||
|
||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_success_regular_payload_with_gap_slot(spec, state): | ||
# TODO: setup state | ||
assert spec.is_transition_completed(state) | ||
# TODO: transition gap slot | ||
|
||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_execution_first_payload(spec, state): | ||
# completely valid payload, but execution itself fails (e.g. block exceeds gas limit) | ||
|
||
# TODO: execution payload. | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_execution_regular_payload(spec, state): | ||
# completely valid payload, but execution itself fails (e.g. block exceeds gas limit) | ||
|
||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_parent_hash_first_payload(spec, state): | ||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_number_first_payload(spec, state): | ||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_everything_first_payload(spec, state): | ||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_timestamp_first_payload(spec, state): | ||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) | ||
|
||
|
||
@with_merge_and_later | ||
@spec_state_test | ||
def test_bad_timestamp_regular_payload(spec, state): | ||
# TODO: execution payload | ||
execution_payload = spec.ExecutionPayload() | ||
|
||
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from eth2spec.test.helpers.state import ( | ||
state_transition_and_sign_block | ||
) | ||
from eth2spec.test.helpers.block import ( | ||
build_empty_block_for_next_slot | ||
) | ||
from eth2spec.test.context import ( | ||
with_all_phases, spec_state_test | ||
) | ||
|
||
|
||
@with_all_phases | ||
@spec_state_test | ||
def test_empty_block_transition(spec, state): | ||
yield 'pre', state | ||
|
||
block = build_empty_block_for_next_slot(spec, state) | ||
assert len(block.body.execution_payload.transactions) == 0 | ||
|
||
signed_block = state_transition_and_sign_block(spec, state, block) | ||
|
||
yield 'blocks', [signed_block] | ||
yield 'post', state | ||
|
||
# TODO: tests with EVM, mock or replacement? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two issues:
tests/formats/operations/README.md
and I just realized that we putsync_aggregate
as part of "operation" tests butprocess_sync_committee
is not part of theprocess_operations
function likeexecution_payload
.sync_aggregate
as an operation.execution_payload
could also be considered as an operation since it's essentially "transactions".is_transition_completed(state) or is_transition_block(state, block)
condition checks fromprocess_execution_payload
, but it makes it a bit unclear where/how to test these conditions in test cases.What do you think about:
process_sync_committee
andprocess_execution_payload
calls intoprocess_operations
.if is_transition_completed(state) or is_transition_block(state, block)
checks extraction.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process_sync_committee
: sure, but let's keep that separate from merge-testing for now. Moving theprocess_execution_payload
toprocess_operations
would be good too. I'm honestly surprised we didn't do the same for the Randao and Eth1Data functionality.process_execution_payload
function: this way the function input can just be anExecutionPayload
, and not a complete beacon block body. This helps with future compatibility and reduces test complexity (We don't have to set up a complete beacon block for every single test case). We can test that one if-statement as part of the regular full beacon block testing. And in future phases after the merge, we may even remove the if-statement entirely, since the merge-transition check will always be true.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For (2), it might make sense to put it behind a function
is_execution_enabled(state, block)
. This is semantically cleaner for any chain/setting that doesn't have the transition. They can just override the function to returnTrue
and as you mentioned, in the future we can just remove it.