From a771a64d3f0874b809cce210cb1536b9cce8facb Mon Sep 17 00:00:00 2001 From: Jacob Elias Date: Thu, 30 May 2024 14:33:46 -0500 Subject: [PATCH] feat: Merge op ufm into infra (#3) * skeleton, poll, heartbeat, configs, healthz, dockerfile * signer tls, send tx on heartbeat * round trip receipt * tx_pool, heartbeat * example.config.toml * add . * addr semgrep * metrics, instrumented clients, already known nonce handling and retry * refactor metrics, add metric debug * metric with error details * fix imports * fixes, deployed to dev * semgrep * lint * add kmstool * lint err * fix(ufm): betterer error metric label (#6535) * fix(ufm): betterer error metric label * remove commented code * feat(ufm): track nonce locally * format log ctx * build(deps): bump github.com/ethereum/go-ethereum in /op-ufm Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.12.0 to 1.12.1. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.12.0...v1.12.1) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production ... Signed-off-by: dependabot[bot] * add cooldown * config update * improve error metrics * use network nonce * use network gas * remove gas configs * send mutex * add debug tools to docker file * update example.config.toml * remove gas configs * timeout metrics * build(deps): bump github.com/supranational/blst in /op-ufm Bumps [github.com/supranational/blst](https://github.com/supranational/blst) from 0.3.11-0.20230406105308-e9dfc5ee724b to 0.3.11. - [Release notes](https://github.com/supranational/blst/releases) - [Commits](https://github.com/supranational/blst/commits/v0.3.11) --- updated-dependencies: - dependency-name: github.com/supranational/blst dependency-type: indirect ... Signed-off-by: dependabot[bot] * deps: golang 1.21 Updates the go version used to 1.21 * modify external files * build(deps): bump github.com/consensys/gnark-crypto in /op-ufm Bumps [github.com/consensys/gnark-crypto](https://github.com/consensys/gnark-crypto) from 0.10.0 to 0.12.0. - [Release notes](https://github.com/consensys/gnark-crypto/releases) - [Changelog](https://github.com/Consensys/gnark-crypto/blob/master/CHANGELOG.md) - [Commits](https://github.com/consensys/gnark-crypto/compare/v0.10.0...v0.12.0) --- updated-dependencies: - dependency-name: github.com/consensys/gnark-crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] * build(deps): bump golang.org/x/net from 0.12.0 to 0.17.0 in /op-ufm Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.12.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] * fix(ufm): gracefully handle failed tx creation * chore(ufm): update deps * address rabbits concerns * address mr rabbits concerns part ii * extract gas tip cap const * fix: resolving dependabot vulnerabilities. (#8643) * resolving dependabot vulnerabilities. setting go-ethereum v.1.13.5 everywhere and grpc to v1.56.3 * fixing sum sha * build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 in /op-ufm (#8672) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * op-ufm: use `fmt.Errorf()` for `panic()` (#8754) * chore: clean up makefiles across the monorepo (#8862) * Update op-geth dependency to upstream geth v1.13.8 and migrate to slog (#8917) * Update op-geth dependency (v1.13.8) & migrate to slog * op-e2e: format system_test.go * op-chain-ops/genesis: Ignore nil addresses in BuildL1DeveloperGenesis * go: Update to latest op-geth commit fb90ca39bc5c4f45e99ef320abfab85eeb56c561 * update latest op-geth dependency * op-program,op-wheel: Use new StateDB.OpenStorageTrie * all: fix more slog stuff after merging * proxyd: update geth 1.13.8 & migrate to slog * op-ufm: update monorepo dependency to prev commit * testlog: Return pointer with FindLog * genesis: Parse addresses from dump string keys in BuildL1DeveloperGenesis * op-ufm: go mod tidy * update to latest op-geth * genesis: Update ForgeDump.UnmarshalJSON to latest geth types * eth: Use hexutils.U256 instead of uint256.Int as type in ExecutionPayload This fixes JSON mashaling. * op-e2e: fix usage of legacy geth levels * go: update latest op-geth dependency * check-ecotone: adapt to field type change * Resolve remaining TODOs * op-program: remove json-pretty formatting option from test * go: update to latest op-geth v1.101308.0-rc.1 * op-dispute-mon: Fix logger setup * log: improve LevelFromString docs * op-e2e/config: treat EthNodeVerbosity as legacy log level * all: fix order of imports * ci: Skip flaky test (#9437) * op-ufm: Update mod tidy again (#9456) * op-ufm: mod tidy (#9537) * op-ufm: Update go.mod (#9631) * op-ufm: pin monorepo dependency, remove replace (#9641) also udpate op-geth dependency while we're at it * Fix check-changed for cannon and op-ufm go.mod checks (#9733) * cannon: Update go mod in examples and fix check-changed to catch this in future * Break op-ufm go.sum to work out what fails when it's out of date. * Update go.sum * op-ufm: Switch back to using latest monorepo code in op-ufm. * op-ufm: Include op-ufm in mod tidy checks * Remove hardfork activation time overrides (#9642) * go get github.com/ethereum-optimism/superchain-registry/superchain@1203fe7 && go mod tidy * update op-geth dependency * remove hardfork activation time overrides * remove unused consts * Revert "remove hardfork activation time overrides" This reverts commit 34b761f267aa3bbeaaa16f4d328ad91dec20d4e9. * update superchain * Revert "remove unused consts" This reverts commit d1fca170f24f608f75d0d9ca8c12597bffd9df83. * get Canyon,Delta and EcotoneTime fron superchain chain config * remove sepolia devnet specialcase (will be moved to superchain registry) * update op-geth * add oplabs-devnet-0-sepolia-dev-0 to chains_test * fix correct DeltaTime for sepoliaDev0Cfg * go get github.com/ethereum-optimism/superchain-registry/superchain@2dcb036 && go mod tidy * remove TODO * go get github.com/ethereum-optimism/superchain-registry/superchain@c2e25cc && go mod tidy * update dependency on op-geth * update expected CanyonTime for oplabs-devnet-0-sepolia-dev-0 * update dependency on superchain and op-geth * make mod-tidy * go get github.com/ethereum-optimism/superchain-registry/superchain@c557df8 && go mod tidy * update op-geth to v1.101308.3-rc.1 && go mod tidy * make mod-tidy * Update dependency on superchain-registry (#9929) * go get github.com/ethereum-optimism/superchain-registry/superchain@52d3dbd1605dd43f419e838584abd0ec163d462b * make mod-tidy * remove goerli chaincfg * op-program: replace references to goerli with sepolia * op-node: remove references to goerli or replace with sepolia * remove unused var * go: update op-geth to latest v1.101308.4-rc.1 * op-node: remove Goerli references & config overrides --------- Co-authored-by: Sebastian Stammler * build(deps): bump google.golang.org/protobuf in /op-ufm (#9854) Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update op-geth dependency to v1.101309.0-rc.2 (#9935) * use updated op-geth * update geth moar * resource usage cleanup * remove sleeping * go: fix broken indirect dependency of github.com/kataras/iris/v12 * proxyd: update to geth v1.13.10 * proxyd: update to geth v1.13.14 * op-chain-ops: reduce diff, utilize chainID from caller to make Permit2 path use correct chainID during deployment * op-chain-ops: L2 backend only, enable shanghai/canyon by default, fix doc comments * op-chain-ops: simulator without L2 features, to support op-upgrade --------- Co-authored-by: protolambda * update geth dependency to version w/ v1.13.11 upstream commits (#10041) * update geth dependency to version with upstream updates to v1.13.11 * fix op-program tests * update op-geth dep to tagged commit * op-plasma: basic da server with S3 and leveldb storage (#9813) * feat: basic plasma da server * catch all not found errors * feat: switch to minio and file server * add file * fix: handle feedback * fix: tidy * fix: tidy mods again * chore: add S3 config info * Update dependency on `superchain` package (#10204) * pin superchain registry to a commit on https://github.com/ethereum-optimism/superchain-registry/pull/190 * slice into mapping with superchain name * update superchain-registry to latest commit on main * build(deps): bump golang.org/x/net from 0.21.0 to 0.23.0 in /op-ufm (#10233) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * geth: update to v1.13.15 (#10353) * point at op-geth v1.13.13 * update triedb import path * update deprecated core structs to types package * Update op-geth with tx pool fix. * point at v1.13.14 * point at geth v1.13.15 * fix NewStackTrie call * remove pin * use op-geth v1.101315.0-rc.1 --------- Co-authored-by: Adrian Sutton * dependabot(gomod): bump golang.org/x/crypto from 0.21.0 to 0.23.0 (#10420) * dependabot(gomod): bump golang.org/x/crypto from 0.21.0 to 0.23.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.23.0. - [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * deps: update * deps: update --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mark Tyneway * op-node: Add dedicated PlasmaConfig struct to rollup.Config (#10499) * op-node: Add dedicated PlasmaConfig struct to rollup.Config This commit adds a dedicated PlasmaConfig section to the rollup config. This collects all Plasma Mode configuration to the same place and enables future expansion of Plasma Mode configuration to be confined to a single location. In the interim, the op-node can read from both the legacy fields and the dedicated struct. If both are set, they must be consistent. The genesis creation tooling currently only writes with the new config. * Set plasma mode from superchain registry * Remove LegacyUsePlasma usage * dependabot(gomod): bump github.com/decred/dcrd/dcrec/secp256k1/v4 from 4.2.0 to 4.3.0 (#10009) * dependabot(gomod): bump github.com/decred/dcrd/dcrec/secp256k1/v4 Bumps [github.com/decred/dcrd/dcrec/secp256k1/v4](https://github.com/decred/dcrd) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/decred/dcrd/releases) - [Changelog](https://github.com/decred/dcrd/blob/master/CHANGES) - [Commits](https://github.com/decred/dcrd/compare/dcrec/secp256k1/v4.2.0...dcrec/secp256k1/v4.3.0) --- updated-dependencies: - dependency-name: github.com/decred/dcrd/dcrec/secp256k1/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * deps: update --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mark Tyneway * Enable Fjord devnet activation (#10573) * Enable Fjord devnet activation * chaincfg: add Fjord time to sepolia-devnet-0 * Activate Fjord on Sepolia (#10610) * feat: circle ci configurations for op-ufm * feat: updated go.mod to contain remote reference github.com/ethereum-optimism/optimism * feat: fix linting errors, and add golangci-lint config --------- Signed-off-by: dependabot[bot] Co-authored-by: Felipe Andrade Co-authored-by: OptimismBot <112345690+OptimismBot@users.noreply.github.com> Co-authored-by: felipe andrade <130432649+felipe-op@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mark Tyneway Co-authored-by: Sabnock01 <24715302+Sabnock01@users.noreply.github.com> Co-authored-by: Raffaele <151576068+raffaele-oplabs@users.noreply.github.com> Co-authored-by: Taeguk Kwon Co-authored-by: refcell.eth Co-authored-by: Sebastian Stammler Co-authored-by: Matthew Slipper Co-authored-by: Adrian Sutton Co-authored-by: George C. Knee Co-authored-by: Roberto Bayardo Co-authored-by: protolambda Co-authored-by: tdot Co-authored-by: Axel Kingsley Co-authored-by: Joshua Gutow --- .circleci/config.yml | 1 + .circleci/continue_config.yml | 33 +- op-ufm/.gitignore | 4 + op-ufm/.golangci.yml | 10 + op-ufm/Dockerfile | 30 ++ op-ufm/Makefile | 36 +++ op-ufm/README.md | 23 ++ op-ufm/cmd/ufm/main.go | 84 +++++ op-ufm/entrypoint.sh | 6 + op-ufm/example.config.toml | 83 +++++ op-ufm/go.mod | 101 ++++++ op-ufm/go.sum | 461 +++++++++++++++++++++++++++ op-ufm/pkg/config/config.go | 171 ++++++++++ op-ufm/pkg/config/toml_duration.go | 15 + op-ufm/pkg/metrics/clients/eth.go | 123 +++++++ op-ufm/pkg/metrics/clients/signer.go | 42 +++ op-ufm/pkg/metrics/metrics.go | 166 ++++++++++ op-ufm/pkg/provider/heartbeat.go | 104 ++++++ op-ufm/pkg/provider/provider.go | 72 +++++ op-ufm/pkg/provider/roundtrip.go | 312 ++++++++++++++++++ op-ufm/pkg/provider/tx_pool.go | 40 +++ op-ufm/pkg/service/healthz_server.go | 42 +++ op-ufm/pkg/service/metrics_server.go | 27 ++ op-ufm/pkg/service/service.go | 111 +++++++ op-ufm/tools/kmstool/main.go | 72 +++++ 25 files changed, 2168 insertions(+), 1 deletion(-) create mode 100644 op-ufm/.gitignore create mode 100644 op-ufm/.golangci.yml create mode 100644 op-ufm/Dockerfile create mode 100644 op-ufm/Makefile create mode 100644 op-ufm/README.md create mode 100644 op-ufm/cmd/ufm/main.go create mode 100644 op-ufm/entrypoint.sh create mode 100644 op-ufm/example.config.toml create mode 100644 op-ufm/go.mod create mode 100644 op-ufm/go.sum create mode 100644 op-ufm/pkg/config/config.go create mode 100644 op-ufm/pkg/config/toml_duration.go create mode 100644 op-ufm/pkg/metrics/clients/eth.go create mode 100644 op-ufm/pkg/metrics/clients/signer.go create mode 100644 op-ufm/pkg/metrics/metrics.go create mode 100644 op-ufm/pkg/provider/heartbeat.go create mode 100644 op-ufm/pkg/provider/provider.go create mode 100644 op-ufm/pkg/provider/roundtrip.go create mode 100644 op-ufm/pkg/provider/tx_pool.go create mode 100644 op-ufm/pkg/service/healthz_server.go create mode 100644 op-ufm/pkg/service/metrics_server.go create mode 100644 op-ufm/pkg/service/service.go create mode 100644 op-ufm/tools/kmstool/main.go diff --git a/.circleci/config.yml b/.circleci/config.yml index e0418e5..7ac83a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,6 +16,7 @@ workflows: # mapping: | op-conductor-mon/.* run-build-op-conductor-mon true + op-ufm/.* run-build-op-ufm true .circleci/.* run-all true .github/.* run-all true diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index e1b1c0f..adcc7af 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -10,6 +10,9 @@ parameters: run-build-op-conductor-mon: type: boolean default: false + run-build-op-ufm: + type: boolean + default: false run-all: type: boolean default: false @@ -224,7 +227,11 @@ jobs: - run: name: run lint command: | - golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" --timeout "3m0s" ./... + if [ -f .golangci.yml ]; then + golangci-lint run -c .golangci.yml -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" --timeout "3m0s" ./... + else + golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint -e "errors.As" -e "errors.Is" --timeout "3m0s" ./... + fi working_directory: <> go-test: @@ -357,3 +364,27 @@ workflows: - oplabs-gcr requires: - op-conductor-mon-docker-build + op-ufm: + when: + or: [<< pipeline.parameters.run-build-op-ufm >>, << pipeline.parameters.run-all >>] + jobs: + - go-lint: + name: op-conductor-mon-lint + module: op-ufm + - go-test: + name: op-conductor-mon-tests + module: op-ufm + - docker-build: + name: op-ufm-docker-build + docker_file: op-ufm/Dockerfile + docker_name: op-ufm + docker_tags: <>,<> + docker_context: . + - docker-publish: + name: op-ufm-docker-publish + docker_name: op-ufm + docker_tags: <>,<> + context: + - oplabs-gcr + requires: + - op-ufm-docker-build diff --git a/op-ufm/.gitignore b/op-ufm/.gitignore new file mode 100644 index 0000000..47ea0d9 --- /dev/null +++ b/op-ufm/.gitignore @@ -0,0 +1,4 @@ +bin +tls + +config.toml diff --git a/op-ufm/.golangci.yml b/op-ufm/.golangci.yml new file mode 100644 index 0000000..e19ed9b --- /dev/null +++ b/op-ufm/.golangci.yml @@ -0,0 +1,10 @@ +linters-settings: + staticcheck: + checks: ["all"] + +issues: + exclude-rules: + - path: "pkg/provider/roundtrip.go" + linters: + - staticcheck + text: "SA4006" diff --git a/op-ufm/Dockerfile b/op-ufm/Dockerfile new file mode 100644 index 0000000..0a2644e --- /dev/null +++ b/op-ufm/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.21.1-alpine3.18 as builder + +ARG GITCOMMIT=docker +ARG GITDATE=docker +ARG GITVERSION=docker + +RUN apk add make jq git gcc musl-dev linux-headers + +COPY ./op-ufm /app + +WORKDIR /app + +RUN make ufm + +FROM alpine:3.18 + +COPY --from=builder /app/entrypoint.sh /bin/entrypoint.sh +COPY --from=builder /app/bin/ufm /bin/ufm + +RUN apk update && \ + chmod +x /bin/entrypoint.sh + +RUN apk add ca-certificates jq curl bind-tools + +VOLUME /etc/ufm + +EXPOSE 8080 + +ENTRYPOINT ["/bin/entrypoint.sh"] +CMD ["/bin/ufm", "/etc/ufm/config.toml"] diff --git a/op-ufm/Makefile b/op-ufm/Makefile new file mode 100644 index 0000000..7dbd96f --- /dev/null +++ b/op-ufm/Makefile @@ -0,0 +1,36 @@ +LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT) +LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) +LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION) +LDFLAGS := -ldflags "$(LDFLAGSSTRING)" + +ufm: + go build -v $(LDFLAGS) -o ./bin/ufm ./cmd/ufm +.PHONY: ufm + +fmt: + go mod tidy + gofmt -w . +.PHONY: fmt + +test: + go test -race -v ./... +.PHONY: test + +lint: + go vet ./... +.PHONY: lint + +tls: + kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."tls.key"' | base64 --decode > tls/tls.key + kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."tls.crt"' | base64 --decode > tls/tls.crt + kubectl get secrets op-ufm-client-tls -o yaml | yq '.data."ca.crt"' | base64 --decode > tls/ca.crt +.PHONY: tls + +mod-tidy: + # Below GOPRIVATE line allows mod-tidy to be run immediately after + # releasing new versions. This bypasses the Go modules proxy, which + # can take a while to index new versions. + # + # See https://proxy.golang.org/ for more info. + export GOPRIVATE="github.com/ethereum-optimism" && go mod tidy +.PHONY: mod-tidy diff --git a/op-ufm/README.md b/op-ufm/README.md new file mode 100644 index 0000000..235273a --- /dev/null +++ b/op-ufm/README.md @@ -0,0 +1,23 @@ +# OP User Facing Monitoring + +This project simulates a synthetic user interacting with a OP Stack chain. + +It is intended to be used as a tool for monitoring +the health of the network by measuring end-to-end transaction latency. + + +## Metrics + +* Round-trip duration time to get transaction receipt (from creation timestamp) + +* First-seen duration time (from creation timestamp) + + +## Usage + +Run `make ufm` to build the binary. No additional dependencies are necessary. + +Copy `example.config.toml` to `config.toml` and edit the file to configure the service. + +Start the service with `ufm config.toml`. + diff --git a/op-ufm/cmd/ufm/main.go b/op-ufm/cmd/ufm/main.go new file mode 100644 index 0000000..f4f753a --- /dev/null +++ b/op-ufm/cmd/ufm/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/signal" + "syscall" + + "golang.org/x/exp/slog" + + "github.com/ethereum/go-ethereum/log" + + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-ufm/pkg/config" + "github.com/ethereum-optimism/optimism/op-ufm/pkg/service" +) + +var ( + GitVersion = "" + GitCommit = "" + GitDate = "" +) + +func main() { + oplog.SetGlobalLogHandler(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) + + log.Info("initializing", + "version", GitVersion, + "commit", GitCommit, + "date", GitDate) + + if len(os.Args) < 2 { + log.Crit("must specify a config file on the command line") + } + cfg := initConfig(os.Args[1]) + + ctx := context.Background() + svc := service.New(cfg) + svc.Start(ctx) + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + recvSig := <-sig + log.Info("caught signal, shutting down", + "signal", recvSig) + + svc.Shutdown() +} + +func initConfig(cfgFile string) *config.Config { + cfg, err := config.New(cfgFile) + if err != nil { + log.Crit("error reading config file", + "file", cfgFile, + "err", err) + } + + // update log level from config + logLevel, err := oplog.LevelFromString(cfg.LogLevel) + if err != nil { + logLevel = log.LevelInfo + if cfg.LogLevel != "" { + log.Warn("invalid server.log_level", + "log_level", cfg.LogLevel) + } + } + oplog.SetGlobalLogHandler(slog.NewJSONHandler( + os.Stdout, &slog.HandlerOptions{Level: logLevel})) + + // readable parsed config + jsonCfg, _ := json.MarshalIndent(cfg, "", " ") + fmt.Printf("%s", string(jsonCfg)) + + err = cfg.Validate() + if err != nil { + log.Crit("invalid config", + "err", err) + } + + return cfg +} diff --git a/op-ufm/entrypoint.sh b/op-ufm/entrypoint.sh new file mode 100644 index 0000000..ef83fa8 --- /dev/null +++ b/op-ufm/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Updating CA certificates." +update-ca-certificates +echo "Running CMD." +exec "$@" \ No newline at end of file diff --git a/op-ufm/example.config.toml b/op-ufm/example.config.toml new file mode 100644 index 0000000..13b62a3 --- /dev/null +++ b/op-ufm/example.config.toml @@ -0,0 +1,83 @@ +# Log level. +# Possible values: trace | debug | info | warn | error | crit +# Default: debug +log_level = "debug" + +[signer_service] +# URL to the signer service +url = "http://localhost:1234" +tls_ca_cert = "tls/ca.crt" +tls_cert = "tls/tls.crt" +tls_key = "tls/tls.key" + +[healthz] +# Whether or not to enable healthz endpoint +enabled = true +# Host for the healthz endpoint to listen on +host = "0.0.0.0" +# Port for the above. +port = "8080" + +[metrics] +# Whether or not to enable Prometheus metrics +enabled = true +# Host for the Prometheus metrics endpoint to listen on. +host = "0.0.0.0" +# Port for the above. +port = "9761" + +[wallets.default] +# OP Stack Chain ID +# see https://community.optimism.io/docs/useful-tools/networks/ +chain_id = 420 +# Signer method to use +# Possible values: signer | static +signer_method = "static" +# Address used to send transactions +address = "0x0000000000000000000000000000000000000000" +# For static signer method, the private key to use +private_key = "0000000000000000000000000000000000000000000000000000000000000000" +# Transaction value in wei +tx_value = 100000000000000 + +[providers.p1] +# URL to the RPC provider +url = "http://localhost:8551" +# Read only providers are only used to check for transactions +read_only = true +# Interval to poll the provider for expected transactions +read_interval = "10s" +# Interval to submit new transactions to the provider +send_interval = "30s" +# Interval between send transaction when we get "already known" txpool err +send_transaction_retry_interval = "100ms" +# Max time to retry +send_transaction_retry_timeout = "5s" +# Interval between each send transaction to the same network +send_transaction_cool_down = "30s" +# Interval between receipt retrieval +receipt_retrieval_interval = "500ms" +# Max time to check for receipt +receipt_retrieval_timeout = "2m" + +[providers.p2] +# Uncomment to disable this provider +# disabled=true +# URL to the RPC provider +url = "http://localhost:8552" +# Read only providers are only used to check for transactions +read_only = false +# Interval to poll the provider for expected transactions +read_interval = "10s" +# Interval to submit new transactions to the provider +send_interval = "30s" +# Interval between send transaction when we get "already known" txpool err +send_transaction_retry_interval = "100ms" +# Max time to retry +send_transaction_retry_timeout = "5s" +# Interval between each send transaction to the same network +send_transaction_cool_down = "30s" +# Interval between receipt retrieval +receipt_retrieval_interval = "500ms" +# Max time to check for receipt +receipt_retrieval_timeout = "2m" diff --git a/op-ufm/go.mod b/op-ufm/go.mod new file mode 100644 index 0000000..92d5e36 --- /dev/null +++ b/op-ufm/go.mod @@ -0,0 +1,101 @@ +module github.com/ethereum-optimism/optimism/op-ufm + +go 1.21 + +toolchain go1.21.6 + +require ( + cloud.google.com/go/kms v1.12.1 + github.com/BurntSushi/toml v1.3.2 + github.com/ethereum-optimism/optimism v1.7.7-0.20240529212843-af0999ccc31d + github.com/ethereum/go-ethereum v1.13.15 + github.com/gorilla/mux v1.8.0 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.19.0 + github.com/rs/cors v1.9.0 + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 +) + +require ( + cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.0 // indirect + github.com/DataDog/zstd v1.5.2 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.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.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240522134500-19555bdbdc95 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + github.com/klauspost/compress v1.17.6 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rivo/uniseg v0.4.3 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/urfave/cli/v2 v2.27.1 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/api v0.132.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101315.1-rc.5 diff --git a/op-ufm/go.sum b/op-ufm/go.sum new file mode 100644 index 0000000..1165628 --- /dev/null +++ b/op-ufm/go.sum @@ -0,0 +1,461 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.0 h1:67gSqaPukx7O8WLLHMa0PNs3EBGd2eE4d+psbO/CO94= +cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= +cloud.google.com/go/kms v1.12.1 h1:xZmZuwy2cwzsocmKDOPu4BL7umg8QXagQx6fKVmf45U= +cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +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 v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +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 v0.0.0-20231018212520-f6cde3fc2fa4 h1:PuHFhOUMnD62r80dN+Ik5qco2drekgsUSVdcHsvllec= +github.com/cockroachdb/pebble v0.0.0-20231018212520-f6cde3fc2fa4/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum-optimism/op-geth v1.101315.1-rc.5 h1:yaBvV/GfDuZOecDsGXyzsa/g8+AEBG+Bj1+NFyZRkdw= +github.com/ethereum-optimism/op-geth v1.101315.1-rc.5/go.mod h1:8tQ6r0e1NNJbSVHzYKafQqf62gV9BzZR+SKkXRckjLM= +github.com/ethereum-optimism/optimism v1.7.7-0.20240529212843-af0999ccc31d h1:bVre/IJ14z9JtVlEHw6q1hIhE6O8o4C8jt69TmRWrAU= +github.com/ethereum-optimism/optimism v1.7.7-0.20240529212843-af0999ccc31d/go.mod h1:BgTeAhCkyj+v7PQPoDyvgXnjqAiwr+qcdMIeggigcC4= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240522134500-19555bdbdc95 h1:GjXKQg6u6WkEIcY0dvW2IKhMRY8cVjwdw+rNKhduAo8= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240522134500-19555bdbdc95/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +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-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-ole/go-ole v1.2.6/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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-bexpr v0.1.11 h1:6DqdA/KBjurGby9yTY0bmkathya0lfwF2SeuubCI7dY= +github.com/hashicorp/go-bexpr v0.1.11/go.mod h1:f03lAo0duBlDIUMGCuad8oLcgejw4m7U+N8T+6Kz1AE= +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.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +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/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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/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.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= +github.com/mitchellh/pointerstructure v1.2.1/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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= +github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +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/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/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.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +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.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/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= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.132.0 h1:8t2/+qZ26kAOGSmOiHwVycqVaDg7q3JDILrNi/Z6rvc= +google.golang.org/api v0.132.0/go.mod h1:AeTBC6GpJnJSRJjktDcPX0QwtS8pGYZOV6MSuSCusw0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/op-ufm/pkg/config/config.go b/op-ufm/pkg/config/config.go new file mode 100644 index 0000000..e0c80d6 --- /dev/null +++ b/op-ufm/pkg/config/config.go @@ -0,0 +1,171 @@ +package config + +import ( + "math/big" + + "github.com/BurntSushi/toml" + "github.com/pkg/errors" +) + +type Config struct { + LogLevel string `toml:"log_level"` + + Signer SignerServiceConfig `toml:"signer_service"` + Metrics MetricsConfig `toml:"metrics"` + Healthz HealthzConfig `toml:"healthz"` + + Wallets map[string]*WalletConfig `toml:"wallets"` + Providers map[string]*ProviderConfig `toml:"providers"` +} + +type SignerServiceConfig struct { + URL string `toml:"url"` + TLSCaCert string `toml:"tls_ca_cert"` + TLSCert string `toml:"tls_cert"` + TLSKey string `toml:"tls_key"` +} + +type MetricsConfig struct { + Enabled bool `toml:"enabled"` + Debug bool `toml:"debug"` + Host string `toml:"host"` + Port string `toml:"port"` +} + +type HealthzConfig struct { + Enabled bool `toml:"enabled"` + Host string `toml:"host"` + Port string `toml:"port"` +} + +type WalletConfig struct { + ChainID big.Int `toml:"chain_id"` + + // signer | static + SignerMethod string `toml:"signer_method"` + Address string `toml:"address"` + // private key is used for static signing + PrivateKey string `toml:"private_key"` + + // transaction parameters + TxValue big.Int `toml:"tx_value"` +} + +type ProviderConfig struct { + Network string `toml:"network"` + URL string `toml:"url"` + + ReadOnly bool `toml:"read_only"` + ReadInterval TOMLDuration `toml:"read_interval"` + + SendInterval TOMLDuration `toml:"send_interval"` + SendTransactionRetryInterval TOMLDuration `toml:"send_transaction_retry_interval"` + SendTransactionRetryTimeout TOMLDuration `toml:"send_transaction_retry_timeout"` + SendTransactionCoolDown TOMLDuration `toml:"send_transaction_cool_down"` + ReceiptRetrievalInterval TOMLDuration `toml:"receipt_retrieval_interval"` + ReceiptRetrievalTimeout TOMLDuration `toml:"receipt_retrieval_timeout"` + + Wallet string `toml:"wallet"` +} + +func New(file string) (*Config, error) { + cfg := &Config{} + if _, err := toml.DecodeFile(file, cfg); err != nil { + return nil, err + } + return cfg, nil +} + +func (c *Config) Validate() error { + if c.Metrics.Enabled { + if c.Metrics.Host == "" || c.Metrics.Port == "" { + return errors.New("metrics is enabled but host or port are missing") + } + } + if c.Healthz.Enabled { + if c.Healthz.Host == "" || c.Healthz.Port == "" { + return errors.New("healthz is enabled but host or port are missing") + } + } + + if len(c.Wallets) == 0 { + return errors.New("at least one wallet must be set") + } + + if len(c.Providers) == 0 { + return errors.New("at least one provider must be set") + } + + for name, wallet := range c.Wallets { + if wallet.ChainID.BitLen() == 0 { + return errors.Errorf("wallet [%s] chain_id is missing", name) + } + if wallet.SignerMethod != "signer" && wallet.SignerMethod != "static" { + return errors.Errorf("wallet [%s] signer_method is invalid", name) + } + if wallet.SignerMethod == "signer" { + if c.Signer.URL == "" { + return errors.New("signer url is missing") + } + if c.Signer.TLSCaCert == "" { + return errors.New("signer tls_ca_cert is missing") + } + if c.Signer.TLSCert == "" { + return errors.New("signer tls_cert is missing") + } + if c.Signer.TLSKey == "" { + return errors.New("signer tls_key is missing") + } + } + if wallet.SignerMethod == "static" { + if wallet.PrivateKey == "" { + return errors.Errorf("wallet [%s] private_key is missing", name) + } + } + if wallet.Address == "" { + return errors.Errorf("wallet [%s] address is missing", name) + } + if wallet.TxValue.BitLen() == 0 { + return errors.Errorf("wallet [%s] tx_value is missing", name) + } + } + + for name, provider := range c.Providers { + if provider.URL == "" { + return errors.Errorf("provider [%s] url is missing", name) + } + if provider.ReadInterval == 0 { + return errors.Errorf("provider [%s] read_interval is missing", name) + } + if provider.SendInterval == 0 { + return errors.Errorf("provider [%s] send_interval is missing", name) + } + if provider.SendTransactionRetryInterval == 0 { + return errors.Errorf("provider [%s] send_transaction_retry_interval is missing", name) + } + if provider.SendTransactionRetryTimeout == 0 { + return errors.Errorf("provider [%s] send_transaction_retry_timeout is missing", name) + } + if provider.SendTransactionCoolDown == 0 { + return errors.Errorf("provider [%s] send_transaction_cool_down is missing", name) + } + if provider.ReceiptRetrievalInterval == 0 { + return errors.Errorf("provider [%s] receipt_retrieval_interval is missing", name) + } + if provider.ReceiptRetrievalTimeout == 0 { + return errors.Errorf("provider [%s] receipt_retrieval_timeout is missing", name) + } + if provider.Wallet == "" { + return errors.Errorf("provider [%s] wallet is missing", name) + } + if _, ok := c.Wallets[provider.Wallet]; !ok { + return errors.Errorf("provider [%s] has an invalid wallet [%s]", name, provider.Wallet) + } + } + + if c.LogLevel == "" { + c.LogLevel = "debug" + } + + return nil +} diff --git a/op-ufm/pkg/config/toml_duration.go b/op-ufm/pkg/config/toml_duration.go new file mode 100644 index 0000000..64fe368 --- /dev/null +++ b/op-ufm/pkg/config/toml_duration.go @@ -0,0 +1,15 @@ +package config + +import "time" + +type TOMLDuration time.Duration + +func (t *TOMLDuration) UnmarshalText(b []byte) error { + d, err := time.ParseDuration(string(b)) + if err != nil { + return err + } + + *t = TOMLDuration(d) + return nil +} diff --git a/op-ufm/pkg/metrics/clients/eth.go b/op-ufm/pkg/metrics/clients/eth.go new file mode 100644 index 0000000..92d7c25 --- /dev/null +++ b/op-ufm/pkg/metrics/clients/eth.go @@ -0,0 +1,123 @@ +package clients + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +type InstrumentedEthClient struct { + c *ethclient.Client + providerName string +} + +func Dial(providerName string, url string) (*InstrumentedEthClient, error) { + start := time.Now() + c, err := ethclient.Dial(url) + if err != nil { + metrics.RecordErrorDetails(providerName, "ethclient.Dial", err) + return nil, err + } + metrics.RecordRPCLatency(providerName, "ethclient", "Dial", time.Since(start)) + return &InstrumentedEthClient{c: c, providerName: providerName}, nil +} + +func (i *InstrumentedEthClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { + start := time.Now() + tx, isPending, err := i.c.TransactionByHash(ctx, hash) + if err != nil { + if !i.ignorableErrors(err) { + metrics.RecordErrorDetails(i.providerName, "ethclient.TransactionByHash", err) + } + return nil, false, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "TransactionByHash", time.Since(start)) + return tx, isPending, err +} + +func (i *InstrumentedEthClient) PendingNonceAt(ctx context.Context, address string) (uint64, error) { + start := time.Now() + nonce, err := i.c.PendingNonceAt(ctx, common.HexToAddress(address)) + if err != nil { + metrics.RecordErrorDetails(i.providerName, "ethclient.PendingNonceAt", err) + return 0, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "PendingNonceAt", time.Since(start)) + return nonce, err +} + +func (i *InstrumentedEthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + start := time.Now() + receipt, err := i.c.TransactionReceipt(ctx, txHash) + if err != nil { + if !i.ignorableErrors(err) { + metrics.RecordErrorDetails(i.providerName, "ethclient.TransactionReceipt", err) + } + return nil, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "TransactionReceipt", time.Since(start)) + return receipt, err +} + +func (i *InstrumentedEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + start := time.Now() + err := i.c.SendTransaction(ctx, tx) + if err != nil { + if !i.ignorableErrors(err) { + metrics.RecordErrorDetails(i.providerName, "ethclient.SendTransaction", err) + } + return err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "SendTransaction", time.Since(start)) + return err +} + +func (i *InstrumentedEthClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + start := time.Now() + gas, err := i.c.EstimateGas(ctx, msg) + if err != nil { + metrics.RecordErrorDetails(i.providerName, "ethclient.EstimateGas", err) + return 0, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "EstimateGas", time.Since(start)) + return gas, err +} + +func (i *InstrumentedEthClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + start := time.Now() + gasTipCap, err := i.c.SuggestGasTipCap(ctx) + if err != nil { + metrics.RecordErrorDetails(i.providerName, "ethclient.SuggestGasTipCap", err) + return nil, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "SuggestGasTipCap", time.Since(start)) + return gasTipCap, err +} + +func (i *InstrumentedEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + start := time.Now() + header, err := i.c.HeaderByNumber(ctx, number) + if err != nil { + metrics.RecordErrorDetails(i.providerName, "ethclient.HeaderByNumber", err) + return nil, err + } + metrics.RecordRPCLatency(i.providerName, "ethclient", "HeaderByNumber", time.Since(start)) + return header, err +} + +func (i *InstrumentedEthClient) ignorableErrors(err error) bool { + msg := err.Error() + // we dont use errors.Is because eth client actually uses errors.New, + // therefore creating an incomparable instance :( + return msg == ethereum.NotFound.Error() || + msg == txpool.ErrAlreadyKnown.Error() || + msg == core.ErrNonceTooLow.Error() +} diff --git a/op-ufm/pkg/metrics/clients/signer.go b/op-ufm/pkg/metrics/clients/signer.go new file mode 100644 index 0000000..6d0c413 --- /dev/null +++ b/op-ufm/pkg/metrics/clients/signer.go @@ -0,0 +1,42 @@ +package clients + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" + "github.com/ethereum/go-ethereum/common" + + signer "github.com/ethereum-optimism/optimism/op-service/signer" + optls "github.com/ethereum-optimism/optimism/op-service/tls" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +type InstrumentedSignerClient struct { + c *signer.SignerClient + providerName string +} + +func NewSignerClient(providerName string, logger log.Logger, endpoint string, tlsConfig optls.CLIConfig) (*InstrumentedSignerClient, error) { + start := time.Now() + c, err := signer.NewSignerClient(logger, endpoint, tlsConfig) + if err != nil { + metrics.RecordErrorDetails(providerName, "signer.NewSignerClient", err) + return nil, err + } + metrics.RecordRPCLatency(providerName, "signer", "NewSignerClient", time.Since(start)) + return &InstrumentedSignerClient{c: c, providerName: providerName}, nil +} + +func (i *InstrumentedSignerClient) SignTransaction(ctx context.Context, chainId *big.Int, from *common.Address, tx *types.Transaction) (*types.Transaction, error) { + start := time.Now() + tx, err := i.c.SignTransaction(ctx, chainId, *from, tx) + if err != nil { + metrics.RecordErrorDetails(i.providerName, "signer.SignTransaction", err) + return nil, err + } + metrics.RecordRPCLatency(i.providerName, "signer", "SignTransaction", time.Since(start)) + return tx, err +} diff --git a/op-ufm/pkg/metrics/metrics.go b/op-ufm/pkg/metrics/metrics.go new file mode 100644 index 0000000..6c8a64a --- /dev/null +++ b/op-ufm/pkg/metrics/metrics.go @@ -0,0 +1,166 @@ +package metrics + +import ( + fmt "fmt" + "regexp" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +const ( + MetricsNamespace = "ufm" +) + +var ( + Debug bool + + errorsTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "errors_total", + Help: "Count of errors", + }, []string{ + "provider", + "error", + }) + + rpcLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "rpc_latency", + Help: "RPC latency per provider, client and method (ms)", + }, []string{ + "provider", + "client", + "method", + }) + + roundTripLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "roundtrip_latency", + Help: "Round trip latency per provider (ms)", + }, []string{ + "provider", + }) + + gasUsed = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "gas_used", + Help: "Gas used per provider", + }, []string{ + "provider", + }) + + firstSeenLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "first_seen_latency", + Help: "First seen latency latency per provider (ms)", + }, []string{ + "provider_source", + "provider_seen", + }) + + providerToProviderLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "provider_to_provider_latency", + Help: "Provider to provider latency (ms)", + }, []string{ + "provider_source", + "provider_seen", + }) + + networkTransactionsInFlight = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "transactions_inflight", + Help: "Transactions in flight, per network", + }, []string{ + "network", + }) +) + +var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z ]+`) + +func RecordError(provider string, errorLabel string) { + if Debug { + log.Debug("metric inc", + "m", "errors_total", + "provider", provider, + "error", errorLabel) + } + errorsTotal.WithLabelValues(provider, errorLabel).Inc() +} + +// RecordErrorDetails concats the error message to the label removing non-alpha chars +func RecordErrorDetails(provider string, label string, err error) { + errClean := nonAlphanumericRegex.ReplaceAllString(err.Error(), "") + errClean = strings.ReplaceAll(errClean, " ", "_") + errClean = strings.ReplaceAll(errClean, "__", "_") + label = fmt.Sprintf("%s.%s", label, errClean) + RecordError(provider, label) +} + +func RecordRPCLatency(provider string, client string, method string, latency time.Duration) { + if Debug { + log.Debug("metric set", + "m", "rpc_latency", + "provider", provider, + "client", client, + "method", method, + "latency", latency) + } + rpcLatency.WithLabelValues(provider, client, method).Set(float64(latency.Milliseconds())) +} + +func RecordRoundTripLatency(provider string, latency time.Duration) { + if Debug { + log.Debug("metric set", + "m", "roundtrip_latency", + "provider", provider, + "latency", latency) + } + roundTripLatency.WithLabelValues(provider).Set(float64(latency.Milliseconds())) +} + +func RecordGasUsed(provider string, val uint64) { + if Debug { + log.Debug("metric add", + "m", "gas_used", + "provider", provider, + "val", val) + } + gasUsed.WithLabelValues(provider).Set(float64(val)) +} + +func RecordFirstSeenLatency(providerSource string, providerSeen string, latency time.Duration) { + if Debug { + log.Debug("metric set", + "m", "first_seen_latency", + "provider_source", providerSource, + "provider_seen", providerSeen, + "latency", latency) + } + firstSeenLatency.WithLabelValues(providerSource, providerSeen).Set(float64(latency.Milliseconds())) +} + +func RecordProviderToProviderLatency(providerSource string, providerSeen string, latency time.Duration) { + if Debug { + log.Debug("metric set", + "m", "provider_to_provider_latency", + "provider_source", providerSource, + "provider_seen", providerSeen, + "latency", latency) + } + providerToProviderLatency.WithLabelValues(providerSource, providerSeen).Set(float64(latency.Milliseconds())) +} + +func RecordTransactionsInFlight(network string, count int) { + if Debug { + log.Debug("metric set", + "m", "transactions_inflight", + "network", network, + "count", count) + } + networkTransactionsInFlight.WithLabelValues(network).Set(float64(count)) +} diff --git a/op-ufm/pkg/provider/heartbeat.go b/op-ufm/pkg/provider/heartbeat.go new file mode 100644 index 0000000..ddc6ed2 --- /dev/null +++ b/op-ufm/pkg/provider/heartbeat.go @@ -0,0 +1,104 @@ +package provider + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics/clients" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/log" + "github.com/pkg/errors" +) + +// Heartbeat polls for expected in-flight transactions +func (p *Provider) Heartbeat(ctx context.Context) { + log.Debug("heartbeat", + "provider", p.name, + "count", len(p.txPool.Transactions)) + + metrics.RecordTransactionsInFlight(p.config.Network, len(p.txPool.Transactions)) + + // let's exclude transactions already seen by this provider, or originated by it + expectedTransactions := make([]*TransactionState, 0, len(p.txPool.Transactions)) + alreadySeen := 0 + for _, st := range p.txPool.Transactions { + if st.ProviderSource == p.name { + continue + } + if _, exist := st.SeenBy[p.name]; exist { + alreadySeen++ + continue + } + expectedTransactions = append(expectedTransactions, st) + } + + if len(expectedTransactions) == 0 { + log.Debug("no expected txs", + "count", len(p.txPool.Transactions), + "provider", p.name, + "alreadySeen", alreadySeen) + return + } + + client, err := clients.Dial(p.name, p.config.URL) + if err != nil { + log.Error("cant dial to provider", + "provider", p.name, + "url", p.config.URL, + "err", err) + } + + log.Debug("checking in-flight tx", + "count", len(p.txPool.Transactions), + "provider", p.name, + "alreadySeen", alreadySeen) + for _, st := range expectedTransactions { + hash := st.Hash.Hex() + + _, isPending, err := client.TransactionByHash(ctx, st.Hash) + if err != nil && !errors.Is(err, ethereum.NotFound) { + log.Error("cant check transaction", + "provider", p.name, + "hash", hash, + "url", p.config.URL, + "err", err) + continue + } + + log.Debug("got transaction", + "provider", p.name, + "hash", hash, + "isPending", isPending) + + // mark transaction as seen by this provider + st.M.Lock() + latency := time.Since(st.SentAt) + if st.FirstSeen.IsZero() { + st.FirstSeen = time.Now() + metrics.RecordFirstSeenLatency(st.ProviderSource, p.name, latency) + log.Info("transaction first seen", + "hash", hash, + "firstSeenLatency", latency, + "providerSource", st.ProviderSource, + "providerSeen", p.name) + } + if _, exist := st.SeenBy[p.name]; !exist { + st.SeenBy[p.name] = time.Now() + metrics.RecordProviderToProviderLatency(st.ProviderSource, p.name, latency) + } + st.M.Unlock() + + // check if transaction have been seen by all providers + p.txPool.M.Lock() + if len(st.SeenBy) == p.txPool.Expected { + log.Debug("transaction seen by all", + "hash", hash, + "expected", p.txPool.Expected, + "seenBy", len(st.SeenBy)) + delete(p.txPool.Transactions, st.Hash.Hex()) + } + p.txPool.M.Unlock() + } +} diff --git a/op-ufm/pkg/provider/provider.go b/op-ufm/pkg/provider/provider.go new file mode 100644 index 0000000..31626e3 --- /dev/null +++ b/op-ufm/pkg/provider/provider.go @@ -0,0 +1,72 @@ +package provider + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/config" +) + +type Provider struct { + name string + config *config.ProviderConfig + signerConfig *config.SignerServiceConfig + walletConfig *config.WalletConfig + txPool *NetworkTransactionPool + + cancelFunc context.CancelFunc +} + +func New(name string, cfg *config.ProviderConfig, + signerConfig *config.SignerServiceConfig, + walletConfig *config.WalletConfig, + txPool *NetworkTransactionPool) *Provider { + p := &Provider{ + name: name, + config: cfg, + signerConfig: signerConfig, + walletConfig: walletConfig, + txPool: txPool, + } + return p +} + +func (p *Provider) Start(ctx context.Context) { + providerCtx, cancelFunc := context.WithCancel(ctx) + p.cancelFunc = cancelFunc + + schedule(providerCtx, time.Duration(p.config.ReadInterval), p.Heartbeat) + if !p.config.ReadOnly { + schedule(providerCtx, time.Duration(p.config.SendInterval), p.RoundTrip) + } +} + +func (p *Provider) Shutdown() { + if p.cancelFunc != nil { + p.cancelFunc() + } +} + +func (p *Provider) Name() string { + return p.name +} + +func (p *Provider) URL() string { + return p.config.URL +} + +func schedule(ctx context.Context, interval time.Duration, handler func(ctx context.Context)) { + go func() { + for { + timer := time.NewTimer(interval) + handler(ctx) + + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + return + } + } + }() +} diff --git a/op-ufm/pkg/provider/roundtrip.go b/op-ufm/pkg/provider/roundtrip.go new file mode 100644 index 0000000..04b9af0 --- /dev/null +++ b/op-ufm/pkg/provider/roundtrip.go @@ -0,0 +1,312 @@ +package provider + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" + iclients "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics/clients" + "github.com/ethereum/go-ethereum/core" + + "github.com/ethereum-optimism/optimism/op-service/tls" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// RoundTrip send a new transaction to measure round trip latency +func (p *Provider) RoundTrip(ctx context.Context) { + log.Debug("RoundTrip", + "provider", p.name) + + client, err := iclients.Dial(p.name, p.config.URL) + if err != nil { + log.Error("cant dial to provider", + "provider", p.name, + "url", p.config.URL, + "err", err) + return + } + + p.txPool.ExclusiveSend.Lock() + defer p.txPool.ExclusiveSend.Unlock() + + // lint:ignore SA4006 txHash is set and used within tx sending loop + txHash := common.Hash{} + attempt := 0 + nonce := uint64(0) + + // used for timeout + firstAttemptAt := time.Now() + // used for actual round trip time (disregard retry time) + var roundTripStartedAt time.Time + for { + + // sleep until we get a clear to send + for { + coolDown := time.Duration(p.config.SendTransactionCoolDown) - time.Since(p.txPool.LastSend) + if coolDown > 0 { + time.Sleep(coolDown) + } else { + break + } + } + + from, tx, err := p.createTx(ctx, client, nonce) + if err != nil { + log.Error("cant create tx", + "provider", p.name, + "nonce", nonce, + "err", err) + return + } + nonce = tx.Nonce() + + signedTx, err := p.sign(ctx, from, tx) + if err != nil { + log.Error("cant sign tx", + "provider", p.name, + "tx", tx, + "err", err) + return + } + txHash = signedTx.Hash() + + roundTripStartedAt = time.Now() + err = client.SendTransaction(ctx, signedTx) + if err != nil { + if err.Error() == txpool.ErrAlreadyKnown.Error() || + err.Error() == txpool.ErrReplaceUnderpriced.Error() || + err.Error() == core.ErrNonceTooLow.Error() { + + log.Warn("cant send transaction (retryable)", + "provider", p.name, + "err", err, + "nonce", nonce) + + if time.Since(firstAttemptAt) >= time.Duration(p.config.SendTransactionRetryTimeout) { + log.Error("send transaction timed out (known already)", + "provider", p.name, + "hash", txHash.Hex(), + "nonce", nonce, + "elapsed", time.Since(firstAttemptAt), + "attempt", attempt) + metrics.RecordErrorDetails(p.name, "send.timeout", err) + return + } + + log.Warn("tx already known, incrementing nonce and trying again", + "provider", p.name, + "nonce", nonce) + time.Sleep(time.Duration(p.config.SendTransactionRetryInterval)) + + nonce++ + attempt++ + if attempt%10 == 0 { + log.Debug("retrying send transaction...", + "provider", p.name, + "attempt", attempt, + "nonce", nonce, + "elapsed", time.Since(firstAttemptAt)) + } + } else { + log.Error("cant send transaction", + "provider", p.name, + "nonce", nonce, + "err", err) + metrics.RecordErrorDetails(p.name, "ethclient.SendTransaction", err) + return + } + } else { + break + } + } + + log.Info("transaction sent", + "provider", p.name, + "hash", txHash.Hex(), + "nonce", nonce) + + // add to pool + sentAt := time.Now() + p.txPool.M.Lock() + p.txPool.Transactions[txHash.Hex()] = &TransactionState{ + Hash: txHash, + ProviderSource: p.name, + SentAt: sentAt, + SeenBy: make(map[string]time.Time), + } + p.txPool.LastSend = sentAt + p.txPool.M.Unlock() + + var receipt *types.Receipt + attempt = 0 + for receipt == nil { + if time.Since(sentAt) >= time.Duration(p.config.ReceiptRetrievalTimeout) { + log.Error("receipt retrieval timed out", + "provider", p.name, + "hash", txHash, + "nonce", nonce, + "elapsed", time.Since(sentAt)) + metrics.RecordErrorDetails(p.name, "receipt.timeout", err) + return + } + time.Sleep(time.Duration(p.config.ReceiptRetrievalInterval)) + if attempt%10 == 0 { + log.Debug("checking for receipt...", + "provider", p.name, + "hash", txHash, + "nonce", nonce, + "attempt", attempt, + "elapsed", time.Since(sentAt)) + } + receipt, err = client.TransactionReceipt(ctx, txHash) + if err != nil && !errors.Is(err, ethereum.NotFound) { + log.Error("cant get receipt for transaction", + "provider", p.name, + "hash", txHash.Hex(), + "nonce", nonce, + "err", err) + return + } + attempt++ + } + + roundTripLatency := time.Since(roundTripStartedAt) + + metrics.RecordRoundTripLatency(p.name, roundTripLatency) + metrics.RecordGasUsed(p.name, receipt.GasUsed) + + log.Info("got transaction receipt", + "hash", txHash.Hex(), + "nonce", nonce, + "roundTripLatency", roundTripLatency, + "provider", p.name, + "blockNumber", receipt.BlockNumber, + "blockHash", receipt.BlockHash, + "gasUsed", receipt.GasUsed) +} + +func (p *Provider) createTx(ctx context.Context, client *iclients.InstrumentedEthClient, nonce uint64) (*common.Address, *types.Transaction, error) { + var err error + if nonce == 0 { + nonce, err = client.PendingNonceAt(ctx, p.walletConfig.Address) + if err != nil { + log.Error("cant get nonce", + "provider", p.name, + "nonce", nonce, + "err", err) + return nil, nil, err + } + } + + gasTipCap, err := client.SuggestGasTipCap(ctx) + if err != nil { + log.Error("cant get gas tip cap", + "provider", p.name, + "err", err) + return nil, nil, err + } + + // adjust gas tip cap by 110% + const GasTipCapAdjustmentMultiplier = 110 + const GasTipCapAdjustmentDivisor = 100 + gasTipCap = new(big.Int).Mul(gasTipCap, big.NewInt(GasTipCapAdjustmentMultiplier)) + gasTipCap = new(big.Int).Div(gasTipCap, big.NewInt(GasTipCapAdjustmentDivisor)) + + head, err := client.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("cant get base fee from head", + "provider", p.name, + "err", err) + return nil, nil, err + } + baseFee := head.BaseFee + + gasFeeCap := new(big.Int).Add( + gasTipCap, + new(big.Int).Mul(baseFee, big.NewInt(2))) + + addr := common.HexToAddress(p.walletConfig.Address) + var data []byte + dynamicTx := &types.DynamicFeeTx{ + ChainID: &p.walletConfig.ChainID, + Nonce: nonce, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + To: &addr, + Value: &p.walletConfig.TxValue, + Data: data, + } + + gas, err := client.EstimateGas(ctx, ethereum.CallMsg{ + From: addr, + To: &addr, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Data: dynamicTx.Data, + Value: dynamicTx.Value, + }) + if err != nil { + log.Error("cant estimate gas", + "provider", p.name, + "err", err) + return nil, nil, err + } + dynamicTx.Gas = gas + tx := types.NewTx(dynamicTx) + + log.Info("tx created", + "provider", p.name, + "from", addr, + "to", dynamicTx.To, + "nonce", dynamicTx.Nonce, + "value", dynamicTx.Value, + "gas", dynamicTx.Gas, + "gasTipCap", dynamicTx.GasTipCap, + "gasFeeCap", dynamicTx.GasFeeCap, + ) + + return &addr, tx, nil +} + +func (p *Provider) sign(ctx context.Context, from *common.Address, tx *types.Transaction) (*types.Transaction, error) { + if p.walletConfig.SignerMethod == "static" { + log.Debug("using static signer") + privateKey, err := crypto.HexToECDSA(p.walletConfig.PrivateKey) + if err != nil { + log.Error("failed to parse private key", "err", err) + return nil, err + } + return types.SignTx(tx, types.LatestSignerForChainID(&p.walletConfig.ChainID), privateKey) + } else if p.walletConfig.SignerMethod == "signer" { + tlsConfig := tls.CLIConfig{ + TLSCaCert: p.signerConfig.TLSCaCert, + TLSCert: p.signerConfig.TLSCert, + TLSKey: p.signerConfig.TLSKey, + } + client, err := iclients.NewSignerClient(p.name, log.Root(), p.signerConfig.URL, tlsConfig) + if err != nil || client == nil { + log.Error("failed to create signer client", "err", err) + } + + if client == nil { + return nil, errors.New("could not initialize signer client") + } + + signedTx, err := client.SignTransaction(ctx, &p.walletConfig.ChainID, from, tx) + if err != nil { + return nil, err + } + + return signedTx, nil + } else { + return nil, errors.New("invalid signer method") + } +} diff --git a/op-ufm/pkg/provider/tx_pool.go b/op-ufm/pkg/provider/tx_pool.go new file mode 100644 index 0000000..2ea5f6a --- /dev/null +++ b/op-ufm/pkg/provider/tx_pool.go @@ -0,0 +1,40 @@ +package provider + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +// TransactionPool is used locally to share transactions between providers under the same pool +type TransactionPool map[string]*NetworkTransactionPool + +// NetworkTransactionPool is used locally to share transactions between providers under the same network +type NetworkTransactionPool struct { + M sync.Mutex + Transactions map[string]*TransactionState + Expected int + + // Last time a transaction was sent + LastSend time.Time + // Prevents concurrent transaction send + ExclusiveSend sync.Mutex +} + +type TransactionState struct { + // Transaction hash + Hash common.Hash + + // Mutex + M sync.Mutex + + SentAt time.Time + ProviderSource string + + FirstSeen time.Time + + // Map of providers that have seen this transaction, and when + // Once all providers have seen the transaction it is removed from the pool + SeenBy map[string]time.Time +} diff --git a/op-ufm/pkg/service/healthz_server.go b/op-ufm/pkg/service/healthz_server.go new file mode 100644 index 0000000..2fceea6 --- /dev/null +++ b/op-ufm/pkg/service/healthz_server.go @@ -0,0 +1,42 @@ +package service + +import ( + "context" + "net/http" + + "github.com/gorilla/mux" + "github.com/rs/cors" + + "github.com/ethereum/go-ethereum/log" +) + +type HealthzServer struct { + ctx context.Context + server *http.Server +} + +func (h *HealthzServer) Start(ctx context.Context, addr string) error { + hdlr := mux.NewRouter() + hdlr.HandleFunc("/healthz", h.Handle).Methods("GET") + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + }) + server := &http.Server{ + Handler: c.Handler(hdlr), + Addr: addr, + } + h.server = server + h.ctx = ctx + return h.server.ListenAndServe() +} + +func (h *HealthzServer) Shutdown() error { + return h.server.Shutdown(h.ctx) +} + +func (h *HealthzServer) Handle(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("OK")) + if err != nil { + log.Error("error handling HealthzServer response") + } +} diff --git a/op-ufm/pkg/service/metrics_server.go b/op-ufm/pkg/service/metrics_server.go new file mode 100644 index 0000000..4371d48 --- /dev/null +++ b/op-ufm/pkg/service/metrics_server.go @@ -0,0 +1,27 @@ +package service + +import ( + "context" + "net/http" + + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type MetricsServer struct { + ctx context.Context + server *http.Server +} + +func (m *MetricsServer) Start(ctx context.Context, addr string) error { + server := &http.Server{ + Handler: promhttp.Handler(), + Addr: addr, + } + m.server = server + m.ctx = ctx + return m.server.ListenAndServe() +} + +func (m *MetricsServer) Shutdown() error { + return m.server.Shutdown(m.ctx) +} diff --git a/op-ufm/pkg/service/service.go b/op-ufm/pkg/service/service.go new file mode 100644 index 0000000..c131209 --- /dev/null +++ b/op-ufm/pkg/service/service.go @@ -0,0 +1,111 @@ +package service + +import ( + "context" + "net" + + "github.com/ethereum-optimism/optimism/op-ufm/pkg/config" + "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics" + "github.com/ethereum-optimism/optimism/op-ufm/pkg/provider" + + "github.com/ethereum/go-ethereum/log" +) + +type Service struct { + Config *config.Config + Healthz *HealthzServer + Metrics *MetricsServer + Providers map[string]*provider.Provider +} + +func New(cfg *config.Config) *Service { + s := &Service{ + Config: cfg, + Healthz: &HealthzServer{}, + Metrics: &MetricsServer{}, + Providers: make(map[string]*provider.Provider, len(cfg.Providers)), + } + return s +} + +func (s *Service) Start(ctx context.Context) { + log.Info("service starting") + if s.Config.Healthz.Enabled { + addr := net.JoinHostPort(s.Config.Healthz.Host, s.Config.Healthz.Port) + log.Info("starting healthz server", + "addr", addr) + go func() { + if err := s.Healthz.Start(ctx, addr); err != nil { + log.Error("error starting healthz server", + "err", err) + } + }() + } + + metrics.Debug = s.Config.Metrics.Debug + if s.Config.Metrics.Enabled { + addr := net.JoinHostPort(s.Config.Metrics.Host, s.Config.Metrics.Port) + log.Info("starting metrics server", + "addr", addr) + go func() { + if err := s.Metrics.Start(ctx, addr); err != nil { + log.Error("error starting metrics server", + "err", err) + } + }() + } + + // map networks to its providers + networks := make(map[string][]string) + for name, providerConfig := range s.Config.Providers { + networks[providerConfig.Network] = append(networks[providerConfig.Network], name) + } + + txpool := &provider.TransactionPool{} + for name, providers := range networks { + if len(providers) == 1 { + log.Warn("can't measure first seen for network, please another provider", + "network", name) + } + (*txpool)[name] = &provider.NetworkTransactionPool{} + (*txpool)[name].Transactions = make(map[string]*provider.TransactionState) + // set expected number of providers for this network + // -1 since we don't wait for acking from the same provider + (*txpool)[name].Expected = len(providers) - 1 + } + + for name, providerConfig := range s.Config.Providers { + s.Providers[name] = provider.New(name, + providerConfig, + &s.Config.Signer, + s.Config.Wallets[providerConfig.Wallet], + (*txpool)[providerConfig.Network]) + s.Providers[name].Start(ctx) + log.Info("provider started", + "provider", name) + } + + log.Info("service started") +} + +func (s *Service) Shutdown() { + log.Info("service shutting down") + if s.Config.Healthz.Enabled { + if err := s.Healthz.Shutdown(); err != nil { + log.Error("Error shutting down healthz server", err) + } + log.Info("healthz stopped") + } + if s.Config.Metrics.Enabled { + if err := s.Metrics.Shutdown(); err != nil { + log.Error("Error shutting down metrics server", err) + } + log.Info("metrics stopped") + } + for name, provider := range s.Providers { + provider.Shutdown() + log.Info("provider stopped", + "provider", name) + } + log.Info("service stopped") +} diff --git a/op-ufm/tools/kmstool/main.go b/op-ufm/tools/kmstool/main.go new file mode 100644 index 0000000..b5ff40e --- /dev/null +++ b/op-ufm/tools/kmstool/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "fmt" + "os" + + kms "cloud.google.com/go/kms/apiv1" + "cloud.google.com/go/kms/apiv1/kmspb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func main() { + println("kmstool - usage: kmstool ") + + if len(os.Args) < 2 { + panic("missing ") + } + + keyName := os.Args[1] + + ctx := context.Background() + client, err := kms.NewKeyManagementClient(ctx) + if err != nil { + panic(fmt.Errorf("failed to create kms client: %w", err)) + } + defer client.Close() + + addr, err := resolveAddr(ctx, client, keyName) + if err != nil { + panic(fmt.Errorf("failed to retrieve the key: %w", err)) + } + fmt.Printf("ethereum addr: %s", addr) + println() + println() +} + +func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName string) (common.Address, error) { + resp, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: keyName}) + if err != nil { + return common.Address{}, fmt.Errorf("google kms public key %q lookup: %w", keyName, err) + } + keyPem := resp.Pem + + block, _ := pem.Decode([]byte(keyPem)) + if block == nil { + return common.Address{}, fmt.Errorf("google kms public key %q pem empty: %.130q", keyName, keyPem) + } + + var info struct { + AlgID pkix.AlgorithmIdentifier + Key asn1.BitString + } + _, err = asn1.Unmarshal(block.Bytes, &info) + if err != nil { + return common.Address{}, fmt.Errorf("google kms public key %q pem block %q: %w", keyName, block.Type, err) + } + + return pubKeyAddr(info.Key.Bytes), nil +} + +// PubKeyAddr returns the Ethereum address for the (uncompressed) key bytes. +func pubKeyAddr(bytes []byte) common.Address { + digest := crypto.Keccak256(bytes[1:]) + var addr common.Address + copy(addr[:], digest[12:]) + return addr +}