Skip to content

Commit 10d045f

Browse files
tools(feat): Add defined exceptions (#384)
* tools(feat): Add exception type * tests(fix): Use exception type * docs: Add exception tests description * docs(fix): tox fixes * docs(fix): more tox fixes * docs: Typed exceptions to consuming tests section * docs: remove link * changelog * feat(docs): add links to test examples using each exception type * Update docs/writing_tests/exception_tests.md Co-authored-by: danceratopz <[email protected]> --------- Co-authored-by: danceratopz <[email protected]>
1 parent e2b84cc commit 10d045f

29 files changed

+450
-92
lines changed

docs/CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Test fixtures for use by clients are available for each release on the [Github r
5454
- 🔀 Updates fork name from Merge to Paris ([#363](https://github.com/ethereum/execution-spec-tests/pull/363)).
5555
- ✨ Add framework unit tests for post state exception verification ([#350](https://github.com/ethereum/execution-spec-tests/pull/350)).
5656
- ✨ Add [solc 0.8.23](https://github.com/ethereum/solidity/releases/tag/v0.8.23) support ([#373](https://github.com/ethereum/execution-spec-tests/pull/373)).
57+
- 💥 Tests must now use `BlockException` and `TransactionException` to define the expected exception of a given test, which can be used to test whether the client is hitting the proper exception when processing the block or transaction ([#384](https://github.com/ethereum/execution-spec-tests/pull/384)).
5758

5859
### 🔧 EVM Tools
5960

@@ -81,6 +82,33 @@ Test fixtures for use by clients are available for each release on the [Github r
8182
Fixture name example:
8283
- Previous fixture name: `fork=Frontier`
8384
- New fixture name: `fork_Frontier`
85+
4. Produced `blockchain_tests` fixtures and their corresponding `blockchain_tests_hive` fixtures now contain the named exceptions `BlockException` and `TransactionException` as strings in the `expectException` and `validationError` fields, respectively. These exceptions can be used to test whether the client is hitting the proper exception when processing an invalid block.
86+
87+
Blockchain test:
88+
89+
```json
90+
"blocks": [
91+
{
92+
...
93+
"expectException": "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS",
94+
...
95+
}
96+
...
97+
]
98+
```
99+
100+
Blockchain hive test:
101+
102+
```json
103+
"engineNewPayloads": [
104+
{
105+
...
106+
"validationError": "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS",
107+
...
108+
}
109+
...
110+
]
111+
```
84112

85113
## [v1.0.6](https://github.com/ethereum/execution-spec-tests/releases/tag/v1.0.6) - 2023-10-19: 🐍🏖️ Cancun Devnet 10
86114

docs/consuming_tests/blockchain_test.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ Optional list of withdrawals included in the block RLP.
197197

198198
### `InvalidFixtureBlock`
199199

200-
#### - `expectException`: `str`
200+
#### - `expectException`: [`TransactionException`](./exceptions.md#transactionexception)` | `[`BlockException`](./exceptions.md#blockexception)
201201

202-
Expected exception message that invalidates the block.
202+
Expected exception that invalidates the block.
203203

204204
#### - `rlp`: [`Bytes`](./common_types.md#bytes)
205205

docs/consuming_tests/blockchain_test_hive.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,17 @@ They can mismatch the hashes of the versioned blobs in the execution payload, fo
8282

8383
Hash of the parent beacon block root.
8484

85-
#### - `valid`: `bool`
85+
#### - `validationError`: [`TransactionException`](./exceptions.md#transactionexception)` | `[`BlockException`](./exceptions.md#blockexception)
8686

87-
To be deprecated: Whether the execution payload is valid or not. Expectation is `VALID` if `true`, `INVALID` if `false`, in the `status` field of [PayloadStatusV1](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1).
87+
Validation error expected when executing the payload.
88+
89+
When the payload is valid, this field is not present, and a `VALID` status is
90+
expected in the `status` field of
91+
[PayloadStatusV1](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1).
92+
93+
If this field is present, the `status` field of
94+
[PayloadStatusV1](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1)
95+
is expected to be `INVALID`.
8896

8997
#### - `version`: [`Number`](./common_types.md#number)
9098

docs/consuming_tests/exceptions.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Exceptions
2+
3+
Exception types are represented as a JSON string in the test fixtures.
4+
5+
The exception converted into a string is composed of the exception type name,
6+
followed by a period, followed by the specific exception name.
7+
8+
For example, the exception `INSUFFICIENT_ACCOUNT_FUNDS` of type
9+
`TransactionException` is represented as
10+
`"TransactionException.INSUFFICIENT_ACCOUNT_FUNDS"`.
11+
12+
The JSON string can contain multiple exception types, separated by the `|`
13+
character, denoting that the transaction or block can throw either one of
14+
the exceptions.
15+
16+
## `TransactionException`
17+
18+
::: ethereum_test_tools.TransactionException
19+
20+
## `BlockException`
21+
22+
::: ethereum_test_tools.BlockException

docs/consuming_tests/state_test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ Expected state root value that results of applying the transaction to the pre-st
167167
Hash of the RLP representation of the state logs result of applying the transaction to the pre-state
168168
(TODO: double-check this.)
169169

170-
#### - `expectException`: `str`
170+
#### - `expectException`: [`TransactionException`](./exceptions.md#transactionexception)
171171

172172
Exception that is expected to be thrown by the transaction execution (Field is missing if the transaction is expected to succeed)
173173

docs/navigation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
* [Writing a New Test](writing_tests/writing_a_new_test.md)
1616
* [Referencing an EIP Spec Version](writing_tests/reference_specification.md)
1717
* [Verifying Changes Locally](writing_tests/verifying_changes.md)
18+
* [Exception Tests](writing_tests/exception_tests.md)
1819
* Tutorials
1920
* [State Transition Tests](tutorials/state_transition.md)
2021
* [Consuming Tests](consuming_tests/index.md)
2122
* [State Tests](consuming_tests/state_test.md)
2223
* [Blockchain Tests](consuming_tests/blockchain_test.md)
2324
* [Blockchain Hive Tests](consuming_tests/blockchain_test_hive.md)
2425
* [Common Types](consuming_tests/common_types.md)
26+
* [Exceptions](consuming_tests/exceptions.md)
2527
* [Getting Help](getting_help/index.md)
2628
* [Developer Doc](dev/index.md)
2729
* [Documentation](dev/docs.md)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Exception Tests
2+
3+
Exception tests are a special type of test which verify that an invalid transaction or an invalid block are correctly rejected with the expected error.
4+
5+
## Creating an Exception Test
6+
7+
To test for an exception, the test can use either of the following types from `ethereum_test_tools` library:
8+
9+
1. [`TransactionException`](../consuming_tests/exceptions.md#transactionexception): To be added to the `error` field of the `Transaction` object, and to the `exception` field of the `Block` object that includes the transaction; this exception type is used when a transaction is invalid, and therefore when included in a block, the block is expected to be invalid too. This is different from valid transactions where an exception during EVM execution is expected (e.g. a revert, or out-of-gas), which can be included in valid blocks.
10+
11+
For an example, see [`eip3860_initcode.test_initcode.test_contract_creating_tx`](../tests/shanghai/eip3860_initcode/test_initcode/index.md#tests.shanghai.eip3860_initcode.test_initcode.test_contract_creating_tx) which raises `TransactionException.INITCODE_SIZE_EXCEEDED` in the case that the initcode size exceeds the maximum allowed size.
12+
13+
2. [`BlockException`](../consuming_tests/exceptions.md#blockexception): To be added to the `exception` field of the `Block` object; this exception type is used when a block is expected to be invalid, but the exception is related to a block property, e.g. an invalid value of the block header.
14+
15+
For an example, see [`eip4844_blobs.test_excess_blob_gas.test_invalid_static_excess_blob_gas`](../tests/cancun/eip4844_blobs/test_excess_blob_gas/index.md#tests.cancun.eip4844_blobs.test_excess_blob_gas.test_invalid_static_excess_blob_gas) which raises `BlockException.INCORRECT_EXCESS_BLOB_GAS` in the case that the the `excessBlobGas` remains unchanged
16+
but the parent blobs included are not `TARGET_BLOBS_PER_BLOCK`.
17+
18+
Although exceptions can be combined with the `|` operator to indicate that a test vector can throw either one of multiple exceptions, ideally the tester should aim to use only one exception per test vector, and only use multiple exceptions on the rare instance when it is not possible to know which exception will be thrown because it depends on client implementation.
19+
20+
## Adding a new exception
21+
22+
If a test requires a new exception, because none of the existing ones is suitable for the test, a new exception can be added to either [`TransactionException`](../consuming_tests/exceptions.md#transactionexception) or [`BlockException`](../consuming_tests/exceptions.md#blockexception) classes.
23+
24+
The new exception should be added as a new enum value, and the docstring of the attribute should be a string that describes the exception.
25+
26+
The name of the exception should be unique, and should not be used by any other exception.
27+
28+
## Test runner behavior on exception tests
29+
30+
When an exception is added to a test vector, the test runner must check that the transaction or block is rejected with the expected exception.
31+
32+
The test runner must map the exception key to the corresponding error string that is expected to be returned by the client.
33+
34+
Exception mapping are particularly important in blockchain tests because the block can be invalid for multiple reasons, and the client returning a different error can mean that a verification in the client is faulty.

src/ethereum_test_tools/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
to_hash_bytes,
4444
transaction_list_root,
4545
)
46+
from .exceptions import BlockException, ExceptionList, ExceptionType, TransactionException
4647
from .reference_spec import ReferenceSpec, ReferenceSpecTypes
4748
from .spec import (
4849
SPEC_TYPES,
@@ -68,13 +69,16 @@
6869
"Block",
6970
"BlockchainTest",
7071
"BlockchainTestFiller",
71-
"Case",
72+
"BlockException",
7273
"CalldataCase",
74+
"Case",
7375
"Code",
7476
"CodeGasMeasure",
7577
"Conditional",
7678
"EngineAPIError",
7779
"Environment",
80+
"ExceptionList",
81+
"ExceptionType",
7882
"FixtureCollector",
7983
"Header",
8084
"HistoryStorageAddress",
@@ -97,6 +101,7 @@
97101
"TestPrivateKey",
98102
"TestPrivateKey2",
99103
"Transaction",
104+
"TransactionException",
100105
"Withdrawal",
101106
"Yul",
102107
"YulCompiler",

src/ethereum_test_tools/common/types.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from ethereum_test_forks import Fork
2929

30+
from ..exceptions import ExceptionList, TransactionException
3031
from .constants import AddrAA, TestPrivateKey
3132
from .conversions import (
3233
BytesConvertible,
@@ -1264,7 +1265,7 @@ class Transaction:
12641265
skip=True,
12651266
),
12661267
)
1267-
error: Optional[str] = field(
1268+
error: Optional[TransactionException | ExceptionList] = field(
12681269
default=None,
12691270
json_encoder=JSONEncoder.Field(
12701271
skip=True,
@@ -1339,7 +1340,7 @@ def __post_init__(self) -> None:
13391340
if self.ty >= 2 and self.max_priority_fee_per_gas is None:
13401341
self.max_priority_fee_per_gas = 0
13411342

1342-
def with_error(self, error: str) -> "Transaction":
1343+
def with_error(self, error: TransactionException | ExceptionList) -> "Transaction":
13431344
"""
13441345
Create a copy of the transaction with an added error.
13451346
"""
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
Exceptions for invalid execution.
3+
"""
4+
5+
from .exceptions import BlockException, ExceptionList, ExceptionType, TransactionException
6+
7+
__all__ = ["BlockException", "ExceptionType", "ExceptionList", "TransactionException"]

0 commit comments

Comments
 (0)