Skip to content

Commit 8f3ffd3

Browse files
marioevzSamWilsn
andcommitted
Invalid block if system contract is empty on call or call fails (#1183)
* 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]>
1 parent 8799542 commit 8f3ffd3

File tree

12 files changed

+288
-93
lines changed

12 files changed

+288
-93
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pip-delete-this-directory.txt
5050
/docs
5151

5252
.coverage
53+
.coverage.*
5354

5455
/doc/diffs
5556
/doc/autoapi

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ test =
171171
GitPython>=3.1.0,<3.2
172172
filelock>=3.12.3,<3.13
173173
requests
174+
requests-cache>=1.2.1,<2
174175

175176
lint =
176177
types-setuptools>=68.1.0.1,<69

src/ethereum/cancun/fork.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -482,17 +482,23 @@ def make_receipt(
482482
def process_system_transaction(
483483
block_env: vm.BlockEnvironment,
484484
target_address: Address,
485+
system_contract_code: Bytes,
485486
data: Bytes,
486487
) -> MessageCallOutput:
487488
"""
488-
Process a system transaction.
489+
Process a system transaction with the given code.
490+
491+
Prefer calling `process_unchecked_system_transaction` unless the contract
492+
code has already been read from the state.
489493
490494
Parameters
491495
----------
492496
block_env :
493497
The block scoped environment.
494498
target_address :
495499
Address of the contract to call.
500+
system_contract_code :
501+
Code of the contract to call.
496502
data :
497503
Data to pass to the contract.
498504
@@ -501,8 +507,6 @@ def process_system_transaction(
501507
system_tx_output : `MessageCallOutput`
502508
Output of processing the system transaction.
503509
"""
504-
system_contract_code = get_account(block_env.state, target_address).code
505-
506510
tx_env = vm.TransactionEnvironment(
507511
origin=SYSTEM_ADDRESS,
508512
gas_price=block_env.base_fee_per_gas,
@@ -540,6 +544,38 @@ def process_system_transaction(
540544
return system_tx_output
541545

542546

547+
def process_unchecked_system_transaction(
548+
block_env: vm.BlockEnvironment,
549+
target_address: Address,
550+
data: Bytes,
551+
) -> MessageCallOutput:
552+
"""
553+
Process a system transaction without checking if the contract contains code
554+
or if the transaction fails.
555+
556+
Parameters
557+
----------
558+
block_env :
559+
The block scoped environment.
560+
target_address :
561+
Address of the contract to call.
562+
data :
563+
Data to pass to the contract.
564+
565+
Returns
566+
-------
567+
system_tx_output : `MessageCallOutput`
568+
Output of processing the system transaction.
569+
"""
570+
system_contract_code = get_account(block_env.state, target_address).code
571+
return process_system_transaction(
572+
block_env,
573+
target_address,
574+
system_contract_code,
575+
data,
576+
)
577+
578+
543579
def apply_body(
544580
block_env: vm.BlockEnvironment,
545581
transactions: Tuple[Union[LegacyTransaction, Bytes], ...],
@@ -571,7 +607,7 @@ def apply_body(
571607
"""
572608
block_output = vm.BlockOutput()
573609

574-
process_system_transaction(
610+
process_unchecked_system_transaction(
575611
block_env=block_env,
576612
target_address=BEACON_ROOTS_ADDRESS,
577613
data=block_env.parent_beacon_block_root,

src/ethereum/prague/fork.py

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -514,17 +514,25 @@ def make_receipt(
514514
def process_system_transaction(
515515
block_env: vm.BlockEnvironment,
516516
target_address: Address,
517+
system_contract_code: Bytes,
517518
data: Bytes,
518519
) -> MessageCallOutput:
519520
"""
520-
Process a system transaction.
521+
Process a system transaction with the given code.
522+
523+
Prefer calling `process_checked_system_transaction` or
524+
`process_unchecked_system_transaction` depending on
525+
whether missing code or an execution error should cause
526+
the block to be rejected.
521527
522528
Parameters
523529
----------
524530
block_env :
525531
The block scoped environment.
526532
target_address :
527533
Address of the contract to call.
534+
system_contract_code :
535+
Code of the contract to call.
528536
data :
529537
Data to pass to the contract.
530538
@@ -533,8 +541,6 @@ def process_system_transaction(
533541
system_tx_output : `MessageCallOutput`
534542
Output of processing the system transaction.
535543
"""
536-
system_contract_code = get_account(block_env.state, target_address).code
537-
538544
tx_env = vm.TransactionEnvironment(
539545
origin=SYSTEM_ADDRESS,
540546
gas_price=block_env.base_fee_per_gas,
@@ -574,6 +580,85 @@ def process_system_transaction(
574580
return system_tx_output
575581

576582

583+
def process_checked_system_transaction(
584+
block_env: vm.BlockEnvironment,
585+
target_address: Address,
586+
data: Bytes,
587+
) -> MessageCallOutput:
588+
"""
589+
Process a system transaction and raise an error if the contract does not
590+
contain code or if the transaction fails.
591+
592+
Parameters
593+
----------
594+
block_env :
595+
The block scoped environment.
596+
target_address :
597+
Address of the contract to call.
598+
data :
599+
Data to pass to the contract.
600+
601+
Returns
602+
-------
603+
system_tx_output : `MessageCallOutput`
604+
Output of processing the system transaction.
605+
"""
606+
system_contract_code = get_account(block_env.state, target_address).code
607+
608+
if len(system_contract_code) == 0:
609+
raise InvalidBlock(
610+
f"System contract address {target_address.hex()} does not "
611+
"contain code"
612+
)
613+
614+
system_tx_output = process_system_transaction(
615+
block_env,
616+
target_address,
617+
system_contract_code,
618+
data,
619+
)
620+
621+
if system_tx_output.error:
622+
raise InvalidBlock(
623+
f"System contract ({target_address.hex()}) call failed: "
624+
f"{system_tx_output.error}"
625+
)
626+
627+
return system_tx_output
628+
629+
630+
def process_unchecked_system_transaction(
631+
block_env: vm.BlockEnvironment,
632+
target_address: Address,
633+
data: Bytes,
634+
) -> MessageCallOutput:
635+
"""
636+
Process a system transaction without checking if the contract contains code
637+
or if the transaction fails.
638+
639+
Parameters
640+
----------
641+
block_env :
642+
The block scoped environment.
643+
target_address :
644+
Address of the contract to call.
645+
data :
646+
Data to pass to the contract.
647+
648+
Returns
649+
-------
650+
system_tx_output : `MessageCallOutput`
651+
Output of processing the system transaction.
652+
"""
653+
system_contract_code = get_account(block_env.state, target_address).code
654+
return process_system_transaction(
655+
block_env,
656+
target_address,
657+
system_contract_code,
658+
data,
659+
)
660+
661+
577662
def apply_body(
578663
block_env: vm.BlockEnvironment,
579664
transactions: Tuple[Union[LegacyTransaction, Bytes], ...],
@@ -605,13 +690,13 @@ def apply_body(
605690
"""
606691
block_output = vm.BlockOutput()
607692

608-
process_system_transaction(
693+
process_unchecked_system_transaction(
609694
block_env=block_env,
610695
target_address=BEACON_ROOTS_ADDRESS,
611696
data=block_env.parent_beacon_block_root,
612697
)
613698

614-
process_system_transaction(
699+
process_unchecked_system_transaction(
615700
block_env=block_env,
616701
target_address=HISTORY_STORAGE_ADDRESS,
617702
data=block_env.block_hashes[-1], # The parent hash
@@ -650,7 +735,7 @@ def process_general_purpose_requests(
650735
if len(deposit_requests) > 0:
651736
requests_from_execution.append(DEPOSIT_REQUEST_TYPE + deposit_requests)
652737

653-
system_withdrawal_tx_output = process_system_transaction(
738+
system_withdrawal_tx_output = process_checked_system_transaction(
654739
block_env=block_env,
655740
target_address=WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
656741
data=b"",
@@ -661,7 +746,7 @@ def process_general_purpose_requests(
661746
WITHDRAWAL_REQUEST_TYPE + system_withdrawal_tx_output.return_data
662747
)
663748

664-
system_consolidation_tx_output = process_system_transaction(
749+
system_consolidation_tx_output = process_checked_system_transaction(
665750
block_env=block_env,
666751
target_address=CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
667752
data=b"",

src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ def process_general_purpose_requests(self) -> Any:
6363
return self._module("fork").process_general_purpose_requests
6464

6565
@property
66-
def process_system_transaction(self) -> Any:
67-
"""process_system_transaction function of the given fork."""
68-
return self._module("fork").process_system_transaction
66+
def process_unchecked_system_transaction(self) -> Any:
67+
"""process_unchecked_system_transaction function of the given fork."""
68+
return self._module("fork").process_unchecked_system_transaction
6969

7070
@property
7171
def process_withdrawals(self) -> Any:

src/ethereum_spec_tools/evm_tools/t8n/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,14 @@ def run_state_test(self) -> Any:
203203

204204
def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None:
205205
if self.fork.is_after_fork("ethereum.prague"):
206-
self.fork.process_system_transaction(
206+
self.fork.process_unchecked_system_transaction(
207207
block_env=block_env,
208208
target_address=self.fork.HISTORY_STORAGE_ADDRESS,
209209
data=block_env.block_hashes[-1], # The parent hash
210210
)
211211

212212
if self.fork.is_after_fork("ethereum.cancun"):
213-
self.fork.process_system_transaction(
213+
self.fork.process_unchecked_system_transaction(
214214
block_env=block_env,
215215
target_address=self.fork.BEACON_ROOTS_ADDRESS,
216216
data=block_env.parent_beacon_block_root,

tests/cancun/test_evm_tools.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
FORK_NAME = "Cancun"
1616

1717
SLOW_TESTS = (
18-
"CALLBlake2f_MaxRounds",
19-
"CALLCODEBlake2f",
20-
"CALLBlake2f",
21-
"loopExp",
22-
"loopMul",
18+
"GeneralStateTests/stTimeConsuming/CALLBlake2f_MaxRounds.json::CALLBlake2f_MaxRounds-fork_[Cancun-Prague]-d0g0v0",
19+
"GeneralStateTests/VMTests/vmPerformance/loopExp.json::loopExp-fork_[Cancun-Prague]-d[0-14]g0v0",
20+
"GeneralStateTests/VMTests/vmPerformance/loopMul.json::loopMul-fork_[Cancun-Prague]-d[0-2]g0v0",
2321
)
2422

2523

0 commit comments

Comments
 (0)