From 60608603f201d39eaf51b1384b48e4d755daf013 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:21:14 +0530 Subject: [PATCH 01/39] init: basic template dockerfile --- Dockerfile.template | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Dockerfile.template diff --git a/Dockerfile.template b/Dockerfile.template new file mode 100644 index 00000000..4e959cc6 --- /dev/null +++ b/Dockerfile.template @@ -0,0 +1,18 @@ +# Use a base image with a minimal Linux distribution +FROM alpine:latest + +# Install dependencies +RUN apk update && apk add --no-cache wget + +# COMMIT-HASH=First 8 characters of the commit hash of the Geth version +# GETH_VERSION=Version of Geth +# PLATFORM=Platform of the Geth binary (arm, arm64, amd64, etc.) + +# Download and install Ethereum Geth binary (the plan is to keep updating this) +RUN wget https://gethstore.blob.core.windows.net/builds/geth-linux-${PLATFORM}64-${GETH_VERSION}-${COMMIT-HASH}.tar.gz -O /tmp/geth.tar.gz && \ + tar -zxvf /tmp/geth.tar.gz -C /tmp/ && \ + mv /tmp/geth-linux-amd64-1.11.10-3e064192/geth /usr/local/bin/geth && \ + rm -rf /tmp/geth.tar.gz /tmp/geth-linux-amd64-1.11.10-3e064192 + +# Expose Ethereum network ports +EXPOSE 8545 8546 30303 30303/udp \ No newline at end of file From fccf518f21ff9281f8a104f3d3435b9bc4c91a10 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:26:11 +0530 Subject: [PATCH 02/39] init: basic first draft for install --- Dockerfile.template | 2 +- geth/install.py | 86 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/Dockerfile.template b/Dockerfile.template index 4e959cc6..ce0baea9 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -9,7 +9,7 @@ RUN apk update && apk add --no-cache wget # PLATFORM=Platform of the Geth binary (arm, arm64, amd64, etc.) # Download and install Ethereum Geth binary (the plan is to keep updating this) -RUN wget https://gethstore.blob.core.windows.net/builds/geth-linux-${PLATFORM}64-${GETH_VERSION}-${COMMIT-HASH}.tar.gz -O /tmp/geth.tar.gz && \ +RUN wget https://gethstore.blob.core.windows.net/builds/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH}.tar.gz -O /tmp/geth.tar.gz && \ tar -zxvf /tmp/geth.tar.gz -C /tmp/ && \ mv /tmp/geth-linux-amd64-1.11.10-3e064192/geth /usr/local/bin/geth && \ rm -rf /tmp/geth.tar.gz /tmp/geth-linux-amd64-1.11.10-3e064192 diff --git a/geth/install.py b/geth/install.py index dca49918..e7766e0c 100644 --- a/geth/install.py +++ b/geth/install.py @@ -6,6 +6,7 @@ import os import stat import subprocess +import requests import sys import tarfile @@ -376,11 +377,87 @@ def install_from_source_code_release(identifier): V1_13_8: install_v1_13_8, V1_13_9: install_v1_13_9, V1_13_10: install_v1_13_10, - }, + } } +def map_architecture(architecture: str): + architecture_mapping = { + "x86_64": "amd64", + "armv7l": "arm", + "aarch64": "arm64", + } + + if architecture not in architecture_mapping: + raise ValueError(f"Unknown architecture: {architecture}") + + return architecture_mapping[architecture] + +def generate_dockerfile(docker_install_version=None): + GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" + + if docker_install_version is None: + docker_install_version = "latest" + else: + docker_install_version = f"tags/{docker_install_version}" + + r = requests.get(f"{GITHUB_API}/{docker_install_version}") + if r.status_code == 404: + raise ValueError(f"Unable to find docker install version: {docker_install_version}") + elif r.status_code != 200: + raise ValueError(f"Unexpected status code while checking for geth versions: {r.status_code}") + + release_data = r.json() + if docker_install_version == "latest": + docker_install_version = release_data.get("tag_name") + commit_tag = release_data.get("target_commitish") + + if docker_install_version is None or commit_tag is None: + raise ValueError(f"Unable to find docker install version/commit tag: {docker_install_version}/{commit_tag}") + + COMMIT_HASH_API = GITHUB_API + "git/refs/heads/" + commit_tag + r = requests.get(COMMIT_HASH_API) + if r.status_code != 200: + raise ValueError(f"Unexpected status code while checking for commit hash: {r.status_code}") + + commit_data = r.json() + commit_hash = commit_data.get("object", {}).get("sha") + if commit_hash is None: + raise ValueError(f"Unable to find commit hash: {commit_hash}") + + # detect arm or amd64 + arc = os.uname().machine + architecture = map_architecture(arc) + + if docker_install_version.startswith("tags/"): + docker_install_version = docker_install_version[5:] + + # https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.10-bc0be1b1.tar.gz + gethstore_url = f"https://gethstore.blob.core.windows.net/builds/geth-linux-{architecture}-{docker_install_version}-{commit_hash}.tar.gz" + + check_existence = requests.head(gethstore_url) + if check_existence.status_code != 200: + raise ValueError(f"Unable to find binary at: {gethstore_url}") + + # upload this somewhere + with open("Dockerfile.template", "r") as f: + template = f.read() + + template = template.replace("${PLATFORM}", architecture) + template = template.replace("${VERSION}", docker_install_version) + template = template.replace("${COMMIT_HASH}", commit_hash[:8]) + + with open("Dockerfile", "w") as f: + f.write(template) + + print(f"Generated Dockerfile for geth {docker_install_version} ({commit_hash[:8]})") + + +def install_geth(identifier, platform=None, docker=False, docker_install_version=None): + if docker: + # for testing purposes + generate_dockerfile(docker_install_version=docker_install_version) + return -def install_geth(identifier, platform=None): if platform is None: platform = get_platform() @@ -403,10 +480,13 @@ def install_geth(identifier, platform=None): if __name__ == "__main__": try: identifier = sys.argv[1] + if len(sys.argv) > 2: + docker = sys.argv[2] + except IndexError: print( "Invocation error. Should be invoked as `python -m geth.install `" # noqa: E501 ) sys.exit(1) - install_geth(identifier) + install_geth(identifier, docker=docker) From e5681a4d70c7d77d87e7f23cde5cf391f140bedb Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:38:02 +0530 Subject: [PATCH 03/39] progress: ready for first draft --- geth/install.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/geth/install.py b/geth/install.py index e7766e0c..309495b3 100644 --- a/geth/install.py +++ b/geth/install.py @@ -384,7 +384,9 @@ def map_architecture(architecture: str): architecture_mapping = { "x86_64": "amd64", "armv7l": "arm", + "arm64": "arm64", "aarch64": "arm64", + "amd64": "amd64" } if architecture not in architecture_mapping: @@ -398,11 +400,15 @@ def generate_dockerfile(docker_install_version=None): if docker_install_version is None: docker_install_version = "latest" else: - docker_install_version = f"tags/{docker_install_version}" + docker_install_version = f"{docker_install_version}" - r = requests.get(f"{GITHUB_API}/{docker_install_version}") + RELEASES_API = GITHUB_API + "releases/" + + release_url = f"{RELEASES_API}{docker_install_version}" + + r = requests.get(release_url) if r.status_code == 404: - raise ValueError(f"Unable to find docker install version: {docker_install_version}") + raise ValueError(f"Unable to find docker install version: {docker_install_version} from URL: {release_url}") elif r.status_code != 200: raise ValueError(f"Unexpected status code while checking for geth versions: {r.status_code}") @@ -428,8 +434,10 @@ def generate_dockerfile(docker_install_version=None): arc = os.uname().machine architecture = map_architecture(arc) - if docker_install_version.startswith("tags/"): - docker_install_version = docker_install_version[5:] + if docker_install_version.startswith("v"): + docker_install_version = docker_install_version[1:] + + commit_hash = commit_hash[:8] # https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.10-bc0be1b1.tar.gz gethstore_url = f"https://gethstore.blob.core.windows.net/builds/geth-linux-{architecture}-{docker_install_version}-{commit_hash}.tar.gz" @@ -437,16 +445,18 @@ def generate_dockerfile(docker_install_version=None): check_existence = requests.head(gethstore_url) if check_existence.status_code != 200: raise ValueError(f"Unable to find binary at: {gethstore_url}") + + # check if file Dockerfile.template exists, if not, download from github - # upload this somewhere - with open("Dockerfile.template", "r") as f: + # get Dockerfile.template at ~/.py-geth/Dockerfile.template + with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "r") as f: template = f.read() template = template.replace("${PLATFORM}", architecture) - template = template.replace("${VERSION}", docker_install_version) - template = template.replace("${COMMIT_HASH}", commit_hash[:8]) + template = template.replace("${GETH_VERSION}", docker_install_version) + template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} - with open("Dockerfile", "w") as f: + with open("/Users/aditya/Documents/OSS/py-geth/geth/Dockerfile", "w") as f: f.write(template) print(f"Generated Dockerfile for geth {docker_install_version} ({commit_hash[:8]})") @@ -481,7 +491,7 @@ def install_geth(identifier, platform=None, docker=False, docker_install_version try: identifier = sys.argv[1] if len(sys.argv) > 2: - docker = sys.argv[2] + docker = sys.argv[2] == "docker" except IndexError: print( From 70efd11d6e5b88128aa82d31e25ead5cf0e7818f Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:39:44 +0530 Subject: [PATCH 04/39] progress: ready for first draft --- geth/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geth/install.py b/geth/install.py index 309495b3..e97f2523 100644 --- a/geth/install.py +++ b/geth/install.py @@ -446,7 +446,7 @@ def generate_dockerfile(docker_install_version=None): if check_existence.status_code != 200: raise ValueError(f"Unable to find binary at: {gethstore_url}") - # check if file Dockerfile.template exists, if not, download from github + # check if file Dockerfile.template exists, if not, download from github # get Dockerfile.template at ~/.py-geth/Dockerfile.template with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "r") as f: From 00c4c74458d6cf5e7d96a2358b4587afe0fd3944 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:43:04 +0530 Subject: [PATCH 05/39] progress: making docker install independent of my machine --- geth/install.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/geth/install.py b/geth/install.py index e97f2523..560e4a02 100644 --- a/geth/install.py +++ b/geth/install.py @@ -446,7 +446,16 @@ def generate_dockerfile(docker_install_version=None): if check_existence.status_code != 200: raise ValueError(f"Unable to find binary at: {gethstore_url}") - # check if file Dockerfile.template exists, if not, download from github + # check if file Dockerfile.template exists, if not, download from github + if not os.path.exists(os.path.expanduser("~/.py-geth/Dockerfile.template")): + # for now + dockerfile_template_url = "https://raw.githubusercontent.com/0x0elliot/py-geth/0x0elliot/py-geth-docker/Dockerfile.template" + r = requests.get(dockerfile_template_url) + if r.status_code != 200: + raise ValueError(f"Unable to download Dockerfile.template from github: {r.status_code}") + + with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "w") as f: + f.write(r.text) # get Dockerfile.template at ~/.py-geth/Dockerfile.template with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "r") as f: @@ -456,10 +465,10 @@ def generate_dockerfile(docker_install_version=None): template = template.replace("${GETH_VERSION}", docker_install_version) template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} - with open("/Users/aditya/Documents/OSS/py-geth/geth/Dockerfile", "w") as f: + with open(os.path.expanduser("~/.py-geth/Dockerfile"), "w") as f: f.write(template) - print(f"Generated Dockerfile for geth {docker_install_version} ({commit_hash[:8]})") + print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at ~/.py-geth/Dockerfile!") def install_geth(identifier, platform=None, docker=False, docker_install_version=None): From f2505521d36b50781e42488b2cf9c65319c2d9da Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:05:54 +0530 Subject: [PATCH 06/39] progress: adding image building support --- geth/install.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/geth/install.py b/geth/install.py index 560e4a02..59e8b0c3 100644 --- a/geth/install.py +++ b/geth/install.py @@ -394,7 +394,8 @@ def map_architecture(architecture: str): return architecture_mapping[architecture] -def generate_dockerfile(docker_install_version=None): +# returns the latest version of geth +def generate_dockerfile(docker_install_version=None) -> str: GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" if docker_install_version is None: @@ -458,23 +459,42 @@ def generate_dockerfile(docker_install_version=None): f.write(r.text) # get Dockerfile.template at ~/.py-geth/Dockerfile.template - with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "r") as f: + with open(os.path.expanduser(f"~/.py-geth/Dockerfile.template"), "r") as f: template = f.read() template = template.replace("${PLATFORM}", architecture) template = template.replace("${GETH_VERSION}", docker_install_version) template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} - with open(os.path.expanduser("~/.py-geth/Dockerfile"), "w") as f: + with open(os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}"), "w") as f: f.write(template) print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at ~/.py-geth/Dockerfile!") + return docker_install_version +def build_image(docker_install_version=None): + docker_install_version = generate_dockerfile(docker_install_version=docker_install_version) + + # check if "py-geth:{docker_install_version}" exists + import docker + client = docker.from_env() + try: + client.images.get(f"py-geth:{docker_install_version}") + print(f"py-geth:{docker_install_version} already exists, skipping build...") + return + except docker.errors.ImageNotFound: + pass + + # build image + print(f"Building image py-geth:{docker_install_version}...") + client.images.build(path=os.path.expanduser("~/.py-geth"), tag=f"py-geth:{docker_install_version}") + + print(f"Successfully built image py-geth:{docker_install_version}!") def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: # for testing purposes - generate_dockerfile(docker_install_version=docker_install_version) + build_image(docker_install_version=docker_install_version) return if platform is None: From abd6b797e91fbf02ee58c9787154b2ff2b26e2d3 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:10:42 +0530 Subject: [PATCH 07/39] progress: cleanup --- geth/install.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/geth/install.py b/geth/install.py index 59e8b0c3..af348b55 100644 --- a/geth/install.py +++ b/geth/install.py @@ -466,10 +466,12 @@ def generate_dockerfile(docker_install_version=None) -> str: template = template.replace("${GETH_VERSION}", docker_install_version) template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} - with open(os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}"), "w") as f: + geth_docker_path = os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}") + + with open(geth_docker_path, "w") as f: f.write(template) - print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at ~/.py-geth/Dockerfile!") + print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at {geth_docker_path}!") return docker_install_version def build_image(docker_install_version=None): @@ -484,10 +486,12 @@ def build_image(docker_install_version=None): return except docker.errors.ImageNotFound: pass + + geth_docker_path = os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}") # build image print(f"Building image py-geth:{docker_install_version}...") - client.images.build(path=os.path.expanduser("~/.py-geth"), tag=f"py-geth:{docker_install_version}") + client.images.build(path=geth_docker_path, tag=f"py-geth:{docker_install_version}") print(f"Successfully built image py-geth:{docker_install_version}!") From a5d2d9e3538af679951f168030f72c8c3d733aef Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:30:28 +0530 Subject: [PATCH 08/39] progress: building works (?) --- geth/install.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/geth/install.py b/geth/install.py index af348b55..cfb7184b 100644 --- a/geth/install.py +++ b/geth/install.py @@ -466,9 +466,11 @@ def generate_dockerfile(docker_install_version=None) -> str: template = template.replace("${GETH_VERSION}", docker_install_version) template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} - geth_docker_path = os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}") + geth_docker_path = os.path.expanduser(f"~/.py-geth/{docker_install_version}") + if not os.path.exists(geth_docker_path): + os.makedirs(geth_docker_path) - with open(geth_docker_path, "w") as f: + with open(f"{geth_docker_path}/Dockerfile", "w") as f: f.write(template) print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at {geth_docker_path}!") @@ -487,11 +489,11 @@ def build_image(docker_install_version=None): except docker.errors.ImageNotFound: pass - geth_docker_path = os.path.expanduser(f"~/.py-geth/Dockerfile.{docker_install_version}") + geth_docker_folder = os.path.expanduser(f"~/.py-geth/{docker_install_version}") # build image - print(f"Building image py-geth:{docker_install_version}...") - client.images.build(path=geth_docker_path, tag=f"py-geth:{docker_install_version}") + print(f"Building image py-geth:{docker_install_version} at {geth_docker_folder}...") + client.images.build(path=geth_docker_folder, tag=f"py-geth:{docker_install_version}") print(f"Successfully built image py-geth:{docker_install_version}!") From 9457c911078560151894ae1f30ba96aa9aea1bc8 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:37:28 +0530 Subject: [PATCH 09/39] progress: docker build fix --- Dockerfile.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.template b/Dockerfile.template index ce0baea9..2cf1f507 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -11,8 +11,8 @@ RUN apk update && apk add --no-cache wget # Download and install Ethereum Geth binary (the plan is to keep updating this) RUN wget https://gethstore.blob.core.windows.net/builds/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH}.tar.gz -O /tmp/geth.tar.gz && \ tar -zxvf /tmp/geth.tar.gz -C /tmp/ && \ - mv /tmp/geth-linux-amd64-1.11.10-3e064192/geth /usr/local/bin/geth && \ - rm -rf /tmp/geth.tar.gz /tmp/geth-linux-amd64-1.11.10-3e064192 + mv /tmp/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH}/geth /usr/local/bin/geth && \ + rm -rf /tmp/geth.tar.gz /tmp/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH} # Expose Ethereum network ports EXPOSE 8545 8546 30303 30303/udp \ No newline at end of file From 31883c1f88310662f3010bb7de8db41c54141aad Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:39:10 +0530 Subject: [PATCH 10/39] progress: docker build fix --- geth/install.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/geth/install.py b/geth/install.py index cfb7184b..6af03efa 100644 --- a/geth/install.py +++ b/geth/install.py @@ -440,31 +440,23 @@ def generate_dockerfile(docker_install_version=None) -> str: commit_hash = commit_hash[:8] - # https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.13.10-bc0be1b1.tar.gz gethstore_url = f"https://gethstore.blob.core.windows.net/builds/geth-linux-{architecture}-{docker_install_version}-{commit_hash}.tar.gz" check_existence = requests.head(gethstore_url) if check_existence.status_code != 200: raise ValueError(f"Unable to find binary at: {gethstore_url}") - # check if file Dockerfile.template exists, if not, download from github - if not os.path.exists(os.path.expanduser("~/.py-geth/Dockerfile.template")): - # for now - dockerfile_template_url = "https://raw.githubusercontent.com/0x0elliot/py-geth/0x0elliot/py-geth-docker/Dockerfile.template" - r = requests.get(dockerfile_template_url) - if r.status_code != 200: - raise ValueError(f"Unable to download Dockerfile.template from github: {r.status_code}") - - with open(os.path.expanduser("~/.py-geth/Dockerfile.template"), "w") as f: - f.write(r.text) - - # get Dockerfile.template at ~/.py-geth/Dockerfile.template - with open(os.path.expanduser(f"~/.py-geth/Dockerfile.template"), "r") as f: - template = f.read() + + dockerfile_template_url = "https://raw.githubusercontent.com/0x0elliot/py-geth/0x0elliot/py-geth-docker/Dockerfile.template" + r = requests.get(dockerfile_template_url) + if r.status_code != 200: + raise ValueError(f"Unable to download Dockerfile.template from github: {r.status_code}") + + template = r.text template = template.replace("${PLATFORM}", architecture) template = template.replace("${GETH_VERSION}", docker_install_version) - template = template.replace("${COMMIT-HASH}", commit_hash) # ${COMMIT-HASH} + template = template.replace("${COMMIT-HASH}", commit_hash) geth_docker_path = os.path.expanduser(f"~/.py-geth/{docker_install_version}") if not os.path.exists(geth_docker_path): From fc42ad83434c47526061cc58d1956260b422e9cc Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:42:55 +0530 Subject: [PATCH 11/39] progress: image building works! --- geth/install.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/geth/install.py b/geth/install.py index 6af03efa..ea61fef0 100644 --- a/geth/install.py +++ b/geth/install.py @@ -474,8 +474,11 @@ def build_image(docker_install_version=None): # check if "py-geth:{docker_install_version}" exists import docker client = docker.from_env() + + tag = f"py-geth:{docker_install_version}" + try: - client.images.get(f"py-geth:{docker_install_version}") + client.images.get(tag) print(f"py-geth:{docker_install_version} already exists, skipping build...") return except docker.errors.ImageNotFound: @@ -484,10 +487,16 @@ def build_image(docker_install_version=None): geth_docker_folder = os.path.expanduser(f"~/.py-geth/{docker_install_version}") # build image - print(f"Building image py-geth:{docker_install_version} at {geth_docker_folder}...") - client.images.build(path=geth_docker_folder, tag=f"py-geth:{docker_install_version}") + print(f"Building image {tag} at {geth_docker_folder}...") + client.images.build(path=geth_docker_folder, tag=tag) + + try: + # check if image exists + client.images.get(tag) + except docker.errors.ImageNotFound: + raise ValueError(f"Unable to find image {tag} after building it!") - print(f"Successfully built image py-geth:{docker_install_version}!") + print(f"Successfully built image {tag}!") def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: From 3ed4e390a0e348819ebafe72dde5b283a543b240 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:56:54 +0530 Subject: [PATCH 12/39] progress: basic typing --- geth/install.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geth/install.py b/geth/install.py index ea61fef0..51e20047 100644 --- a/geth/install.py +++ b/geth/install.py @@ -446,7 +446,6 @@ def generate_dockerfile(docker_install_version=None) -> str: if check_existence.status_code != 200: raise ValueError(f"Unable to find binary at: {gethstore_url}") - dockerfile_template_url = "https://raw.githubusercontent.com/0x0elliot/py-geth/0x0elliot/py-geth-docker/Dockerfile.template" r = requests.get(dockerfile_template_url) if r.status_code != 200: @@ -525,7 +524,8 @@ def install_geth(identifier, platform=None, docker=False, docker_install_version if __name__ == "__main__": try: - identifier = sys.argv[1] + identifier: str = sys.argv[1] + docker: bool = False if len(sys.argv) > 2: docker = sys.argv[2] == "docker" From c1650bbb9fe9f0928da46d1e9f304dc494ae97f4 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 13 Jan 2024 20:20:34 +0530 Subject: [PATCH 13/39] deps: adding dependency for necessary modules --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a8318391..9f9d1ee0 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ py_modules=["geth"], install_requires=[ "semantic-version>=2.6.0", + "requests>=2.28.2" ], python_requires=">=3.8, <4", extras_require=extras_require, From 91ab27541e1cb734ec03b98c23bc760378c31952 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 16:52:37 +0530 Subject: [PATCH 14/39] progress: refactoring the image & container workflow --- geth/install.py | 103 ++++++++++++++++++------------------------------ 1 file changed, 38 insertions(+), 65 deletions(-) diff --git a/geth/install.py b/geth/install.py index 51e20047..c6806290 100644 --- a/geth/install.py +++ b/geth/install.py @@ -6,6 +6,7 @@ import os import stat import subprocess +import docker import requests import sys import tarfile @@ -395,7 +396,9 @@ def map_architecture(architecture: str): return architecture_mapping[architecture] # returns the latest version of geth -def generate_dockerfile(docker_install_version=None) -> str: +def verify_version_and_get_tag(docker_install_version=None) -> str: + # if docker_install_version="latest", return latest tag + GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" if docker_install_version is None: @@ -420,87 +423,57 @@ def generate_dockerfile(docker_install_version=None) -> str: if docker_install_version is None or commit_tag is None: raise ValueError(f"Unable to find docker install version/commit tag: {docker_install_version}/{commit_tag}") - - COMMIT_HASH_API = GITHUB_API + "git/refs/heads/" + commit_tag - r = requests.get(COMMIT_HASH_API) - if r.status_code != 200: - raise ValueError(f"Unexpected status code while checking for commit hash: {r.status_code}") - - commit_data = r.json() - commit_hash = commit_data.get("object", {}).get("sha") - if commit_hash is None: - raise ValueError(f"Unable to find commit hash: {commit_hash}") - + # detect arm or amd64 arc = os.uname().machine architecture = map_architecture(arc) - if docker_install_version.startswith("v"): - docker_install_version = docker_install_version[1:] - - commit_hash = commit_hash[:8] + # check if image ethereum/client-go:{docker_install_version}-{architecture} exists + repository = "ethereum/client-go" + tag = f"{docker_install_version}-{architecture}" - gethstore_url = f"https://gethstore.blob.core.windows.net/builds/geth-linux-{architecture}-{docker_install_version}-{commit_hash}.tar.gz" - - check_existence = requests.head(gethstore_url) - if check_existence.status_code != 200: - raise ValueError(f"Unable to find binary at: {gethstore_url}") - - dockerfile_template_url = "https://raw.githubusercontent.com/0x0elliot/py-geth/0x0elliot/py-geth-docker/Dockerfile.template" - r = requests.get(dockerfile_template_url) + # check if tag exists on docker hub + image_url = f"https://hub.docker.com/v2/repositories/{repository}/tags/{tag}" + r = requests.head(image_url) if r.status_code != 200: - raise ValueError(f"Unable to download Dockerfile.template from github: {r.status_code}") - - template = r.text + raise ValueError(f"Unable to find docker image {tag} from URL: {image_url}") - template = template.replace("${PLATFORM}", architecture) - template = template.replace("${GETH_VERSION}", docker_install_version) - template = template.replace("${COMMIT-HASH}", commit_hash) - - geth_docker_path = os.path.expanduser(f"~/.py-geth/{docker_install_version}") - if not os.path.exists(geth_docker_path): - os.makedirs(geth_docker_path) - - with open(f"{geth_docker_path}/Dockerfile", "w") as f: - f.write(template) + total_image_tag = f"{repository}:{tag}" - print(f"Generated Dockerfile for geth {docker_install_version}/{commit_hash} at {geth_docker_path}!") - return docker_install_version + return total_image_tag -def build_image(docker_install_version=None): - docker_install_version = generate_dockerfile(docker_install_version=docker_install_version) +def build_container(docker_install_version=None): + # get the latest version of geth + tag = verify_version_and_get_tag(docker_install_version=docker_install_version) - # check if "py-geth:{docker_install_version}" exists - import docker + # build image client = docker.from_env() - - tag = f"py-geth:{docker_install_version}" - - try: - client.images.get(tag) - print(f"py-geth:{docker_install_version} already exists, skipping build...") - return - except docker.errors.ImageNotFound: - pass - - geth_docker_folder = os.path.expanduser(f"~/.py-geth/{docker_install_version}") - # build image - print(f"Building image {tag} at {geth_docker_folder}...") - client.images.build(path=geth_docker_folder, tag=tag) - + # check if image exists try: - # check if image exists client.images.get(tag) except docker.errors.ImageNotFound: - raise ValueError(f"Unable to find image {tag} after building it!") + print(f"Pulling image: {tag}") + try: + client.images.pull(tag) + except docker.errors.APIError as e: + raise ValueError(f"Unable to pull image: {tag}") from e - print(f"Successfully built image {tag}!") + # check if container exists + try: + client.containers.get(tag) + except docker.errors.NotFound: + # create container + print(f"Creating container: {tag}") + try: + client.containers.create(tag, detach=True) + except docker.errors.APIError as e: + raise ValueError(f"Unable to create container: {tag}") from e def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: # for testing purposes - build_image(docker_install_version=docker_install_version) + build_container(docker_install_version=docker_install_version) return if platform is None: @@ -525,9 +498,9 @@ def install_geth(identifier, platform=None, docker=False, docker_install_version if __name__ == "__main__": try: identifier: str = sys.argv[1] - docker: bool = False + docker_option: bool = False if len(sys.argv) > 2: - docker = sys.argv[2] == "docker" + docker_option = sys.argv[2] == "docker" except IndexError: print( @@ -535,4 +508,4 @@ def install_geth(identifier, platform=None, docker=False, docker_install_version ) sys.exit(1) - install_geth(identifier, docker=docker) + install_geth(identifier, docker=docker_option) From d328a565c8ad31b90a4a8fe78583eaf8d171185d Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:02:37 +0530 Subject: [PATCH 15/39] progress: refactoring the image & container workflow (1) --- geth/install.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/geth/install.py b/geth/install.py index c6806290..87fb18ac 100644 --- a/geth/install.py +++ b/geth/install.py @@ -396,7 +396,7 @@ def map_architecture(architecture: str): return architecture_mapping[architecture] # returns the latest version of geth -def verify_version_and_get_tag(docker_install_version=None) -> str: +def verify_to_tag(docker_install_version=None) -> str: # if docker_install_version="latest", return latest tag GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" @@ -442,9 +442,9 @@ def verify_version_and_get_tag(docker_install_version=None) -> str: return total_image_tag -def build_container(docker_install_version=None): +def image_fix(docker_install_version=None): # get the latest version of geth - tag = verify_version_and_get_tag(docker_install_version=docker_install_version) + tag = verify_to_tag(docker_install_version=docker_install_version) # build image client = docker.from_env() @@ -452,28 +452,18 @@ def build_container(docker_install_version=None): # check if image exists try: client.images.get(tag) + print(f"Image already exists: {tag}") except docker.errors.ImageNotFound: print(f"Pulling image: {tag}") try: client.images.pull(tag) except docker.errors.APIError as e: raise ValueError(f"Unable to pull image: {tag}") from e - - # check if container exists - try: - client.containers.get(tag) - except docker.errors.NotFound: - # create container - print(f"Creating container: {tag}") - try: - client.containers.create(tag, detach=True) - except docker.errors.APIError as e: - raise ValueError(f"Unable to create container: {tag}") from e def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: # for testing purposes - build_container(docker_install_version=docker_install_version) + image_fix(docker_install_version=docker_install_version) return if platform is None: From b0e6e539c627527e25be108cb36a80c2bd214430 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:04:48 +0530 Subject: [PATCH 16/39] progress: removing Dockerfile.template (not needed anymore) --- Dockerfile.template | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Dockerfile.template diff --git a/Dockerfile.template b/Dockerfile.template deleted file mode 100644 index 2cf1f507..00000000 --- a/Dockerfile.template +++ /dev/null @@ -1,18 +0,0 @@ -# Use a base image with a minimal Linux distribution -FROM alpine:latest - -# Install dependencies -RUN apk update && apk add --no-cache wget - -# COMMIT-HASH=First 8 characters of the commit hash of the Geth version -# GETH_VERSION=Version of Geth -# PLATFORM=Platform of the Geth binary (arm, arm64, amd64, etc.) - -# Download and install Ethereum Geth binary (the plan is to keep updating this) -RUN wget https://gethstore.blob.core.windows.net/builds/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH}.tar.gz -O /tmp/geth.tar.gz && \ - tar -zxvf /tmp/geth.tar.gz -C /tmp/ && \ - mv /tmp/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH}/geth /usr/local/bin/geth && \ - rm -rf /tmp/geth.tar.gz /tmp/geth-linux-${PLATFORM}-${GETH_VERSION}-${COMMIT-HASH} - -# Expose Ethereum network ports -EXPOSE 8545 8546 30303 30303/udp \ No newline at end of file From ee661b85a0c5ba0de146d94f049ab23ffb2ec1f6 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:38:31 +0530 Subject: [PATCH 17/39] progress: create folders for volume in ~/.pygeth --- geth/install.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/geth/install.py b/geth/install.py index 87fb18ac..46814371 100644 --- a/geth/install.py +++ b/geth/install.py @@ -396,7 +396,7 @@ def map_architecture(architecture: str): return architecture_mapping[architecture] # returns the latest version of geth -def verify_to_tag(docker_install_version=None) -> str: +def verify_and_get_tag(docker_install_version=None) -> str: # if docker_install_version="latest", return latest tag GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" @@ -442,9 +442,11 @@ def verify_to_tag(docker_install_version=None) -> str: return total_image_tag -def image_fix(docker_install_version=None): +# return image tag (useful for external use) +# just in case, "latest" was given +def image_fix(docker_install_version=None) -> str: # get the latest version of geth - tag = verify_to_tag(docker_install_version=docker_install_version) + tag = verify_and_get_tag(docker_install_version=docker_install_version) # build image client = docker.from_env() @@ -459,6 +461,19 @@ def image_fix(docker_install_version=None): client.images.pull(tag) except docker.errors.APIError as e: raise ValueError(f"Unable to pull image: {tag}") from e + + # create folder with geth version in ~/.py-geth + geth_version = tag.split(":")[1] + path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, "geth") + ethereum_path = os.path.join(os.path.expanduser("~"), ".ethereum") + + if not os.path.exists(path): + os.makedirs(path) + + if not os.path.exists(ethereum_path): + os.makedirs(ethereum_path) + + return tag def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: From e7d1297b6dfe4ef138cb24d278e5b82c90bf85c2 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 18:10:40 +0530 Subject: [PATCH 18/39] progress: adding docker to setup.py --- geth/install.py | 10 ++++++---- setup.py | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/geth/install.py b/geth/install.py index 46814371..e28f61a5 100644 --- a/geth/install.py +++ b/geth/install.py @@ -444,9 +444,11 @@ def verify_and_get_tag(docker_install_version=None) -> str: # return image tag (useful for external use) # just in case, "latest" was given -def image_fix(docker_install_version=None) -> str: - # get the latest version of geth - tag = verify_and_get_tag(docker_install_version=docker_install_version) +def image_fix(docker_install_version=None, docker_image_tag=None) -> str: + tag = docker_image_tag + if tag is None: + # get the latest version of geth + tag = verify_and_get_tag(docker_install_version=docker_install_version) # build image client = docker.from_env() @@ -461,7 +463,7 @@ def image_fix(docker_install_version=None) -> str: client.images.pull(tag) except docker.errors.APIError as e: raise ValueError(f"Unable to pull image: {tag}") from e - + # create folder with geth version in ~/.py-geth geth_version = tag.split(":")[1] path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, "geth") diff --git a/setup.py b/setup.py index 9f9d1ee0..f6494dfd 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,8 @@ py_modules=["geth"], install_requires=[ "semantic-version>=2.6.0", - "requests>=2.28.2" + "requests>=2.28.2", + "docker>=6.0.1", ], python_requires=">=3.8, <4", extras_require=extras_require, From 7d0f460d3f32689cace3b9127fdcbf78164a3787 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:41:02 +0530 Subject: [PATCH 19/39] progress: more utility, making things make sense --- geth/install.py | 100 +----------------------- geth/process.py | 9 +++ geth/utils/docker.py | 177 +++++++++++++++++++++++++++++++++++++++++++ geth/wrapper.py | 4 +- 4 files changed, 190 insertions(+), 100 deletions(-) create mode 100644 geth/utils/docker.py diff --git a/geth/install.py b/geth/install.py index e28f61a5..f8eb929a 100644 --- a/geth/install.py +++ b/geth/install.py @@ -6,11 +6,11 @@ import os import stat import subprocess -import docker -import requests import sys import tarfile +from geth.utils.docker import image_fix + V1_11_0 = "v1.11.0" V1_11_1 = "v1.11.1" V1_11_2 = "v1.11.2" @@ -381,102 +381,6 @@ def install_from_source_code_release(identifier): } } -def map_architecture(architecture: str): - architecture_mapping = { - "x86_64": "amd64", - "armv7l": "arm", - "arm64": "arm64", - "aarch64": "arm64", - "amd64": "amd64" - } - - if architecture not in architecture_mapping: - raise ValueError(f"Unknown architecture: {architecture}") - - return architecture_mapping[architecture] - -# returns the latest version of geth -def verify_and_get_tag(docker_install_version=None) -> str: - # if docker_install_version="latest", return latest tag - - GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" - - if docker_install_version is None: - docker_install_version = "latest" - else: - docker_install_version = f"{docker_install_version}" - - RELEASES_API = GITHUB_API + "releases/" - - release_url = f"{RELEASES_API}{docker_install_version}" - - r = requests.get(release_url) - if r.status_code == 404: - raise ValueError(f"Unable to find docker install version: {docker_install_version} from URL: {release_url}") - elif r.status_code != 200: - raise ValueError(f"Unexpected status code while checking for geth versions: {r.status_code}") - - release_data = r.json() - if docker_install_version == "latest": - docker_install_version = release_data.get("tag_name") - commit_tag = release_data.get("target_commitish") - - if docker_install_version is None or commit_tag is None: - raise ValueError(f"Unable to find docker install version/commit tag: {docker_install_version}/{commit_tag}") - - # detect arm or amd64 - arc = os.uname().machine - architecture = map_architecture(arc) - - # check if image ethereum/client-go:{docker_install_version}-{architecture} exists - repository = "ethereum/client-go" - tag = f"{docker_install_version}-{architecture}" - - # check if tag exists on docker hub - image_url = f"https://hub.docker.com/v2/repositories/{repository}/tags/{tag}" - r = requests.head(image_url) - if r.status_code != 200: - raise ValueError(f"Unable to find docker image {tag} from URL: {image_url}") - - total_image_tag = f"{repository}:{tag}" - - return total_image_tag - -# return image tag (useful for external use) -# just in case, "latest" was given -def image_fix(docker_install_version=None, docker_image_tag=None) -> str: - tag = docker_image_tag - if tag is None: - # get the latest version of geth - tag = verify_and_get_tag(docker_install_version=docker_install_version) - - # build image - client = docker.from_env() - - # check if image exists - try: - client.images.get(tag) - print(f"Image already exists: {tag}") - except docker.errors.ImageNotFound: - print(f"Pulling image: {tag}") - try: - client.images.pull(tag) - except docker.errors.APIError as e: - raise ValueError(f"Unable to pull image: {tag}") from e - - # create folder with geth version in ~/.py-geth - geth_version = tag.split(":")[1] - path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, "geth") - ethereum_path = os.path.join(os.path.expanduser("~"), ".ethereum") - - if not os.path.exists(path): - os.makedirs(path) - - if not os.path.exists(ethereum_path): - os.makedirs(ethereum_path) - - return tag - def install_geth(identifier, platform=None, docker=False, docker_install_version=None): if docker: # for testing purposes diff --git a/geth/process.py b/geth/process.py index 77e0458a..4f198bdc 100644 --- a/geth/process.py +++ b/geth/process.py @@ -1,5 +1,6 @@ import logging import os +import docker import socket import subprocess import time @@ -52,6 +53,7 @@ class BaseGethProcess(object): _proc = None + container = None def __init__( self, @@ -71,6 +73,10 @@ def __init__( def start(self): if self.is_running: raise ValueError("Already running") + + if self.docker: + self.start_docker() + self.is_running = True logger.info("Launching geth: %s", " ".join(self.command)) @@ -80,6 +86,9 @@ def start(self): stdout=self.stdout, stderr=self.stderr, ) + + def start_docker(self): + def __enter__(self): self.start() diff --git a/geth/utils/docker.py b/geth/utils/docker.py new file mode 100644 index 00000000..4dd61a0a --- /dev/null +++ b/geth/utils/docker.py @@ -0,0 +1,177 @@ +import os +import docker +import requests +from typing import List + +client = docker.from_env() + +# the philosophy of this module is that, for now we will only support +# one geth container running at a time for simplicity, for a single version +# multiple geth containers can be unstable and unpredictable for now + +def map_architecture(architecture: str): + architecture_mapping = { + "x86_64": "amd64", + "armv7l": "arm", + "arm64": "arm64", + "aarch64": "arm64", + "amd64": "amd64" + } + + if architecture not in architecture_mapping: + raise ValueError(f"Unknown architecture: {architecture}") + + return architecture_mapping[architecture] + +# returns the latest version of geth +def verify_and_get_tag(docker_install_version=None) -> str: + # if docker_install_version="latest", return latest tag + + GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" + + if docker_install_version is None: + docker_install_version = "latest" + else: + docker_install_version = f"{docker_install_version}" + + RELEASES_API = GITHUB_API + "releases/" + + release_url = f"{RELEASES_API}{docker_install_version}" + + r = requests.get(release_url) + if r.status_code == 404: + raise ValueError(f"Unable to find docker install version: {docker_install_version} from URL: {release_url}") + elif r.status_code != 200: + raise ValueError(f"Unexpected status code while checking for geth versions: {r.status_code}") + + release_data = r.json() + if docker_install_version == "latest": + docker_install_version = release_data.get("tag_name") + commit_tag = release_data.get("target_commitish") + + if docker_install_version is None or commit_tag is None: + raise ValueError(f"Unable to find docker install version/commit tag: {docker_install_version}/{commit_tag}") + + # detect arm or amd64 + arc = os.uname().machine + architecture = map_architecture(arc) + + # check if image ethereum/client-go:{docker_install_version}-{architecture} exists + repository = "ethereum/client-go" + tag = f"{docker_install_version}-{architecture}" + + # check if tag exists on docker hub + image_url = f"https://hub.docker.com/v2/repositories/{repository}/tags/{tag}" + r = requests.head(image_url) + if r.status_code != 200: + raise ValueError(f"Unable to find docker image {tag} from URL: {image_url}") + + total_image_tag = f"{repository}:{tag}" + + return total_image_tag + +# return image tag (useful for external use) +# just in case, "latest" was given +def image_fix(docker_install_version=None, docker_image_tag=None) -> str: + tag = docker_image_tag + if tag is None: + # get the latest version of geth + tag = verify_and_get_tag(docker_install_version=docker_install_version) + + # check if image exists + try: + client.images.get(tag) + print(f"Image already exists: {tag}") + except docker.errors.ImageNotFound: + print(f"Pulling image: {tag}") + try: + client.images.pull(tag) + except docker.errors.APIError as e: + raise ValueError(f"Unable to pull image: {tag}") from e + + # create folder with geth version in ~/.py-geth + geth_version = tag.split(":")[1] + ethereum_path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, ".ethereum") + + if not os.path.exists(ethereum_path): + os.makedirs(ethereum_path) + + return tag + +# stop and remove containers using image_name +def stop_containers(image_name: str): + containers = image_to_containers(image_name) + for container in containers: + container.stop() + container.remove() + +# returns a list of all containers using image_name +def image_to_containers(image_name: str) -> List[docker.models.containers.Container]: + try: + client.images.get(image_name) + except docker.errors.ImageNotFound: + return [] + + containers = client.containers.list(all=True, filters={"ancestor": image_name}) + if len(containers) == 0: + return [] + else: + return containers + +# returns if container exists and if so, the container id +def check_container_existence(version: str) ->(bool, str): + docker_info_path = os.path.join(os.path.expanduser("~"), ".py-geth", version, ".docker_info") + if not os.path.exists(docker_info_path): + return False, None + + with open(docker_info_path, "r") as f: + container_id = f.read() + + try: + client.containers.get(container_id) + except docker.errors.NotFound: + return False, None + + return True, container_id + +# image must be existing +# this function assumes that image_name has +# the version number in it as it's tag +def start_container(image_name: str): + # check if image exists + try: + client.images.get(image_name) + except docker.errors.ImageNotFound as e: + raise ValueError("Image not found") from e + + image_version_with_arc = image_name.split(":")[1] + image_version = image_version_with_arc.split("-")[0] + + ethereum_path = os.path.join(os.path.expanduser("~"), ".py-geth", image_version, ".ethereum") + + if not os.path.exists(ethereum_path): + os.makedirs(ethereum_path) + + docker_info_path = os.path.join(os.path.expanduser("~"), ".py-geth", image_version, ".docker_info") + + # check if container already exists in ~/.py-geth/{image_version}/.docker_info + with open(docker_info_path, "r") as f: + container_id = f.read() + + # build container with image_name + # and mount ethereum_path to /root/.ethereum + container = client.containers.run( + image_name, + detach=True, + volumes={ + ethereum_path: { + "bind": "/root/.ethereum", + "mode": "rw" + } + } + ) + + with open(docker_info_path, "w") as f: + f.write(container.id) + + return container diff --git a/geth/wrapper.py b/geth/wrapper.py index 4647e6c3..80362839 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -94,7 +94,6 @@ def construct_test_chain_kwargs(**overrides): return overrides - def get_geth_binary_path(): return os.environ.get("GETH_BINARY", "geth") @@ -147,7 +146,7 @@ def construct_popen_command( tx_pool_global_slots=None, tx_pool_price_limit=None, cache=None, - gcmode=None, + gcmode=None ): if geth_executable is None: geth_executable = get_geth_binary_path() @@ -156,6 +155,7 @@ def construct_popen_command( raise ValueError( "No geth executable found. Please ensure geth is installed and " "available on your PATH or use the GETH_BINARY environment variable" + "Or else, use the docker flag to run geth in a docker container" ) if ipc_api is not None: From 424b94f4a23ca810e561966c103ffa4d8f650db6 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 00:59:18 +0530 Subject: [PATCH 20/39] progress: more utility, making things make sense (1) --- geth/process.py | 18 ++++++++++++++++++ geth/utils/docker.py | 38 ++++++++++++++------------------------ 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/geth/process.py b/geth/process.py index 4f198bdc..f9a1aa2b 100644 --- a/geth/process.py +++ b/geth/process.py @@ -6,6 +6,8 @@ import time import warnings +from geth.utils.docker import verify_and_get_tag + try: from urllib.request import ( URLError, @@ -61,12 +63,20 @@ def __init__( stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + docker=False, + client_version_for_docker=None, ): self.geth_kwargs = geth_kwargs self.command = construct_popen_command(**geth_kwargs) self.stdin = stdin self.stdout = stdout self.stderr = stderr + self.docker = docker + self.client: docker.DockerClient = None + self.client_version_for_docker = client_version_for_docker + if self.docker: + # exposing for easier testing + self.client = docker.from_env() is_running = False @@ -88,8 +98,16 @@ def start(self): ) def start_docker(self): + if self.client_version_for_docker is None: + # default to latest + self.client_version_for_docker = "latest" + # check if image exists + image_name = verify_and_get_tag(self.client_version_for_docker) + if self.client_version_for_docker == "latest": + self.client_version_for_docker = image_name.split(":")[1] + def __enter__(self): self.start() return self diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 4dd61a0a..1ad18a78 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -9,6 +9,8 @@ # one geth container running at a time for simplicity, for a single version # multiple geth containers can be unstable and unpredictable for now +# and right now, we will only be supporting linux/unix systems for simplicity + def map_architecture(architecture: str): architecture_mapping = { "x86_64": "amd64", @@ -107,6 +109,9 @@ def stop_containers(image_name: str): # returns a list of all containers using image_name def image_to_containers(image_name: str) -> List[docker.models.containers.Container]: + if image_name == "latest": + image_name = verify_and_get_tag() + try: client.images.get(image_name) except docker.errors.ImageNotFound: @@ -118,21 +123,13 @@ def image_to_containers(image_name: str) -> List[docker.models.containers.Contai else: return containers -# returns if container exists and if so, the container id -def check_container_existence(version: str) ->(bool, str): - docker_info_path = os.path.join(os.path.expanduser("~"), ".py-geth", version, ".docker_info") - if not os.path.exists(docker_info_path): - return False, None - - with open(docker_info_path, "r") as f: - container_id = f.read() - - try: - client.containers.get(container_id) - except docker.errors.NotFound: - return False, None - - return True, container_id +def fix_containers(image_name: str): + containers = image_to_containers(image_name) + for container in containers: + container.stop() + container.remove() + + # image must be existing # this function assumes that image_name has @@ -151,12 +148,8 @@ def start_container(image_name: str): if not os.path.exists(ethereum_path): os.makedirs(ethereum_path) - - docker_info_path = os.path.join(os.path.expanduser("~"), ".py-geth", image_version, ".docker_info") - - # check if container already exists in ~/.py-geth/{image_version}/.docker_info - with open(docker_info_path, "r") as f: - container_id = f.read() + + fix_containers(image_name) # build container with image_name # and mount ethereum_path to /root/.ethereum @@ -171,7 +164,4 @@ def start_container(image_name: str): } ) - with open(docker_info_path, "w") as f: - f.write(container.id) - return container From 817608e68ca9c481e0bebbe88e55e3b7f5071fc2 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:41:38 +0530 Subject: [PATCH 21/39] draft: making start(docker=True) work --- geth/process.py | 15 +++++++++++---- geth/utils/docker.py | 14 ++++++++------ geth/wrapper.py | 6 +++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/geth/process.py b/geth/process.py index f9a1aa2b..6b0d3895 100644 --- a/geth/process.py +++ b/geth/process.py @@ -6,7 +6,7 @@ import time import warnings -from geth.utils.docker import verify_and_get_tag +from geth.utils.docker import start_container, verify_and_get_tag try: from urllib.request import ( @@ -67,7 +67,7 @@ def __init__( client_version_for_docker=None, ): self.geth_kwargs = geth_kwargs - self.command = construct_popen_command(**geth_kwargs) + self.command = construct_popen_command(**geth_kwargs, docker=docker) self.stdin = stdin self.stdout = stdout self.stderr = stderr @@ -83,13 +83,15 @@ def __init__( def start(self): if self.is_running: raise ValueError("Already running") - + if self.docker: self.start_docker() self.is_running = True logger.info("Launching geth: %s", " ".join(self.command)) + + # i will let self.proc be empty if docker is True self.proc = subprocess.Popen( self.command, stdin=self.stdin, @@ -101,12 +103,17 @@ def start_docker(self): if self.client_version_for_docker is None: # default to latest self.client_version_for_docker = "latest" - + # check if image exists image_name = verify_and_get_tag(self.client_version_for_docker) if self.client_version_for_docker == "latest": self.client_version_for_docker = image_name.split(":")[1] + + start_container( + image_name, + commands=self.command, + ) def __enter__(self): self.start() diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 1ad18a78..ff0fa434 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -1,10 +1,13 @@ import os import docker +import logging import requests from typing import List client = docker.from_env() +logger = logging.getLogger(__name__) + # the philosophy of this module is that, for now we will only support # one geth container running at a time for simplicity, for a single version # multiple geth containers can be unstable and unpredictable for now @@ -83,9 +86,9 @@ def image_fix(docker_install_version=None, docker_image_tag=None) -> str: # check if image exists try: client.images.get(tag) - print(f"Image already exists: {tag}") + logger.info(f"Image already exists: {tag}") except docker.errors.ImageNotFound: - print(f"Pulling image: {tag}") + logger.info(f"Pulling image: {tag}") try: client.images.pull(tag) except docker.errors.APIError as e: @@ -129,12 +132,10 @@ def fix_containers(image_name: str): container.stop() container.remove() - - # image must be existing # this function assumes that image_name has # the version number in it as it's tag -def start_container(image_name: str): +def start_container(image_name: str, commands: List[str] = []): # check if image exists try: client.images.get(image_name) @@ -161,7 +162,8 @@ def start_container(image_name: str): "bind": "/root/.ethereum", "mode": "rw" } - } + }, + command=" ".join(commands) ) return container diff --git a/geth/wrapper.py b/geth/wrapper.py index 80362839..bb7b09b6 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -146,11 +146,15 @@ def construct_popen_command( tx_pool_global_slots=None, tx_pool_price_limit=None, cache=None, - gcmode=None + gcmode=None, + docker=False ): if geth_executable is None: geth_executable = get_geth_binary_path() + if docker: + geth_executable = " " + if not is_executable_available(geth_executable): raise ValueError( "No geth executable found. Please ensure geth is installed and " From cf4e4961f34f2ba94644cfa8022191785b1b64eb Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:45:35 +0530 Subject: [PATCH 22/39] draft: plugging it in MainnetGethProcess --- geth/process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geth/process.py b/geth/process.py index 6b0d3895..5d38e173 100644 --- a/geth/process.py +++ b/geth/process.py @@ -235,14 +235,14 @@ def wait_for_dag(self, timeout=0): class MainnetGethProcess(BaseGethProcess): - def __init__(self, geth_kwargs=None): + def __init__(self, geth_kwargs=None, docker=False): if geth_kwargs is None: geth_kwargs = {} if "data_dir" in geth_kwargs: raise ValueError("You cannot specify `data_dir` for a MainnetGethProcess") - super(MainnetGethProcess, self).__init__(geth_kwargs) + super(MainnetGethProcess, self).__init__(geth_kwargs, docker=docker) @property def data_dir(self): From 7c98866690c025bc009345fc3481965f12d8ac07 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:46:49 +0530 Subject: [PATCH 23/39] draft: plugging it in MainnetGethProcess (1) --- geth/wrapper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geth/wrapper.py b/geth/wrapper.py index bb7b09b6..cfe801a1 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -155,11 +155,10 @@ def construct_popen_command( if docker: geth_executable = " " - if not is_executable_available(geth_executable): + if not is_executable_available(geth_executable) and not docker: raise ValueError( "No geth executable found. Please ensure geth is installed and " "available on your PATH or use the GETH_BINARY environment variable" - "Or else, use the docker flag to run geth in a docker container" ) if ipc_api is not None: From e956cb92e3312e70c2fa0e14d39859e80d0e8f6b Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:48:57 +0530 Subject: [PATCH 24/39] draft: plugging it in MainnetGethProcess (2) --- geth/process.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geth/process.py b/geth/process.py index 5d38e173..3435fa17 100644 --- a/geth/process.py +++ b/geth/process.py @@ -1,6 +1,6 @@ import logging import os -import docker +import docker as dockerlib import socket import subprocess import time @@ -72,11 +72,11 @@ def __init__( self.stdout = stdout self.stderr = stderr self.docker = docker - self.client: docker.DockerClient = None + self.client: dockerlib.DockerClient = None self.client_version_for_docker = client_version_for_docker - if self.docker: - # exposing for easier testing - self.client = docker.from_env() + # if self.docker: + # # exposing for easier testing + # self.client = dockerlib.from_env() is_running = False From 37d759e80e4308a2810ba42a5b6723fcc33d2a96 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:05:58 +0530 Subject: [PATCH 25/39] draft: plugging it in MainnetGethProcess (3) --- geth/process.py | 14 +++++++++----- geth/utils/docker.py | 24 ++++++++++++++++-------- geth/wrapper.py | 1 - 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/geth/process.py b/geth/process.py index 3435fa17..37897744 100644 --- a/geth/process.py +++ b/geth/process.py @@ -6,7 +6,7 @@ import time import warnings -from geth.utils.docker import start_container, verify_and_get_tag +from geth.utils.docker import start_container, stop_container, verify_and_get_tag try: from urllib.request import ( @@ -73,10 +73,11 @@ def __init__( self.stderr = stderr self.docker = docker self.client: dockerlib.DockerClient = None + self.container: dockerlib.models.containers.Container = None self.client_version_for_docker = client_version_for_docker - # if self.docker: - # # exposing for easier testing - # self.client = dockerlib.from_env() + if self.docker: + # exposing for easier testing + self.client = dockerlib.from_env() is_running = False @@ -110,7 +111,7 @@ def start_docker(self): if self.client_version_for_docker == "latest": self.client_version_for_docker = image_name.split(":")[1] - start_container( + self.container = start_container( image_name, commands=self.command, ) @@ -122,6 +123,9 @@ def __enter__(self): def stop(self): if not self.is_running: raise ValueError("Not running") + + if self.docker: + stop_container(self.container) if self.proc.poll() is None: kill_proc(self.proc) diff --git a/geth/utils/docker.py b/geth/utils/docker.py index ff0fa434..40a88397 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -103,15 +103,12 @@ def image_fix(docker_install_version=None, docker_image_tag=None) -> str: return tag -# stop and remove containers using image_name -def stop_containers(image_name: str): - containers = image_to_containers(image_name) - for container in containers: - container.stop() - container.remove() +def stop_container(container: docker.models.containers.Container): + container.stop() + container.remove() # returns a list of all containers using image_name -def image_to_containers(image_name: str) -> List[docker.models.containers.Container]: +def image_to_containers(image_name: str, running=False) -> List[docker.models.containers.Container]: if image_name == "latest": image_name = verify_and_get_tag() @@ -120,7 +117,14 @@ def image_to_containers(image_name: str) -> List[docker.models.containers.Contai except docker.errors.ImageNotFound: return [] - containers = client.containers.list(all=True, filters={"ancestor": image_name}) + containers = client.containers.list( + all=True, + filters={ + "ancestor": image_name, + "status": "running" if running else "all" + } + ) + if len(containers) == 0: return [] else: @@ -132,6 +136,10 @@ def fix_containers(image_name: str): container.stop() container.remove() +def check_image_container(image_name: str): + containers = image_to_containers(image_name, running=True) + return len(containers) > 0 + # image must be existing # this function assumes that image_name has # the version number in it as it's tag diff --git a/geth/wrapper.py b/geth/wrapper.py index cfe801a1..2a3974fe 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -97,7 +97,6 @@ def construct_test_chain_kwargs(**overrides): def get_geth_binary_path(): return os.environ.get("GETH_BINARY", "geth") - class CommandBuilder: def __init__(self): self.command = [] From 6cd66e282790cb1f58a69f0003bf4c637bfbc208 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:08:55 +0530 Subject: [PATCH 26/39] draft: plugging it in MainnetGethProcess (4) --- geth/process.py | 2 +- geth/utils/docker.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/geth/process.py b/geth/process.py index 37897744..9cb6acbc 100644 --- a/geth/process.py +++ b/geth/process.py @@ -127,7 +127,7 @@ def stop(self): if self.docker: stop_container(self.container) - if self.proc.poll() is None: + if self.proc.poll() is None and not self.docker: kill_proc(self.proc) self.is_running = False diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 40a88397..356e58c7 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -108,7 +108,7 @@ def stop_container(container: docker.models.containers.Container): container.remove() # returns a list of all containers using image_name -def image_to_containers(image_name: str, running=False) -> List[docker.models.containers.Container]: +def image_to_containers(image_name: str) -> List[docker.models.containers.Container]: if image_name == "latest": image_name = verify_and_get_tag() @@ -121,7 +121,6 @@ def image_to_containers(image_name: str, running=False) -> List[docker.models.co all=True, filters={ "ancestor": image_name, - "status": "running" if running else "all" } ) @@ -136,10 +135,6 @@ def fix_containers(image_name: str): container.stop() container.remove() -def check_image_container(image_name: str): - containers = image_to_containers(image_name, running=True) - return len(containers) > 0 - # image must be existing # this function assumes that image_name has # the version number in it as it's tag From d16fe1b022cafd1bae23847f3bac97bd7660acc6 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:17:05 +0530 Subject: [PATCH 27/39] draft: plugging it in MainnetGethProcess (5) --- geth/wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geth/wrapper.py b/geth/wrapper.py index 2a3974fe..40851863 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -167,7 +167,7 @@ def construct_popen_command( ) builder = CommandBuilder() - if nice and is_nice_available(): + if nice and is_nice_available() and not docker: builder.extend(("nice", "-n", "20")) builder.append(geth_executable) From 458be9baa4236683fe2e93d00ff2086037b0939f Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 15 Jan 2024 02:21:55 +0530 Subject: [PATCH 28/39] draft: making stop() work --- geth/process.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/geth/process.py b/geth/process.py index 9cb6acbc..296b464d 100644 --- a/geth/process.py +++ b/geth/process.py @@ -93,12 +93,13 @@ def start(self): logger.info("Launching geth: %s", " ".join(self.command)) # i will let self.proc be empty if docker is True - self.proc = subprocess.Popen( - self.command, - stdin=self.stdin, - stdout=self.stdout, - stderr=self.stderr, - ) + if not self.docker: + self.proc = subprocess.Popen( + self.command, + stdin=self.stdin, + stdout=self.stdout, + stderr=self.stderr, + ) def start_docker(self): if self.client_version_for_docker is None: @@ -127,8 +128,9 @@ def stop(self): if self.docker: stop_container(self.container) - if self.proc.poll() is None and not self.docker: - kill_proc(self.proc) + if not self.docker: + if self.proc.poll() is None: + kill_proc(self.proc) self.is_running = False From 873542d86ccc3e6e024574df3dd8a7fa2eeb2577 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:38:29 +0530 Subject: [PATCH 29/39] progress: working on self.data_dir for main --- geth/accounts.py | 4 ++-- geth/chain.py | 32 ++++++++++++++++++++++++++++---- geth/process.py | 16 ++++++++-------- geth/wrapper.py | 19 ++++++++++--------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/geth/accounts.py b/geth/accounts.py index 84921669..d63de8ff 100644 --- a/geth/accounts.py +++ b/geth/accounts.py @@ -9,7 +9,7 @@ ) -def get_accounts(data_dir, **geth_kwargs): +def get_accounts(data_dir, docker=False, **geth_kwargs): """ Returns all geth accounts as tuple of hex encoded strings @@ -17,7 +17,7 @@ def get_accounts(data_dir, **geth_kwargs): ... ('0x...', '0x...') """ command, proc = spawn_geth( - dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs) + dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs, docker) ) stdoutdata, stderrdata = proc.communicate() diff --git a/geth/chain.py b/geth/chain.py index 40eb61b1..d89e2852 100644 --- a/geth/chain.py +++ b/geth/chain.py @@ -14,11 +14,37 @@ ) -def get_live_data_dir(): +def get_live_data_dir(docker=False, docker_geth_version=None): """ `py-geth` needs a base directory to store it's chain data. By default this is the directory that `geth` uses as it's `datadir`. """ + + if docker: + if docker_geth_version is None: + raise ValueError( + "Must specify `docker_geth_version` when using `docker=True`" + ) + + if not docker_geth_version.startswith("v"): + docker_geth_version = f"v{docker_geth_version}" + + data_dir = os.path.expanduser( + os.path.join( + "~", + ".py-geth", + docker_geth_version, + ".ethereum", + ) + ) + + # check if the docker data dir exists + if not os.path.exists(data_dir): + raise ValueError( + "The docker data dir does not exist." + f" Are you sure that your volumes have been mounted at {data_dir}?" + ) + if sys.platform == "darwin": data_dir = os.path.expanduser( os.path.join( @@ -54,7 +80,6 @@ def get_live_data_dir(): ) return data_dir - def get_ropsten_data_dir(): return os.path.abspath( os.path.expanduser( @@ -70,12 +95,11 @@ def get_default_base_dir(): return get_live_data_dir() -def get_chain_data_dir(base_dir, name): +def get_chain_data_dir(base_dir, name, docker=False): data_dir = os.path.abspath(os.path.join(base_dir, name)) ensure_path_exists(data_dir) return data_dir - def get_genesis_file_path(data_dir): return os.path.join(data_dir, "genesis.json") diff --git a/geth/process.py b/geth/process.py index 296b464d..280cf32c 100644 --- a/geth/process.py +++ b/geth/process.py @@ -64,7 +64,7 @@ def __init__( stdout=subprocess.PIPE, stderr=subprocess.PIPE, docker=False, - client_version_for_docker=None, + geth_version_docker=None, ): self.geth_kwargs = geth_kwargs self.command = construct_popen_command(**geth_kwargs, docker=docker) @@ -74,7 +74,7 @@ def __init__( self.docker = docker self.client: dockerlib.DockerClient = None self.container: dockerlib.models.containers.Container = None - self.client_version_for_docker = client_version_for_docker + self.geth_version_docker = geth_version_docker if self.docker: # exposing for easier testing self.client = dockerlib.from_env() @@ -102,15 +102,15 @@ def start(self): ) def start_docker(self): - if self.client_version_for_docker is None: + if self.geth_version_docker is None: # default to latest - self.client_version_for_docker = "latest" + self.geth_version_docker = "latest" # check if image exists - image_name = verify_and_get_tag(self.client_version_for_docker) + image_name = verify_and_get_tag(self.geth_version_docker) - if self.client_version_for_docker == "latest": - self.client_version_for_docker = image_name.split(":")[1] + if self.geth_version_docker == "latest": + self.geth_version_docker = image_name.split(":")[1] self.container = start_container( image_name, @@ -252,7 +252,7 @@ def __init__(self, geth_kwargs=None, docker=False): @property def data_dir(self): - return get_live_data_dir() + return get_live_data_dir(self.docker, self.geth_version_docker) class LiveGethProcess(MainnetGethProcess): diff --git a/geth/wrapper.py b/geth/wrapper.py index 40851863..80ac0d28 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -327,15 +327,16 @@ def geth_wrapper(**geth_kwargs): def spawn_geth( - geth_kwargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + geth_kwargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, docker=False ): - command = construct_popen_command(**geth_kwargs) - - proc = subprocess.Popen( - command, - stdin=stdin, - stdout=stdout, - stderr=stderr, - ) + command = construct_popen_command(**geth_kwargs, docker=docker) + + if not docker: + proc = subprocess.Popen( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + ) return command, proc From 88e26b17e965daf226c514c44c410d4f9075da49 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:43:08 +0530 Subject: [PATCH 30/39] progress: working on self.data_dir for main --- geth/accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geth/accounts.py b/geth/accounts.py index d63de8ff..3dbb7e26 100644 --- a/geth/accounts.py +++ b/geth/accounts.py @@ -17,7 +17,7 @@ def get_accounts(data_dir, docker=False, **geth_kwargs): ... ('0x...', '0x...') """ command, proc = spawn_geth( - dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs, docker) + dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs), docker=docker ) stdoutdata, stderrdata = proc.communicate() From 8718dbe85aaa472d890d967acc02d685cdcafab0 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:57:50 +0530 Subject: [PATCH 31/39] progress: cleaning up stuff --- geth/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geth/process.py b/geth/process.py index 280cf32c..5740e35b 100644 --- a/geth/process.py +++ b/geth/process.py @@ -252,7 +252,7 @@ def __init__(self, geth_kwargs=None, docker=False): @property def data_dir(self): - return get_live_data_dir(self.docker, self.geth_version_docker) + return get_live_data_dir(docker=self.docker, docker_geth_version=self.geth_version_docker) class LiveGethProcess(MainnetGethProcess): From bdf31c7fdf8b1f001a58b14a27f468fe83564879 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:14:36 +0530 Subject: [PATCH 32/39] progress: less chances of getting rate-limited --- geth/utils/docker.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 356e58c7..ae44f731 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -78,6 +78,22 @@ def verify_and_get_tag(docker_install_version=None) -> str: # return image tag (useful for external use) # just in case, "latest" was given def image_fix(docker_install_version=None, docker_image_tag=None) -> str: + found_locally = False + + # check all folders initialised in ~/.py-geth that start with "v" + path = os.path.join(os.path.expanduser("~"), ".py-geth") + if os.path.exists(path): + listed = os.listdir(path) + for folder in listed: + if folder.startswith("v"): + if docker_install_version is None: + # use the first folder + docker_install_version = folder + architecture = map_architecture(os.uname().machine) + docker_image_tag = f"ethereum/client-go:{docker_install_version}-{architecture}" + # found_locally = True + break + tag = docker_image_tag if tag is None: # get the latest version of geth From aecfbfb6f8ae3b8d746bbc2eccbe87b5698051f8 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:16:59 +0530 Subject: [PATCH 33/39] progress: less chances of getting rate-limited --- geth/utils/docker.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/geth/utils/docker.py b/geth/utils/docker.py index ae44f731..46ba77bd 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -78,8 +78,6 @@ def verify_and_get_tag(docker_install_version=None) -> str: # return image tag (useful for external use) # just in case, "latest" was given def image_fix(docker_install_version=None, docker_image_tag=None) -> str: - found_locally = False - # check all folders initialised in ~/.py-geth that start with "v" path = os.path.join(os.path.expanduser("~"), ".py-geth") if os.path.exists(path): From 67a93f968115a97b918986161743644f342c5dd1 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:38:44 +0530 Subject: [PATCH 34/39] progress: less chances of getting rate-limited and more dynamic pick ups --- geth/utils/docker.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 46ba77bd..9cdd609f 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -32,6 +32,21 @@ def map_architecture(architecture: str): def verify_and_get_tag(docker_install_version=None) -> str: # if docker_install_version="latest", return latest tag + # check all folders initialised in ~/.py-geth that start with "v" + path = os.path.join(os.path.expanduser("~"), ".py-geth") + if os.path.exists(path) and docker_install_version is None: + listed = os.listdir(path) + for folder in listed: + if folder.startswith("v"): + docker_install_version = folder + # read folder/.docker_tag + tag_path = os.path.join(path, folder, ".docker_tag") + if os.path.exists(tag_path): + with open(tag_path, "r") as f: + tag = f.read() + return tag + logger.warning(f"verify_and_get_tag - Unable to find .docker_tag in {tag_path}") + GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" if docker_install_version is None: @@ -78,25 +93,11 @@ def verify_and_get_tag(docker_install_version=None) -> str: # return image tag (useful for external use) # just in case, "latest" was given def image_fix(docker_install_version=None, docker_image_tag=None) -> str: - # check all folders initialised in ~/.py-geth that start with "v" - path = os.path.join(os.path.expanduser("~"), ".py-geth") - if os.path.exists(path): - listed = os.listdir(path) - for folder in listed: - if folder.startswith("v"): - if docker_install_version is None: - # use the first folder - docker_install_version = folder - architecture = map_architecture(os.uname().machine) - docker_image_tag = f"ethereum/client-go:{docker_install_version}-{architecture}" - # found_locally = True - break - tag = docker_image_tag if tag is None: # get the latest version of geth tag = verify_and_get_tag(docker_install_version=docker_install_version) - + # check if image exists try: client.images.get(tag) @@ -111,7 +112,12 @@ def image_fix(docker_install_version=None, docker_image_tag=None) -> str: # create folder with geth version in ~/.py-geth geth_version = tag.split(":")[1] ethereum_path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, ".ethereum") - + tag_path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, ".docker_tag") + + if not os.path.exists(tag_path): + with open(tag_path, "w") as f: + f.write(tag) + if not os.path.exists(ethereum_path): os.makedirs(ethereum_path) From 52364b36aa40dca83ae34b96f21296ec63059595 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 02:58:25 +0530 Subject: [PATCH 35/39] progress: less chances of getting rate-limited and more dynamic pick ups --- geth/install.py | 5 ++++- geth/utils/docker.py | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/geth/install.py b/geth/install.py index f8eb929a..59263e39 100644 --- a/geth/install.py +++ b/geth/install.py @@ -381,11 +381,14 @@ def install_from_source_code_release(identifier): } } -def install_geth(identifier, platform=None, docker=False, docker_install_version=None): +def install_geth(identifier=None, platform=None, docker=False, docker_install_version=None): if docker: # for testing purposes image_fix(docker_install_version=docker_install_version) return + + if identifier is None: + raise ValueError("Must specify a geth version to install if not using docker") if platform is None: platform = get_platform() diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 9cdd609f..4ee266ec 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -31,10 +31,12 @@ def map_architecture(architecture: str): # returns the latest version of geth def verify_and_get_tag(docker_install_version=None) -> str: # if docker_install_version="latest", return latest tag + print("Version specified: ", docker_install_version) # check all folders initialised in ~/.py-geth that start with "v" path = os.path.join(os.path.expanduser("~"), ".py-geth") - if os.path.exists(path) and docker_install_version is None: + if os.path.exists(path) and docker_install_version is None or docker_install_version == "latest": + print(f"Checking for geth versions in {path}") listed = os.listdir(path) for folder in listed: if folder.startswith("v"): @@ -45,7 +47,10 @@ def verify_and_get_tag(docker_install_version=None) -> str: with open(tag_path, "r") as f: tag = f.read() return tag + print(f"Warning: Unable to find .docker_tag in {tag_path}") logger.warning(f"verify_and_get_tag - Unable to find .docker_tag in {tag_path}") + + print("Querying GitHub API for latest geth version") GITHUB_API = "https://api.github.com/repos/ethereum/go-ethereum/" @@ -110,16 +115,17 @@ def image_fix(docker_install_version=None, docker_image_tag=None) -> str: raise ValueError(f"Unable to pull image: {tag}") from e # create folder with geth version in ~/.py-geth - geth_version = tag.split(":")[1] + geth_version = tag.split(":")[1].split("-")[0] + ethereum_path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, ".ethereum") tag_path = os.path.join(os.path.expanduser("~"), ".py-geth", geth_version, ".docker_tag") - if not os.path.exists(tag_path): - with open(tag_path, "w") as f: - f.write(tag) - if not os.path.exists(ethereum_path): os.makedirs(ethereum_path) + + if not os.path.exists(tag_path): + with open(tag_path, "w+") as f: + f.write(tag) return tag From 1b21eb35c1335c5453b16712da5fb06f1b12f575 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 03:08:45 +0530 Subject: [PATCH 36/39] progress: making states easier to manage. less requests --- geth/process.py | 2 +- geth/utils/docker.py | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/geth/process.py b/geth/process.py index 5740e35b..4b15e004 100644 --- a/geth/process.py +++ b/geth/process.py @@ -110,7 +110,7 @@ def start_docker(self): image_name = verify_and_get_tag(self.geth_version_docker) if self.geth_version_docker == "latest": - self.geth_version_docker = image_name.split(":")[1] + self.geth_version_docker = image_name.split(":")[1].split("-")[0] self.container = start_container( image_name, diff --git a/geth/utils/docker.py b/geth/utils/docker.py index 4ee266ec..faf0d8be 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -35,19 +35,23 @@ def verify_and_get_tag(docker_install_version=None) -> str: # check all folders initialised in ~/.py-geth that start with "v" path = os.path.join(os.path.expanduser("~"), ".py-geth") - if os.path.exists(path) and docker_install_version is None or docker_install_version == "latest": + if os.path.exists(path): # and docker_install_version is None or docker_install_version == "latest": print(f"Checking for geth versions in {path}") listed = os.listdir(path) for folder in listed: if folder.startswith("v"): - docker_install_version = folder - # read folder/.docker_tag - tag_path = os.path.join(path, folder, ".docker_tag") - if os.path.exists(tag_path): - with open(tag_path, "r") as f: - tag = f.read() - return tag - print(f"Warning: Unable to find .docker_tag in {tag_path}") + if docker_install_version == "latest" or docker_install_version is None: + docker_install_version = folder + + if (docker_install_version in folder or folder in docker_install_version): + docker_install_version = folder + # read folder/.docker_tag + tag_path = os.path.join(path, folder, ".docker_tag") + if os.path.exists(tag_path): + with open(tag_path, "r") as f: + tag = f.read() + return tag + print(f"Warning: Unable to find .docker_tag in {tag_path}") logger.warning(f"verify_and_get_tag - Unable to find .docker_tag in {tag_path}") print("Querying GitHub API for latest geth version") From dfdefafd317e5c24ae8a0297c7180b5d0ace9560 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 03:11:14 +0530 Subject: [PATCH 37/39] progress: fixing self.data_dir --- geth/chain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geth/chain.py b/geth/chain.py index d89e2852..9a695496 100644 --- a/geth/chain.py +++ b/geth/chain.py @@ -45,6 +45,8 @@ def get_live_data_dir(docker=False, docker_geth_version=None): f" Are you sure that your volumes have been mounted at {data_dir}?" ) + return data_dir + if sys.platform == "darwin": data_dir = os.path.expanduser( os.path.join( From 8831eacbb33a42925e04c812a24d3c4bb73b72ff Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 06:48:43 +0530 Subject: [PATCH 38/39] progress: saving progress --- geth/accounts.py | 31 ++++++++++++++++++++++++++----- geth/process.py | 3 ++- geth/wrapper.py | 23 +++++++++++++++++++---- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/geth/accounts.py b/geth/accounts.py index 3dbb7e26..2a75e1e8 100644 --- a/geth/accounts.py +++ b/geth/accounts.py @@ -9,7 +9,7 @@ ) -def get_accounts(data_dir, docker=False, **geth_kwargs): +def get_accounts(data_dir, docker_container=None, **geth_kwargs): """ Returns all geth accounts as tuple of hex encoded strings @@ -17,11 +17,32 @@ def get_accounts(data_dir, docker=False, **geth_kwargs): ... ('0x...', '0x...') """ command, proc = spawn_geth( - dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs), docker=docker + dict(data_dir=data_dir, suffix_args=["account", "list"], **geth_kwargs), docker_container=docker_container ) - stdoutdata, stderrdata = proc.communicate() - if proc.returncode: + if docker_container is not None: + print("proc returned: ", proc) + exitcode = proc.exit_code + + stderrdata, stdoutdata = "", "" + + if exitcode != 0: + stderrdata = proc.output + else: + stdoutdata = proc.output + + print("stdoutdata: ", stdoutdata) + print("stderrdata: ", stderrdata) + print("exitcode: ", exitcode) + + condition = exitcode != 0 + + else: + stdoutdata, stderrdata = proc.communicate() + condition = proc.returncode != 0 + exitcode = proc.returncode + + if condition: if "no keys in store" in stderrdata.decode("utf-8"): return tuple() else: @@ -29,7 +50,7 @@ def get_accounts(data_dir, docker=False, **geth_kwargs): format_error_message( "Error trying to list accounts", command, - proc.returncode, + exitcode, stdoutdata, stderrdata, ) diff --git a/geth/process.py b/geth/process.py index 4b15e004..85e05172 100644 --- a/geth/process.py +++ b/geth/process.py @@ -147,7 +147,8 @@ def is_stopped(self): @property def accounts(self): - return get_accounts(**self.geth_kwargs) + print("data_dir: ", self.data_dir) + return get_accounts(data_dir=self.data_dir, **self.geth_kwargs, docker_container=self.container) @property def rpc_enabled(self): diff --git a/geth/wrapper.py b/geth/wrapper.py index 80ac0d28..519bd656 100644 --- a/geth/wrapper.py +++ b/geth/wrapper.py @@ -203,7 +203,10 @@ def construct_popen_command( builder.extend(("--ws.api", ws_api)) if data_dir is not None: - builder.extend(("--datadir", data_dir)) + if docker: + builder.extend(("--datadir", "/root/.ethereum")) + else: + builder.extend(("--datadir", data_dir)) if max_peers is not None: builder.extend(("--maxpeers", max_peers)) @@ -327,11 +330,23 @@ def geth_wrapper(**geth_kwargs): def spawn_geth( - geth_kwargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, docker=False + geth_kwargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, docker_container=None ): - command = construct_popen_command(**geth_kwargs, docker=docker) + docker_opt = False + if docker_container is not None: + docker_opt = True - if not docker: + command = construct_popen_command(**geth_kwargs, docker=docker_opt) + + if docker_opt: + # execute command in docker + + command_str = " ".join(command) + + command = "/usr/local/bin/geth" + command_str + print("Executing command: ", command) + proc = docker_container.exec_run(command, stdin=True, stdout=True, stderr=True) + else: proc = subprocess.Popen( command, stdin=stdin, From 85a78baca539ac75177d8d4646799adfb3aad707 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sun, 21 Jan 2024 06:57:42 +0530 Subject: [PATCH 39/39] progress: saving progress --- geth/process.py | 5 ++++- geth/utils/docker.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/geth/process.py b/geth/process.py index 85e05172..b1232886 100644 --- a/geth/process.py +++ b/geth/process.py @@ -6,7 +6,7 @@ import time import warnings -from geth.utils.docker import start_container, stop_container, verify_and_get_tag +from geth.utils.docker import cleanup_chaindata, start_container, stop_container, verify_and_get_tag try: from urllib.request import ( @@ -116,6 +116,9 @@ def start_docker(self): image_name, commands=self.command, ) + + def cleanup_docker_chain_data(self): + cleanup_chaindata(self.geth_version_docker) def __enter__(self): self.start() diff --git a/geth/utils/docker.py b/geth/utils/docker.py index faf0d8be..abd92c69 100644 --- a/geth/utils/docker.py +++ b/geth/utils/docker.py @@ -1,5 +1,6 @@ import os import docker +import shutil import logging import requests from typing import List @@ -165,6 +166,19 @@ def fix_containers(image_name: str): container.stop() container.remove() +def cleanup_chaindata(version): + if version == "latest" or None: + raise ValueError("Cannot cleanup chaindata for latest/None version") + + if not version.startswith("v"): + version = f"v{version}" + + path = os.path.join(os.path.expanduser("~"), ".py-geth", version, ".ethereum") + if os.path.exists(path): + logger.info(f"Cleaning up chaindata for version {version}") + shutil.rmtree(path) + + # image must be existing # this function assumes that image_name has # the version number in it as it's tag