From 01644c8b0eda287085f66ddc9f0fdcc2dfac72da Mon Sep 17 00:00:00 2001 From: fearlessfe <505380967@qq.com> Date: Thu, 26 Dec 2024 20:39:34 +0800 Subject: [PATCH] feat: make metrics works --- .gitignore | 1 + Makefile | 4 +- cmd/shisui/config.go | 241 ++++++++++++++++++++++++++++++++++++++ cmd/shisui/main.go | 175 ++------------------------- cmd/shisui/utils/flags.go | 101 +++++++--------- go.sum | 4 - 6 files changed, 295 insertions(+), 231 deletions(-) create mode 100644 cmd/shisui/config.go diff --git a/.gitignore b/.gitignore index 9ef9604..6fedac3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build +.vscode diff --git a/Makefile b/Makefile index b1ec09c..d783257 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ GIT_DATE := $(shell git log -1 --format=%ci | cut -d ' ' -f 1) #? shisui: Build shisui shisui: - go build -ldflags "-X github.com/zen-eth/shisui/internal/version.gitCommit=$(GIT_COMMIT) -X github.com/zen-eth/shisui/internal/version.gitDate=$(GIT_DATE)" ./cmd/shisui/main.go + go build -ldflags "-X github.com/zen-eth/shisui/internal/version.gitCommit=$(GIT_COMMIT) -X github.com/zen-eth/shisui/internal/version.gitDate=$(GIT_DATE)" ./cmd/shisui mkdir -p $(GOBIN) - mv main $(GOBIN)/shisui + mv shisui $(GOBIN)/shisui @echo "Done building." @echo "Run \"$(GOBIN)/shisui\" to launch shisui." diff --git a/cmd/shisui/config.go b/cmd/shisui/config.go new file mode 100644 index 0000000..6f6f9ca --- /dev/null +++ b/cmd/shisui/config.go @@ -0,0 +1,241 @@ +package main + +import ( + "crypto/ecdsa" + "encoding/hex" + "net" + "os" + "path/filepath" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/urfave/cli/v2" + "github.com/zen-eth/shisui/cmd/shisui/utils" + "github.com/zen-eth/shisui/portalwire" +) + +func getPortalConfig(ctx *cli.Context) (*Config, error) { + config := &Config{ + Protocol: portalwire.DefaultPortalProtocolConfig(), + Metrics: &metrics.DefaultConfig, + } + + httpAddr := ctx.String(utils.PortalRPCListenAddrFlag.Name) + httpPort := ctx.String(utils.PortalRPCPortFlag.Name) + config.RpcAddr = net.JoinHostPort(httpAddr, httpPort) + config.DataDir = ctx.String(utils.PortalDataDirFlag.Name) + config.DataCapacity = ctx.Uint64(utils.PortalDataCapacityFlag.Name) + config.LogLevel = ctx.Int(utils.PortalLogLevelFlag.Name) + port := ctx.String(utils.PortalUDPPortFlag.Name) + if !strings.HasPrefix(port, ":") { + config.Protocol.ListenAddr = ":" + port + } else { + config.Protocol.ListenAddr = port + } + + err := setPrivateKey(ctx, config) + if err != nil { + return config, err + } + + natString := ctx.String(utils.PortalNATFlag.Name) + if natString != "" { + natInterface, err := nat.Parse(natString) + if err != nil { + return config, err + } + config.Protocol.NAT = natInterface + } + + setPortalBootstrapNodes(ctx, config) + config.Networks = ctx.StringSlice(utils.PortalNetworksFlag.Name) + + applyMetricConfig(ctx, config) + return config, nil +} + +func setPrivateKey(ctx *cli.Context, config *Config) error { + var privateKey *ecdsa.PrivateKey + var err error + keyStr := ctx.String(utils.PortalPrivateKeyFlag.Name) + if keyStr != "" { + keyBytes, err := hexutil.Decode(keyStr) + if err != nil { + return err + } + privateKey, err = crypto.ToECDSA(keyBytes) + if err != nil { + return err + } + } else { + fullPath := filepath.Join(config.DataDir, privateKeyFileName) + if _, err := os.Stat(fullPath); err == nil { + log.Info("Loading private key from file", "datadir", config.DataDir, "file", privateKeyFileName) + privateKey, err = readPrivateKey(config, privateKeyFileName) + if err != nil { + return err + } + } else { + if os.IsNotExist(err) { + err := os.MkdirAll(config.DataDir, os.ModePerm) + if err != nil { + log.Error("Failed to create directory:", "err", err) + } + file, err := os.Create(fullPath) + if err != nil { + log.Error("Failed to create file:", "err", err) + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Failed to close file:", "err", err) + } + }(file) + } + log.Info("Creating new private key") + privateKey, err = crypto.GenerateKey() + if err != nil { + return err + } + } + } + + config.PrivateKey = privateKey + err = writePrivateKey(privateKey, config, privateKeyFileName) + if err != nil { + return err + } + return nil +} + +func writePrivateKey(privateKey *ecdsa.PrivateKey, config *Config, fileName string) error { + keyEnc := hex.EncodeToString(crypto.FromECDSA(privateKey)) + + fullPath := filepath.Join(config.DataDir, fileName) + file, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return err + } + defer func(file *os.File) { + err = file.Close() + if err != nil { + log.Error("Failed to close file", "err", err) + } + }(file) + + _, err = file.WriteString(keyEnc) + if err != nil { + return err + } + + return nil +} + +func readPrivateKey(config *Config, fileName string) (*ecdsa.PrivateKey, error) { + fullPath := filepath.Join(config.DataDir, fileName) + + keyBytes, err := os.ReadFile(fullPath) + if err != nil { + return nil, err + } + + keyEnc := string(keyBytes) + key, err := crypto.HexToECDSA(keyEnc) + if err != nil { + return nil, err + } + + return key, nil +} + +// setPortalBootstrapNodes creates a list of bootstrap nodes from the command line +// flags, reverting to pre-configured ones if none have been specified. +func setPortalBootstrapNodes(ctx *cli.Context, config *Config) { + urls := portalwire.PortalBootnodes + if ctx.IsSet(utils.PortalBootNodesFlag.Name) { + flag := ctx.String(utils.PortalBootNodesFlag.Name) + if flag == "none" { + return + } + urls = utils.SplitAndTrim(flag) + } + + for _, url := range urls { + if url != "" { + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + log.Error("Bootstrap URL invalid", "enode", url, "err", err) + continue + } + config.Protocol.BootstrapNodes = append(config.Protocol.BootstrapNodes, node) + } + } +} + +func applyMetricConfig(ctx *cli.Context, cfg *Config) { + if ctx.IsSet(utils.MetricsEnabledFlag.Name) { + cfg.Metrics.Enabled = ctx.Bool(utils.MetricsEnabledFlag.Name) + } + if ctx.IsSet(utils.MetricsHTTPFlag.Name) { + cfg.Metrics.HTTP = ctx.String(utils.MetricsHTTPFlag.Name) + } + if ctx.IsSet(utils.MetricsPortFlag.Name) { + cfg.Metrics.Port = ctx.Int(utils.MetricsPortFlag.Name) + } + if ctx.IsSet(utils.MetricsEnableInfluxDBFlag.Name) { + cfg.Metrics.EnableInfluxDB = ctx.Bool(utils.MetricsEnableInfluxDBFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBEndpointFlag.Name) { + cfg.Metrics.InfluxDBEndpoint = ctx.String(utils.MetricsInfluxDBEndpointFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { + cfg.Metrics.InfluxDBDatabase = ctx.String(utils.MetricsInfluxDBDatabaseFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBUsernameFlag.Name) { + cfg.Metrics.InfluxDBUsername = ctx.String(utils.MetricsInfluxDBUsernameFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBPasswordFlag.Name) { + cfg.Metrics.InfluxDBPassword = ctx.String(utils.MetricsInfluxDBPasswordFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBTagsFlag.Name) { + cfg.Metrics.InfluxDBTags = ctx.String(utils.MetricsInfluxDBTagsFlag.Name) + } + if ctx.IsSet(utils.MetricsEnableInfluxDBV2Flag.Name) { + cfg.Metrics.EnableInfluxDBV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBTokenFlag.Name) { + cfg.Metrics.InfluxDBToken = ctx.String(utils.MetricsInfluxDBTokenFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name) { + cfg.Metrics.InfluxDBBucket = ctx.String(utils.MetricsInfluxDBBucketFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBOrganizationFlag.Name) { + cfg.Metrics.InfluxDBOrganization = ctx.String(utils.MetricsInfluxDBOrganizationFlag.Name) + } + // Sanity-check the commandline flags. It is fine if some unused fields is part + // of the toml-config, but we expect the commandline to only contain relevant + // arguments, otherwise it indicates an error. + var ( + enableExport = ctx.Bool(utils.MetricsEnableInfluxDBFlag.Name) + enableExportV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name) + ) + if enableExport || enableExportV2 { + v1FlagIsSet := ctx.IsSet(utils.MetricsInfluxDBUsernameFlag.Name) || + ctx.IsSet(utils.MetricsInfluxDBPasswordFlag.Name) + + v2FlagIsSet := ctx.IsSet(utils.MetricsInfluxDBTokenFlag.Name) || + ctx.IsSet(utils.MetricsInfluxDBOrganizationFlag.Name) || + ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name) + + if enableExport && v2FlagIsSet { + utils.Fatalf("Flags --influxdb.metrics.organization, --influxdb.metrics.token, --influxdb.metrics.bucket are only available for influxdb-v2") + } else if enableExportV2 && v1FlagIsSet { + utils.Fatalf("Flags --influxdb.metrics.username, --influxdb.metrics.password are only available for influxdb-v1") + } + } +} diff --git a/cmd/shisui/main.go b/cmd/shisui/main.go index bdcd512..4efac1b 100644 --- a/cmd/shisui/main.go +++ b/cmd/shisui/main.go @@ -4,23 +4,18 @@ import ( "context" "crypto/ecdsa" "database/sql" - "encoding/hex" "fmt" "net" "net/http" "os/signal" "path" - "path/filepath" "slices" - "strings" "syscall" "time" "os" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/discover" @@ -61,6 +56,7 @@ type Config struct { DataCapacity uint64 LogLevel int Networks []string + Metrics *metrics.Config } type Client struct { @@ -146,11 +142,14 @@ func shisui(ctx *cli.Context) error { return err } + config, err := getPortalConfig(ctx) + if err != nil { + return nil + } + // Start metrics export if enabled - utils.SetupMetrics(ctx) + utils.SetupMetrics(config.Metrics) - // Start system runtime metrics collection - go metrics.CollectProcessMetrics(3 * time.Second) go portalwire.CollectPortalMetrics(5*time.Second, ctx.StringSlice(utils.PortalNetworksFlag.Name), ctx.String(utils.PortalDataDirFlag.Name)) if metrics.Enabled() { @@ -158,11 +157,6 @@ func shisui(ctx *cli.Context) error { storageCapacity.Update(ctx.Int64(utils.PortalDataCapacityFlag.Name)) } - config, err := getPortalConfig(ctx) - if err != nil { - return nil - } - clientChan := make(chan *Client, 1) go handlerInterrupt(clientChan) @@ -528,158 +522,3 @@ func initState(config Config, server *rpc.Server, conn discover.UDPConn, localNo historyNetwork := state.NewStateNetwork(protocol, client) return historyNetwork, historyNetwork.Start() } - -func getPortalConfig(ctx *cli.Context) (*Config, error) { - config := &Config{ - Protocol: portalwire.DefaultPortalProtocolConfig(), - } - - httpAddr := ctx.String(utils.PortalRPCListenAddrFlag.Name) - httpPort := ctx.String(utils.PortalRPCPortFlag.Name) - config.RpcAddr = net.JoinHostPort(httpAddr, httpPort) - config.DataDir = ctx.String(utils.PortalDataDirFlag.Name) - config.DataCapacity = ctx.Uint64(utils.PortalDataCapacityFlag.Name) - config.LogLevel = ctx.Int(utils.PortalLogLevelFlag.Name) - port := ctx.String(utils.PortalUDPPortFlag.Name) - if !strings.HasPrefix(port, ":") { - config.Protocol.ListenAddr = ":" + port - } else { - config.Protocol.ListenAddr = port - } - - err := setPrivateKey(ctx, config) - if err != nil { - return config, err - } - - natString := ctx.String(utils.PortalNATFlag.Name) - if natString != "" { - natInterface, err := nat.Parse(natString) - if err != nil { - return config, err - } - config.Protocol.NAT = natInterface - } - - setPortalBootstrapNodes(ctx, config) - config.Networks = ctx.StringSlice(utils.PortalNetworksFlag.Name) - return config, nil -} - -func setPrivateKey(ctx *cli.Context, config *Config) error { - var privateKey *ecdsa.PrivateKey - var err error - keyStr := ctx.String(utils.PortalPrivateKeyFlag.Name) - if keyStr != "" { - keyBytes, err := hexutil.Decode(keyStr) - if err != nil { - return err - } - privateKey, err = crypto.ToECDSA(keyBytes) - if err != nil { - return err - } - } else { - fullPath := filepath.Join(config.DataDir, privateKeyFileName) - if _, err := os.Stat(fullPath); err == nil { - log.Info("Loading private key from file", "datadir", config.DataDir, "file", privateKeyFileName) - privateKey, err = readPrivateKey(config, privateKeyFileName) - if err != nil { - return err - } - } else { - if os.IsNotExist(err) { - err := os.MkdirAll(config.DataDir, os.ModePerm) - if err != nil { - log.Error("Failed to create directory:", "err", err) - } - file, err := os.Create(fullPath) - if err != nil { - log.Error("Failed to create file:", "err", err) - } - defer func(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Failed to close file:", "err", err) - } - }(file) - } - log.Info("Creating new private key") - privateKey, err = crypto.GenerateKey() - if err != nil { - return err - } - } - } - - config.PrivateKey = privateKey - err = writePrivateKey(privateKey, config, privateKeyFileName) - if err != nil { - return err - } - return nil -} - -func writePrivateKey(privateKey *ecdsa.PrivateKey, config *Config, fileName string) error { - keyEnc := hex.EncodeToString(crypto.FromECDSA(privateKey)) - - fullPath := filepath.Join(config.DataDir, fileName) - file, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - return err - } - defer func(file *os.File) { - err = file.Close() - if err != nil { - log.Error("Failed to close file", "err", err) - } - }(file) - - _, err = file.WriteString(keyEnc) - if err != nil { - return err - } - - return nil -} - -func readPrivateKey(config *Config, fileName string) (*ecdsa.PrivateKey, error) { - fullPath := filepath.Join(config.DataDir, fileName) - - keyBytes, err := os.ReadFile(fullPath) - if err != nil { - return nil, err - } - - keyEnc := string(keyBytes) - key, err := crypto.HexToECDSA(keyEnc) - if err != nil { - return nil, err - } - - return key, nil -} - -// setPortalBootstrapNodes creates a list of bootstrap nodes from the command line -// flags, reverting to pre-configured ones if none have been specified. -func setPortalBootstrapNodes(ctx *cli.Context, config *Config) { - urls := portalwire.PortalBootnodes - if ctx.IsSet(utils.PortalBootNodesFlag.Name) { - flag := ctx.String(utils.PortalBootNodesFlag.Name) - if flag == "none" { - return - } - urls = utils.SplitAndTrim(flag) - } - - for _, url := range urls { - if url != "" { - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - log.Error("Bootstrap URL invalid", "enode", url, "err", err) - continue - } - config.Protocol.BootstrapNodes = append(config.Protocol.BootstrapNodes, node) - } - } -} diff --git a/cmd/shisui/utils/flags.go b/cmd/shisui/utils/flags.go index 592cefc..101fef9 100644 --- a/cmd/shisui/utils/flags.go +++ b/cmd/shisui/utils/flags.go @@ -182,65 +182,52 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. } ) -func SetupMetrics(ctx *cli.Context) { - if metrics.Enabled() { - log.Info("Enabling metrics collection") - - var ( - enableExport = ctx.Bool(MetricsEnableInfluxDBFlag.Name) - enableExportV2 = ctx.Bool(MetricsEnableInfluxDBV2Flag.Name) - ) - - if enableExport || enableExportV2 { - CheckExclusive(ctx, MetricsEnableInfluxDBFlag, MetricsEnableInfluxDBV2Flag) - - v1FlagIsSet := ctx.IsSet(MetricsInfluxDBUsernameFlag.Name) || - ctx.IsSet(MetricsInfluxDBPasswordFlag.Name) - - v2FlagIsSet := ctx.IsSet(MetricsInfluxDBTokenFlag.Name) || - ctx.IsSet(MetricsInfluxDBOrganizationFlag.Name) || - ctx.IsSet(MetricsInfluxDBBucketFlag.Name) - - if enableExport && v2FlagIsSet { - Fatalf("Flags --influxdb.metrics.organization, --influxdb.metrics.token, --influxdb.metrics.bucket are only available for influxdb-v2") - } else if enableExportV2 && v1FlagIsSet { - Fatalf("Flags --influxdb.metrics.username, --influxdb.metrics.password are only available for influxdb-v1") - } - } - - var ( - endpoint = ctx.String(MetricsInfluxDBEndpointFlag.Name) - database = ctx.String(MetricsInfluxDBDatabaseFlag.Name) - username = ctx.String(MetricsInfluxDBUsernameFlag.Name) - password = ctx.String(MetricsInfluxDBPasswordFlag.Name) - - token = ctx.String(MetricsInfluxDBTokenFlag.Name) - bucket = ctx.String(MetricsInfluxDBBucketFlag.Name) - organization = ctx.String(MetricsInfluxDBOrganizationFlag.Name) - ) - - if enableExport { - tagsMap := SplitTagsFlag(ctx.String(MetricsInfluxDBTagsFlag.Name)) - - log.Info("Enabling metrics export to InfluxDB") - - go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) - } else if enableExportV2 { - tagsMap := SplitTagsFlag(ctx.String(MetricsInfluxDBTagsFlag.Name)) - - log.Info("Enabling metrics export to InfluxDB (v2)") - - go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap) - } +func SetupMetrics(cfg *metrics.Config) { + if !cfg.Enabled { + return + } + log.Info("Enabling metrics collection") + metrics.Enable() + + // InfluxDB exporter. + var ( + enableExport = cfg.EnableInfluxDB + enableExportV2 = cfg.EnableInfluxDBV2 + ) + if cfg.EnableInfluxDB && cfg.EnableInfluxDBV2 { + Fatalf("Flags %v can't be used at the same time", strings.Join([]string{MetricsEnableInfluxDBFlag.Name, MetricsEnableInfluxDBV2Flag.Name}, ", ")) + } + var ( + endpoint = cfg.InfluxDBEndpoint + database = cfg.InfluxDBDatabase + username = cfg.InfluxDBUsername + password = cfg.InfluxDBPassword + + token = cfg.InfluxDBToken + bucket = cfg.InfluxDBBucket + organization = cfg.InfluxDBOrganization + tagsMap = SplitTagsFlag(cfg.InfluxDBTags) + ) + if enableExport { + log.Info("Enabling metrics export to InfluxDB") + go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) + } else if enableExportV2 { + tagsMap := SplitTagsFlag(cfg.InfluxDBTags) + log.Info("Enabling metrics export to InfluxDB (v2)") + go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap) + } - if ctx.IsSet(MetricsHTTPFlag.Name) { - address := net.JoinHostPort(ctx.String(MetricsHTTPFlag.Name), fmt.Sprintf("%d", ctx.Int(MetricsPortFlag.Name))) - log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) - exp.Setup(address) - } else if ctx.IsSet(MetricsPortFlag.Name) { - log.Warn(fmt.Sprintf("--%s specified without --%s, metrics server will not start.", MetricsPortFlag.Name, MetricsHTTPFlag.Name)) - } + // Expvar exporter. + if cfg.HTTP != "" { + address := net.JoinHostPort(cfg.HTTP, fmt.Sprintf("%d", cfg.Port)) + log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) + exp.Setup(address) + } else if cfg.HTTP == "" && cfg.Port != 0 { + log.Warn(fmt.Sprintf("--%s specified without --%s, metrics server will not start.", MetricsPortFlag.Name, MetricsHTTPFlag.Name)) } + + // Enable system metrics collection. + go metrics.CollectProcessMetrics(3 * time.Second) } // CheckExclusive verifies that only a single instance of the provided flags was diff --git a/go.sum b/go.sum index d50f601..579cc21 100644 --- a/go.sum +++ b/go.sum @@ -168,10 +168,6 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/optimism-java/shisui v1.14.6-0.20241223065521-fe0986aca609 h1:JriowqbrQ7GB4OuhkA/blQYcICvR5a5TZU/gsBk+V+c= github.com/optimism-java/shisui v1.14.6-0.20241223065521-fe0986aca609/go.mod h1:iRpLj7SCldtgT/7/p8YZVwor0qCfukfyi12azji/bo0= -github.com/optimism-java/utp-go v0.0.0-20241220063931-e7f66577a96c h1:eyOa3zWzO+RAQGWq6V91mgCybfsuROLJZxuQpY+/Nbg= -github.com/optimism-java/utp-go v0.0.0-20241220063931-e7f66577a96c/go.mod h1:dJZNMUlyNpjM2VkUEHhmFprLei6gCg3r7U9qj9MmJNQ= -github.com/optimism-java/utp-go v0.0.0-20241223023954-dac5386d1424 h1:OM23WGNMteIkM7IXzkGtoL+X//sFHy3M5LhrqTfvEuY= -github.com/optimism-java/utp-go v0.0.0-20241223023954-dac5386d1424/go.mod h1:dJZNMUlyNpjM2VkUEHhmFprLei6gCg3r7U9qj9MmJNQ= github.com/optimism-java/utp-go v0.0.0-20241223124724-19352018cc52 h1:5wq4o4t/umlNii796cEwNyhIT6KefKZv0uUEE6OJjvA= github.com/optimism-java/utp-go v0.0.0-20241223124724-19352018cc52/go.mod h1:dJZNMUlyNpjM2VkUEHhmFprLei6gCg3r7U9qj9MmJNQ= github.com/optimism-java/zrnt v0.32.4-0.20240415084906-d9dbf06b32f7 h1:ZTQWXQ8xblCRUXhZs3h5qrBMSAHe8iNH7BG7a7IVFlI=