Skip to content

Invalid block if system contract is empty on call or call fails #1183

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

Conversation

marioevz
Copy link
Member

@marioevz marioevz commented Apr 4, 2025

What was wrong?

EIPs 7002 and 7251 have been updated to define the expected outcome in case of execution failure or code absence:

ethereum/EIPs#9508
ethereum/EIPs#9582

How was it fixed?

Conditions for zero-length code and execution failure have been added to the system contract calls.

I'm not sure if this change affects EIP-2935, although I think we should not cherry-pick which system-contracts invalidate a block and which don't.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@gurukamath
Copy link
Collaborator

Consider re-directing this PR at the forks/prague branch instead. That has the latest spec updates including the latest re-factors from Prague.

@marioevz
Copy link
Member Author

Might need to take another look at this since it might modify behavior of prior system contracts, but according to this message https://discord.com/channels/595666850260713488/1351309992837251195/1361391543595962669, that should not be the case.

@marioevz marioevz force-pushed the invalid-block-on-missing-system-contracts branch from bea2827 to 739829b Compare April 15, 2025 12:47
@marioevz marioevz changed the base branch from devnets/prague/6 to forks/prague April 15, 2025 12:48
@marioevz marioevz force-pushed the invalid-block-on-missing-system-contracts branch 2 times, most recently from 28fd22f to c159057 Compare April 16, 2025 21:14
@marioevz
Copy link
Member Author

Ready to review, it's now independent of #1182 since I've removed the commits from that branch, although it will have a conflict because it touches the same code as that PR.

Comment on lines 487 to 488
raise_on_empty_code: bool,
raise_on_error: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something feels really weird about this, but I can't quite put my finger on it.

If we were going heavily into traditional exceptions, I'd expect to see a pattern like:

try:
    process_system_transaction(block_env, target_address, data)
except SystemContractEmpty:
    pass
except SystemContractRevert:
    pass

But we've taken the approach of minimizing our use of exceptions as much as possible to keep the code flow extremely readable.

Conditionally throwing exceptions seems to violate traditional exception patterns and our own minimal exception style.

Instead, what if we did something like:

def process_checked_system_transaction(
    block_env: vm.BlockEnvironment,
    target_address: Address,
    data: Bytes,
) -> MessageCallOutput:
    system_contract_code = get_account(block_env.state, target_address).code

    if len(system_contract_code) == 0:
        raise InvalidBlock(
            f"System contract address {target_address.hex()} does not contain code"
        )

    system_tx_output = process_system_transaction(
        block_env,
        target_address,
        system_contract_code,
        data,
    )

    if system_tx_output.error:
        raise InvalidBlock(
            f"System contract ({target_address.hex()}) call failed: "
            f"{system_tx_output.error}"
        )

def process_unchecked_system_transaction(
    block_env: vm.BlockEnvironment,
    target_address: Address,
    data: Bytes,
) -> MessageCallOutput:
    system_contract_code = get_account(block_env.state, target_address).code
    return process_system_transaction(
        block_env,
        target_address,
        system_contract_code,
        data,
    )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it! thanks for the suggestion, I'll work on the modifications 👍

@marioevz marioevz force-pushed the invalid-block-on-missing-system-contracts branch from c159057 to 2cce276 Compare April 23, 2025 00:21
@marioevz
Copy link
Member Author

@SamWilsn applied the changes, let me know what you think.

@SamWilsn SamWilsn force-pushed the invalid-block-on-missing-system-contracts branch 2 times, most recently from 6a8d3eb to a7fc1fe Compare April 24, 2025 15:00
@SamWilsn
Copy link
Contributor

It's possible I broke something while rebasing, but I'm getting this error in the tests:

ethereum.exceptions.InvalidBlock: System contract address 00000961ef480eb55e80d19ad83579a64c007002 does not contain code

From test:

tests/prague/test_state_transition.py::test_ethereum_tests[stEIP1153-transientStorage - 10_revertUndoesStoreAfterReturn_d0g0v0_Prague]

@marioevz
Copy link
Member Author

It's possible I broke something while rebasing, but I'm getting this error in the tests:

ethereum.exceptions.InvalidBlock: System contract address 00000961ef480eb55e80d19ad83579a64c007002 does not contain code

From test:

tests/prague/test_state_transition.py::test_ethereum_tests[stEIP1153-transientStorage - 10_revertUndoesStoreAfterReturn_d0g0v0_Prague]

Interesting, perhaps we broke state tests parsing/running with this change? Because I think this check should be skipped on state tests.

@marioevz
Copy link
Member Author

@SamWilsn wait, I'm confused, is this failure happening on the execution of a state test or blockchain test?

If it's on a blockchain test then it's expected because ethereum/tests I believe don't add the Prague system contracts to the blockchain tests.

@SamWilsn
Copy link
Contributor

I would guess this is a blockchain test:

# Run tests from ethereum/tests
@pytest.mark.parametrize(
"test_case",
fetch_tests(ETHEREUM_BLOCKCHAIN_TESTS_DIR),
ids=idfn,
)
def test_ethereum_tests(test_case: Dict) -> None:
run_tests(test_case)

marioevz and others added 2 commits May 6, 2025 14:58
Hoists the file system locks to the session level (instead of only while
fetching) so the whole fixtures folder is protected while the tests run.
Previously, running a second instance of the tests could swap out the
fixtures while the first instance is using them.

Re-download and re-extract HTTP tarball fixtures for every pytest
session. Previously it was assumed that if the directory exists, it was
the correct version of the fixtures. Now the fixtures are guaranteed to
be up-to-date.

HTTP responses are now cached in the user's home directory to avoid
re-downloading from GitHub when possible. The cache gets cleaned during
each pytest session, so it isn't particularly effective when changing
versions, but at least runs with the same version don't waste bandwidth.
@SamWilsn SamWilsn force-pushed the invalid-block-on-missing-system-contracts branch from 073ba3c to d168673 Compare May 6, 2025 19:00
@@ -573,6 +573,10 @@ def process_system_transaction(
Output of processing the system transaction.
"""
system_contract_code = get_account(state, target_address).code
if len(system_contract_code) == 0:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introducing these checks here will also apply these checks to the History storage contract from EIP-2935 and the parent beacon block root contract from cancun. If that is not the intent, we should apply the checks more selectively to the the relevant contracts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is taken care of with process_unchecked_system_transaction

"loopExp",
"loopMul",
"GeneralStateTests/stTimeConsuming/CALLBlake2f_MaxRounds.json::CALLBlake2f_MaxRounds-fork_[Cancun-Prague]-d0g0v0",
"GeneralStateTests/stTimeConsuming/CALLBlake2f_MaxRounds.json::CALLBlake2f_MaxRounds-fork_[Cancun-Prague]-d0g0v0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two entries seem to be the same

"evm_tools_testdata": {
"url": "https://github.com/gurukamath/evm-tools-testdata.git",
"commit_hash": "792422d",
"fixture_path": "tests/fixtures/evm_tools_testdata",
},
"ethereum_tests": {
"url": "https://github.com/ethereum/tests.git",
"commit_hash": "afed83b",
"commit_hash": "07f85aafbf2966b4db2b7d9cdf55248f91661ef9",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tox -e py3 ends up throwing stderr: 'fatal: reference is not a tree: 07f85aafbf2966b4db2b7d9cdf55248f91661ef9' on my local

@SamWilsn SamWilsn force-pushed the invalid-block-on-missing-system-contracts branch 2 times, most recently from 620c0ed to ca5f0ea Compare May 15, 2025 04:49
@SamWilsn SamWilsn force-pushed the invalid-block-on-missing-system-contracts branch from ca5f0ea to 3e5358e Compare May 15, 2025 04:49
@gurukamath gurukamath merged commit 3a0eb89 into ethereum:forks/prague May 15, 2025
3 of 5 checks passed
SamWilsn added a commit that referenced this pull request May 15, 2025
* Invalid block if system contract is empty on call or call fails

* Improve fixture fetching

Hoists the file system locks to the session level (instead of only while
fetching) so the whole fixtures folder is protected while the tests run.
Previously, running a second instance of the tests could swap out the
fixtures while the first instance is using them.

Re-download and re-extract HTTP tarball fixtures for every pytest
session. Previously it was assumed that if the directory exists, it was
the correct version of the fixtures. Now the fixtures are guaranteed to
be up-to-date.

HTTP responses are now cached in the user's home directory to avoid
re-downloading from GitHub when possible. The cache gets cleaned during
each pytest session, so it isn't particularly effective when changing
versions, but at least runs with the same version don't waste bandwidth.

* Ignore more coverage files

* Update to more recent ethereum/tests

---------

Co-authored-by: Sam Wilson <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants