-
Notifications
You must be signed in to change notification settings - Fork 117
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
base: main
Are you sure you want to change the base?
Conversation
β¦valid_deposit_withdrawal_consolidation_requests
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.
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') ++ ...) |
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 actually slightly different to what the test is actually doing:
b'\0'.hex()
is equal to00
b'0x00'.hex()
is equal to30783030
, the ascii representation of string"0x00"
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.
You are right, I think I wanted to do sth like bytes([0x00])
but we can just leave it as it was
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.
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", | ||
# ), | ||
# ) |
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.
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 |
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 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, |
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 more of an union:
DepositRequest | WithdrawalRequest | ConsolidationRequest
ParameterSet
only applies if the pytest.param
is called.
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.
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
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.
Oh really? It might be then that there's another type that is actually being returned, because mypy is usually correct.
# 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 |
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.
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 π
) # 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 |
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.
We can add a mark to this test to avoid this and give the transition tool more time to respond:
marks=pytest.mark.slow(),
β¦ added mypy silencer for type warning
β¦lows 18 withdrawals per block
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 mainly reviewed test_modified_withdrawal_contract.py here, but some thoughts:
- I would use
--traces
when runninguv 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 @@ | |||
""" |
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.
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) |
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 think you can simplify this a bit by using the convention in here:
execution-spec-tests/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py
Lines 48 to 56 in 8713172
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:
execution-spec-tests/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py
Lines 76 to 82 in 8713172
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( |
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.
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( |
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.
Taking the previous comments into account, I think the modifications should be:
- Create test parameter
requests_list
, that you can parametrize with values from this class:class WithdrawalRequest(WithdrawalRequestBase): - The
requests_list
is passed to eitherWithdrawalRequestTransaction
orWithdrawalRequestContract
, and then from the instance created you can callupdate_pre
to automatically prepare the senders of the withdrawals, and thentransactions
to obtain the transactions to put in the block to trigger the withdrawals. - Finally
requests_list
is used in this line too to obtain the expected requests hash:
# 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.
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.
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.
4f602a2
to
b61fe3e
Compare
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.
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.""" |
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.
Thanks! π
) | ||
from ethereum_test_types import Requests | ||
|
||
from ..eip7002_el_triggerable_withdrawals.helpers import ( |
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.
from ..eip7002_el_triggerable_withdrawals.helpers import ( | |
from .helpers import ( |
# WithdrawalRequestContract, | ||
WithdrawalRequestTransaction, | ||
) | ||
from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002 |
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.
from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002 | |
from .spec import Spec as Spec_EIP7002 |
# @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), | ||
# ), | ||
# ], | ||
# ) |
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.
# @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.
# 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]), |
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.
# 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.
# 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), | ||
), | ||
] | ||
|
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.
# 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.
ποΈ Description
WIP
π Related Issues
β Checklist
mkdocs serve
locally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.