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

feat: add default public RPC endpoints for default configured networks #1866

Merged
merged 14 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
135 changes: 135 additions & 0 deletions scripts/update_rpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
""" Script to generate a python const file with public chain RPC endpoints from the Ethereum
Foundation ethereum-lists/chains repo.

Usage
-----
python scripts/update_rpc.py
"""
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
from urllib.parse import urljoin

import requests
from pydantic import BaseModel

from ape.logging import logger

CHAIN_IDS = {
"base": {
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
"mainnet": 8453,
"sepolia": 84532,
},
"ethereum": {
"mainnet": 1,
"goerli": 5,
"sepolia": 11155111,
},
"gnosis": {
"mainnet": 100,
},
"optimism": {
"mainnet": 10,
"goerli": 420,
"sepolia": 11155420,
},
"polygon": {
"mainnet": 137,
"mumbai": 80001,
},
"polygon-zkevm": {
"mainnet": 1101,
"testnet": 1442,
},
}
INCLUDE_PROTOCOLS = ["http", "https"]
SOURCE_URL = "https://raw.githubusercontent.com/ethereum-lists/chains/master/_data/chains/"
CHAIN_CONST_FILE = Path(__file__).parent.parent / "src" / "ape_geth" / "chains.py"


class Chain(BaseModel):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this reminds of me of https://github.com/ApeWorX/ape/issues/254

Specifically this comment: #254 (comment)
If we did that we could use in ape-etherscan to automatically set the URLs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is like a packaging step away from that comment. Though not sure it's worth the time now.

name: str
chain: str
icon: Optional[str] = None
rpc: List[str]
features: Optional[List[Dict[str, str]]] = None
faucets: List[str]
nativeCurrency: Dict[str, Any]
infoURL: str
shortName: str
chainId: int
networkId: int
slip44: Optional[int] = None
ens: Optional[Dict[str, str]] = None
explorers: List[Dict[str, str]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I wonder if we should do something similar in ape-etherscan (As a future)



def stamp() -> str:
"""UTC timestamp for file header"""
return str(datetime.utcnow())


def ensure_dict(d: Dict[str, Any], key: str):
if key in d and isinstance(d[key], dict):
return
d[key] = dict()


def fetch_chain(chain_id: int) -> Chain:
"""Fetch a chain from the ethereum-lists repo."""
url = urljoin(SOURCE_URL, f"eip155-{chain_id}.json")

logger.info(f"GET {url}")
r = requests.get(url)
r.raise_for_status()

chain = Chain.model_validate_json(r.text)

# Filter out unwanted protocols (e.g. websocket)
chain.rpc = list(filter(lambda rpc: rpc.split(":")[0] in INCLUDE_PROTOCOLS, chain.rpc))

return chain


def fetch_chains() -> Dict[str, Dict[str, Chain]]:
"""Fetch all chains from the ethereum-lists repo."""
chains: Dict[str, Dict[str, Chain]] = {}
for ecosystem in CHAIN_IDS.keys():
for network, chain_id in CHAIN_IDS[ecosystem].items():
logger.info(f"Fetching chain {ecosystem}:{network} ({chain_id})")
ensure_dict(chains, ecosystem)
chains[ecosystem][network] = fetch_chain(chain_id)
return chains


def write_chain_const(chains: Dict[str, Dict[str, Chain]]):
"""Write the file with Python constant"""
with CHAIN_CONST_FILE.open("w") as const_file:
const_file.write("# This file is auto-generated by scripts/update_rpc.py\n")
const_file.write(f"# {stamp()}\n")
const_file.write("# Do not edit this file directly.\n")
const_file.write("from typing import Dict, List\n")
const_file.write("PUBLIC_CHAIN_RPCS: Dict[str, Dict[str, List[str]]] = {\n")
for ecosystem in chains.keys():
const_file.write(f' "{ecosystem}": {{\n')
for network, chain in chains[ecosystem].items():
const_file.write(f' "{network}": [\n')
for rpc in chain.rpc:
if "${" in rpc:
continue
const_file.write(f' "{rpc}",\n')
const_file.write(" ],\n")
const_file.write(" },\n")
const_file.write("}\n")


def main():
logger.info("Fetching chain data...")
logger.info(f" Source: {SOURCE_URL}")
logger.info(f" Dest: {CHAIN_CONST_FILE}")
chains = fetch_chains()
write_chain_const(chains)


if __name__ == "__main__":
main()
100 changes: 100 additions & 0 deletions src/ape_geth/chains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# This file is auto-generated by scripts/update_rpc.py
# 2024-01-17 20:19:55.820921
# Do not edit this file directly.
from typing import Dict, List

PUBLIC_CHAIN_RPCS: Dict[str, Dict[str, List[str]]] = {
"base": {
"mainnet": [
"https://mainnet.base.org/",
"https://developer-access-mainnet.base.org/",
"https://base.gateway.tenderly.co",
"https://base.publicnode.com",
],
"sepolia": [
"https://sepolia.base.org",
],
},
"ethereum": {
"mainnet": [
"https://api.mycryptoapi.com/eth",
"https://cloudflare-eth.com",
"https://ethereum.publicnode.com",
"https://mainnet.gateway.tenderly.co",
"https://rpc.blocknative.com/boost",
"https://rpc.flashbots.net",
"https://rpc.flashbots.net/fast",
"https://rpc.mevblocker.io",
"https://rpc.mevblocker.io/fast",
"https://rpc.mevblocker.io/noreverts",
"https://rpc.mevblocker.io/fullprivacy",
],
"goerli": [
"https://rpc.goerli.mudit.blog/",
"https://ethereum-goerli.publicnode.com",
"https://goerli.gateway.tenderly.co",
],
"sepolia": [
"https://rpc.sepolia.org",
"https://rpc2.sepolia.org",
"https://rpc-sepolia.rockx.com",
"https://rpc.sepolia.ethpandaops.io",
"https://sepolia.gateway.tenderly.co",
"https://ethereum-sepolia.publicnode.com",
],
},
"gnosis": {
"mainnet": [
"https://rpc.gnosischain.com",
"https://rpc.gnosis.gateway.fm",
"https://rpc.ankr.com/gnosis",
"https://gnosischain-rpc.gateway.pokt.network",
"https://gnosis-mainnet.public.blastapi.io",
"https://gnosis.api.onfinality.io/public",
"https://gnosis.blockpi.network/v1/rpc/public",
"https://web3endpoints.com/gnosischain-mainnet",
"https://gnosis.oat.farm",
"https://gnosis.publicnode.com",
],
},
"optimism": {
"mainnet": [
"https://mainnet.optimism.io",
"https://optimism.publicnode.com",
"https://optimism.gateway.tenderly.co",
],
"goerli": [
"https://goerli.optimism.io",
"https://optimism-goerli.publicnode.com",
"https://optimism-goerli.gateway.tenderly.co",
],
"sepolia": [
"https://sepolia.optimism.io",
],
},
"polygon": {
"mainnet": [
"https://polygon-rpc.com/",
"https://rpc-mainnet.matic.network",
"https://matic-mainnet.chainstacklabs.com",
"https://rpc-mainnet.maticvigil.com",
"https://rpc-mainnet.matic.quiknode.pro",
"https://matic-mainnet-full-rpc.bwarelabs.com",
"https://polygon-bor.publicnode.com",
"https://polygon.gateway.tenderly.co",
],
"mumbai": [
"https://rpc-mumbai.maticvigil.com",
"https://polygon-mumbai-bor.publicnode.com",
"https://polygon-mumbai.gateway.tenderly.co",
],
},
"polygon-zkevm": {
"mainnet": [
"https://zkevm-rpc.com",
],
"testnet": [
"https://rpc.public.zkevm-test.net",
],
},
}
29 changes: 25 additions & 4 deletions src/ape_geth/provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import atexit
import random
import shutil
from itertools import tee
from pathlib import Path
Expand Down Expand Up @@ -40,6 +41,7 @@
DEFAULT_SETTINGS,
EthereumNodeProvider,
)
from ape_geth.chains import PUBLIC_CHAIN_RPCS


class GethDevProcess(BaseGethProcess):
Expand Down Expand Up @@ -197,9 +199,9 @@ def wait(self, *args, **kwargs):

class GethNetworkConfig(PluginConfig):
# Make sure you are running the right networks when you try for these
mainnet: Dict = DEFAULT_SETTINGS.copy()
goerli: Dict = DEFAULT_SETTINGS.copy()
sepolia: Dict = DEFAULT_SETTINGS.copy()
mainnet: Dict = {"uri": random.choice(PUBLIC_CHAIN_RPCS["ethereum"]["mainnet"])}
goerli: Dict = {"uri": random.choice(PUBLIC_CHAIN_RPCS["ethereum"]["goerli"])}
sepolia: Dict = {"uri": random.choice(PUBLIC_CHAIN_RPCS["ethereum"]["sepolia"])}
# Make sure to run via `geth --dev` (or similar)
local: Dict = {**DEFAULT_SETTINGS.copy(), "chain_id": DEFAULT_TEST_CHAIN_ID}

mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -469,4 +471,23 @@ def build_command(self) -> List[str]:

# NOTE: The default behavior of EthereumNodeBehavior assumes geth.
class Geth(EthereumNodeProvider):
pass
@property
def uri(self) -> str:
uri = super().uri
ecosystem = self.network.ecosystem.name
network = self.network.name

# If we didn't find one in config, look for a public RPC.
if not uri or uri == DEFAULT_SETTINGS["uri"]:
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
# Do not override explicit configuration
if hasattr(self.config, ecosystem):
antazoey marked this conversation as resolved.
Show resolved Hide resolved
# Shape of this is odd. Pydantic model containing dicts
antazoey marked this conversation as resolved.
Show resolved Hide resolved
if network_config := self.config[ecosystem].get(network):
if "uri" in network_config:
return network_config["uri"]

# Use public RPC if available
if ecosystem in PUBLIC_CHAIN_RPCS and network in PUBLIC_CHAIN_RPCS[ecosystem]:
uri = random.choice(PUBLIC_CHAIN_RPCS[ecosystem][network])

return uri
Loading