-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Avoid skipping of events #2274
Avoid skipping of events #2274
Conversation
related to: #2167 |
Ethers.js misses logs, cuz GETH nodes return "[]";
We store hashes per block with events, and active polling intervals, to prevent raising duplicated event (if hash and block are same). |
Thanks for your message @timaiv That is fair solution for those cases one manually polls logs 🙂 In my case, I would like to be able to rely on const contract = new ethers.Contract("0x8fa59693458289914db0097f5f366d771b7a7c3f", [
"event Sync(uint112 reserve0, uint112 reserve1)"
], provider);
contract.on("Sync", (reserve0, reserve1) => {
// ...
}); |
Our solutions allows this and we use this actually. export class BaseProviderEx extends ethers.providers.BaseProvider
{
..
public static pollingFuncNamesToOverride(): string[]
{
return [
BaseProviderEx.prototype.poll.name,
BaseProviderEx.prototype._logAsync.name,
BaseProviderEx.prototype._traceProblem.name,
BaseProviderEx.prototype._onLogRangeProcessed.name,
BaseProviderEx.prototype._onLogRangeRequested.name,
BaseProviderEx.prototype._isBlockExistsInAnyActiveRange.name
];
}
} And helper that changes prototype functions in BaseProvider export class EthersLibFixer
{
/**
* Fixes missed logs due to chain reorganization or empty list from GETH based nodes.
* @param chainPastOffsets_ Shifts in past to still take logs, should be equal to confirmation in logic per chain.
*/
public static fixPolling(chainPastOffsets_: Map<number, number>)
{
EthersFixerSettings.defaultChainLogPollingPastOffset = chainPastOffsets_;
let baseProviderPrototype: any = ethers.providers.BaseProvider.prototype;
let baseProviderExPrototype: any = BaseProviderEx.prototype;
let functions = BaseProviderEx.pollingFuncNamesToOverride();
for (let functionName of functions)
{
baseProviderPrototype[functionName] = baseProviderExPrototype[functionName];
}
}
} Usage EthersLibFixer.fixPolling(confirmationsForPolling) |
Thank you for sharing @timaiv I don't doubt that your solution works. It just seems odd to me that such a solution is needed in the first place. If I could choose between patching Have you tried to get your solution integrated into |
Or is |
Sorry, just saw this issue now for some reason. Are you using a FallbackProvider? The FallbackProvider uses If there is something special for BSC, we can add the custom logic to the In v6, there is a larger variety of Subscriber models, including one which uses getChanges. Does the BSC load balancer honour the getChanges for a given filter ID? The INFURA backends have the same issue with their load balancer. |
I spent about two weeks in total in investigating on problems with events.
Desynchronization Blockchain reorganization |
Thanks for the replies @ricmoo and @timaiv
No, I have not been using
I don't think this specific to BSC. It is just more obvious in the case of BSC, because of the rather short block length.
I am assuming that you are referring to something like this #1768 (comment). If I remember correctly, I did not manage to get that working, but it might be worthwhile to give it another try. |
@claasahl I have a similar issue as you. |
@claasahl I tried with your updates and seems like it's being triggered 2 times for one event. |
@atropos0902 thanks for your reply (and pings in #2652 and #2167). The quickest solution is probably to implement your own polling strategy (i.e. something that repeatedly polls the latest events with Problemethers.js/packages/providers/src.ts/base-provider.ts Lines 909 to 929 in 7b134bd
The above code snippet also uses While this issue is more noticable on blockchains with short block lengths (e.g. BSC with its 3 second block length), it is not limited to them. The greatest potential for this issue to occur is whenever a new block was just mined. The shorter the time span since the mining of a new block, the higher the risk of this issue occurring (i.e. the risk that a node is lagging behind is greatest at this point). The more time passes, the greater the chance that all / enough nodes in the blockchain will have caught with the newly mined block and thus reducing the frequency of this issue. Example
We are subscribing to "some event"-events like so: const contract = new ethers.Contract(...);
contract.on('some event', () => {
// handle event
});
If the request goes to node A, the response will include all 6 "some event"-events (i.e. the events from block 995 and 1000). If the request goes to node B, the response will only include 1 "some event"-event (i.e. the event from block 995). Solution
|
Ok... It was a lot of experimentation and research, but I think this has been fixed in v5.6. Please give it a try and let me know if you still have any problems. :) |
This was fixed, so I'll close it. Please re-open or create a new issue if there are any problems. Thanks! :) |
Background
One my projects subscribes to
Sync
events from Uniswap-like pairs on Binance Smart Chain (BSC). Over time I realised that some of these events were skipped for no obvious reason (e.g. no network issues, no memory issues, etc.).This example demonstrates this behavior. It requests
Sync
events for every new block twice usingprovider.getLogs(...)
. In an ideal world, both requests yield the same result. In practice, this is not always the case and the results might show a different number of events (e.g. one request has 5 events and the other has zero events).The simplest explanation that I could come up with, is that the official JSON RPC endpoint for BSC (https://bsc-dataseed.binance.org/) is a load balancer, which distributes requests to various nodes. Not all of these nodes necessarily have the exact same state. Since BSC's average block length is about 3 seconds (https://bscscan.com/chart/blocktime), it can happen that some nodes lag slightly (i.e. they might not yet have caught up with the most recent block).
Every now and then, one of the requests in my example would go to an "up-to-date" node while the other request would go to an "out-of-date" node. Thus creating a situation where the requests return different results. I expect this effect to be most pronounced on smart contracts which frequently emit events (i.e. one or more events per block) and less pronounced on smart contracts which emit few events. The above example uses a smart contract which emits events every few seconds (https://bscscan.com/address/0x8fa59693458289914db0097f5f366d771b7a7c3f#events).
The fact that some nodes can be ever so slightly "out-of-date" is not taken into account in the following snippet. Instead, it is assumed that all requested events are returned regardless of the fact whether the requested data is available (in the node) or not. For slightly "out-of-date" nodes, this is a bit like requesting data for a future block.
https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/base-provider.ts#L871-L887
Solution
My proposed change is to inspect the returned data, keeping track of the latest returned block number rather than assuming that all events up to
toBlock
were included (i.e.returned block number <= toBlock
). For those cases where no new events become available for a longer period of time, a maximum range of 12 blocks is proposed (i.e. just like when removing obsolete events).https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/base-provider.ts#L841-L845
I suppose, it can be argued that this is a problem with BSC. My take is that this is not unique to BSC and an avoidable source of very subtle bugs.