-
Notifications
You must be signed in to change notification settings - Fork 304
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
Invalid block if system contract is empty on call or call fails #1183
Conversation
Consider re-directing this PR at the |
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. |
bea2827
to
739829b
Compare
28fd22f
to
c159057
Compare
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. |
src/ethereum/cancun/fork.py
Outdated
raise_on_empty_code: bool, | ||
raise_on_error: bool, |
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.
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,
)
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.
I like it! thanks for the suggestion, I'll work on the modifications 👍
c159057
to
2cce276
Compare
@SamWilsn applied the changes, let me know what you think. |
6a8d3eb
to
a7fc1fe
Compare
It's possible I broke something while rebasing, but I'm getting this error in the tests:
From test:
|
Interesting, perhaps we broke state tests parsing/running with this change? Because I think this check should be skipped on state tests. |
@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. |
I would guess this is a blockchain test: execution-specs/tests/prague/test_state_transition.py Lines 87 to 94 in b1edd6b
|
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.
073ba3c
to
d168673
Compare
src/ethereum/prague/fork.py
Outdated
@@ -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: |
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.
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
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.
This is taken care of with process_unchecked_system_transaction
tests/cancun/test_evm_tools.py
Outdated
"loopExp", | ||
"loopMul", | ||
"GeneralStateTests/stTimeConsuming/CALLBlake2f_MaxRounds.json::CALLBlake2f_MaxRounds-fork_[Cancun-Prague]-d0g0v0", | ||
"GeneralStateTests/stTimeConsuming/CALLBlake2f_MaxRounds.json::CALLBlake2f_MaxRounds-fork_[Cancun-Prague]-d0g0v0", |
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.
These two entries seem to be the same
tests/helpers/__init__.py
Outdated
"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", |
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.
tox -e py3
ends up throwing stderr: 'fatal: reference is not a tree: 07f85aafbf2966b4db2b7d9cdf55248f91661ef9'
on my local
620c0ed
to
ca5f0ea
Compare
ca5f0ea
to
3e5358e
Compare
* 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]>
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