Contract methods that send transactions are approx 5x slower than they could be. The reason seems to be poll function used in sendTransaction method of JsonRpcProvider, with a hardcoded interval.
This is a crucial factor of the speed of test execution, written in ethers.js.
JsonRpcProviderI prepared a simple test comparing the speed of test execution of truffle vs ethers, here is a snippet:
...
it("truffle", async () => {
...
console.time(" truffle send");
await metaCoinInstance.sendCoin(
safeMathInstance.address,
accountTwo,
amount,
{ from: accountOne }
);
console.timeEnd(" truffle send");
console.time(" truffle call");
await metaCoinInstance.balances(accountTwo);
console.timeEnd(" truffle call");
});
it('ethers', async () => {
...
console.time(" ethers send");
await coinContract.sendCoin(mathContract.address, accountTwo, amount);
console.timeEnd(" ethers send");
console.time(" ethers call");
(await coinContract.balances(accountTwo));
console.timeEnd(" ethers call");
});
Common results are sth like this:
Contract: MetaCoin with truffle
truffle send: 50.286ms
truffle call: 40.342ms
✓ should send coin correctly (104ms)
MetaCoin with ethers.js
ethers send: 138.884ms
ethers call: 13.162ms
✓ ethers (406ms)
Results vary a lot from run to run, but the general pattern is:
My guess is the reason poll methods used in JsonRpcProvider here.
It seems to use options.onceBlock, that does not work well for subsecond block intervals here.
Add option for faster polling for tests.
This is only an issue, like you said, for testing, or on PoA networks in general.
There are a few solutions; you can use the sendUncheckedTransaction method (see this issue for details https://github.com/ethers-io/ethers.js/issues/340#issuecomment-447512944 on how to extend a Signer to use it)
Or you can decrease the polling interval: provider.pollingInterval = 500. Keep in mind not to do that to Etherscan or INFURA though, or you will get soft-banned pretty quick. :)
hi @ricmoo,
Thanks for a lightning answer ❤️❤️❤️.
provider.pollingInterval would definitely do the thing, as we could change it for tests (and for tests only).
The only thing is, it does not seem to be working, here is updated code.
That produces the following output:
Contract: MetaCoin with truffle
truffle send: 20.959ms
truffle call: 17.263ms
✓ should send coin correctly (55ms)
MetaCoin with ethers.js
ethers send: 96.146ms
ethers call: 10.781ms
✓ ethers (245ms)
If I understand the code of ethers.js correctly pollingInterval is not used by JsonRpcProvier. It uses options.onceBlock instead.
Let me know if I am doing anything wrong.
sendUncheckedTransaction would be nasty on the other hand, as we would like to use high-level contract abstraction, which in IMHO would be impossible in this case (hard?).
Hmmm. I have noticed that onceBlock can be slow on certain nodes, because while eth_sendTransacrion returns a hash immediately, eth_getTransactionByHash does not return the transaction immediately when then queried. I have be planning to add a wuickCheck option that will re-poll the first time quickly (e.g. 100ms) after the first failure. Let me experiment, and possibly send you some code to hack in to see if that resolves the slowness. :)
@ricmoo Did you have a chance/will have a chance to look at it any time soon?
Oh, sorry. I didn't have a chance today, it took a lot longer than I expected to get #405 done... I plan to dig into the Provider issues early next week.
Lightning answer as always ❤️
Looking forward 👍
Going to add my vote on this issue :)!
I've moved it to "on deck". I have some "pays the rent" work to get done first, but this is on the "short term" stack of things to do for ethers. :)
We have the same issue after upgrading from 4.0.20 to 4.0.25. Tests take approximately 4 times longer.
@elenadimitrova I think your issue may be the way Ganache mints blocks (or any other PoA Network). Does it only happen when you are calling waitForTransaction (or similar call)?
I’ll be addressing this issue next, by having a second fast-check for poll when a onceBlock is passed in.
One other idea to experiment with, after each call to waitForTransaction, can you call provider._doPoll()?
@elenadimitrova - I opened https://github.com/ethers-io/ethers.js/issues/424 to track this
I've put a new version up (not published to NPM, but available on the GitHub master branch) which should hopefully fix this. Can someone try it out and let me know if it helps?
@ricmoo Going to test next week, but if you ever want some benchmark tests, we have about 600 unit tests using Ethers and ganache here: https://github.com/horizon-games/multi-token-standard
Just need to git clone, yarn install && yarn build && yarn ganache and in another terminal yarn test
Takes about 40-50 minutes on my machine right now to run all the tests.
I've already posted an answer in #424 that the fix works. Thanks again @ricmoo
This is now published in 4.0.26. Let me know if there are any problems!
Thanks! :)
Most helpful comment
I've moved it to "on deck". I have some "pays the rent" work to get done first, but this is on the "short term" stack of things to do for ethers. :)