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

Filters that don't rely on server state #551

Closed
pipermerriam opened this issue Jan 12, 2018 · 9 comments
Closed

Filters that don't rely on server state #551

pipermerriam opened this issue Jan 12, 2018 · 9 comments
Assignees

Comments

@pipermerriam
Copy link
Member

pipermerriam commented Jan 12, 2018

What was wrong?

Filters suck.

  • Sometimes the server loses the filter_id
  • You don't really have any clarity to what is actually happening under the hood on the server.
  • You can't use filters with infura
  • They don't work if you're load balanced across multiple nodes.

How can it be fixed?

I propose we implement a new opt-in middleware to handle log filters locally.

Consider the following generator.

from cytoolz import (
    merge,
)


def log_filter(web3, filter_params):
    # TODO: handle pending/latest/earliest and cases where these are not included in the filter params.
    from_block = filter_params['fromBlock']
    to_block = filter_params['toBlock']

    # TODO: handle dynamic block ranges to enforce reasonable request sizes.
    # TODO: handle things like `latest` and query what the latest block number is from the last block number that we checked.
    for block_number in range(from_block, to_block + 1):
        params = merge(filter_params, {'fromBlock': block_number, 'toBlock': block_number})
        logs = web3.eth.getLogs(params)
        yield from logs

This log_filter generator can be used under the hood in place of using the node based filters to handle all filter logic locally, only relying on the node for eth_getLogs which is stateless.

The middleware would then resemble the following.

def filter_middleware(make_request, web3):
    filters = {}
    filter_id_counter = itertools.count()

    def middleware(method, params):
        if method == 'eth_newFilter':
            filter_id = next(filter_id_counter)
            filter = log_filter(web3, params[0])
            filters[filter_id] = filter
            return {'result': filter_id}
        elif method == 'eth_getFilterChanges' or method == 'eth_getFilterLogs':
            # do appropriate logic to return logs.
        else:
            return make_request(method, params)

The middleware creates and tracks these generator functions locally, retrieving them when requests are made to retrieve the logs for a given filter and using them to return the appropriate log entries.
Each time a new filter is created, the filter backend will create one of these generators for it and keep track of it internally. Then, any calls to eth_getFilterChanges can just return the next(...) for the appropriate filter generator. We should be able to do something similar with eth_getFilterLogs by re-generating the original filter generator and then returning all of the values up-to-the-current state of the generator.

@pipermerriam
Copy link
Member Author

I just realized this fits perfectly into the middleware framework. No need for a new API, just a new middleware.

@carver
Copy link
Collaborator

carver commented Feb 18, 2018

It's worth taking a look at the issues that the following project has with filters in v3: https://github.com/makerdao/pymaker/blob/7f2047b87d6982ce3cfadae07b8fad0026896202/pymaker/lifecycle.py

I think this proposal does a great job of addressing them all, but a thorough look before implementing this approach would be wise.

@lpassos
Copy link

lpassos commented Mar 7, 2018

Any idea when this could get in?

@pipermerriam
Copy link
Member Author

I suspect it will be part of the next major release cycle but there's no firm timeline for when that will be.

@dylanjw
Copy link
Contributor

dylanjw commented Mar 16, 2018

Im starting on this.

@jasonrhaas
Copy link
Contributor

jasonrhaas commented Sep 27, 2018

Looks like the pull request is about to be merged in. Excited to try this out in the next release! I implemented something using filters only to find out that Infura doesn't support them :(

@dylanjw
Copy link
Contributor

dylanjw commented Sep 27, 2018

#732 Was merged. @jasonrhaas Give it a try, and let me know what you think!

@dylanjw dylanjw closed this as completed Sep 27, 2018
@jasonrhaas
Copy link
Contributor

@dylanjw @carver @pipermerriam Any chance of getting this feature into the new web3py release? I am trying to use filters and have found them horribly unreliable and inconsistent. Hoping that this middleware is a good alternative.

@carver
Copy link
Collaborator

carver commented Oct 23, 2018

Sure, I should be able to get it out tomorrow morning. Note that it would be a v5 alpha release, so no guarantees about a stable API for a little while.

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

No branches or pull requests

5 participants