diff --git a/.config/dictionary.txt b/.config/dictionary.txt new file mode 100644 index 0000000..8ab7b1d --- /dev/null +++ b/.config/dictionary.txt @@ -0,0 +1,25 @@ +Apks +avsexporter +Buildx +cenkalti +CODEOWNERS +codespell +Confirmer +devcontainer +Eigen +eigenda +eigenlayer +ethclient +ethcommon +golangci +Holeksy +holesky +IBLS +markdownlint +Nethermind +promauto +promhttp +Pubkeys +Rpcs +stretchr +twinstake diff --git a/.config/markdownlint.yaml b/.config/markdownlint.yaml new file mode 100644 index 0000000..9ac2f57 --- /dev/null +++ b/.config/markdownlint.yaml @@ -0,0 +1 @@ +line_length: false \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 978c07f..0146e6b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,39 +3,29 @@ "build": { "dockerfile": "Dockerfile" }, - // 👇 Features to add to the Dev Container. More info: https://containers.dev/implementors/features. - // "features": {}, - + "features": { + "ghcr.io/devcontainers/features/go:1": { + "version": "1.23.1" + }, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/gvatsal60/dev-container-features/pre-commit:1": {} + }, // 👇 Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // 👇 Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "sudo apt install -y graphviz", - // 👇 Configure tool-specific properties. "customizations": { - "vscode": { - "extensions":[ - "ms-python.python", - "njpwerner.autodocstring", - "ms-python.vscode-pylance", - "amazonwebservices.aws-toolkit-vscode", + "vscode": { + "extensions": [ + "golang.go", + "Gruntfuggly.todo-tree", "streetsidesoftware.code-spell-checker", - "GitHub.copilot", - "ms-python.isort", - "redhat.vscode-yaml", - "hashicorp.terraform", - "yzhang.markdown-all-in-one", - "DavidAnson.vscode-markdownlint", - "eamodio.gitlens", - "github.vscode-github-actions" + "DavidAnson.vscode-markdownlint" ] - } - }, - "features": { } - + } // 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" -} +} \ No newline at end of file diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..96d56f8 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,20 @@ +name: golangci-lint +on: + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.61.0 diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml deleted file mode 100644 index 9d7669a..0000000 --- a/.github/workflows/publish-docker.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Publish Docker image - -on: - push: - branches: [main] - - workflow_dispatch: - inputs: - image-name: - description: Image name - required: true - default: eigenda-blob-scraper - tag: - description: Image tag - required: true - dockerfile: - description: Dockerfile - required: true - default: Dockerfile - fetch-interval: - description: Fetch interval in seconds, default 1 minute - required: false - default: 60 - api-url: - description: Endpoint to get the blob json data from - required: false - default: https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs - -jobs: - publish-docker: - name: Publish to Docker Hub - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - name: Build and push image to Docker Hub (staging) - run: | - branch=$(echo "${{ github.ref }}" | sed -e "s/refs\/heads\///g") - original_tag=${{ github.event.inputs.tag || '$branch' }} - tag=$(echo "$original_tag" | sed 's/\//-/g') # replace '/' with '-' in tag na - echo "Building image with tag $tag" - - docker buildx build --platform=linux/amd64,linux/arm64 \ - -f ${{ github.event.inputs.dockerfile || 'Dockerfile' }} \ - -t "nethermind/${{ github.event.inputs.image-name }}:$tag" \ - . --push - - docker buildx build --platform=linux/amd64,linux/arm64 \ - -f ${{ github.event.inputs.dockerfile || 'Dockerfile' }} \ - -t "nethermind/${{ github.event.inputs.image-name }}:latest" \ - . --push \ No newline at end of file diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 0000000..ae1d40e --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,56 @@ +name: CI/CD pipeline + +env: + DOCKER_REGISTRY: nethermind.jfrog.io + + REPO_DEV: angkor-oci-local-dev + REPO_STAGING: angkor-oci-local-staging + REPO_PROD: angkor-oci-local-prod + IMAGE_NAME: eigenlayer-onchain-exporter + + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + id-token: write + contents: write + +jobs: + build_docker_image: + runs-on: ubuntu-latest + outputs: + DOCKER_IMAGE_NAME: ${{ env.IMAGE_NAME }} + DOCKER_IMAGE_TAG: ${{ steps.set_tag.outputs.DOCKER_IMAGE_TAG }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Define image tag + id: set_tag + run: | + export DOCKER_IMAGE_TAG=$(git describe --tags) + # This one is to be able to use the image tag in the next steps in this job + echo "DOCKER_IMAGE_TAG=$DOCKER_IMAGE_TAG" >> $GITHUB_ENV + # This one is to be able to use the image tag in the next jobs + echo "DOCKER_IMAGE_TAG=$DOCKER_IMAGE_TAG" >> $GITHUB_OUTPUT + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + run: | + docker login ${{ env.DOCKER_REGISTRY }} -u ${{ secrets.ARTIFACTORY_ANGKOR_USERNAME }} -p ${{ secrets.ARTIFACTORY_ANGKOR_TOKEN_CONTRIBUTOR }} + - name: Build and Push + uses: docker/build-push-action@v6 + with: + context: . + platforms: "linux/amd64" + push: true + tags: | + ${{ env.DOCKER_REGISTRY }}/${{ env.REPO_DEV }}/${{ env.IMAGE_NAME }}:latest diff --git a/.gitignore b/.gitignore index 1c18fbf..67cbe1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,162 +1,52 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll *.so +*.dylib -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy +# Test binary, built with `go test -c` +*.test -# Sphinx documentation -docs/_build/ +# Output of the go coverage tool, specifically when used with LiteIDE +*.out -# PyBuilder -.pybuilder/ -target/ +# Dependency directories (remove the comment below to include it) +# vendor/ -# Jupyter Notebook -.ipynb_checkpoints +# Go workspace file +go.work -# IPython -profile_default/ -ipython_config.py +# IDE-specific files +.idea/ +.vscode/ -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version +# OS-specific files +.DS_Store +Thumbs.db -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py +# Log files +*.log -# Environments +# Environment variables file .env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json +# Build output directory +/build/ -# Pyre type checker -.pyre/ +# Temporary files +/tmp/ +*.tmp -# pytype static type analyzer -.pytype/ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a -# Cython debug symbols -cython_debug/ +# Debug files +debug -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# Project-specific ignore patterns (if any) +# Add any other patterns specific to your project here -resources/* \ No newline at end of file +eoe-config.yml +bin/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b271e29 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +exclude: > + (?x)^( + .devcontainer/devcontainer.json| + .vscode/extensions.json| + .vscode/settings.json| + )$ +repos: + - repo: https://github.com/streetsidesoftware/cspell-cli + rev: v8.7.0 + hooks: + - id: cspell + exclude: > + (?x)^( + \.github/CODEOWNERS| + \.github/workflows/.*\.yml| + \.gitignore| + \.pre-commit-config\.yaml| + )$ + # entry: codespell --relative + # args: [--relative, --no-progress, --no-summary] + name: Spell check with cspell + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.41.0 + hooks: + - id: markdownlint + args: + - "--config=.config/markdownlint.yaml" + - repo: https://github.com/golangci/golangci-lint + rev: v1.61.0 + hooks: + - id: golangci-lint diff --git a/Dockerfile b/Dockerfile index 07a8b1c..3eea862 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,28 @@ -# Use an official Python runtime as a parent image -FROM python:3.9-slim -# Set the working directory in the container -WORKDIR /usr/src/app +# Build stage +FROM golang:1.23-alpine AS builder -# Copy the current directory contents into the container at /usr/src/app +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies +RUN go mod download + +# Copy the source code COPY . . -# Install any needed packages specified in requirements.txt -RUN pip install --no-cache-dir -r requirements.txt +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -o eoe cmd/eoe/main.go + +# Final stage +FROM alpine:latest -# Make port 9600 available to the world outside this container -EXPOSE 9600 +WORKDIR /root/ -# Define environment variable -ENV FETCH_INTERVAL=60 -ENV API_URL=https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs +# Copy the binary from builder +COPY --from=builder /app/eoe . -# Run main.py when the container launches -CMD ["python", "./main.py"] +# Command to run the executable +CMD ["./eoe", "run"] diff --git a/Makefile b/Makefile index 3b4a5fa..a1b528a 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,14 @@ DEFAULT_GOAL := help .PHONY: env -pyenv: ## Activate pyenv - @source venv/bin/activate +run: + go run cmd/eoe/main.go run -upgrade-pip: ## Upgrade pip version - @pip3 install --upgrade pip +build: + go build -o ./bin/eoe cmd/eoe/main.go -install-deps-locally: upgrade-pip ## Install dependencies in project folder - @pip3 install -r requirements.txt -t . +build-docker: + docker build -t eoe . -install-deps: upgrade-pip ## Install dependencies - @pip3 install -r requirements.txt - -freeze: ## Freeze dependencies - @rm -f requirements.txt - @pip3 freeze > requirements.txt - -test: ## Run unit tests - @python -m unittest discover - -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' \ No newline at end of file +pre-commit: + pre-commit run --all-files diff --git a/README.md b/README.md index 6485e07..114b8b4 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,137 @@ -# eigenda-blob-scraper +# EigenLayer AVS OnChain Exporter -A Python application that scrapes the Eigenda website for the latest blob data and exposes it via Prometheus metrics. +EigenLayer AVS OnChain Exporter (EOE) is a tool designed to export on-chain data from various Actively Validator Set (AVS) services within the EigenLayer ecosystem as Prometheus metrics. -## Project Description +## Features -The application gets the blob data in a json form from an EigenDA public endpoint and transforms it into Prometheus metrics every `FETCH_INTERVAL` seconds. The metrics are then exposed via a Prometheus server at port 9600. +### Supported AVSs and Prometheus metrics -The application is deployed via Docker image to DockerHub. The image can be found here: https://hub.docker.com/repository/docker/nethermind/eigenda-blob-scraper. +#### EigenDA -## Environment Variables +For EigenDA, Holeksy and Mainnet are supported and exposes the following metrics: -The application uses the following environment variables: +- `eoe_eigenda_exporter_latest_block{network=""}`: Latest block number that the EigenDA exporter of the specific network has processed. +- `eoe_eigenda_onchain_batches_total{network=""}`: Total number of onchain batches that the EigenDA exporter of the specific network has processed. This is a counter that increments with each block and resets to 0 if the exporter is restarted. +- `eoe_eigenda_onchain_batches{operator="", network="", status=""}`: Number of onchain batches missed by an operator in the specific network. For now the only status is `missed`. +- `eoe_eigenda_onchain_quorum_status{operator="", network="", quorum=""}`: The status of the operator in the specific network and quorum. The value could be 1 if the operator is in quorum, 0 if the operator is not in quorum. +- `eoe_eigenda_exporter_up{avsEnv=""}`: The status of the exporter. The value could be 1 if the exporter is running, 0 if the exporter is not running. -| Variable | Description | Default Value | -| --- | --- | --- | -| API_URL | Endpoint to get the blob json data from | https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs | -| FETCH_INTERVAL | Fetch interval in seconds | 60 | +> If the exporter might track operators already running before it's deployed, set the `eoe_eigenda_onchain_quorum_status` initial value by configuring `operators[i].eigenDAConfig.quorums[j]` to `true` when the operator is in quorum at the exporter's start. -## Docker image +##### Labels -`nethermind/eigenda-blob-scraper:latest` +- `network`: The network name (e.g., `holesky`, `mainnet`). +- `operator`: The operator name (e.g., `nethermind`, `twinstake`). The operator name corresponds to the name specified in the configuration file. +- `quorum`: The quorum index (e.g., `0`, `1`). +- `status`: The status of the onchain batches. Currently, the only supported status is `missed`. If EigenDA developers add more events to the EigenDA contracts, the exporter will be able to report additional statuses. A `signed` status cannot be used because the emitted event does not contain the signers' public keys. -## How to Run the Application +## Installation -### Docker +There are two options for installing the EigenLayer AVS OnChain Exporter: -The application can be run via Docker. The Docker image is available on DockerHub. To run the application, execute the following command: +### Option 1: Building the Docker Image -```bash -docker run -p 9600:9600 nethermind/eigenda-blob-scraper:latest -``` +1. Ensure you have Docker installed on your system. +2. Clone the repository: -or alternatively, if you want to modify the env variables: + ```shell + git clone https://github.com/NethermindEth/eigenlayer-onchain-exporter.git + cd eigenlayer-onchain-exporter + ``` -```bash -docker run -p 9600:9600 -e API_URL=https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs -e FETCH_INTERVAL=60 nethermind/eigenda-blob-scraper:latest -``` +3. Build the Docker image: -### CLI + ```shell + docker build -t eigenlayer-onchain-exporter . + ``` -The application can also be run via the CLI. To run the application, execute the following command: +4. Run the container: -```bash -python3 main.py -``` + ```shell + docker run \ + -p 8080:8080 \ + -v $(pwd)/config.yml:/config.yml \ + eigenlayer-onchain-exporter + ``` + +### Option 2: Building the Go Binary Directly + +1. Ensure you have Go 1.23 or later installed on your system. +2. Clone the repository: + + ```shell + git clone https://github.com/NethermindEth/eigenlayer-onchain-exporter.git + cd eigenlayer-onchain-exporter + ``` + +3. Build the binary: + + ```shell + go build -o eigenlayer-onchain-exporter + ``` + +4. Run the binary: -or alternatively, if you want to modify the env variables: + ```shell + ./eigenlayer-onchain-exporter --config config.yml run + ``` + +Choose the installation method that best suits your needs and environment. + +## Usage + +Run the `eoe --help` command to see all the command options. + +```shell +$ ./bin/eoe --help +EigenLayer On-chain Exporter (eoe) exposes Prometheus metrics about AVS protocols and EigenLayer's Node Operator. + +Usage: + eoe [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + run Run the application + +Flags: + -c, --config string path to config file (default "config.yml") + -h, --help help for eoe + +Use "eoe [command] --help" for more information about a command. +``` -```bash -API_URL=https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs FETCH_INTERVAL=60 python3 main.py +## Configuration + +The application uses a YAML configuration file. Here's an example of the `config.yml`: + +```yaml +operators: + - name: nethermind + address: 0x57b6FdEF3A23B81547df68F44e5524b987755c99 + blsPublicKey: ["8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"] + avsEnvs: + - eigenda-holesky + eigenDAConfig: + quorums: + - 0: false + - name: nethermind + address: 0x110af279aAFfB0d182697d7fC87653838AA5945e + blsPublicKey: ["2358328128321302874738169219641985530311496023056707902743599195833986584402", "20423525555617668586476030951095516580576618542850420469015501514067149320880"] + avsEnvs: + - eigenda-mainnet + eigenDAConfig: + quorums: + - 0: true +rpcs: + - holesky: https://ethereum-holesky-rpc.publicnode.com + - mainnet: https://ethereum-rpc.publicnode.com ``` -## How to Update the Code and Trigger CI/CD +## Structure Overview -The repository uses GitHub Actions for Continuous Integration and Continuous Deployment. The workflow is defined in the `.github/workflows/publish-docker.yml` file. To update the code and trigger the CI/CD pipeline, follow these steps: +![diagram](./img/eoe-diagram.png) -1. Make your code changes locally. -2. Commit and push the changes to the main branch. -3. GitHub Actions will automatically build and push the docker image to DockerHub. +## Contributing -The CI/CD pipeline is triggered on every push to the main branch, but can also be triggered manually by clicking on the "Actions" tab in the repository and selecting the "Publish Docker image" workflow. Then click on the "Run workflow" button. \ No newline at end of file +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/api_client.py b/api_client.py deleted file mode 100644 index c7c75a0..0000000 --- a/api_client.py +++ /dev/null @@ -1,25 +0,0 @@ -import requests - -def fetch_data_from_api(url): - """ - Fetch data from the given API URL. - - Args: - url (str): The URL of the API endpoint. - - Returns: - dict: The JSON response data. - """ - response = requests.get(url) - if response.status_code == 200: - return response.json() - else: - raise Exception(f"Failed to fetch data from API. Status code: {response.status_code}") - -# # Test the function with the provided URL -# test_url = "https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs" -# try: -# data = fetch_data_from_api(test_url) -# print("Data fetched successfully.") -# except Exception as e: -# print(str(e)) diff --git a/cmd/eoe/main.go b/cmd/eoe/main.go new file mode 100644 index 0000000..828dbf3 --- /dev/null +++ b/cmd/eoe/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "log/slog" + "os" + "os/signal" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/cli" +) + +func main() { + rootCmd := cli.RootCmd() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + go func() { + <-signalChan + cancel() + }() + err := rootCmd.ExecuteContext(ctx) + if err != nil { + slog.Error("error executing root command", "error", err) + os.Exit(1) + } +} diff --git a/cspell.config.yaml b/cspell.config.yaml new file mode 100644 index 0000000..f14b743 --- /dev/null +++ b/cspell.config.yaml @@ -0,0 +1,25 @@ +version: "0.2" +ignorePaths: + - .devcontainer + - .vscode + - internal/avs/eigenda/contracts/abi + - go.sum + - go.mod +dictionaryDefinitions: + - name: words + path: .config/dictionary.txt + addWords: true +dictionaries: + - en_US + - bash + - words + - softwareTerms + - misc +words: [] +ignoreWords: [] +import: [] +enableFiletypes: + - go + - yaml + - yml + - md diff --git a/docker-compose.yml b/docker-compose.yml index 8fee0f9..ae45c44 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,20 @@ -version: '3' - services: - scraper: + onchain-exporter: + container_name: eigenlayer-onchain-exporter build: . - ports: - - "9600:9600" + expose: + - "9090" restart: unless-stopped - environment: - - FETCH_INTERVAL=60 + volumes: + - ./eoe-config.yml:/root/eoe-config.yml prometheus: + container_name: eoe-prometheus image: prom/prometheus volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" depends_on: - - scraper + - onchain-exporter restart: unless-stopped diff --git a/e2e/e2e.go b/e2e/e2e.go new file mode 100644 index 0000000..5a9c4ee --- /dev/null +++ b/e2e/e2e.go @@ -0,0 +1,56 @@ +package e2e + +import ( + "os/exec" + "path/filepath" + "runtime" + "testing" +) + +type E2ETestCase struct { + T *testing.T + TestDir string + RepoPath string + BinaryName string +} + +func (e *E2ETestCase) BinaryPath() string { + binaryName := e.BinaryName + if runtime.GOOS == "windows" { + binaryName += ".exe" + } + return filepath.Join(e.TestDir, binaryName) +} + +func (e *E2ETestCase) InstallGoModules() { + e.T.Helper() + cmd := exec.Command("go", "mod", "download") + cmd.Dir = e.RepoPath + e.T.Logf("Installing Go modules in %s", e.RepoPath) + if err := cmd.Run(); err != nil { + e.T.Fatalf("error installing Go modules: %v", err) + } else { + e.T.Logf("Go modules installed") + } +} + +func (e *E2ETestCase) Build() { + e.T.Helper() + e.T.Logf("Building binary to %s", e.BinaryPath()) + err := exec.Command("go", "build", "-o", e.BinaryPath(), filepath.Join(e.RepoPath, "cmd", e.BinaryName, "main.go")).Run() + if err != nil { + e.T.Fatalf("error building binary: %v", err) + } else { + e.T.Logf("binary built") + } +} + +func CheckGoInstalled(t *testing.T) { + t.Helper() + err := exec.Command("go", "version").Run() + if err != nil { + t.Fatalf("error checking Go installation: %v", err) + } else { + t.Logf("Go installed") + } +} diff --git a/e2e/eoe/e2e.go b/e2e/eoe/e2e.go new file mode 100644 index 0000000..0978a8e --- /dev/null +++ b/e2e/eoe/e2e.go @@ -0,0 +1,65 @@ +package e2e + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + base "github.com/NethermindEth/eigenlayer-onchain-exporter/e2e" +) + +type ( + e2eArranger func(t *testing.T, testDir string) error + e2eAct func(t *testing.T, testDir string) *exec.Cmd + e2eAssert func(t *testing.T) +) + +type e2eEOETestCase struct { + base.E2ETestCase + arranger e2eArranger + act e2eAct + assert e2eAssert + pid int +} + +func newE2eEOETestCase(t *testing.T, arranger e2eArranger, act e2eAct, assert e2eAssert) *e2eEOETestCase { + t.Helper() + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + tc := &e2eEOETestCase{ + E2ETestCase: base.E2ETestCase{ + T: t, + TestDir: t.TempDir(), + RepoPath: filepath.Dir(filepath.Dir(wd)), + BinaryName: "eoe", + }, + arranger: arranger, + act: act, + assert: assert, + } + t.Logf("Creating new E2E test case (%p). TestDir: %s", tc, tc.TestDir) + base.CheckGoInstalled(t) + tc.E2ETestCase.InstallGoModules() + tc.E2ETestCase.Build() + return tc +} + +func (e *e2eEOETestCase) run() { + // Cleanup environment before and after test + if e.arranger != nil { + err := e.arranger(e.T, e.TestDir) + if err != nil { + e.T.Fatalf("error in Arrange step: %v", err) + } + } + if e.act != nil { + cmd := e.act(e.T, e.TestDir) + e.pid = cmd.Process.Pid + } + if e.assert != nil { + e.assert(e.T) + } +} diff --git a/e2e/eoe/eigenda_test.go b/e2e/eoe/eigenda_test.go new file mode 100644 index 0000000..a0c99c5 --- /dev/null +++ b/e2e/eoe/eigenda_test.go @@ -0,0 +1,156 @@ +package e2e + +import ( + "io" + "net/http" + "os" + "os/exec" + "testing" + "time" + + base "github.com/NethermindEth/eigenlayer-onchain-exporter/e2e" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestEigenDAExporterUpMetric(t *testing.T) { + var ( + cmdErr error + prometheusResp *http.Response + prometheusErr error + ) + + e2eTestCase := newE2eEOETestCase(t, + // Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "debug", + }) + return nil + }, + // Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, _ := base.RunCommandCMD(t, appPath, "eoe", "run") + + // Wait for the Prometheus server to start and metrics to be collected + time.Sleep(10 * time.Second) + + // Get Prometheus metrics + prometheusResp, prometheusErr = http.Get("http://localhost:9090/metrics") + + // Stop EOE + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + + // Wait for EOE to exit + cmdErr = cmd.Wait() + return cmd + }, + // Assert + func(t *testing.T) { + // Check command ran successfully + assert.NoError(t, cmdErr) + + // Check Prometheus metrics were collected successfully + assert.NoError(t, prometheusErr) + assert.Equal(t, http.StatusOK, prometheusResp.StatusCode) + + // Check for the presence of eigenda_exporter_up metrics + responseBody, err := io.ReadAll(prometheusResp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %s", err) + } + assert.Contains(t, string(responseBody), "eigenda_exporter_up") + }, + ) + + e2eTestCase.run() +} + +func TestEigenDAExporterLatestBlockMetric(t *testing.T) { + var ( + cmdErr error + prometheusResp *http.Response + prometheusErr error + ) + + e2eTestCase := newE2eEOETestCase(t, + // Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "debug", + }) + return nil + }, + // Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, _ := base.RunCommandCMD(t, appPath, "eoe", "run") + + // Wait for the Prometheus server to start and metrics to be collected + time.Sleep(time.Minute) + + // Get Prometheus metrics + prometheusResp, prometheusErr = http.Get("http://localhost:9090/metrics") + + // Stop EOE + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + + // Wait for EOE to exit + cmdErr = cmd.Wait() + return cmd + }, + // Assert + func(t *testing.T) { + // Check command ran successfully + assert.NoError(t, cmdErr) + + // Check Prometheus metrics were collected successfully + assert.NoError(t, prometheusErr) + assert.Equal(t, http.StatusOK, prometheusResp.StatusCode) + + // Check for the presence of eigenda_exporter_latest_block metric + responseBody, err := io.ReadAll(prometheusResp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %s", err) + } + assert.Contains(t, string(responseBody), "eoe_eigenda_exporter_latest_block") + }, + ) + + e2eTestCase.run() +} diff --git a/e2e/eoe/run_test.go b/e2e/eoe/run_test.go new file mode 100644 index 0000000..ba22b2b --- /dev/null +++ b/e2e/eoe/run_test.go @@ -0,0 +1,275 @@ +package e2e + +import ( + "net/http" + "os" + "os/exec" + "testing" + "time" + + base "github.com/NethermindEth/eigenlayer-onchain-exporter/e2e" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestE2E_ConfigNotFound(t *testing.T) { + // Test context + var ( + cmdErr error + stdErr string + ) + // Build test case + e2eTestCase := newE2eEOETestCase(t, + //Arrange + func(t *testing.T, appPath string) error { + return nil + }, + //Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, stdErrReader := base.RunCommandCMD(t, appPath, "eoe", "run") + go copyOutputString(t, stdErrReader, &stdErr) + cmdErr = cmd.Wait() + return cmd + }, + //Assert + func(t *testing.T) { + // CMD should fail + assert.Error(t, cmdErr) + // Check if the error message contains the expected error + assert.Contains(t, stdErr, "Error: open eoe-config.yml: no such file or directory") + }, + ) + // Run test case + e2eTestCase.run() +} + +func TestE2E_ConfigFound(t *testing.T) { + // Test context + var ( + cmdErr error + ) + // Build test case + e2eTestCase := newE2eEOETestCase(t, + //Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "debug", + }) + return nil + }, + //Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, _ := base.RunCommandCMD(t, appPath, "eoe", "run") + + time.Sleep(2 * time.Second) + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + + cmdErr = cmd.Wait() + return cmd + }, + //Assert + func(t *testing.T) { + // CMD should fail + assert.NoError(t, cmdErr) + }, + ) + // Run test case + e2eTestCase.run() +} + +func TestE2E_LogLevelDebug(t *testing.T) { + var ( + cmdErr error + stdErr string + ) + + e2eTestCase := newE2eEOETestCase(t, + // Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "debug", + }) + return nil + }, + // Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, stdErrReader := base.RunCommandCMD(t, appPath, "eoe", "run") + + // Capture the output + go func() { + copyOutputString(t, stdErrReader, &stdErr) + }() + + // Let it run for a short time + time.Sleep(2 * time.Second) + + // Stop the command + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + + cmdErr = cmd.Wait() + return cmd + }, + // Assert + func(t *testing.T) { + assert.NoError(t, cmdErr) + assert.Contains(t, stdErr, "DEBUG") + }, + ) + + e2eTestCase.run() +} + +func TestE2E_LogLevelInfo(t *testing.T) { + var ( + cmdErr error + stdErr string + ) + + e2eTestCase := newE2eEOETestCase(t, + // Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "info", + }) + return nil + }, + // Act + func(t *testing.T, appPath string) *exec.Cmd { + cmd, _, stdErrReader := base.RunCommandCMD(t, appPath, "eoe", "run") + + // Capture the output + go func() { + copyOutputString(t, stdErrReader, &stdErr) + }() + + // Let it run for a short time + time.Sleep(2 * time.Second) + + // Stop the command + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + + cmdErr = cmd.Wait() + return cmd + }, + // Assert + func(t *testing.T) { + assert.NoError(t, cmdErr) + assert.Contains(t, stdErr, "INFO") + }, + ) + + e2eTestCase.run() +} + +func TestE2E_Prometheus(t *testing.T) { + // Test context + var ( + cmdErr error + prometheusResp *http.Response + prometheusErr error + ) + // Build test case + e2eTestCase := newE2eEOETestCase(t, + //Arrange + func(t *testing.T, appPath string) error { + setupConfigFile(t, appPath, &config.Config{ + Operators: []config.OperatorConfig{ + { + Name: "nethermind", + Address: "0x57b6FdEF3A23B81547df68F44e5524b987755c99", + BLSPublicKey: [2]string{"8888183187486914528692107799849671390221086122063975348075796070706039667533", "1162660161480410110225128994312394399428655142287492115882227161635275660953"}, + AVSEnvs: []string{"eigenda-holesky"}, + EigenDAConfig: config.EigenDAConfig{ + Quorums: map[int]bool{ + 0: true, + }, + }, + }, + }, + RPCs: map[string]string{ + "holesky": "https://ethereum-holesky-rpc.publicnode.com", + }, + LogLevel: "debug", + }) + return nil + }, + // Act + func(t *testing.T, appPath string) *exec.Cmd { + // Run EOE + cmd, _, _ := base.RunCommandCMD(t, appPath, "eoe", "run") + // Wait for the Prometheus server to start + time.Sleep(2 * time.Second) + // Get Prometheus metrics + prometheusResp, prometheusErr = http.Get("http://localhost:9090/metrics") + // Stop EOE + if err := cmd.Process.Signal(os.Interrupt); err != nil { + t.Fatalf("Failed to send interrupt signal: %s", err) + } + // Wait for EOE to exit + cmdErr = cmd.Wait() + return cmd + }, + // Assert + func(t *testing.T) { + assert.NoError(t, prometheusErr) + assert.Equal(t, http.StatusOK, prometheusResp.StatusCode) + assert.NoError(t, cmdErr) + }, + ) + // Run test case + e2eTestCase.run() +} diff --git a/e2e/eoe/utils.go b/e2e/eoe/utils.go new file mode 100644 index 0000000..ffbe84e --- /dev/null +++ b/e2e/eoe/utils.go @@ -0,0 +1,37 @@ +package e2e + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "gopkg.in/yaml.v2" +) + +func copyOutputString(t *testing.T, reader io.ReadCloser, out *string) { + t.Helper() + outBuffer := new(bytes.Buffer) + _, err := io.Copy(outBuffer, reader) + if err != nil { + panic("Failed to copy output: " + err.Error()) + } + *out = outBuffer.String() +} + +func setupConfigFile(t *testing.T, appPath string, config *config.Config) { + t.Helper() + t.Logf("appPath: %s", appPath) + configFile, err := os.Create(filepath.Join(appPath, "eoe-config.yml")) + if err != nil { + t.Fatalf("Failed to create config file: %s", err) + } + defer configFile.Close() + + encoder := yaml.NewEncoder(configFile) + if err := encoder.Encode(config); err != nil { + t.Fatalf("Failed to write config to file: %s", err) + } +} diff --git a/e2e/utils.go b/e2e/utils.go new file mode 100644 index 0000000..bef8a8b --- /dev/null +++ b/e2e/utils.go @@ -0,0 +1,55 @@ +package e2e + +import ( + "io" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func RunCommand(t *testing.T, path string, binaryName string, args ...string) error { + _, _, err := runCommandOutput(t, path, binaryName, args...) + return err +} + +func RunCommandCMD(t *testing.T, path string, binaryName string, args ...string) (cmd *exec.Cmd, stdOut io.ReadCloser, stdErr io.ReadCloser) { + t.Helper() + t.Logf("Running command: %s %s", binaryName, strings.Join(args, " ")) + cmd = exec.Command(filepath.Join(path, binaryName), args...) + + cmd.Dir = path + + stdOut, err := cmd.StdoutPipe() + if err != nil { + t.Fatalf("Failed to pipe stdout: %s", err) + } + stdErr, err = cmd.StderrPipe() + if err != nil { + t.Fatalf("Failed to pipe stderr: %s", err) + } + + if err := cmd.Start(); err != nil { + t.Fatalf("Failed to start command: %s %s", binaryName, strings.Join(args, " ")) + } + + return cmd, stdOut, stdErr +} + +func runCommandOutput(t *testing.T, path string, binaryName string, args ...string) ([]byte, *exec.Cmd, error) { + t.Helper() + t.Logf("Binary path: %s", path) + t.Logf("Running command: %s %s", binaryName, strings.Join(args, " ")) + cmd := exec.Command(path, args...) + out, err := cmd.CombinedOutput() + t.Logf("===== OUTPUT =====\n%s\n==================", out) + return out, cmd, err +} + +func LogAndPipeError(t *testing.T, prefix string, err error) error { + t.Helper() + if err != nil { + t.Log(prefix, err) + } + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..780caf3 --- /dev/null +++ b/go.mod @@ -0,0 +1,67 @@ +module github.com/NethermindEth/eigenlayer-onchain-exporter + +go 1.23.1 + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 + github.com/ethereum/go-ethereum v1.14.8 + github.com/prometheus/client_golang v1.20.3 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/holiman/uint256 v1.3.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ca72654 --- /dev/null +++ b/go.sum @@ -0,0 +1,236 @@ +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= +github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig= +github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/img/eoe-diagram.png b/img/eoe-diagram.png new file mode 100644 index 0000000..1e1f0bd Binary files /dev/null and b/img/eoe-diagram.png differ diff --git a/internal/avs/eigenda/abi.go b/internal/avs/eigenda/abi.go new file mode 100644 index 0000000..f80d799 --- /dev/null +++ b/internal/avs/eigenda/abi.go @@ -0,0 +1,70 @@ +package eigenda + +import ( + _ "embed" + "encoding/json" + "fmt" + "math/big" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/avs/eigenda/contracts" +) + +type confirmBatchInput struct { + /* The confirmBatch input also has a 0th input which is not used. A new + type could be created and added here if it becomes necessary.*/ + + NonSignerStakesAndSignature nonSignerStakesAndSignature // input 1 +} + +type nonSignerStakesAndSignature struct { + NonSignerQuorumBitmapIndices []uint32 `json:"nonSignerQuorumBitmapIndices"` + NonSignerPubkeys []g1Point `json:"nonSignerPubkeys"` + QuorumApks []g1Point `json:"quorumApks"` + ApkG2 g2Point `json:"apkG2"` + Sigma g1Point `json:"sigma"` + QuorumApkIndices []uint32 `json:"quorumApkIndices"` + TotalStakeIndices []uint32 `json:"totalStakeIndices"` + NonSignerStakeIndices [][]uint32 `json:"nonSignerStakeIndices"` +} + +type g1Point struct { + X *big.Int `json:"X"` + Y *big.Int `json:"Y"` +} + +type g2Point struct { + X [2]*big.Int `json:"x"` + Y [2]*big.Int `json:"y"` +} + +func unpackConfirmBatchInput(avsEnv string, data []byte) (*confirmBatchInput, error) { + contract, err := contracts.GetServiceManagerContract(avsEnv) + if err != nil { + return nil, fmt.Errorf("failed to get service manager contract: %v", err) + } + + method, exists := contract.Abi.Methods["confirmBatch"] + if !exists { + return nil, fmt.Errorf("confirmBatch method not found in ABI") + } + + inputs, err := method.Inputs.Unpack(data) + if err != nil { + return nil, fmt.Errorf("failed to unpack confirmBatch input: %v", err) + } + + // Unpack nonSignerStakesAndSignature + var nonSignerStakesAndSignature nonSignerStakesAndSignature + jsonRaw, err := json.Marshal(inputs[1]) + if err != nil { + return nil, fmt.Errorf("failed to marshal nonSignerStakesAndSignature: %v", err) + } + err = json.Unmarshal(jsonRaw, &nonSignerStakesAndSignature) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal nonSignerStakesAndSignature: %v", err) + } + + return &confirmBatchInput{ + NonSignerStakesAndSignature: nonSignerStakesAndSignature, + }, nil +} diff --git a/internal/avs/eigenda/contracts/abi/holesky-bls-apk-registry.json b/internal/avs/eigenda/contracts/abi/holesky-bls-apk-registry.json new file mode 100644 index 0000000..3006195 --- /dev/null +++ b/internal/avs/eigenda/contracts/abi/holesky-bls-apk-registry.json @@ -0,0 +1,619 @@ +[ + { + "inputs": [ + { + "internalType": "contract IRegistryCoordinator", + "name": "_registryCoordinator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN254.G1Point", + "name": "pubkeyG1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "indexed": false, + "internalType": "struct BN254.G2Point", + "name": "pubkeyG2", + "type": "tuple" + } + ], + "name": "NewPubkeyRegistration", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "OperatorAddedToQuorums", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "OperatorRemovedFromQuorums", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "apkHistory", + "outputs": [ + { + "internalType": "bytes24", + "name": "apkHash", + "type": "bytes24" + }, + { + "internalType": "uint32", + "name": "updateBlockNumber", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nextUpdateBlockNumber", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "name": "currentApk", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "deregisterOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "getApk", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "blockNumber", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getApkHashAtBlockNumberAndIndex", + "outputs": [ + { + "internalType": "bytes24", + "name": "", + "type": "bytes24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "getApkHistoryLength", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getApkIndicesAtBlockNumber", + "outputs": [ + { + "internalType": "uint32[]", + "name": "", + "type": "uint32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getApkUpdateAtIndex", + "outputs": [ + { + "components": [ + { + "internalType": "bytes24", + "name": "apkHash", + "type": "bytes24" + }, + { + "internalType": "uint32", + "name": "updateBlockNumber", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nextUpdateBlockNumber", + "type": "uint32" + } + ], + "internalType": "struct IBLSApkRegistry.ApkUpdate", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "pubkeyHash", + "type": "bytes32" + } + ], + "name": "getOperatorFromPubkeyHash", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getOperatorId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getRegisteredPubkey", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "initializeQuorum", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "operatorToPubkey", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "operatorToPubkeyHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "pubkeyHashToOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyRegistrationSignature", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyG1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "pubkeyG2", + "type": "tuple" + } + ], + "internalType": "struct IBLSApkRegistry.PubkeyRegistrationParams", + "name": "params", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyRegistrationMessageHash", + "type": "tuple" + } + ], + "name": "registerBLSPublicKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registryCoordinator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/internal/avs/eigenda/contracts/abi/holesky-service-manager.json b/internal/avs/eigenda/contracts/abi/holesky-service-manager.json new file mode 100644 index 0000000..bc83b9a --- /dev/null +++ b/internal/avs/eigenda/contracts/abi/holesky-service-manager.json @@ -0,0 +1,1126 @@ +[ + { + "inputs": [ + { + "internalType": "contract IAVSDirectory", + "name": "__avsDirectory", + "type": "address" + }, + { + "internalType": "contract IRewardsCoordinator", + "name": "__rewardsCoordinator", + "type": "address" + }, + { + "internalType": "contract IRegistryCoordinator", + "name": "__registryCoordinator", + "type": "address" + }, + { + "internalType": "contract IStakeRegistry", + "name": "__stakeRegistry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "batchHeaderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "batchId", + "type": "uint32" + } + ], + "name": "BatchConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "batchConfirmer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "BatchConfirmerStatusChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IPauserRegistry", + "name": "pauserRegistry", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IPauserRegistry", + "name": "newPauserRegistry", + "type": "address" + } + ], + "name": "PauserRegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "prevRewardsInitiator", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRewardsInitiator", + "type": "address" + } + ], + "name": "RewardsInitiatorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "StaleStakesForbiddenUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "BLOCK_STALE_MEASURE", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STORE_DURATION_BLOCKS", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "THRESHOLD_DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "avsDirectory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchId", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "name": "batchIdToBatchMetadataHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blsApkRegistry", + "outputs": [ + { + "internalType": "contract IBLSApkRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "uint32[]", + "name": "nonSignerQuorumBitmapIndices", + "type": "uint32[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "nonSignerPubkeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "quorumApks", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "quorumApkIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[]", + "name": "totalStakeIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[][]", + "name": "nonSignerStakeIndices", + "type": "uint32[][]" + } + ], + "internalType": "struct IBLSSignatureChecker.NonSignerStakesAndSignature", + "name": "params", + "type": "tuple" + } + ], + "name": "checkSignatures", + "outputs": [ + { + "components": [ + { + "internalType": "uint96[]", + "name": "signedStakeForQuorum", + "type": "uint96[]" + }, + { + "internalType": "uint96[]", + "name": "totalStakeForQuorum", + "type": "uint96[]" + } + ], + "internalType": "struct IBLSSignatureChecker.QuorumStakeTotals", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "blobHeadersRoot", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signedStakeForQuorums", + "type": "bytes" + }, + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + } + ], + "internalType": "struct IEigenDAServiceManager.BatchHeader", + "name": "batchHeader", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint32[]", + "name": "nonSignerQuorumBitmapIndices", + "type": "uint32[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "nonSignerPubkeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "quorumApks", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "quorumApkIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[]", + "name": "totalStakeIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[][]", + "name": "nonSignerStakeIndices", + "type": "uint32[][]" + } + ], + "internalType": "struct IBLSSignatureChecker.NonSignerStakesAndSignature", + "name": "nonSignerStakesAndSignature", + "type": "tuple" + } + ], + "name": "confirmBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "contract IStrategy", + "name": "strategy", + "type": "address" + }, + { + "internalType": "uint96", + "name": "multiplier", + "type": "uint96" + } + ], + "internalType": "struct IRewardsCoordinator.StrategyAndMultiplier[]", + "name": "strategiesAndMultipliers", + "type": "tuple[]" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "startTimestamp", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "duration", + "type": "uint32" + } + ], + "internalType": "struct IRewardsCoordinator.RewardsSubmission[]", + "name": "rewardsSubmissions", + "type": "tuple[]" + } + ], + "name": "createAVSRewardsSubmission", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delegation", + "outputs": [ + { + "internalType": "contract IDelegationManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "deregisterOperatorFromAVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getOperatorRestakedStrategies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRestakeableStrategies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "_pauserRegistry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_initialPausedStatus", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_initialOwner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_batchConfirmers", + "type": "address[]" + }, + { + "internalType": "address", + "name": "_rewardsInitiator", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isBatchConfirmer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + } + ], + "name": "latestServeUntilBlock", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "index", + "type": "uint8" + } + ], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauserRegistry", + "outputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumAdversaryThresholdPercentages", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumConfirmationThresholdPercentages", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumNumbersRequired", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + } + ], + "internalType": "struct ISignatureUtils.SignatureWithSaltAndExpiry", + "name": "operatorSignature", + "type": "tuple" + } + ], + "name": "registerOperatorToAVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registryCoordinator", + "outputs": [ + { + "internalType": "contract IRegistryCoordinator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsInitiator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_batchConfirmer", + "type": "address" + } + ], + "name": "setBatchConfirmer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "newPauserRegistry", + "type": "address" + } + ], + "name": "setPauserRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRewardsInitiator", + "type": "address" + } + ], + "name": "setRewardsInitiator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "setStaleStakesForbidden", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakeRegistry", + "outputs": [ + { + "internalType": "contract IStakeRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "staleStakesForbidden", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "taskNumber", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "apk", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + } + ], + "name": "trySignatureAndApkVerification", + "outputs": [ + { + "internalType": "bool", + "name": "pairingSuccessful", + "type": "bool" + }, + { + "internalType": "bool", + "name": "siganatureIsValid", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_metadataURI", + "type": "string" + } + ], + "name": "updateAVSMetadataURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/internal/avs/eigenda/contracts/abi/mainnet-bls-apk-registry.json b/internal/avs/eigenda/contracts/abi/mainnet-bls-apk-registry.json new file mode 100644 index 0000000..3006195 --- /dev/null +++ b/internal/avs/eigenda/contracts/abi/mainnet-bls-apk-registry.json @@ -0,0 +1,619 @@ +[ + { + "inputs": [ + { + "internalType": "contract IRegistryCoordinator", + "name": "_registryCoordinator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct BN254.G1Point", + "name": "pubkeyG1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "indexed": false, + "internalType": "struct BN254.G2Point", + "name": "pubkeyG2", + "type": "tuple" + } + ], + "name": "NewPubkeyRegistration", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "OperatorAddedToQuorums", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "OperatorRemovedFromQuorums", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "apkHistory", + "outputs": [ + { + "internalType": "bytes24", + "name": "apkHash", + "type": "bytes24" + }, + { + "internalType": "uint32", + "name": "updateBlockNumber", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nextUpdateBlockNumber", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "name": "currentApk", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "deregisterOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "getApk", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "blockNumber", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getApkHashAtBlockNumberAndIndex", + "outputs": [ + { + "internalType": "bytes24", + "name": "", + "type": "bytes24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "getApkHistoryLength", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getApkIndicesAtBlockNumber", + "outputs": [ + { + "internalType": "uint32[]", + "name": "", + "type": "uint32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getApkUpdateAtIndex", + "outputs": [ + { + "components": [ + { + "internalType": "bytes24", + "name": "apkHash", + "type": "bytes24" + }, + { + "internalType": "uint32", + "name": "updateBlockNumber", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nextUpdateBlockNumber", + "type": "uint32" + } + ], + "internalType": "struct IBLSApkRegistry.ApkUpdate", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "pubkeyHash", + "type": "bytes32" + } + ], + "name": "getOperatorFromPubkeyHash", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getOperatorId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getRegisteredPubkey", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "quorumNumber", + "type": "uint8" + } + ], + "name": "initializeQuorum", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "operatorToPubkey", + "outputs": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "operatorToPubkeyHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "pubkeyHashToOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyRegistrationSignature", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyG1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "pubkeyG2", + "type": "tuple" + } + ], + "internalType": "struct IBLSApkRegistry.PubkeyRegistrationParams", + "name": "params", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "pubkeyRegistrationMessageHash", + "type": "tuple" + } + ], + "name": "registerBLSPublicKey", + "outputs": [ + { + "internalType": "bytes32", + "name": "operatorId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registryCoordinator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/internal/avs/eigenda/contracts/abi/mainnet-service-manager.json b/internal/avs/eigenda/contracts/abi/mainnet-service-manager.json new file mode 100644 index 0000000..17bc356 --- /dev/null +++ b/internal/avs/eigenda/contracts/abi/mainnet-service-manager.json @@ -0,0 +1,1019 @@ +[ + { + "inputs": [ + { + "internalType": "contract IAVSDirectory", + "name": "__avsDirectory", + "type": "address" + }, + { + "internalType": "contract IRegistryCoordinator", + "name": "__registryCoordinator", + "type": "address" + }, + { + "internalType": "contract IStakeRegistry", + "name": "__stakeRegistry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "batchHeaderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "batchId", + "type": "uint32" + } + ], + "name": "BatchConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "batchConfirmer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "BatchConfirmerStatusChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IPauserRegistry", + "name": "pauserRegistry", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IPauserRegistry", + "name": "newPauserRegistry", + "type": "address" + } + ], + "name": "PauserRegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "StaleStakesForbiddenUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "BLOCK_STALE_MEASURE", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STORE_DURATION_BLOCKS", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "THRESHOLD_DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "avsDirectory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchId", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "name": "batchIdToBatchMetadataHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blsApkRegistry", + "outputs": [ + { + "internalType": "contract IBLSApkRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "uint32[]", + "name": "nonSignerQuorumBitmapIndices", + "type": "uint32[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "nonSignerPubkeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "quorumApks", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "quorumApkIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[]", + "name": "totalStakeIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[][]", + "name": "nonSignerStakeIndices", + "type": "uint32[][]" + } + ], + "internalType": "struct IBLSSignatureChecker.NonSignerStakesAndSignature", + "name": "params", + "type": "tuple" + } + ], + "name": "checkSignatures", + "outputs": [ + { + "components": [ + { + "internalType": "uint96[]", + "name": "signedStakeForQuorum", + "type": "uint96[]" + }, + { + "internalType": "uint96[]", + "name": "totalStakeForQuorum", + "type": "uint96[]" + } + ], + "internalType": "struct IBLSSignatureChecker.QuorumStakeTotals", + "name": "", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "blobHeadersRoot", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "quorumNumbers", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signedStakeForQuorums", + "type": "bytes" + }, + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + } + ], + "internalType": "struct IEigenDAServiceManager.BatchHeader", + "name": "batchHeader", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint32[]", + "name": "nonSignerQuorumBitmapIndices", + "type": "uint32[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "nonSignerPubkeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point[]", + "name": "quorumApks", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "quorumApkIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[]", + "name": "totalStakeIndices", + "type": "uint32[]" + }, + { + "internalType": "uint32[][]", + "name": "nonSignerStakeIndices", + "type": "uint32[][]" + } + ], + "internalType": "struct IBLSSignatureChecker.NonSignerStakesAndSignature", + "name": "nonSignerStakesAndSignature", + "type": "tuple" + } + ], + "name": "confirmBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delegation", + "outputs": [ + { + "internalType": "contract IDelegationManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "deregisterOperatorFromAVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "getOperatorRestakedStrategies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRestakeableStrategies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "_pauserRegistry", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_initialPausedStatus", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_initialOwner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_batchConfirmers", + "type": "address[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isBatchConfirmer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "referenceBlockNumber", + "type": "uint32" + } + ], + "name": "latestServeUntilBlock", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "index", + "type": "uint8" + } + ], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauserRegistry", + "outputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumAdversaryThresholdPercentages", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumConfirmationThresholdPercentages", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumNumbersRequired", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + } + ], + "internalType": "struct ISignatureUtils.SignatureWithSaltAndExpiry", + "name": "operatorSignature", + "type": "tuple" + } + ], + "name": "registerOperatorToAVS", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registryCoordinator", + "outputs": [ + { + "internalType": "contract IRegistryCoordinator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_batchConfirmer", + "type": "address" + } + ], + "name": "setBatchConfirmer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IPauserRegistry", + "name": "newPauserRegistry", + "type": "address" + } + ], + "name": "setPauserRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "setStaleStakesForbidden", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakeRegistry", + "outputs": [ + { + "internalType": "contract IStakeRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "staleStakesForbidden", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "taskNumber", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "apk", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256[2]", + "name": "X", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2]", + "name": "Y", + "type": "uint256[2]" + } + ], + "internalType": "struct BN254.G2Point", + "name": "apkG2", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "X", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "Y", + "type": "uint256" + } + ], + "internalType": "struct BN254.G1Point", + "name": "sigma", + "type": "tuple" + } + ], + "name": "trySignatureAndApkVerification", + "outputs": [ + { + "internalType": "bool", + "name": "pairingSuccessful", + "type": "bool" + }, + { + "internalType": "bool", + "name": "siganatureIsValid", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newPausedStatus", + "type": "uint256" + } + ], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_metadataURI", + "type": "string" + } + ], + "name": "updateAVSMetadataURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/internal/avs/eigenda/contracts/blsApkRegistry.go b/internal/avs/eigenda/contracts/blsApkRegistry.go new file mode 100644 index 0000000..d023d21 --- /dev/null +++ b/internal/avs/eigenda/contracts/blsApkRegistry.go @@ -0,0 +1,75 @@ +package contracts + +import ( + "bytes" + _ "embed" + "fmt" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +// Holesky variables +var ( + //go:embed abi/holesky-bls-apk-registry.json + holeskyBlsApkRegistryABIBytes []byte + // TODO: add a configuration option for the address + holeskyBlsApkRegistryAddress = common.HexToAddress("0x066cF95c1bf0927124DFB8B02B401bc23A79730D") + holeskyBlsApkRegistryContract *BlsApkRegistryContract +) + +// Mainnet variables +var ( + //go:embed abi/mainnet-bls-apk-registry.json + mainnetBlsApkRegistryABIBytes []byte + // TODO: add a configuration option for the address + mainnetBlsApkRegistryAddress = common.HexToAddress("0x00A5Fd09F6CeE6AE9C8b0E5e33287F7c82880505") + mainnetBlsApkRegistryContract *BlsApkRegistryContract +) + +type BlsApkRegistryContract struct { + Address common.Address + Abi abi.ABI +} + +func GetBlsApkRegistryContract(avsEnv string) (*BlsApkRegistryContract, error) { + switch avsEnv { + case config.AVSEnvEigenDAHolesky: + if holeskyBlsApkRegistryContract != nil { + return holeskyBlsApkRegistryContract, nil + } + return getHoleskyBlsApkRegistryContract() + case config.AVSEnvEigenDAMainnet: + if mainnetBlsApkRegistryContract != nil { + return mainnetBlsApkRegistryContract, nil + } + return getMainnetBlsApkRegistryContract() + default: + return nil, fmt.Errorf("invalid avs environment: %s", avsEnv) + } +} + +func getHoleskyBlsApkRegistryContract() (*BlsApkRegistryContract, error) { + abi, err := abi.JSON(bytes.NewReader(holeskyBlsApkRegistryABIBytes)) + if err != nil { + return nil, err + } + holeskyBlsApkRegistryContract = &BlsApkRegistryContract{ + Address: holeskyBlsApkRegistryAddress, + Abi: abi, + } + return holeskyBlsApkRegistryContract, nil +} + +func getMainnetBlsApkRegistryContract() (*BlsApkRegistryContract, error) { + abi, err := abi.JSON(bytes.NewReader(mainnetBlsApkRegistryABIBytes)) + if err != nil { + return nil, err + } + mainnetBlsApkRegistryContract = &BlsApkRegistryContract{ + Address: mainnetBlsApkRegistryAddress, + Abi: abi, + } + return mainnetBlsApkRegistryContract, nil +} diff --git a/internal/avs/eigenda/contracts/serviceManager.go b/internal/avs/eigenda/contracts/serviceManager.go new file mode 100644 index 0000000..e216d44 --- /dev/null +++ b/internal/avs/eigenda/contracts/serviceManager.go @@ -0,0 +1,74 @@ +package contracts + +import ( + "bytes" + _ "embed" + "fmt" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +// Holesky variables +var ( + //go:embed abi/holesky-service-manager.json + holeskyServiceManagerABIBytes []byte + // TODO: add a configuration option for the address + holeskyServiceManagerAddress = common.HexToAddress("0xD4A7E1Bd8015057293f0D0A557088c286942e84b") + holeskyServiceManagerContract *ServiceManagerContract +) + +var ( + //go:embed abi/mainnet-service-manager.json + mainnetServiceManagerABIBytes []byte + // TODO: add a configuration option for the address + mainnetServiceManagerAddress = common.HexToAddress("0x870679E138bCdf293b7Ff14dD44b70FC97e12fc0") + mainnetServiceManagerContract *ServiceManagerContract +) + +type ServiceManagerContract struct { + Address common.Address + Abi abi.ABI +} + +func GetServiceManagerContract(avsEnv string) (*ServiceManagerContract, error) { + switch avsEnv { + case config.AVSEnvEigenDAHolesky: + if holeskyServiceManagerContract != nil { + return holeskyServiceManagerContract, nil + } + return getHoleskyServiceManagerContract() + case config.AVSEnvEigenDAMainnet: + if mainnetServiceManagerContract != nil { + return mainnetServiceManagerContract, nil + } + return getMainnetServiceManagerContract() + default: + return nil, fmt.Errorf("invalid avs environment: %s", avsEnv) + } +} + +func getHoleskyServiceManagerContract() (*ServiceManagerContract, error) { + abi, err := abi.JSON(bytes.NewReader(holeskyServiceManagerABIBytes)) + if err != nil { + return nil, err + } + holeskyServiceManagerContract = &ServiceManagerContract{ + Address: holeskyServiceManagerAddress, + Abi: abi, + } + return holeskyServiceManagerContract, nil +} + +func getMainnetServiceManagerContract() (*ServiceManagerContract, error) { + abi, err := abi.JSON(bytes.NewReader(mainnetServiceManagerABIBytes)) + if err != nil { + return nil, err + } + mainnetServiceManagerContract = &ServiceManagerContract{ + Address: mainnetServiceManagerAddress, + Abi: abi, + } + return mainnetServiceManagerContract, nil +} diff --git a/internal/avs/eigenda/eigenda.go b/internal/avs/eigenda/eigenda.go new file mode 100644 index 0000000..3888325 --- /dev/null +++ b/internal/avs/eigenda/eigenda.go @@ -0,0 +1,369 @@ +package eigenda + +import ( + "bytes" + "context" + "fmt" + "log/slog" + "math/big" + "slices" + "sort" + "strconv" + "time" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/avs/eigenda/contracts" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/avsexporter" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/rpc" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type eigenDAOnChainExporter struct { + avsEnv string + network string + operators []config.OperatorConfig + ethClient rpc.EthEvmRpc +} + +func NewEigenDAOnChainExporter(avsEnv string, c *config.Config) (avsexporter.AVSExporter, error) { + // Set exporter status to DOWN by default + metricExporterStatus.WithLabelValues(avsEnv).Set(0) + + // Filter operators by AVS environment + var operators []config.OperatorConfig + for _, operator := range c.Operators { + if slices.Contains(operator.AVSEnvs, avsEnv) { + operators = append(operators, operator) + } + } + // Get the network from the AVS environment + var network string + switch avsEnv { + case config.AVSEnvEigenDAHolesky: + network = "holesky" + case config.AVSEnvEigenDAMainnet: + network = "mainnet" + default: + return nil, fmt.Errorf("invalid AVS environment: %s", avsEnv) + } + e := &eigenDAOnChainExporter{ + avsEnv: avsEnv, + network: network, + operators: operators, + } + if err := e.init(c.RPCs); err != nil { + return nil, fmt.Errorf("failed to initialize exporter: %v", err) + } + slog.Info("initialized exporter |", "avsEnv", e.avsEnv, "operators", len(e.operators)) + return e, nil +} + +func (e *eigenDAOnChainExporter) Name() string { + return e.avsEnv +} + +func (e *eigenDAOnChainExporter) Run(ctx context.Context, c *config.Config) error { + // TODO: Add a configuration option for the ticker time + tickerTime := time.Second * 30 + slog.Info("running exporter |", "avsEnv", e.avsEnv, "interval", tickerTime) + + // Load contracts + serviceManagerContract, err := contracts.GetServiceManagerContract(e.avsEnv) + if err != nil { + return err + } + blsApkRegistryContract, err := contracts.GetBlsApkRegistryContract(e.avsEnv) + if err != nil { + return err + } + + // Get current block to start from + // TODO: Should we add a configuration option to start from a specific block? + latestBlock, err := e.getLatestBlock() + if err != nil { + return err + } + + // Set exporter status to UP + metricExporterStatus.WithLabelValues(e.avsEnv).Set(1) + + ticker := time.Tick(tickerTime) + for { + select { + case <-ctx.Done(): + slog.Info("exiting exporter |", "avsEnv", e.avsEnv) + return nil + case <-ticker: + // Get the next block range + fromBlock, toBlock, err := e.nextBlockRange(latestBlock, tickerTime) + if err != nil { + slog.Error("exporter error |", "avsEnv", e.avsEnv, "error", err) + continue + } + if fromBlock == nil || toBlock == nil { + continue + } + // Get logs from current block range + logs, err := e.getLogs(fromBlock, toBlock) + if err != nil { + slog.Error("exporter error |", "avsEnv", e.avsEnv, "error", err) + continue + } + + for _, vLog := range logs { + switch vLog.Topics[0].Hex() { + case serviceManagerContract.Abi.Events["BatchConfirmed"].ID.Hex(): + if err := e.processBatchConfirmedLog(vLog); err != nil { + slog.Error("exporter error |", "avsEnv", e.avsEnv, "error", err) + continue + } + case blsApkRegistryContract.Abi.Events["OperatorRemovedFromQuorums"].ID.Hex(): + if err := e.processOperatorRemovedFromQuorumsLog(vLog); err != nil { + slog.Error("exporter error |", "avsEnv", e.avsEnv, "error", err) + continue + } + case blsApkRegistryContract.Abi.Events["OperatorAddedToQuorums"].ID.Hex(): + if err := e.processOperatorAddedToQuorumsLog(vLog); err != nil { + slog.Error("exporter error |", "avsEnv", e.avsEnv, "error", err) + continue + } + } + } + metricExporterLatestBlock.WithLabelValues(e.network).Set(float64(toBlock.Int64())) + latestBlock = new(big.Int).Add(toBlock, big.NewInt(1)) + } + } +} + +func (e *eigenDAOnChainExporter) checkAVSEnv(avsEnv string) error { + if avsEnv != config.AVSEnvEigenDAHolesky && avsEnv != config.AVSEnvEigenDAMainnet { + return fmt.Errorf("invalid AVS environment: %s", avsEnv) + } + return nil +} + +func (e *eigenDAOnChainExporter) init(rpcs map[string]string) error { + if err := e.checkAVSEnv(e.avsEnv); err != nil { + return fmt.Errorf("failed to check AVS environment: %v", err) + } + + if err := e.initRPC(rpcs); err != nil { + return fmt.Errorf("failed to initialize RPC: %v", err) + } + + if err := e.initPrometheusMetrics(); err != nil { + return fmt.Errorf("failed to initialize prometheus metrics: %v", err) + } + + return nil +} + +func (e *eigenDAOnChainExporter) initPrometheusMetrics() error { + for _, operator := range e.operators { + for quorum, in := range operator.EigenDAConfig.Quorums { + if in { + metricOnchainQuorumStatus.WithLabelValues(operator.Name, e.network, strconv.Itoa(quorum)).Set(1) + } else { + metricOnchainQuorumStatus.WithLabelValues(operator.Name, e.network, strconv.Itoa(quorum)).Set(0) + } + } + } + return nil +} + +func (e *eigenDAOnChainExporter) initRPC(rpcs map[string]string) error { + if rpcURL, ok := rpcs[e.network]; !ok { + return fmt.Errorf("no RPC URL found for network: %s", e.network) + } else { + ethClient, err := rpc.NewEthEvmRpc(e.network, rpcURL, 3) + if err != nil { + return fmt.Errorf("failed to initialize RPC: %v", err) + } + e.ethClient = ethClient + } + return nil +} + +func (e *eigenDAOnChainExporter) getLatestBlock() (*big.Int, error) { + blockNumber, err := e.ethClient.BlockNumber(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get the block number: %v", err) + } + return big.NewInt(int64(blockNumber)), nil +} + +func (e *eigenDAOnChainExporter) nextBlockRange(latestBlock *big.Int, tickerTime time.Duration) (*big.Int, *big.Int, error) { + toBlock, err := e.getLatestBlock() + if err != nil { + return nil, nil, err + } + if latestBlock.Cmp(toBlock) >= 0 { + slog.Debug("latest RPC block is not greater than latest exporter block. Retrying after interval time |", "avsEnv", e.avsEnv, "rpcLatestBlock", toBlock, "exporterLatestBlock", latestBlock, "sleepTime", tickerTime) + return nil, nil, nil + } + maxPaginationBlock := new(big.Int).Add(latestBlock, big.NewInt(1000)) + if toBlock.Cmp(maxPaginationBlock) > 0 { + slog.Debug("latest block is greater than max pagination block. Using max pagination block instead |", "avsEnv", e.avsEnv, "latestBlock", toBlock, "maxPaginationBlock", maxPaginationBlock, "diff", new(big.Int).Sub(toBlock, maxPaginationBlock)) + toBlock = maxPaginationBlock + } + return latestBlock, toBlock, nil +} + +func (e *eigenDAOnChainExporter) getLogs(fromBlock *big.Int, toBlock *big.Int) ([]types.Log, error) { + // Load contracts + serviceManagerContract, err := contracts.GetServiceManagerContract(e.avsEnv) + if err != nil { + return nil, err + } + blsApkRegistryContract, err := contracts.GetBlsApkRegistryContract(e.avsEnv) + if err != nil { + return nil, err + } + + // Build the filter query + query := ethereum.FilterQuery{ + FromBlock: fromBlock, + ToBlock: toBlock, + Addresses: []common.Address{ + serviceManagerContract.Address, + blsApkRegistryContract.Address, + }, + Topics: [][]common.Hash{ + { + serviceManagerContract.Abi.Events["BatchConfirmed"].ID, + blsApkRegistryContract.Abi.Events["OperatorAddedToQuorums"].ID, + blsApkRegistryContract.Abi.Events["OperatorRemovedFromQuorums"].ID, + }, + }, + } + + // Get the logs + slog.Debug("filtering logs |", "avsEnv", e.avsEnv, "fromBlock", query.FromBlock, "toBlock", query.ToBlock) + logs, err := e.ethClient.FilterLogs(context.Background(), query) + if err != nil { + return nil, err + } + + // Sort logs by block number and tx index + sort.Slice(logs, func(i, j int) bool { + if logs[i].BlockNumber == logs[j].BlockNumber { + return logs[i].TxIndex < logs[j].TxIndex + } + return logs[i].BlockNumber < logs[j].BlockNumber + }) + + return logs, nil +} + +func (e *eigenDAOnChainExporter) processBatchConfirmedLog(log types.Log) error { + // Load contracts + serviceManagerContract, err := contracts.GetServiceManagerContract(e.avsEnv) + if err != nil { + return err + } + + // Increase the number of batches counter + metricOnchainBatchesTotal.WithLabelValues(e.network).Inc() + slog.Info("batch confirmed |", "avsEnv", e.avsEnv, "blockNumber", log.BlockNumber, "txHash", log.TxHash) + + // TODO: Ignoring the isPending output. Need to research more on this. + tx, _, err := e.ethClient.TransactionByHash(context.Background(), log.TxHash) + if err != nil { + return fmt.Errorf("failed to get transaction by hash: %v", err) + } + + // Get the function signature (first 4 bytes of the input data) + funcSignature := tx.Data()[:4] + if !bytes.Equal(funcSignature, serviceManagerContract.Abi.Methods["confirmBatch"].ID) { + return nil + } + + // Unpack the input data + input, err := unpackConfirmBatchInput(e.avsEnv, tx.Data()[4:]) + if err != nil { + return fmt.Errorf("failed to unpack confirmBatch input: %v", err) + } + + // Iterate over the not signers and check if they are operators as not signers + for _, operator := range e.operators { + for _, pubkey := range input.NonSignerStakesAndSignature.NonSignerPubkeys { + operatorBLSPubkeyX, operatorBLSPubkeyY, err := getOperatorBLSPubkey(operator) + if err != nil { + return fmt.Errorf("failed to get operator BLS public key: %v", err) + } + if operatorBLSPubkeyX.Cmp(pubkey.X) == 0 || operatorBLSPubkeyY.Cmp(pubkey.Y) == 0 { + metricOnchainBatches.WithLabelValues(operator.Name, e.network, "missed").Inc() + slog.Info("operator failed to sign batch |", "avsEnv", e.avsEnv, "blockNumber", log.BlockNumber, "txIndex", log.TxIndex, "operator", operator.Name) + } + } + } + + return nil +} + +func (e *eigenDAOnChainExporter) processOperatorRemovedFromQuorumsLog(log types.Log) error { + // Load contracts + blsApkRegistryContract, err := contracts.GetBlsApkRegistryContract(e.avsEnv) + if err != nil { + return err + } + + logInputs, err := blsApkRegistryContract.Abi.Events["OperatorRemovedFromQuorums"].Inputs.Unpack(log.Data) + if err != nil { + return fmt.Errorf("failed to unpack operator removed from quorums log: %v", err) + } + operatorAddress := logInputs[0].(common.Address) + quorumNumbers := logInputs[2].([]uint8) + operatorIndex := slices.IndexFunc(e.operators, func(operator config.OperatorConfig) bool { + return common.HexToAddress(operator.Address) == operatorAddress + }) + if operatorIndex == -1 { + return nil + } + for _, quorum := range quorumNumbers { + slog.Info("operator removed from quorum |", "avsEnv", e.avsEnv, "blockNumber", log.BlockNumber, "txIndex", log.TxIndex, "operator", e.operators[operatorIndex].Name, "quorum", quorum) + metricOnchainQuorumStatus.WithLabelValues(e.operators[operatorIndex].Name, e.network, strconv.Itoa(int(quorum))).Set(0) + } + + return nil +} + +func (e *eigenDAOnChainExporter) processOperatorAddedToQuorumsLog(log types.Log) error { + // Load contracts + blsApkRegistryContract, err := contracts.GetBlsApkRegistryContract(e.avsEnv) + if err != nil { + return err + } + + logInputs, err := blsApkRegistryContract.Abi.Events["OperatorAddedToQuorums"].Inputs.Unpack(log.Data) + if err != nil { + return fmt.Errorf("failed to unpack operator added to quorums log: %v", err) + } + operatorAddress := logInputs[0].(common.Address) + quorumNumbers := logInputs[2].([]uint8) + operatorIndex := slices.IndexFunc(e.operators, func(operator config.OperatorConfig) bool { + return common.HexToAddress(operator.Address) == operatorAddress + }) + if operatorIndex == -1 { + return nil + } + for _, quorum := range quorumNumbers { + slog.Info("operator added to quorum |", "avsEnv", e.avsEnv, "blockNumber", log.BlockNumber, "txIndex", log.TxIndex, "operator", e.operators[operatorIndex].Name, "quorum", quorum) + metricOnchainQuorumStatus.WithLabelValues(e.operators[operatorIndex].Name, e.network, strconv.Itoa(int(quorum))).Set(1) + } + return nil +} + +func getOperatorBLSPubkey(operator config.OperatorConfig) (*big.Int, *big.Int, error) { + x, ok := new(big.Int).SetString(operator.BLSPublicKey[0], 10) + if !ok { + return nil, nil, fmt.Errorf("failed to set string to big.Int: %s", operator.BLSPublicKey[0]) + } + y, ok := new(big.Int).SetString(operator.BLSPublicKey[1], 10) + if !ok { + return nil, nil, fmt.Errorf("failed to set string to big.Int: %s", operator.BLSPublicKey[1]) + } + return x, y, nil +} diff --git a/internal/avs/eigenda/prometheus.go b/internal/avs/eigenda/prometheus.go new file mode 100644 index 0000000..f306c83 --- /dev/null +++ b/internal/avs/eigenda/prometheus.go @@ -0,0 +1,34 @@ +package eigenda + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + metricExporterLatestBlock = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "eoe", + Name: "eigenda_exporter_latest_block", + Help: "Latest block number that the exporter has processed", + }, []string{"network"}) + metricOnchainBatchesTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "eoe", + Name: "eigenda_onchain_batches_total", + Help: "Total number of eigenda onchain batches", + }, []string{"network"}) + metricOnchainBatches = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "eoe", + Name: "eigenda_onchain_batches", + Help: "Number of eigenda onchain batches", + }, []string{"operator", "network", "status"}) + metricOnchainQuorumStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "eoe", + Name: "eigenda_onchain_quorum_status", + Help: "Quorum status of eigenda onchain", + }, []string{"operator", "network", "quorum"}) + metricExporterStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "eoe", + Name: "eigenda_exporter_up", + Help: "Status of the exporter", + }, []string{"avsEnv"}) +) diff --git a/internal/avsexporter/exporter.go b/internal/avsexporter/exporter.go new file mode 100644 index 0000000..98acf16 --- /dev/null +++ b/internal/avsexporter/exporter.go @@ -0,0 +1,12 @@ +package avsexporter + +import ( + "context" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" +) + +type AVSExporter interface { + Name() string + Run(context.Context, *config.Config) error +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 0000000..74db94c --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,16 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +func RootCmd() *cobra.Command { + var rootCmd = &cobra.Command{ + Use: "eoe", + Short: "EigenLayer On-chain Exporter (eoe) exposes Prometheus metrics about AVS protocols and EigenLayer's Node Operator.", + } + rootCmd.PersistentFlags().StringP("config", "c", "eoe-config.yml", "path to config file") + rootCmd.AddCommand(runCommand()) + + return rootCmd +} diff --git a/internal/cli/run.go b/internal/cli/run.go new file mode 100644 index 0000000..10fad05 --- /dev/null +++ b/internal/cli/run.go @@ -0,0 +1,119 @@ +package cli + +import ( + "context" + "fmt" + "log/slog" + "sync" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/avs/eigenda" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/avsexporter" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/config" + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/prometheus" + "github.com/spf13/cobra" +) + +type exporterError struct { + exporter avsexporter.AVSExporter + err error +} + +func runCommand() *cobra.Command { + var c *config.Config + return &cobra.Command{ + Use: "run", + Short: "Run the application", + PreRunE: func(cmd *cobra.Command, args []string) error { + configPath, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + c, err = config.GetConfig(configPath) + if err != nil { + return err + } + logLevel := slog.Level(slog.LevelInfo) + if err := logLevel.UnmarshalText([]byte(c.LogLevel)); err != nil { + return err + } + slog.SetLogLoggerLevel(logLevel) + go func() { + err := prometheus.StartPrometheusServer(":9090") + if err != nil { + slog.Error("Error starting Prometheus server", "error", err) + } + }() + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + wg sync.WaitGroup + ctx = cmd.Context() + avsEnvs = make(map[string]bool) + exporterErrorCh = make(chan exporterError, len(avsEnvs)) + ) + + // Add all AVS environments from operators + for _, operator := range c.Operators { + for _, env := range operator.AVSEnvs { + avsEnvs[env] = true + } + } + + // Run exporters for each AVS environment + for env := range avsEnvs { + switch env { + case config.AVSEnvEigenDAMainnet, config.AVSEnvEigenDAHolesky: + // Initialize and run the AVS environment exporter + exporter, err := eigenda.NewEigenDAOnChainExporter(env, c) + if err != nil { + return err + } + runExporter(ctx, exporter, &wg, exporterErrorCh, c) + default: + return fmt.Errorf("invalid AVS environment: %s", env) + } + } + + for { + select { + case exporterError := <-exporterErrorCh: + slog.Debug("exporter error", "exporter", exporterError.exporter.Name(), "error", exporterError.err) + runExporter(ctx, exporterError.exporter, &wg, exporterErrorCh, c) + case <-ctx.Done(): + slog.Debug("context done", "error", ctx.Err()) + return gracefulExit(&wg, nil) + } + } + }, + } +} + +// runExporter starts an exporter and adds it to the wait group. It also sends +// any errors to the exporterErrorCh channel. +func runExporter( + ctx context.Context, + exporter avsexporter.AVSExporter, + wg *sync.WaitGroup, + exporterErrorCh chan<- exporterError, + c *config.Config, +) { + wg.Add(1) + go func() { + defer wg.Done() + err := exporter.Run(ctx, c) + if err != nil { + if ctx.Err() == nil { + slog.Error("exporter error", "exporter", exporter.Name(), "error", err) + exporterErrorCh <- exporterError{exporter, err} + } + } + }() +} + +func gracefulExit(wg *sync.WaitGroup, err error) error { + slog.Debug("Shutting down exporters...") + wg.Wait() + slog.Debug("Exporters shutdown complete") + return err +} diff --git a/internal/common/networks.go b/internal/common/networks.go new file mode 100644 index 0000000..8a86f4f --- /dev/null +++ b/internal/common/networks.go @@ -0,0 +1,27 @@ +package common + +import ( + "fmt" + "math/big" +) + +const ( + NetworkHolesky = "holesky" + NetworkMainnet = "mainnet" +) + +func AssertChainID(network string, chainId *big.Int) error { + switch network { + case NetworkHolesky: + if chainId.Cmp(big.NewInt(17000)) != 0 { + return fmt.Errorf("invalid chain id for network: %s", network) + } + case NetworkMainnet: + if chainId.Cmp(big.NewInt(1)) != 0 { + return fmt.Errorf("invalid chain id for network: %s", network) + } + default: + return fmt.Errorf("invalid network: %s", network) + } + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4cbd36e --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,43 @@ +package config + +const ( + // RPCNetwork is the network type for the RPC. + RPCNetworkEthereum = "ethereum" + RPCNetworkHolesky = "holesky" + + // AVSEnv is the environment for the AVS. + AVSEnvEigenDAHolesky = "eigenda-holesky" + AVSEnvEigenDAMainnet = "eigenda-mainnet" +) + +// Config is the configuration for the application. +type Config struct { + // Operators is the list of operators to be tracked. + Operators []OperatorConfig `yaml:"operators"` + // RPCs is the list of RPCs to be used for the AVS exporters. + RPCs map[string]string `yaml:"rpcs"` + // LogLevel is the level of logging to be used. + LogLevel string `yaml:"logLevel"` +} + +// OperatorConfig holds the needed information for an operator to be tracked. +type OperatorConfig struct { + // Name is the name of the operator. + Name string `yaml:"name"` + // Address is the address of the operator. + Address string `yaml:"address"` + // BLSPublicKey is the BLS public key of the operator. + BLSPublicKey [2]string `yaml:"blsPublicKey"` + // AVSEnvs is the list of AVS environments to be tracked. + AVSEnvs []string `yaml:"avsEnvs"` + // EigenDAConfig is the configuration for the EigenDA AVS. + EigenDAConfig EigenDAConfig `yaml:"eigenDAConfig"` +} + +type EigenDAConfig struct { + // Quorums is the initial status of the operator's quorums. If the exporter + // receives events in the future about the operator's quorum status, it will + // update the status in the Prometheus metric. This map is only for bootstrapping + // and does not have a missing Prometheus metric. + Quorums map[int]bool `yaml:"quorums"` +} diff --git a/internal/config/viper.go b/internal/config/viper.go new file mode 100644 index 0000000..721a6e2 --- /dev/null +++ b/internal/config/viper.go @@ -0,0 +1,32 @@ +package config + +import ( + "github.com/spf13/viper" +) + +// GetConfig reads the configuration from the given path and returns a Config struct. +func GetConfig(configPath string) (*Config, error) { + // Set the config file name and type + viper.SetConfigName("eoe-config") + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + + // If a config path is provided, use it + if configPath != "" { + viper.SetConfigFile(configPath) + } + + // Set the environment prefix and automatically use environment variables. + // TODO: Overwriting the config file with environment variables is not working + // as expected. We need to redefine the Config struct to make easier bindings + // with environment variables. + viper.SetEnvPrefix("EOE") + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err != nil { + return nil, err + } + + var c Config + return &c, viper.Unmarshal(&c) +} diff --git a/internal/prometheus/prometheus.go b/internal/prometheus/prometheus.go new file mode 100644 index 0000000..33e69a3 --- /dev/null +++ b/internal/prometheus/prometheus.go @@ -0,0 +1,13 @@ +package prometheus + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// StartPrometheusServer starts a Prometheus server on the given port. +func StartPrometheusServer(port string) error { + http.Handle("/metrics", promhttp.Handler()) + return http.ListenAndServe(port, nil) +} diff --git a/internal/rpc/eth-evm.go b/internal/rpc/eth-evm.go new file mode 100644 index 0000000..e2bf8ce --- /dev/null +++ b/internal/rpc/eth-evm.go @@ -0,0 +1,111 @@ +package rpc + +import ( + "context" + "log/slog" + "time" + + "github.com/NethermindEth/eigenlayer-onchain-exporter/internal/common" + "github.com/cenkalti/backoff/v4" + "github.com/ethereum/go-ethereum" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +var ( + ethEvmRpcs = make(map[string]EthEvmRpc) +) + +// EthEvmRpc is the interface for the Ethereum RPC client. +type EthEvmRpc interface { + BlockNumber(ctx context.Context) (uint64, error) + FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) + TransactionByHash(ctx context.Context, hash ethcommon.Hash) (*types.Transaction, bool, error) +} + +type ethEvmRpc struct { + network string + client *ethclient.Client + maxElapsedTime time.Duration +} + +func NewEthEvmRpc(network string, url string, maxElapsedTime time.Duration) (EthEvmRpc, error) { + if _, ok := ethEvmRpcs[network]; ok { + return ethEvmRpcs[network], nil + } + slog.Debug("initializing new eth-evm rpc |", "network", network) + client, err := ethclient.Dial(url) + if err != nil { + return nil, err + } + + // Check the chain ID of the network + chainId, err := client.ChainID(context.Background()) + if err != nil { + return nil, err + } + err = common.AssertChainID(network, chainId) + if err != nil { + return nil, err + } + + ethEvmRpc := ðEvmRpc{network: network, client: client, maxElapsedTime: maxElapsedTime} + ethEvmRpcs[network] = ethEvmRpc + + return ethEvmRpc, nil +} + +func (e *ethEvmRpc) BlockNumber(ctx context.Context) (uint64, error) { + operation := func() (uint64, error) { + slog.Debug("getting block number |", "rpcNetwork", e.network) + return e.client.BlockNumber(ctx) + } + notify := func(err error, duration time.Duration) { + slog.Error("failed to get block number, retrying... |", "rpc-network", e.network, "duration", duration, "error", err) + } + + return backoff.RetryNotifyWithData( + operation, + backoff.NewExponentialBackOff(backoff.WithMaxElapsedTime(e.maxElapsedTime)), + notify, + ) +} + +func (e *ethEvmRpc) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + operation := func() ([]types.Log, error) { + slog.Debug("filtering logs |", "rpc-network", e.network) + return e.client.FilterLogs(ctx, query) + } + notify := func(err error, duration time.Duration) { + slog.Error("failed to filter logs, retrying... |", "rpc-network", e.network, "duration", duration, "error", err) + } + + return backoff.RetryNotifyWithData( + operation, + backoff.NewExponentialBackOff(backoff.WithMaxElapsedTime(e.maxElapsedTime)), + notify, + ) +} + +func (e *ethEvmRpc) TransactionByHash(ctx context.Context, hash ethcommon.Hash) (*types.Transaction, bool, error) { + type result struct { + tx *types.Transaction + isPending bool + } + operation := func() (result, error) { + slog.Debug("getting transaction by hash |", "rpc-network", e.network, "hash", hash) + tx, isPending, err := e.client.TransactionByHash(ctx, hash) + return result{tx: tx, isPending: isPending}, err + } + notify := func(err error, duration time.Duration) { + slog.Error("failed to get transaction by hash, retrying... |", "rpc-network", e.network, "error", err) + } + + out, err := backoff.RetryNotifyWithData( + operation, + backoff.NewExponentialBackOff(backoff.WithMaxElapsedTime(e.maxElapsedTime)), + notify, + ) + return out.tx, out.isPending, err +} diff --git a/main.py b/main.py deleted file mode 100644 index 0adcb95..0000000 --- a/main.py +++ /dev/null @@ -1,42 +0,0 @@ -from prometheus_client import start_http_server -from api_client import fetch_data_from_api -from metrics import create_metrics, update_metrics -import time -import os - -# URL of the API endpoint -API_URL = os.environ.get('API_URL', "https://blobs-goerli.eigenda.xyz/api/trpc/blobs.getBlobs") - -# How often to fetch new data and update metrics (in seconds) -FETCH_INTERVAL = int(os.environ.get('FETCH_INTERVAL', 60)) - -def main(): - """ - Main function to start the server, fetch data periodically, and update metrics. - """ - # Start up the server to expose the metrics. - start_http_server(9600) - print("Prometheus metrics server running on port 9600") - - # Create metrics - metrics = create_metrics() - - last_timestamp = 0 - - while True: - try: - # Fetch new data from the API - data = fetch_data_from_api(API_URL) - - # Update the metrics with the new data - last_timestamp = update_metrics(metrics, data, last_timestamp) - - print("Metrics updated.") - except Exception as e: - print(f"Error fetching data or updating metrics: {e}") - - # Wait for the next fetch interval - time.sleep(FETCH_INTERVAL) - -if __name__ == "__main__": - main() diff --git a/metrics.py b/metrics.py deleted file mode 100644 index d7f0c99..0000000 --- a/metrics.py +++ /dev/null @@ -1,40 +0,0 @@ -from prometheus_client import Gauge -import time - -# Assuming the structure of the data based on the provided example -# We will create a Gauge for each field in the blob data - -def create_metrics(): - """ - Create Prometheus Gauges for various fields in the blob data. - """ - metrics = { - 'reference_block_number': Gauge('reference_block_number', 'Reference block number'), - 'batch_id': Gauge('batch_id', 'Batch ID'), - 'confirmation_block_number': Gauge('confirmation_block_number', 'Confirmation block number'), - 'requested_at': Gauge('requested_at', 'Time when the blob was requested'), - # Additional metrics can be added here based on the data fields - } - return metrics - -def update_metrics(metrics, data, last_timestamp): - """ - Update the Prometheus metrics with the latest data from the API. - - Args: - metrics (dict): The dictionary of Prometheus Gauges. - data (dict): The data fetched from the API. - """ - # Get data and sort it by requested_at - sorted_data = sorted(data.get('result', {}).get('data', {}).get('json', {}).get('data', []), key=lambda k: k['requested_at']) - for blob in sorted_data: - if blob['requested_at'] <= last_timestamp: - # If the blob has already been processed, skip it - continue - metrics['reference_block_number'].set(blob.get('reference_block_number', 0)) - metrics['batch_id'].set(blob.get('batch_id', 0)) - metrics['confirmation_block_number'].set(blob.get('confirmation_block_number', 0)) - metrics['requested_at'].set(blob.get('requested_at', 0)) - # Update additional metrics here - return sorted_data[-1]['requested_at'] - diff --git a/prometheus.yml b/prometheus.yml index b20a37e..34f29fe 100644 --- a/prometheus.yml +++ b/prometheus.yml @@ -2,6 +2,6 @@ global: scrape_interval: 15s scrape_configs: - - job_name: 'blob-scraper' + - job_name: 'onchain-exporter' static_configs: - - targets: ['scraper:9600'] + - targets: ['onchain-exporter:9090'] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 5c9b59a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -certifi==2023.11.17 -charset-normalizer==3.3.2 -idna==3.6 -prometheus-client==0.19.0 -requests==2.31.0 -urllib3==2.1.0