Skip to content
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

new(tests): add more test cases for request types #1340

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

felix314159
Copy link
Collaborator

πŸ—’οΈ Description

WIP

πŸ”— Related Issues

βœ… Checklist

  • All: Set appropriate labels for the changes.
  • All: Considered squashing commits to improve commit history.
  • All: Added an entry to CHANGELOG.md.
  • All: Considered updating the online docs in the ./docs/ directory.
  • Tests: All converted JSON/YML tests from ethereum/tests have been added to converted-ethereum-tests.txt.
  • Tests: A PR with removal of converted JSON/YML tests from ethereum/tests have been opened.
  • Tests: Included the type and version of evm t8n tool used to locally execute test cases: e.g., ref with commit hash or geth 1.13.1-stable-3f40e65.
  • Tests: Ran mkdocs serve locally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.

…valid_deposit_withdrawal_consolidation_requests
Copy link
Member

@marioevz marioevz left a comment

Choose a reason for hiding this comment

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

Some comments on this preliminary review πŸ™Œ

Let me know if any of them needs more info.

pytest.param(
[],
[
bytes([i]) for i in range(fork.max_request_type() + 1)
], # Using empty requests, calculate the hash using an invalid calculation method:
# sha256(sha256(b"\0") ++ sha256(b"\1") ++ sha256(b"\2") ++ ...)
# sha256(sha256(b'0x00') ++ sha256(b'0x01') ++ sha256(b'0x02') ++ ...)
Copy link
Member

Choose a reason for hiding this comment

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

This is actually slightly different to what the test is actually doing:

  • b'\0'.hex() is equal to 00
  • b'0x00'.hex() is equal to 30783030, the ascii representation of string "0x00"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are right, I think I wanted to do sth like bytes([0x00]) but we can just leave it as it was

Copy link
Member

Choose a reason for hiding this comment

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

Could also do b"\x00" because it's more common and b"\0" is a bit obscure.

# BlockException.INVALID_REQUESTS,
# id="more_than_16_withdrawals_per_block",
# ),
# )
Copy link
Member

Choose a reason for hiding this comment

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

Copy-pasting from discord:
The limit is included in the system contract, not anywhere else, I don't think it's even included in the consensus layer clients.

So since it's not enforced by the execution clients other than by smart contract logic, it's theoretically possible to modify the system contract in a testnet to manually overload the limit, and see what happens.

Given the issue we had already with Sepolia, where the smart contract was different in the testnet, I think it would be interesting to inject a modified contract to force 17 withdrawals and at least verify that the ELs behave the same.

Either:

  • all EL clients mark the block as invalid
  • all EL clients successfully process the block

I think the latter is the one that's going to happen.

See this PR to see how to inject a different contract in the place of the normal system contract: #1280

@@ -5,10 +5,11 @@
""" # noqa: E501

from itertools import permutations
from typing import Any, Dict, Generator, List, Tuple
from typing import Dict, Generator, List, Tuple
Copy link
Member

Choose a reason for hiding this comment

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

I think we should take the opportunity to rename this file to something more generic like test_multi_type_requests.py, so it can be updated in the future if a new request type is added.

def get_permutations(
n: int = 3,
) -> Generator[
ParameterSet,
Copy link
Member

Choose a reason for hiding this comment

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

This is more of an union:

DepositRequest | WithdrawalRequest | ConsolidationRequest

ParameterSet only applies if the pytest.param is called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, I tried that actually but then was shown a mypy type mismatch warning. I have silenced this warning now with # type: ignore and the filling still works without issues when using DepositRequest | WithdrawalRequest | ConsolidationRequest instead of ParameterSet

Copy link
Member

Choose a reason for hiding this comment

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

Oh really? It might be then that there's another type that is actually being returned, because mypy is usually correct.

Comment on lines 310 to 312
# single_consolidation_from_contract(2), # i=2 is not allowed cuz
# only 2 MAX CONSOLIDATIONS PER BLOCK (EIP-7251), but why does the error message
# not inform about this? only reports hash mismatch
Copy link
Member

Choose a reason for hiding this comment

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

That is because the limit not actually enforced by restricting the number of calls that can be made to the consolidation contract, rather, the consolidations executed after the limit is reached will queue up and only will be dequeued on the next block.

In general, the functions in this file that generate calls to the system contracts cannot know if their requests are going to be included in the block or not.

That's the reason behind the requests hash mismatch: the tx that generates the call is added to the block, it's executed, we add it to the list of expected requests (by our tests) in the header, but it will not match because of the queuing mechanism in the contract.

Hope I explained myself ok, but if not let me know πŸ˜„

Comment on lines 338 to 341
) # when using larger numbers: it fails around 1000 due to:
# UnixHTTPConnectionPool(host='localhost', port=None): Read timed out.
# (read timeout=20), workaround: in transition_tool.py use
# SLOW_REQUEST_TIMEOUT (60 sec), so slow=True
Copy link
Member

Choose a reason for hiding this comment

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

We can add a mark to this test to avoid this and give the transition tool more time to respond:

marks=pytest.mark.slow(),

Copy link
Member

@marioevz marioevz left a comment

Choose a reason for hiding this comment

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

I mainly reviewed test_modified_withdrawal_contract.py here, but some thoughts:

  • I would use --traces when running uv run fill to make sure that the transactions are indeed correctly calling the system contract and are not failing or running out of gas.
  • My main concern with the modified contract is that it will be a bit difficult to debug if the system call is not working correctly because currently the t8n does not provide traces for the system call (although that would be a nice feature!).

If you have the modified sys-asm contract to take a look at we could also help out reviewing that πŸ‘

@@ -0,0 +1,97 @@
"""
Copy link
Member

Choose a reason for hiding this comment

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

Just a nitpick but I think this fie should be in tests/prague/eip7002_el_triggerable_withdrawals.

[
# create 18 withdrawal contract requests (usually 16 is max, but modified_code dequeues 18)
[
single_withdrawal_from_contract(i)
Copy link
Member

Choose a reason for hiding this comment

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

I think you can simplify this a bit by using the convention in here:

WithdrawalRequestTransaction(
requests=[
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec.get_fee(0),
)
],
),

WithdrawalRequestTransaction and WithdrawalRequestContract have methods that allow you to automatically generate transactions and call to contracts to generate the withdrawal requests.

They both have these methods:

def transactions(self) -> List[Transaction]:
"""Return a transaction for the withdrawal request."""
raise NotImplementedError
def update_pre(self, pre: Alloc):
"""Return the pre-state of the account."""
raise NotImplementedError

and basically you call them with the test's pre and you should obtain the transactions that need to be put in the block to trigger the withdrawals.

)

sender: EOA = pre.fund_eoa()
tx: Transaction = Transaction(
Copy link
Member

Choose a reason for hiding this comment

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

See my previous comment, transactions to trigger the withdrawals can be obtained using the transactions method mentioned in there.

Block(
txs=[tx],
# header_verify=Header(
# requests_hash=Requests(
Copy link
Member

Choose a reason for hiding this comment

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

Taking the previous comments into account, I think the modifications should be:

Suggested change
# requests_hash=Requests(
requests_hash=Requests(requests_list),

Ideally, given the scenario here, requests_list should contain the 18 elements you want to introduce for this test case.

Copy link
Collaborator Author

@felix314159 felix314159 Mar 25, 2025

Choose a reason for hiding this comment

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

In my latest commit I tried to do these things however there are three issues:

  • The first issue is that the parameterized version (which I hoped would do the same as the code actually being used in my commit) does not work. If you uncomment it out and fill with uv run fill -k "modified_withdrawal_contract" tests/prague/ -m blockchain_test --fork=Prague -vv you can see what I mean
  • The second issue is that with my current code I can not pass Requests(requests_list)
  • The third issue is that I can not add --traces to the fill command because I get odd errors like
with open(src, 'rb') as fsrc:
E   FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpuxnl0ouu/trace-0-0x14cfc1c6268f1e8f70866357f10ad719591f03bdc1b62cfb03330195a70d5b54.jsonl'

The code of my latest commit fills without errors [on my machine] (and is consumed without errors) but does not do what I want.

@felix314159 felix314159 force-pushed the eip-7685-testcases-added branch from 4f602a2 to b61fe3e Compare March 25, 2025 16:37
Copy link
Member

@marioevz marioevz left a comment

Choose a reason for hiding this comment

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

Hey, I addressed your questions in each comment, it works for me locally although I didn't check for 18 withdrawals only two.

@@ -176,7 +176,7 @@ def contract_code(self) -> Bytecode:
return code + self.extra_code

def transactions(self) -> List[Transaction]:
"""Return a transaction for the deposit request."""
"""Return a transaction for the withdrawal request."""
Copy link
Member

Choose a reason for hiding this comment

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

Thanks! πŸ™

)
from ethereum_test_types import Requests

from ..eip7002_el_triggerable_withdrawals.helpers import (
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
from ..eip7002_el_triggerable_withdrawals.helpers import (
from .helpers import (

# WithdrawalRequestContract,
WithdrawalRequestTransaction,
)
from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002
from .spec import Spec as Spec_EIP7002

Comment on lines +31 to +45
# @pytest.mark.parametrize(
# "requests_list",
# [
# WithdrawalRequest(
# validator_pubkey=0x01,
# amount=0,
# fee=Spec_EIP7002.get_fee(0),
# ),
# WithdrawalRequest(
# validator_pubkey=0x01,
# amount=0,
# fee=Spec_EIP7002.get_fee(0),
# ),
# ],
# )
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# @pytest.mark.parametrize(
# "requests_list",
# [
# WithdrawalRequest(
# validator_pubkey=0x01,
# amount=0,
# fee=Spec_EIP7002.get_fee(0),
# ),
# WithdrawalRequest(
# validator_pubkey=0x01,
# amount=0,
# fee=Spec_EIP7002.get_fee(0),
# ),
# ],
# )
@pytest.mark.parametrize(
"requests_list",
[
pytest.param(
[
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
],
id="two_requests", # 18 requests should be very similar but with more requests, you could use range
)
],
)

Regarding comment line 59, reason is that pytest.mark.parametrize is running once with each different request, but what we want is for it to run once with a list of 18 requests, so we should pass a list of lists:

  • the first list being the list of parametrized cases
  • the second list being the list of requests in a single test

I added pytest.param because it makes this a bit clearer, but please note that this case only has 2 requests, so there's modifications left to be done to this code still.

Comment on lines +85 to +86
# TODO: why do i have to put the first element here to avoid an error? there is more than 1 request per block # noqa: E501
requests_hash=Requests(requests_list[0]),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# TODO: why do i have to put the first element here to avoid an error? there is more than 1 request per block # noqa: E501
requests_hash=Requests(requests_list[0]),
requests_hash=Requests(*requests_list),

Sorry about that, my original suggestion was incorrect as Requests expects one positional parameter per request, so we expand the list here by using the* operator.

Comment on lines +59 to +72
# TODO: when i replace the following list with the parameterized version nothing works, why?
requests_list: List[WithdrawalRequest] = [
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
]

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# TODO: when i replace the following list with the parameterized version nothing works, why?
requests_list: List[WithdrawalRequest] = [
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
WithdrawalRequest(
validator_pubkey=0x01,
amount=0,
fee=Spec_EIP7002.get_fee(0),
),
]

Can be removed after previous comment is applied.

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.

2 participants