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

Workaound LlamaNodes.com issues #152

Merged
merged 3 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.22.11

- Add `eth_defi.provider.llamanodes` and work around issues with LlamaNodes.com

# 0.22.10

- Move Ankr specific functionality to its own `eth_defi.provider.ankr` module
Expand Down
33 changes: 23 additions & 10 deletions eth_defi/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from eth_defi.event_reader.conversion import convert_jsonrpc_value_to_int
from eth_defi.middleware import http_retry_request_with_sleep_middleware
from eth_defi.provider.llamanodes import is_llama_bad_grapql_reply
from eth_defi.provider.named import NamedProvider

#: List of chain ids that need to have proof-of-authority middleweare installed
Expand Down Expand Up @@ -161,6 +162,26 @@ def middleware(method: RPCEndpoint, params: Any) -> Optional[RPCResponse]:
return api_counter


def get_graphql_url(provider: BaseProvider) -> str:
"""Resolve potential GraphQL endpoint API for a JSON-RPC provider.

See :py:func:`has_graphql_support`.
"""

# See BaseNamedProvider
if hasattr(provider, "call_endpoint_uri"):
base_url = provider.call_endpoint_uri
elif hasattr(provider, "endpoint_uri"):
# HTTPProvider
base_url = provider.endpoint_uri
else:
raise AssertionError(f"Do not know how to extract endpoint URI: {provider}")

graphql_url = urljoin(base_url, "graphql")

return graphql_url


def has_graphql_support(provider: BaseProvider) -> bool:
"""Check if a node has GoEthereum GraphQL API turned on.

Expand All @@ -179,19 +200,11 @@ def has_graphql_support(provider: BaseProvider) -> bool:
{"data":{"block":{"number":16328259}}}
"""

# See BaseNamedProvider
if hasattr(provider, "call_endpoint_uri"):
base_url = provider.call_endpoint_uri
elif hasattr(provider, "endpoint_uri"):
# HTTPProvider
base_url = provider.endpoint_uri
else:
raise AssertionError(f"Do not know how to extract endpoint URI: {provider}")
graphql_url = get_graphql_url(provider)

graphql_url = urljoin(base_url, "graphql")
try:
resp = requests.get(graphql_url)
return resp.status_code == 400
return resp.status_code == 400 and not is_llama_bad_grapql_reply(resp)
except Exception as e:
# ConnectionError, etc.
return False
Expand Down
5 changes: 3 additions & 2 deletions eth_defi/event_reader/reorganisation_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from tqdm import tqdm
from web3 import Web3, HTTPProvider

from eth_defi.chain import has_graphql_support
from eth_defi.chain import has_graphql_support, get_graphql_url
from eth_defi.event_reader.block_header import BlockHeader, Timestamp
from eth_defi.provider.fallback import FallbackProvider
from eth_defi.provider.mev_blocker import MEVBlockerProvider
Expand Down Expand Up @@ -737,7 +737,8 @@ def create_reorganisation_monitor(web3: Web3, check_depth=250) -> Reorganisation
if has_graphql_support(provider):
# 10x faster /graphql implementation,
# not provided by public nodes
reorg_mon = GraphQLReorganisationMonitor(graphql_url=urljoin(json_rpc_url, "/graphql"), check_depth=check_depth)
graphql_url = get_graphql_url(provider)
reorg_mon = GraphQLReorganisationMonitor(graphql_url=graphql_url, check_depth=check_depth)
else:
# Default slow implementation
logger.warning("The node does not support /graphql interface. " "Downloading block headers and timestamps will be extremely slow." "Check documentation how to configure your node or choose a smaller timeframe for the buffer of trades.")
Expand Down
7 changes: 6 additions & 1 deletion eth_defi/provider/ankr.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""Ankr specific Web3.py functionality.

- Ankr has issues with some JSON-RPC access patterns.
See also :py:mod:`eth_defi.provider.broken_provider`.

.. warning ::

We do not recommend using Ankr as it randomly returns empty responses to eth_call RPC method
and this is not fixable at the client side.

See also :py:mod:`eth_defi.provider.broken_provider`.
"""
from web3 import Web3

Expand Down
23 changes: 23 additions & 0 deletions eth_defi/provider/llamanodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Fixes for quirks and features on LlamaNodes.

- `LlamaNodes <https://llamanodes.com/>`__ runs RPC services at ``llamarpc.com``

- Their RPC nodes have some compatibility issues we address in this module

See also :py:mod:`eth_defi.provider.broken_provider`.
"""
from requests import Response


def is_llama_bad_grapql_reply(resp: Response):
"""Is the web server response fake 404 response from llamarpc.com

llamarpc.com web server does not know how to use HTTP 404 status code.

See :py:func:`eth_defi.chain.has_graphql_support`.
"""
try:
content = resp.json()
return content.get("error").get("message") == "UserKey was not a ULID or UUID"
except Exception:
return False
19 changes: 19 additions & 0 deletions tests/provider/test_llamarpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""LlamaNodes specific tests"""

import os

import pytest
from web3 import Web3, HTTPProvider

from eth_defi.chain import has_graphql_support

JSON_RPC_LLAMA = os.environ.get("JSON_RPC_LLAMA")
pytestmark = pytest.mark.skipif(not JSON_RPC_LLAMA, reason="This test needs LlamaNodes come node via JSON_RPC_LLAMA")


def test_llama_is_bad():
"""Work around fake 404 response from llamarpc.com"""
provider = HTTPProvider(JSON_RPC_LLAMA)
web3 = Web3(provider)
assert not has_graphql_support(provider)
assert web3.eth.block_number > 1