Skip to content

Commit

Permalink
fix(docs): Remove outdated network endpoints and add endpoint check a…
Browse files Browse the repository at this point in the history
…ction (#177)
  • Loading branch information
MalteHerrmann authored Nov 19, 2024
1 parent 3710b43 commit b924ce4
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 46 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/check-endpoints.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Check Endpoints

on:
pull_request:
paths:
- docs/develop/api/networks.mdx
- scripts/check_endpoints/**
schedule:
- cron: '0 0 * * 0' # This cron expression schedules the workflow to run every Sunday at midnight UTC
workflow_dispatch: # allows manual triggering


jobs:
check-endpoints:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.9'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r scripts/check_endpoints/requirements.txt
- name: Run endpoints check
run: python scripts/check_endpoints/main.py
51 changes: 5 additions & 46 deletions docs/develop/api/networks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,45 +23,18 @@ If you are searching for the protobuf interfaces, head over [here](https://buf.b
| --------------------------------------------- | ---------------------- | --------------------------------------- | -----------|
| `https://evmos.lava.build` | `Ethereum` `JSON-RPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `wss://evmos.lava.build/websocket` | `Ethereum` `WSS` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://rest.evmos.lava.build` | `Cosmos` `REST` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://tm.evmos.lava.build` | `Tendermint` `RPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `wss://tm.evmos.lava.build/websocket` | `Tendermint` `WSS` | [Lava Network](https://lavanet.xyz) | Pruned |
| `grpc.evmos.lava.build:443` | `Cosmos` `gRPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://grpc.evmos.lava.build` | `Cosmos` `Web-gRPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://evmos.rest.lava.build` | `Cosmos` `REST` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://evmos.tendermintrpc.lava.build` | `Tendermint` `RPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `wss://evmos.tendermintrpc.lava.build/websocket` | `Tendermint` `WSS` | [Lava Network](https://lavanet.xyz) | Pruned |
| `evmos.grpc.lava.build:443` | `Cosmos` `gRPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://evmos-json-rpc.stakely.io` | `Ethereum` `JSON-RPC` | [Stakely](https://stakely.io/) | Pruned |
| `https://evmos-rpc.stakely.io` | `Cosmos` `RPC` | [Stakely](https://stakely.io/) | Pruned |
| `https://evmos-rpc.stakely.io` | `Tendermint` `RPC` | [Stakely](https://stakely.io/) | Pruned |
| `https://evmos-lcd.stakely.io` | `Cosmos` `REST` | [Stakely](https://stakely.io/) | Pruned |
| `https://jsonrpc-evmos-ia.cosmosia.notional.ventures/` | `Ethereum` `JSON-RPC` | [Notional](https://notional.ventures/) | Pruned |
| `https://rpc-evmos-ia.cosmosia.notional.ventures:443` | `Tendermint` `RPC` | [Notional](https://notional.ventures/) | Pruned |
| `https://grpc-evmos-ia.cosmosia.notional.ventures:443` | `Tendermint` `gRPC` | [Notional](https://notional.ventures/) | Pruned |
| `https://api-evmos-ia.cosmosia.notional.ventures:443` | `Tendermint` `RPC` | [Notional](https://notional.ventures/) | Pruned |
| `https://rpc.evmos.nodestake.top` | `Tendermint` `RPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://grpc.evmos.nodestake.top` | `Cosmos` `gRPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://api.evmos.nodestake.top` | `Cosmos` `REST` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://jsonrpc.evmos.nodestake.top` | `Ethereum` `JSON-RPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://rpc.evmos.chaintools.tech/` | `Tendermint` `RPC` | [ChainTools](https://chaintools.tech/) | Pruned |
| `https://evmos.grpcui.chaintools.host` | `Cosmos` `gRPC` | [ChainTools](https://chaintools.tech/) | Pruned |
| `https://api.evmos.chaintools.tech/` | `Tendermint` `API` | [ChainTools](https://chaintools.tech/) | Pruned |
| `https://rpc.evmos.silknodes.io` | `Tendermint` `RPC` | [Silk Nodes](https://silknodes.io/) | Pruned |
| `https://grpc.evmos.silknodes.io` | `Cosmos` `gRPC` | [Silk Nodes](https://silknodes.io/) | Pruned |
| `https://api.evmos.silknodes.io` | `Cosmos` `REST` | [Silk Nodes](https://silknodes.io/) | Pruned |
| `https://evmos-mainnet.public.blastapi.io` | `Ethereum` `JSON-RPC` | [BLAST](https://blastapi.io/) | Pruned |
| `wss://evmos-mainnet.public.blastapi.io` | `Ethereum` `Websocket` | [BLAST](https://blastapi.io/) | Pruned |
| `https://evmos-evm.publicnode.com` | `Ethereum` `JSON-RPC` | [PublicNode (by Allnodes)](https://evmos.publicnode.com/) | Pruned |
| `https://evmos-rpc.publicnode.com` | `Tendermint` `RPC` | [PublicNode (by Allnodes)](https://evmos.publicnode.com/) | Pruned |
| `https://evmos-rest.publicnode.com` | `Cosmos` `REST` | [PublicNode (by Allnodes)](https://evmos.publicnode.com/) | Pruned |
| `https://evmos-api.validatrium.club` | `Tendermint` `API` | [Validatrium](https://validatrium.com/) | Pruned |
| `https://evmos-rpc.validatrium.club` | `Tendermint` `RPC` | [Validatrium](https://validatrium.com/) | Pruned |
| `https://evmos-rpc.gateway.pokt.network` | `Ethereum` `JSON-RPC` | [PocketNetwork](https://www.pokt.network/) | Pruned |
| `https://api-evmos.mms.team:443` | `Cosmos` `REST` | [MMS](https://mms.team/) | Pruned |
| `https://grpc-evmos.mms.team:443` | `Cosmos` `gRPC` | [MMS](https://mms.team/) | Pruned |
| `https://rpc-evmos.mms.team:443` | `Tendermint` `RPC` | [MMS](https://mms.team/) | Pruned |
| `https://jsonrpc-evmos.mms.team:443` | `Ethereum` `JSON-RPC` | [MMS](https://mms.team/) | Pruned |
| `https://web3endpoints.com/evmos-mainnet` | `Ethereum` `JSON-RPC` | [BlockSpaces](https://www.blockspaces.com/web3-infrastructure) | Pruned |
| `https://evmos-rest.antrixy.org` | `Cosmos` `REST` | Antrix Validators | Pruned |
| `https://evmos-grpc.antrixy.org` | `Cosmos` `gRPC` | Antrix Validators | Pruned |
| `https://evmos-rpc.antrixy.org` | `Tendermint` `RPC` | Antrix Validators | Pruned |
| `https://evmos-json.antrixy.org` | `Ethereum` `JSON-RPC` | Antrix Validators | Pruned |
| `https://evmos.drpc.org` | `Ethereum` `JSON-RPC` | [dRPC](https://drpc.org/) | Pruned |
| `wss://evmos.drpc.org` | `Ethereum` `Websocket` | [dRPC](https://drpc.org/) | Pruned |

Expand All @@ -76,21 +49,7 @@ If you are searching for the protobuf interfaces, head over [here](https://buf.b
| `https://tm.evmos-testnet.lava.build` | `Tendermint` `RPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `wss://tm.evmos-testnet.lava.build/websocket` | `Tendermint` `WSS` | [Lava Network](https://lavanet.xyz) | Pruned |
| `grpc.evmos-testnet.lava.build:443` | `Cosmos` `gRPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://grpc.evmos-testnet.lava.build` | `Cosmos` `Web-gRPC` | [Lava Network](https://lavanet.xyz) | Pruned |
| `https://evmos-testnet-rpc.polkachu.com:443` | `Tendermint` `RPC` | [Polkachu](https://polkachu.com) | Pruned |
| `https://rpc-t.evmos.nodestake.top` | `Tendermint` `RPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://grpc-t.evmos.nodestake.top` | `Cosmos` `gRPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://api-t.evmos.nodestake.top` | `Cosmos` `REST` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://jsonrpc-t.evmos.nodestake.top` | `Ethereum` `JSON-RPC` | [NodeStake](https://nodestake.top/) | Pruned |
| `https://evmos-testnet-rpc.qubelabs.io` | `Tendermint` `RPC` | [Qubelabs](https://qubelabs.io/) | Pruned |
| `https://evmos-testnet-lcd.qubelabs.io` | `Cosmos` `REST` | [Qubelabs](https://qubelabs.io/) | Pruned |
| `https://evmos-testnet-grpc.qubelabs.io` | `Cosmos` `gRPC` | [Qubelabs](https://qubelabs.io/) | Pruned |
| `https://evmos-testnet-json.qubelabs.io` | `Ethereum` `JSON-RPC` | [Qubelabs](https://qubelabs.io/) | Pruned |
| `https://evmos-trpc.antrixy.org` | `Tendermint` `RPC` | Antrix Validators | Pruned |
| `https://evmos-trest.antrixy.org` | `Cosmos` `REST` | Antrix Validators | Pruned |
| `https://evmos-tgrpc.antrixy.org` | `Cosmos` `gRPC` | Antrix Validators | Pruned |
| `https://evmos-testnet.drpc.org` | `Ethereum` `JSON-RPC` | [dRPC](https://drpc.org/) | Pruned |
| `wss://evmos-testnet.drpc.org` | `Ethereum` `Websocket` | [dRPC](https://drpc.org/) | Pruned |


## On-Demand Services
Expand Down
2 changes: 2 additions & 0 deletions scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Python cache
__pycache__/
1 change: 1 addition & 0 deletions scripts/check_endpoints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

93 changes: 93 additions & 0 deletions scripts/check_endpoints/endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
This file contains the logic to extract the available endpoints
from the Markdown file of the docs.
"""

import os
import re
from dataclasses import dataclass
from typing import List, Union


@dataclass
class Endpoint:
address: str
category: str
provider: str


ENDPOINT_PATTERN = re.compile(r'\|\s*`(?P<endpoint>[^`]+)`\s*\|\s*`(?P<type1>[^`]+)`\s*`(?P<type2>[^`]+)`\s*\|\s*\[(?P<provider>[^\]]+)\]'"")


def get_endpoint_from_line(line: str) -> Union[Endpoint, None]:
"""
This method extracts the information of a given endpoint
from one line of the Markdown table.
"""
match = ENDPOINT_PATTERN.search(line)
if not match:
return None

return Endpoint(
address=match.group("endpoint"),
category=f'{match.group("type1")} {match.group("type2")}',
provider=match.group("provider")
)


def extract_endpoints(contents: List[str]) -> List[List[str]]:
"""
This function extracts the available endpoints
for testnet and mainnet from the given file contents.
"""
testnet_endpoints = []
mainnet_endpoints = []

extract_mainnet = False
extract_testnet = False

for line in contents:
if line[0] not in ["#", "|"]:
continue

if "### Mainnet" in line:
extract_mainnet = True
extract_testnet = False
continue
elif "### Testnet" in line:
extract_mainnet = False
extract_testnet = True
continue
elif line[0] != "|":
continue

endpoint = get_endpoint_from_line(line)
if endpoint is None:
continue

if extract_mainnet:
mainnet_endpoints.append(endpoint)
continue

if extract_testnet:
testnet_endpoints.append(endpoint)
continue

raise ValueError(f"unexpected condition: got endpoint {endpoint.address} but neither testnet nor mainnet is active to be extracted")

return mainnet_endpoints, testnet_endpoints


def get_endpoints(file: str) -> List[str]:
"""
This method tries to extract the list of available endpoints from
from the given docs file.
"""
if not os.path.exists(file):
raise FileNotFoundError(f"{file} does not exist")

with open(file, "r", encoding="utf-8") as f:
contents = f.readlines()

return extract_endpoints(contents)

50 changes: 50 additions & 0 deletions scripts/check_endpoints/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
This tool checks the list of available endpoints for their functionality.
Any non-responsive endpoints are being flagged to be either discussed
with the corresponding partners or removed.
"""

from typing import Union
from endpoints import Endpoint, get_endpoints
from queries import query_endpoint

ENDPOINTS_FILE = "docs/develop/api/networks.mdx"


def print_output(endpoint: Endpoint, ok: Union[bool, None]):
ok_emoji = "❓" if ok is None else ("✅" if ok else "❌")
print(f" {ok_emoji} - {endpoint.address}")


def check_endpoints(file: str) -> bool:
"""
This function contains the main logic of the script,
which gets the list of available endpoints on the docs
and runs a query to all of the supported endpoint types.
"""
failed_endpoints = []
mainnet_endpoints, testnet_endpoints = get_endpoints(file)

print("\n--------------\nChecking mainnet endpoints")
for endpoint in mainnet_endpoints:
ok = query_endpoint(endpoint)
if ok is False:
failed_endpoints.append(endpoint.address)

print_output(endpoint, ok)

print("\n--------------\nChecking testnet endpoints")
for endpoint in testnet_endpoints:
ok = query_endpoint(endpoint)
if ok is False:
failed_endpoints.append(endpoint.address)

print_output(endpoint, ok)

return failed_endpoints == []


if __name__ == "__main__":
all_passed = check_endpoints(ENDPOINTS_FILE)
if not all_passed:
raise ValueError("some endpoints failed to return a response; check the output")
68 changes: 68 additions & 0 deletions scripts/check_endpoints/queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
This file contains the required logic to
call a simple query on a given endpoint
based on the type of endpoint.
"""

from endpoints import Endpoint
import requests
from typing import Union


def query_endpoint(ep: Endpoint) -> Union[bool, None]:
"""
This function queries an endpoint based on its type.
"""
if ep.category == "Ethereum JSON-RPC":
return query_json_rpc(ep.address)
elif ep.category == "Cosmos REST":
return query_cosmos_rest(ep.address)
elif ep.category == "Tendermint RPC":
return query_tendermint_rpc(ep.address)
else:
return None


def query_json_rpc(address: str) -> bool:
"""
This method queries the latest block on the Ethereum JSON-RPC.
"""
try:
response = requests.post(
address,
json={
"jsonrpc":"2.0",
"method":"eth_blockNumber",
"params":[],
"id":83
}
)
return response.status_code == requests.codes.OK
except Exception as e:
print(f"failed to query JSON-RPC endpoint {address}: {e}")
return False


def query_cosmos_rest(address: str) -> bool:
"""
This method queries the latest block on the Cosmos REST.
"""
try:
full_address = f"{address}/cosmos/base/tendermint/v1beta1/blocks/latest"
response = requests.get(full_address)
return response.status_code == requests.codes.OK
except Exception as e:
print(f"failed to query Cosmos REST endpoint {address}: {e}")
return False


def query_tendermint_rpc(address: str) -> bool:
"""
This method queries the latest block on the Tendermint RPC.
"""
try:
response = requests.get(f"{address}/block")
return response.status_code == requests.codes.OK
except Exception as e:
print(f"failed to query Tendermint RPC endpoint {address}: {e}")
return False
11 changes: 11 additions & 0 deletions scripts/check_endpoints/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
certifi==2024.8.30
charset-normalizer==3.4.0
exceptiongroup==1.2.0
idna==3.10
iniconfig==2.0.0
packaging==24.0
pluggy==1.4.0
pytest==8.1.1
requests==2.32.3
tomli==2.0.1
urllib3==2.2.3
28 changes: 28 additions & 0 deletions scripts/check_endpoints/test_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
This file contains the tests for the logic
that is extracting the available endpoints
from the Markdown file.
"""

import os
import sys

sys.path.append(os.path.dirname(__file__))

from endpoints import Endpoint, get_endpoint_from_line, get_endpoints


def test_get_endpoint_from_line_pass():
assert get_endpoint_from_line(
"| `https://evmos.lava.build` | `Ethereum` `JSON-RPC` | [Lava Network](https://lavanet.xyz) | Pruned |"
) == Endpoint(
address="https://evmos.lava.build",
category="Ethereum JSON-RPC",
provider="Lava Network"
)


def test_get_endpoints_pass():
mainnet, testnet = get_endpoints(os.path.join(os.path.dirname(__file__), "testdata/networks.mdx"))
assert len(mainnet) > 0, "failed to get mainnet endpoints"
assert len(testnet) > 0, "failed to get testnet endpoints"
Loading

0 comments on commit b924ce4

Please sign in to comment.