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

Fix initialization of Contract function and events #3540

Merged
merged 12 commits into from
Dec 18, 2024

Conversation

fselmo
Copy link
Collaborator

@fselmo fselmo commented Nov 27, 2024

Closes #3553

What was wrong?

  • When we init contracts, we currently have an overhead of initializing 3 ENS contracts per contract instance. If a user doesn't use the ENS contracts, this ends up being very unnecessary overhead. Instead of instantiating the ENS contracts at every contract init (w3.ens), set all initializing vars to w3._ens which is the reference that w3.ens looks for. This leaves the check for an empty w3._ens followed by subsequent instantiation for when w3.ens is called directly by the user, not for any internal setup.
  • We are needlessly re-creating contract functions both for the Contract init, as well as for the ContractCaller. The caller is instantiated in the Contract init when we set self.caller = ContractCaller(...). Pass along the already built ContractFunctions to the ContractCaller so that it doesn't have to re-build them.

How was it fixed?

  • Use reference to w3._ens rather than w3.ens (which initializes an ENS instance if it is empty).
  • Allow passing contract_functions: ContractFunctions to the ContractCaller class so that we can pass the functions around instead of rebuilding them everywhere they are needed.

Todo:

Cute Animal Picture

Screenshot 2024-12-09 at 2 49 24 PM

- ``web3.ens`` invokes the ``@property`` getter on ``ens``. This
  getter sets ``self._ens`` on the ``web3`` object if it is empty.
  We do not need to do invoke this getter until ``ens`` is actually
  called. Instead, use the pointer to ``self._ens`` directly to avoid
  overhead when the majority of uses may not even be using ENS.
- When initializing a contract, we create the ``ContractFunction`` objects
  for each function and assign them to the ``contract.functions`` attribute.
  Then we instantiate the ``contract.caller`` and inside the ``ContractCaller``
  init, we re-instantiate each contract function. Pass these around instead
  which will prevent so many calls for validation. They are already built,
  so there is no reason to build them again.
@fselmo fselmo force-pushed the investigate-contract-inefficiencies branch 2 times, most recently from d716d52 to 926a200 Compare November 27, 2024 02:38
@fselmo fselmo force-pushed the investigate-contract-inefficiencies branch from 926a200 to bf62ecf Compare November 27, 2024 02:40
fselmo and others added 4 commits November 28, 2024 15:51
- Instead of re-building the ``ContractFunction`` object, use the
  already existing ``self`` object reference.
…us errors.

Add fixture contract with ambiguous functions and arguments.
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from df01e5e to a8e5346 Compare December 9, 2024 22:26
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 9, 2024
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 11, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from ccff69b to 4a0975a Compare December 11, 2024 02:57
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 13, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 40cf65c to 170025d Compare December 13, 2024 04:41
@reedsa reedsa changed the title Investigate contract inefficiencies Fix initialization of Contract function and events Dec 13, 2024
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 13, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 170025d to 7588418 Compare December 13, 2024 04:48
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 13, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 7588418 to 67ae66c Compare December 13, 2024 04:59
… arguments.

Initialize ContractFunction and ContractEvent with `signature`.

Create ContractFunctions with properties for the function name and also private properties with each function signature
Create ContractEvents with properties for the event name and also private properties with each event signature
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 13, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 67ae66c to 16a5c2e Compare December 13, 2024 05:03
@reedsa reedsa marked this pull request as ready for review December 13, 2024 05:11
@reedsa reedsa requested review from pacrob and kclowes December 13, 2024 05:11
@reedsa
Copy link
Contributor

reedsa commented Dec 13, 2024

@fselmo couldnt add you as a reviewer of your own PR, but I've got all changes pushed up. I appreciate your feedback!

Copy link
Collaborator

@kclowes kclowes left a comment

Choose a reason for hiding this comment

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

I didn't do a terribly deep dive here, but this looks good on a high level! I left a couple nits, but the biggest things I'm wondering about are:

  • what does the benchmarking say here; does this make a meaningful impact? (I assume yes, but would be nice to know the actual numbers)
  • Are you pretty confident in the test coverage?

web3/utils/abi.py Outdated Show resolved Hide resolved
web3/contract/base_contract.py Outdated Show resolved Hide resolved
== 0
)
assert ambiguous_function_contract.functions.isValidSignature().call() == "valid"
assert (
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We chatted about this but I'll add it here so we can all think on it. I think in most cases this can be pretty harmless, to make a decision that you are calling the (bytes,bytes) version of the method because the first argument is not encodable as bytes32.

Let's say I thought I did some good math and I ended up padding the value I wanted to pass in here to where it was 33 bytes instead of 32. Then, the method I'm actually calling ends up being (bytes,bytes) with no exception or warning but I thought that it was (bytes32,bytes). I understand that had I actually called the (bytes32,bytes), it would've raised (as per the test below)... but since I didn't I never understood the whole flow here.

I can see this being used as an exploit at the worst case scenario, but also leading to undesired behavior at times on a level below that.

I would prefer a default API that gives me full control and confidence that I'm doing the right thing. If I got an exception here that asked me to explicitly get my function by signature before calling it, I think that would give me more confidence that I'm dealing with exactly the methods I want to deal with.

Some lingering questions for me are. How would we draw these lines? We don't have to be as strict as same # of args in function with same name... but maybe that's a good line to draw?

I also might be OK keeping it like this if we document this well enough. But not a whole lot of people read the docs thoroughly before using the library, so this wouldn't address the issue to a lot of users as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Talking more on this, you're right that we ideally wouldn't want to raise on isValidSignature(string) vs isValidSignature(bytes). We should be able to discern between the two in that case.

I think I'm OK with documenting this well enough and giving users the ability to call the function by the signature should empower them with the right approach for their workflow. I would want to be explicit, whereas another user might want the flexibility of knowing they are calling (bytes,bytes) simply because their first arg is not length 32.

@reedsa
Copy link
Contributor

reedsa commented Dec 16, 2024

After profiling the contract code, I found vast improvements from the latest changes here. I wrote a script that only initialized 10 contracts, then called cPython to profile the calls with python -m cProfile -s calls test-contracts.py > temp.txt

Here are my findings running my script:

Version Num Contract factory calls Total function calls in script
7.6.0 1130 calls in 0.336 seconds 3945174 function calls (3881644 primitive calls) in 1.263 seconds
This branch 180 calls in 0.003 seconds 2087387 function calls (2024101 primitive calls) in 1.102 seconds

@fselmo fselmo force-pushed the investigate-contract-inefficiencies branch from 9f18da0 to c82fdb5 Compare December 16, 2024 21:05
@fselmo
Copy link
Collaborator Author

fselmo commented Dec 16, 2024

I can't approve my own PR but this looks good to me once we address the outstanding comments 👍🏼

@reedsa
Copy link
Contributor

reedsa commented Dec 16, 2024

@kclowes we'll add benchmarking tests for contracts at a later time, but the coverage is good. Many of the test cases we already had that broke were fixed along the way. Everything has been addressed up to this point so I'm confident in this being released now! Let me know if there's anything else you'd like to check before I merge.

@reedsa reedsa requested a review from kclowes December 17, 2024 21:12
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from a44ace3 to d9b951f Compare December 18, 2024 16:13
fselmo and others added 2 commits December 18, 2024 09:14
- Pass the already built contract functions in the contract init when
  initializing the contract caller for ``AsyncContract``.
- Add reasonable init time test for ``Contract`` and ``AsyncContract`` so
  that we know when code deviates from the expected performance.
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 18, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from d9b951f to 2c978d1 Compare December 18, 2024 16:14
docs/web3.contract.rst Outdated Show resolved Hide resolved
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 18, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 2c978d1 to 89a0fc4 Compare December 18, 2024 16:18
reedsa added a commit to fselmo/web3.py that referenced this pull request Dec 18, 2024
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from 89a0fc4 to eb99edc Compare December 18, 2024 16:19
docs/web3.contract.rst Outdated Show resolved Hide resolved
Copy link
Contributor

@pacrob pacrob left a comment

Choose a reason for hiding this comment

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

One note on docs formatting intention, otherwise lgtm!

Include comments for overloaded functions with similar arguments.
@reedsa reedsa force-pushed the investigate-contract-inefficiencies branch from eb99edc to f20abf7 Compare December 18, 2024 18:37
@reedsa reedsa merged commit 34d258f into ethereum:main Dec 18, 2024
85 checks passed
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.

Performance issue with 7.6.0
4 participants