Add gas bench fixture capture tooling - #23
Conversation
77136c4 to
6a3c943
Compare
5a92a02 to
2eaf9fa
Compare
| one example tx hash each, and a name from Etherscan's `functionName`.""" | ||
| address = address.lower() | ||
| response = requests.get( | ||
| "https://api.etherscan.io/v2/api", |
There was a problem hiding this comment.
does sourcify have transactions? :)
There was a problem hiding this comment.
No. That’s why we need etherescan or something similar. Archive node also is not enough. We can potentially scan shitload of block for these transactions but it’s just wasting of resources.-
There was a problem hiding this comment.
Ah, right :/
Still, do we need Etherscan at all? We could just take an RPC endpoint instead, which the tool already requires for tracing anyway. No?
There was a problem hiding this comment.
But we need to scan a lot of blocks to do it and parsing them and check the transaction. All I did for generating the fixtures many times I did with free etherscan API plan.
There was a problem hiding this comment.
we could have used our archive node. but anyways. it's fine. something for a follow-up.
There was a problem hiding this comment.
We need also to remember the we would have to somehow undecode selectors to functions names.
|
Also, can you add to the README how to use it? |
clonker
left a comment
There was a problem hiding this comment.
it seems some of the fixtures have transactions that revert (requiredStatus is 0x0). perhaps we should bias towards the happy path. not that reverts aren't important to sample, too, but my feeling is that a reverting transaction is usually not doing much at all.
aave-v3-pool and aave-v4-hub-spoke are not listed in benchmarks.toml. intentional?
also discovery_limit is set to 500 in the targets.toml but the cli defaults to 1000, doesn't it?
beyond all that: i know we haven't been doing it yet but i'd really like to have some testing here.
| ["oeth"] | ||
| source = "https://etherscan.io/address/0xd86756dbb01e75a11aadacb75c8495759ed92033" | ||
| version = "0xd86756dbb01e75a11aadacb75c8495759ed92033" | ||
| discovery_address = "0x856c4efb76c1d1ae02e20ceb03a2a6a08b0b8dc3" |
There was a problem hiding this comment.
the discovery addresses are redundant with the data in the target.tomls, right? should we perhaps just have one of the two?
There was a problem hiding this comment.
no exactly. because we wanna save in the results for the address which was used to discover this transaction. For next capturing we want to use for example different one and leave the old one results too.
There was a problem hiding this comment.
so why do we record anything in benchmarks.toml?
There was a problem hiding this comment.
Just to make it easier to find gas benchmarks for the gas run. Other way we would have to search them in the gas folder. Which means load the targets.toml. It can be also just mapping folder name too. I can change it this way and remove this change in bechmarks.toml
There was a problem hiding this comment.
But wait this is other entry. discovery_address is needed because when we have empty fixtures we need to know how to discover the txs for the contracts.
There was a problem hiding this comment.
Maybe to make it more readable I can move the data in the targets.toml which are related to how the fixture was generated to some kind of a metadata section. WDYT?
There was a problem hiding this comment.
I'd be for removing this field from benchmarks.toml and instead list it in targets.toml like
[[target]]
address = "0xe1e61cc36ebdbe30d51d04f38d7930663dcfe940"
standard_json = "aave-v4-hub.json"
contract_name = "HubInstance"
[target.discovery]
address = "0x973a023a77420ba610f06b3858ad991df6d85a08"
end_block = 25996062
limit = 500There was a problem hiding this comment.
If you remove it from bechmarks.toml it’s impossible to generate gas fixtures based on it and that’s the goal. gas directory content is fully generated using discovery address (+ end_block and limit taken from the console input). Finding these discovery addresses is not super easy and straightforward process. Sometimes it’s difficult to find proper, being used, proxy address which makes delegate calls to address. The process of adding a new fixtures starts from finding the address, dicovery_address a fetching to-be-bechmarked contract sources.
There was a problem hiding this comment.
As a next step I wanted to add automatic script which captures all the fixtures for the all contract deployed tagged. Maybe we can add this discovery_address in bechmarks.toml there. I agree it’s a little in the air now.
There was a problem hiding this comment.
I have this script and used it to regenerate the fixtures. I can add it to this PR too. Just need an hour to clean it up. I believe it’s not super important but if you want it just let me know.
| """The bare name from Etherscan's `functionName` field (e.g. | ||
| "supply(address,...)" -> "supply"), or None if it's not decoded.""" | ||
| name = function_name.split("(", 1)[0].strip() | ||
| return name.lower() if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name) else None |
There was a problem hiding this comment.
why the regex here? shouldnt be the split enough?
There was a problem hiding this comment.
Yeah. Initially I was thinking that it can be anything in case of undecoded function name, but it appeared that the undecoded function names are just empty string.
| path.write_text(tomlkit.dumps(doc)) | ||
|
|
||
|
|
||
| def _fixture_touches_address(fixture_path: Path, address: str) -> bool: |
There was a problem hiding this comment.
so this means that a transaction goes via some address? is that right? i'm not sure i understand the method by its name and/or docstring
There was a problem hiding this comment.
Yes. So if we capture a transaction by the proxy for example we can end up in the situation where the tx does not touch the address we wanna bechmark code of. In this case the fixture is dropped.
There was a problem hiding this comment.
so can we maybe rename the method to _prestate_has_account or so and simplify the docstring?
There was a problem hiding this comment.
Yeah. It’s better. The name was taken from the EL implementation.
| one example tx hash each, and a name from Etherscan's `functionName`.""" | ||
| address = address.lower() | ||
| response = requests.get( | ||
| "https://api.etherscan.io/v2/api", |
There was a problem hiding this comment.
does sourcify have transactions? :)
| try: | ||
| fixture_replay(output_path, evmone_statetest_bin) | ||
| except ReplayMismatch as e: | ||
| print(f"WARNING: {e}", file=sys.stderr) |
There was a problem hiding this comment.
Yeah. I was thinking of it and finally left it. But in case of building the fixture we should raise. But on the other hand if not raise we could continue and re-run on failures after a fix.
| def _keccak256(data: bytes) -> str: | ||
| result = subprocess.run( | ||
| ["cast", "keccak", "0x" + data.hex()], | ||
| capture_output=True, | ||
| text=True, | ||
| check=True, | ||
| ) | ||
| return result.stdout.strip() |
There was a problem hiding this comment.
this could be done with pure python right?
There was a problem hiding this comment.
Maybe. I’m no a python expert. :) Will check and fix.
There was a problem hiding this comment.
Maybe we can use https://github.com/Legrandin/pycryptodome or https://github.com/ethereum/eth-hash
There was a problem hiding this comment.
i'd prefer a python implementation over calling cast here via a subprocess
There was a problem hiding this comment.
In the following PR
There was a problem hiding this comment.
then it should go into the following pr
I can but this is not the command which is going to be used often. The more important command is added by the following pr. |
| build_fixture_for_tx(examples[selector], rpc_url, evmone_statetest_bin, fixture_path, test_name=name) | ||
| except Exception as e: | ||
| print(f"{examples[selector]}: failed to build, skipping ({e})", file=sys.stderr) | ||
| if force and fixture_path.exists(): |
There was a problem hiding this comment.
This will only delete a possible broken fixture if force is given. Maybe we should always delete the generated fixture if the build process fails.
There was a problem hiding this comment.
Yes. It’s leftover from the debugging.
|
Let's add a nice short description to the README.md about this? Let's focus on the "short" :D |
22ac186 to
9404129
Compare
9404129 to
73c8d06
Compare
There was a problem hiding this comment.
What's the actual change here?
There was a problem hiding this comment.
It introduces a way to capture most popular txs from the chain to a contract address.
Based on this data it generates fixtures in the form EEST which can be run by any EL client (including evmone) and measure the gas used.
Using this tooling I generated the fixtures for all the contacts in benchmarks_data which have deployed tag.
It’s a first step to have gas benchmarks which are implemented in following PR. The following PR implements a bytecode swapping in these fixtures and run them on evmone and measure gas usage. The fixtures in the EEST assure that the swapped bytecode does exactly the same what the original does.
There was a problem hiding this comment.
I meant the change in this specific file that I referenced. makerdao-dss-vat.json
There was a problem hiding this comment.
So the discovery_address is needed to discover transaction to a contract implementation which is behind a proxy for example. The interesting implementation contract does not have any tx to itself because they all go through the proxy. So we need go capture txs to the proxy and replay them. On the other hand In the following PR we want to swap the implementation contract bytecode not the proxy. that’s why we need to have the second address in the targets.toml to know which bytecode we have to swap.
There was a problem hiding this comment.
And regarding the file you are referring. :)
So this contract was properly built but failed on first tx, because the newer solidity makes the overflow check by default. I need to wrap some code fragment into unchecked block to make it running properly.
There was a problem hiding this comment.
But this probably means that it should go to the following PR because it’s a problem in it not here yet.
73c8d06 to
753e01f
Compare
363df9c to
42842a3
Compare
| run: | | ||
| nix shell . --inputs-from . nixpkgs#python3Packages.pytest \ | ||
| --command pytest -v tests/test_smoke.py | ||
| --command pytest -v tests/test_smoke.py tests/test_verify_fixtures.py |
There was a problem hiding this comment.
why doesn't this run tests/test_capture_contract_smoke.py? i think it would be better to just run the whole directory.
this should suffice: run: nix develop --command pytest -v -rs
There was a problem hiding this comment.
Forgot to add this
| reuse the clone. Bumping `version` errors out — delete the stale clone and | ||
| re-run. | ||
|
|
||
| ### Gas-bench fixtures (real mainnet transactions) |
There was a problem hiding this comment.
i think this should've gone into the 2nd pr but i really think we should move this forward so let's just keep it here
Add tooling for capturing gas-bench fixtures from mainnet transactions
…d entries Add aave-v4-hub-spoke benchmark and correct deployed addresses
Arguments passed to the capture-contract `--end-block 25996062 --limit 500 --max-selectors 7`
42842a3 to
cd6d730
Compare
dismissing the CR, I think it all has been addressed and otherwise we can fix stuff up later
It introduces a way to capture most popular txs from the chain to a contract address.
Based on this data it generates fixtures in the form EEST which can be run by any EL client (including evmone) and measure the gas used.
Using this tooling I generated the fixtures for all the contacts in benchmarks_data which have deployed tag.
It’s a first step to have gas benchmarks which are implemented in following PR. The following PR implements a bytecode swapping in these fixtures and run them on evmone and measure gas usage. The fixtures in the EEST assure that the swapped bytecode does exactly the same what the original does.
This PR adds: