Skip to content

Commit ad5c0ad

Browse files
refactor(exceptions,specs): Support general block exceptions - Pydantic context to parse using mapper (#1396)
* feat(clis,exceptions,specs): Pydantic validation using exception mapper * fix(specs): Skip block exception check on requests modifications * fix(docs): Links * fix(tests): EIP-4844: mark block as skip block exception verification * refactor(exceptions): Mapper mappings allow regex, convert from `property` to `ClassVar` * fix(docs): tox * chore(deps): remove now unused bdict dependency * Suggestions for #1396, refactor(exceptions,specs): Support general block exceptions - Pydantic context to parse using mapper (#1404) * refactor(fw): use enum helper; rename `ty` to `execution_context` * refactor(fw): use more specific names for exception classes * fix(tests): Add comment explaining `skip_exception_verification` * feat(clis): Add `reliable` flag to exception mapper class * fix: tests * fix(specs,clis,tools): Use `default_t8n` and remove `run_in_serial` from most unit tests * fix(specs): Bug and variable names * fix: bug * feat(clis/evmone): Add insufficient-blob-fee error * fix(tests): Allow similar blob exceptions * Apply suggestions from code review Co-authored-by: danceratopz <[email protected]> --------- Co-authored-by: danceratopz <[email protected]>
1 parent efc39d5 commit ad5c0ad

File tree

29 files changed

+932
-1096
lines changed

29 files changed

+932
-1096
lines changed

docs/consuming_tests/blockchain_test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ Optional list of withdrawals included in the block RLP.
216216

217217
### `InvalidFixtureBlock`
218218

219-
#### - `expectException`: [`TransactionException`](./exceptions.md#transactionexception)` | `[`BlockException`](./exceptions.md#blockexception)
219+
#### - `expectException`: [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException)` | `[`BlockException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.BlockException)
220220

221221
Expected exception that invalidates the block.
222222

docs/consuming_tests/blockchain_test_engine.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ They can mismatch the hashes of the versioned blobs in the execution payload, fo
104104

105105
Hash of the parent beacon block root.
106106

107-
#### - `validationError`: [`TransactionException`](./exceptions.md#transactionexception)` | `[`BlockException`](./exceptions.md#blockexception)
107+
#### - `validationError`: [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException)` | `[`BlockException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.BlockException)
108108

109109
Validation error expected when executing the payload.
110110

docs/consuming_tests/exceptions.md

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,4 @@ The JSON string can contain multiple exception types, separated by the `|`
1313
character, denoting that the transaction or block can throw either one of
1414
the exceptions.
1515

16-
## `TransactionException`
17-
18-
::: ethereum_test_exceptions.TransactionException
19-
20-
## `BlockException`
21-
22-
::: ethereum_test_exceptions.BlockException
23-
24-
## `EOFException`
25-
26-
::: ethereum_test_exceptions.EOFException
16+
For a list of all defined exceptions look at [Ethereum Test Exceptions Package](../library/ethereum_test_exceptions.md).

docs/consuming_tests/state_test.md

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

182-
#### - `expectException`: [`TransactionException`](./exceptions.md#transactionexception)
182+
#### - `expectException`: [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException)
183183

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

docs/consuming_tests/transaction_test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ Total intrinsic gas cost of the transaction (Field is missing if the transaction
6262

6363
Sender address of the transaction (Field is missing if the transaction is expected to fail).
6464

65-
#### - `exception`: [`TransactionException`](./exceptions.md#transactionexception)
65+
#### - `exception`: [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException)
6666

6767
Exception that is expected to be thrown by the transaction parsing (Field is missing if the transaction is expected to succeed).

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ dependencies = [
2525
"gitpython>=3.1.31,<4",
2626
"PyJWT>=2.3.0,<3",
2727
"tenacity>8.2.0,<9",
28-
"bidict>=0.23,<1",
2928
"requests>=2.31.0,<3",
3029
"requests_unixsocket2>=0.4.0",
3130
"colorlog>=6.7.0,<7",

src/conftest.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
"""Local pytest configuration for framework tests."""
22

33
import os
4+
from typing import Generator
45

56
import pytest
67

8+
from ethereum_clis import ExecutionSpecsTransitionTool, TransitionTool
9+
710

811
def pytest_runtest_setup(item):
912
"""Skip tests if running with pytest-xdist in parallel."""
1013
marker = item.get_closest_marker(name="run_in_serial")
1114
if marker is not None:
1215
if os.getenv("PYTEST_XDIST_WORKER_COUNT") not in [None, "1"]:
1316
pytest.skip("Skipping test because pytest-xdist is running with more than one worker.")
17+
18+
19+
DEFAULT_T8N_FOR_UNIT_TESTS = ExecutionSpecsTransitionTool
20+
21+
22+
@pytest.fixture(scope="session")
23+
def default_t8n_instance() -> Generator[TransitionTool, None, None]:
24+
"""Fixture to provide a default t8n instance."""
25+
instance = ExecutionSpecsTransitionTool()
26+
instance.start_server()
27+
yield instance
28+
instance.shutdown()
29+
30+
31+
@pytest.fixture
32+
def default_t8n(
33+
default_t8n_instance: TransitionTool,
34+
) -> TransitionTool:
35+
"""Fixture to provide a default t8n instance."""
36+
return default_t8n_instance

src/ethereum_clis/clis/besu.py

Lines changed: 78 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
import tempfile
88
import textwrap
99
from pathlib import Path
10-
from typing import List, Optional
10+
from typing import ClassVar, Dict, List, Optional
1111

1212
import requests
1313

1414
from ethereum_test_base_types import BlobSchedule
1515
from ethereum_test_exceptions import (
1616
EOFException,
17+
ExceptionBase,
1718
ExceptionMapper,
18-
ExceptionMessage,
1919
TransactionException,
2020
)
2121
from ethereum_test_forks import Fork
@@ -163,7 +163,9 @@ def evaluate(
163163

164164
response = requests.post(self.server_url, json=post_data, timeout=5)
165165
response.raise_for_status() # exception visible in pytest failure output
166-
output: TransitionToolOutput = TransitionToolOutput.model_validate(response.json())
166+
output: TransitionToolOutput = TransitionToolOutput.model_validate(
167+
response.json(), context={"exception_mapper": self.exception_mapper}
168+
)
167169

168170
if debug_output_path:
169171
dump_files_to_directory(
@@ -209,138 +211,76 @@ def is_fork_supported(self, fork: Fork) -> bool:
209211
class BesuExceptionMapper(ExceptionMapper):
210212
"""Translate between EEST exceptions and error strings returned by Besu."""
211213

212-
@property
213-
def _mapping_data(self):
214-
return [
215-
ExceptionMessage(
216-
TransactionException.TYPE_4_TX_CONTRACT_CREATION,
217-
"transaction code delegation transactions must have a non-empty code "
218-
"delegation list",
219-
),
220-
ExceptionMessage(
221-
TransactionException.INSUFFICIENT_ACCOUNT_FUNDS,
222-
"exceeds transaction sender account balance",
223-
),
224-
ExceptionMessage(
225-
TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED,
226-
"would exceed block maximum",
227-
),
228-
ExceptionMessage(
229-
TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS,
230-
"max fee per blob gas less than block blob gas fee",
231-
),
232-
ExceptionMessage(
233-
TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS,
234-
"gasPrice is less than the current BaseFee",
235-
),
236-
ExceptionMessage(
237-
TransactionException.TYPE_3_TX_PRE_FORK,
238-
(
239-
"Transaction type BLOB is invalid, accepted transaction types are "
240-
"[EIP1559, ACCESS_LIST, FRONTIER]"
241-
),
242-
),
243-
ExceptionMessage(
244-
TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH,
245-
"Only supported hash version is 0x01, sha256 hash.",
246-
),
247-
# This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED
248-
ExceptionMessage(
249-
TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED,
250-
"exceed block maximum",
251-
),
252-
ExceptionMessage(
253-
TransactionException.TYPE_3_TX_ZERO_BLOBS,
254-
"Blob transaction must have at least one versioned hash",
255-
),
256-
ExceptionMessage(
257-
TransactionException.INTRINSIC_GAS_TOO_LOW,
258-
"intrinsic gas",
259-
),
260-
ExceptionMessage(
261-
TransactionException.INITCODE_SIZE_EXCEEDED,
262-
"exceeds maximum size",
263-
),
264-
ExceptionMessage(
265-
TransactionException.NONCE_MISMATCH_TOO_LOW,
266-
"below sender account nonce",
267-
),
268-
# TODO EVMONE needs to differentiate when the section is missing in the header or body
269-
ExceptionMessage(EOFException.MISSING_STOP_OPCODE, "err: no_terminating_instruction"),
270-
ExceptionMessage(EOFException.MISSING_CODE_HEADER, "err: code_section_missing"),
271-
ExceptionMessage(EOFException.MISSING_TYPE_HEADER, "err: type_section_missing"),
272-
# TODO EVMONE these exceptions are too similar, this leeds to ambiguity
273-
ExceptionMessage(EOFException.MISSING_TERMINATOR, "err: header_terminator_missing"),
274-
ExceptionMessage(
275-
EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated"
276-
),
277-
ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"),
278-
ExceptionMessage(
279-
EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag"
280-
),
281-
ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"),
282-
ExceptionMessage(
283-
EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type"
284-
),
285-
ExceptionMessage(
286-
EOFException.INVALID_SECTION_BODIES_SIZE, "err: invalid_section_bodies_size"
287-
),
288-
ExceptionMessage(
289-
EOFException.INVALID_TYPE_SECTION_SIZE, "err: invalid_type_section_size"
290-
),
291-
ExceptionMessage(EOFException.INCOMPLETE_SECTION_SIZE, "err: incomplete_section_size"),
292-
ExceptionMessage(
293-
EOFException.INCOMPLETE_SECTION_NUMBER, "err: incomplete_section_number"
294-
),
295-
ExceptionMessage(EOFException.TOO_MANY_CODE_SECTIONS, "err: too_many_code_sections"),
296-
ExceptionMessage(EOFException.ZERO_SECTION_SIZE, "err: zero_section_size"),
297-
ExceptionMessage(EOFException.MISSING_DATA_SECTION, "err: data_section_missing"),
298-
ExceptionMessage(EOFException.UNDEFINED_INSTRUCTION, "err: undefined_instruction"),
299-
ExceptionMessage(
300-
EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT, "err: inputs_outputs_num_above_limit"
301-
),
302-
ExceptionMessage(
303-
EOFException.UNREACHABLE_INSTRUCTIONS, "err: unreachable_instructions"
304-
),
305-
ExceptionMessage(
306-
EOFException.INVALID_RJUMP_DESTINATION, "err: invalid_rjump_destination"
307-
),
308-
ExceptionMessage(
309-
EOFException.UNREACHABLE_CODE_SECTIONS, "err: unreachable_code_sections"
310-
),
311-
ExceptionMessage(EOFException.STACK_UNDERFLOW, "err: stack_underflow"),
312-
ExceptionMessage(
313-
EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit"
314-
),
315-
ExceptionMessage(
316-
EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required"
317-
),
318-
ExceptionMessage(
319-
EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS,
320-
"err: jumpf_destination_incompatible_outputs",
321-
),
322-
ExceptionMessage(
323-
EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"
324-
),
325-
ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"),
326-
ExceptionMessage(EOFException.TRUNCATED_INSTRUCTION, "err: truncated_instruction"),
327-
ExceptionMessage(
328-
EOFException.TOPLEVEL_CONTAINER_TRUNCATED, "err: toplevel_container_truncated"
329-
),
330-
ExceptionMessage(EOFException.ORPHAN_SUBCONTAINER, "err: unreferenced_subcontainer"),
331-
ExceptionMessage(
332-
EOFException.CONTAINER_SIZE_ABOVE_LIMIT, "err: container_size_above_limit"
333-
),
334-
ExceptionMessage(
335-
EOFException.INVALID_CONTAINER_SECTION_INDEX,
336-
"err: invalid_container_section_index",
337-
),
338-
ExceptionMessage(
339-
EOFException.INCOMPATIBLE_CONTAINER_KIND, "err: incompatible_container_kind"
340-
),
341-
ExceptionMessage(EOFException.STACK_HEIGHT_MISMATCH, "err: stack_height_mismatch"),
342-
ExceptionMessage(EOFException.TOO_MANY_CONTAINERS, "err: too_many_container_sections"),
343-
ExceptionMessage(
344-
EOFException.INVALID_CODE_SECTION_INDEX, "err: invalid_code_section_index"
345-
),
346-
]
214+
mapping_substring: ClassVar[Dict[ExceptionBase, str]] = {
215+
TransactionException.TYPE_4_TX_CONTRACT_CREATION: (
216+
"transaction code delegation transactions must have a non-empty code delegation list"
217+
),
218+
TransactionException.INSUFFICIENT_ACCOUNT_FUNDS: (
219+
"exceeds transaction sender account balance"
220+
),
221+
TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED: (
222+
"would exceed block maximum"
223+
),
224+
TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS: (
225+
"max fee per blob gas less than block blob gas fee"
226+
),
227+
TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS: (
228+
"gasPrice is less than the current BaseFee"
229+
),
230+
TransactionException.TYPE_3_TX_PRE_FORK: (
231+
"Transaction type BLOB is invalid, accepted transaction types are "
232+
"[EIP1559, ACCESS_LIST, FRONTIER]"
233+
),
234+
TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH: (
235+
"Only supported hash version is 0x01, sha256 hash."
236+
),
237+
# This message is the same as TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED
238+
TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED: "exceed block maximum",
239+
TransactionException.TYPE_3_TX_ZERO_BLOBS: (
240+
"Blob transaction must have at least one versioned hash"
241+
),
242+
TransactionException.INTRINSIC_GAS_TOO_LOW: "intrinsic gas",
243+
TransactionException.INITCODE_SIZE_EXCEEDED: "exceeds maximum size",
244+
TransactionException.NONCE_MISMATCH_TOO_LOW: "below sender account nonce",
245+
# TODO EVMONE needs to differentiate when the section is missing in the header or body
246+
EOFException.MISSING_STOP_OPCODE: "err: no_terminating_instruction",
247+
EOFException.MISSING_CODE_HEADER: "err: code_section_missing",
248+
EOFException.MISSING_TYPE_HEADER: "err: type_section_missing",
249+
# TODO EVMONE these exceptions are too similar, this leeds to ambiguity
250+
EOFException.MISSING_TERMINATOR: "err: header_terminator_missing",
251+
EOFException.MISSING_HEADERS_TERMINATOR: "err: section_headers_not_terminated",
252+
EOFException.INVALID_VERSION: "err: eof_version_unknown",
253+
EOFException.INVALID_NON_RETURNING_FLAG: "err: invalid_non_returning_flag",
254+
EOFException.INVALID_MAGIC: "err: invalid_prefix",
255+
EOFException.INVALID_FIRST_SECTION_TYPE: "err: invalid_first_section_type",
256+
EOFException.INVALID_SECTION_BODIES_SIZE: "err: invalid_section_bodies_size",
257+
EOFException.INVALID_TYPE_SECTION_SIZE: "err: invalid_type_section_size",
258+
EOFException.INCOMPLETE_SECTION_SIZE: "err: incomplete_section_size",
259+
EOFException.INCOMPLETE_SECTION_NUMBER: "err: incomplete_section_number",
260+
EOFException.TOO_MANY_CODE_SECTIONS: "err: too_many_code_sections",
261+
EOFException.ZERO_SECTION_SIZE: "err: zero_section_size",
262+
EOFException.MISSING_DATA_SECTION: "err: data_section_missing",
263+
EOFException.UNDEFINED_INSTRUCTION: "err: undefined_instruction",
264+
EOFException.INPUTS_OUTPUTS_NUM_ABOVE_LIMIT: "err: inputs_outputs_num_above_limit",
265+
EOFException.UNREACHABLE_INSTRUCTIONS: "err: unreachable_instructions",
266+
EOFException.INVALID_RJUMP_DESTINATION: "err: invalid_rjump_destination",
267+
EOFException.UNREACHABLE_CODE_SECTIONS: "err: unreachable_code_sections",
268+
EOFException.STACK_UNDERFLOW: "err: stack_underflow",
269+
EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT: "err: max_stack_height_above_limit",
270+
EOFException.STACK_HIGHER_THAN_OUTPUTS: "err: stack_higher_than_outputs_required",
271+
EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS: (
272+
"err: jumpf_destination_incompatible_outputs"
273+
),
274+
EOFException.INVALID_MAX_STACK_HEIGHT: "err: invalid_max_stack_height",
275+
EOFException.INVALID_DATALOADN_INDEX: "err: invalid_dataloadn_index",
276+
EOFException.TRUNCATED_INSTRUCTION: "err: truncated_instruction",
277+
EOFException.TOPLEVEL_CONTAINER_TRUNCATED: "err: toplevel_container_truncated",
278+
EOFException.ORPHAN_SUBCONTAINER: "err: unreferenced_subcontainer",
279+
EOFException.CONTAINER_SIZE_ABOVE_LIMIT: "err: container_size_above_limit",
280+
EOFException.INVALID_CONTAINER_SECTION_INDEX: "err: invalid_container_section_index",
281+
EOFException.INCOMPATIBLE_CONTAINER_KIND: "err: incompatible_container_kind",
282+
EOFException.STACK_HEIGHT_MISMATCH: "err: stack_height_mismatch",
283+
EOFException.TOO_MANY_CONTAINERS: "err: too_many_container_sections",
284+
EOFException.INVALID_CODE_SECTION_INDEX: "err: invalid_code_section_index",
285+
}
286+
mapping_regex: ClassVar[Dict[ExceptionBase, str]] = {}

0 commit comments

Comments
 (0)